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/hooks/events.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Unified hook event models for multi-CLI session management.
|
|
2
|
+
|
|
3
|
+
This module defines the unified internal representation for hook events across
|
|
4
|
+
all supported CLIs (Claude Code, Gemini CLI, Codex CLI). Adapters translate
|
|
5
|
+
between CLI-specific formats and these unified types.
|
|
6
|
+
|
|
7
|
+
Design Decision: This file coexists with hook_types.py. The existing HookType enum
|
|
8
|
+
in hook_types.py uses Claude-specific kebab-case names (session-start, pre-tool-use)
|
|
9
|
+
and Pydantic models for input validation. The HookEventType enum here is the unified
|
|
10
|
+
internal representation. Adapters translate between them.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import Any, Literal
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HookEventType(str, Enum):
|
|
20
|
+
"""Unified hook event types across all CLI sources.
|
|
21
|
+
|
|
22
|
+
These map to CLI-specific hook names via adapters:
|
|
23
|
+
- Claude Code: kebab-case (session-start, pre-tool-use)
|
|
24
|
+
- Gemini CLI: PascalCase (SessionStart, BeforeTool)
|
|
25
|
+
- Codex CLI: JSON-RPC methods (thread/started, item/completed)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Session lifecycle
|
|
29
|
+
SESSION_START = "session_start"
|
|
30
|
+
SESSION_END = "session_end"
|
|
31
|
+
|
|
32
|
+
# Agent/turn lifecycle
|
|
33
|
+
BEFORE_AGENT = "before_agent"
|
|
34
|
+
AFTER_AGENT = "after_agent"
|
|
35
|
+
STOP = "stop" # Agent is about to stop/exit (Claude Code only)
|
|
36
|
+
|
|
37
|
+
# Tool lifecycle
|
|
38
|
+
BEFORE_TOOL = "before_tool"
|
|
39
|
+
AFTER_TOOL = "after_tool"
|
|
40
|
+
BEFORE_TOOL_SELECTION = "before_tool_selection" # Gemini only
|
|
41
|
+
|
|
42
|
+
# Model lifecycle (Gemini only)
|
|
43
|
+
BEFORE_MODEL = "before_model"
|
|
44
|
+
AFTER_MODEL = "after_model"
|
|
45
|
+
|
|
46
|
+
# Context management
|
|
47
|
+
PRE_COMPACT = "pre_compact" # Claude: PreCompact, Gemini: PreCompress
|
|
48
|
+
|
|
49
|
+
# Subagent lifecycle (Claude Code only)
|
|
50
|
+
SUBAGENT_START = "subagent_start"
|
|
51
|
+
SUBAGENT_STOP = "subagent_stop"
|
|
52
|
+
|
|
53
|
+
# Permissions & notifications
|
|
54
|
+
PERMISSION_REQUEST = "permission_request" # Claude Code only
|
|
55
|
+
NOTIFICATION = "notification"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SessionSource(str, Enum):
|
|
59
|
+
"""Identifies which CLI originated the session."""
|
|
60
|
+
|
|
61
|
+
CLAUDE = "claude" # Claude Code CLI
|
|
62
|
+
GEMINI = "gemini"
|
|
63
|
+
CODEX = "codex"
|
|
64
|
+
CLAUDE_SDK = "claude_sdk"
|
|
65
|
+
ANTIGRAVITY = "antigravity" # Antigravity IDE (uses Claude Code format)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class HookEvent:
|
|
70
|
+
"""Unified hook event from any CLI source.
|
|
71
|
+
|
|
72
|
+
This dataclass represents a normalized hook event that can originate from
|
|
73
|
+
any supported CLI. Adapters are responsible for translating CLI-specific
|
|
74
|
+
payloads into this format.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
event_type: The type of hook event (from HookEventType enum).
|
|
78
|
+
session_id: External session identifier (external_id for Claude, thread_id for Codex).
|
|
79
|
+
source: Which CLI originated this event.
|
|
80
|
+
timestamp: When the event occurred.
|
|
81
|
+
data: Event-specific payload in native format (adapter passes through).
|
|
82
|
+
|
|
83
|
+
machine_id: Unique identifier for the machine (populated by adapter or manager).
|
|
84
|
+
cwd: Current working directory for the session.
|
|
85
|
+
|
|
86
|
+
user_id: Platform user ID (populated by HookManager after session lookup).
|
|
87
|
+
project_id: Platform project ID (populated by HookManager).
|
|
88
|
+
workflow_id: Future: ID of workflow evaluating this event.
|
|
89
|
+
metadata: Extensible key-value store for adapter-specific data.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
# Core required fields
|
|
93
|
+
event_type: HookEventType
|
|
94
|
+
session_id: str # external_id / thread_id (external ID)
|
|
95
|
+
source: SessionSource
|
|
96
|
+
timestamp: datetime
|
|
97
|
+
data: dict[str, Any] # Event-specific payload (native format)
|
|
98
|
+
|
|
99
|
+
# Context (populated by adapter or manager)
|
|
100
|
+
machine_id: str | None = None
|
|
101
|
+
cwd: str | None = None
|
|
102
|
+
|
|
103
|
+
# Future extensibility
|
|
104
|
+
user_id: str | None = None
|
|
105
|
+
project_id: str | None = None
|
|
106
|
+
task_id: str | None = None
|
|
107
|
+
workflow_id: str | None = None
|
|
108
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class HookResponse:
|
|
113
|
+
"""Unified response returned to CLI.
|
|
114
|
+
|
|
115
|
+
This dataclass represents the response that will be translated back to
|
|
116
|
+
CLI-specific format by the adapter.
|
|
117
|
+
|
|
118
|
+
Attributes:
|
|
119
|
+
decision: Whether to allow, deny, or ask the user about the action.
|
|
120
|
+
context: Text to inject into the agent's context (AI-only).
|
|
121
|
+
system_message: User-visible message to display (e.g., handoff notification).
|
|
122
|
+
reason: Explanation for the decision (useful for denials).
|
|
123
|
+
|
|
124
|
+
modify_args: Future: Dict of argument modifications for the action.
|
|
125
|
+
trigger_action: Future: Action to trigger in the CLI.
|
|
126
|
+
metadata: Extensible key-value store for adapter-specific data.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
decision: Literal["allow", "deny", "ask", "block", "modify"] = "allow"
|
|
130
|
+
context: str | None = None # Inject into agent context (AI-only)
|
|
131
|
+
system_message: str | None = None # User-visible message (e.g., handoff notification)
|
|
132
|
+
reason: str | None = None # Explanation for decision
|
|
133
|
+
|
|
134
|
+
# Future extensibility
|
|
135
|
+
modify_args: dict[str, Any] | None = None
|
|
136
|
+
trigger_action: str | None = None
|
|
137
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Event type mapping table for documentation (see plan-multi-cli.md section 1.2)
|
|
141
|
+
# This is informational - actual mappings are in adapters
|
|
142
|
+
EVENT_TYPE_CLI_SUPPORT: dict[HookEventType, dict[str, str | None]] = {
|
|
143
|
+
HookEventType.SESSION_START: {
|
|
144
|
+
"claude": "SessionStart",
|
|
145
|
+
"gemini": "SessionStart",
|
|
146
|
+
"codex": "thread/started",
|
|
147
|
+
},
|
|
148
|
+
HookEventType.SESSION_END: {
|
|
149
|
+
"claude": "SessionEnd",
|
|
150
|
+
"gemini": "SessionEnd",
|
|
151
|
+
"codex": "thread/archive",
|
|
152
|
+
},
|
|
153
|
+
HookEventType.BEFORE_AGENT: {
|
|
154
|
+
"claude": "UserPromptSubmit",
|
|
155
|
+
"gemini": "BeforeAgent",
|
|
156
|
+
"codex": "turn/started",
|
|
157
|
+
},
|
|
158
|
+
HookEventType.AFTER_AGENT: {
|
|
159
|
+
"claude": "Stop",
|
|
160
|
+
"gemini": "AfterAgent",
|
|
161
|
+
"codex": "turn/completed",
|
|
162
|
+
},
|
|
163
|
+
HookEventType.STOP: {
|
|
164
|
+
"claude": "Stop",
|
|
165
|
+
"gemini": None,
|
|
166
|
+
"codex": None,
|
|
167
|
+
},
|
|
168
|
+
HookEventType.BEFORE_TOOL: {
|
|
169
|
+
"claude": "PreToolUse",
|
|
170
|
+
"gemini": "BeforeTool",
|
|
171
|
+
"codex": "requestApproval",
|
|
172
|
+
},
|
|
173
|
+
HookEventType.AFTER_TOOL: {
|
|
174
|
+
"claude": "PostToolUse",
|
|
175
|
+
"gemini": "AfterTool",
|
|
176
|
+
"codex": "item/completed",
|
|
177
|
+
},
|
|
178
|
+
HookEventType.BEFORE_TOOL_SELECTION: {
|
|
179
|
+
"claude": None,
|
|
180
|
+
"gemini": "BeforeToolSelection",
|
|
181
|
+
"codex": None,
|
|
182
|
+
},
|
|
183
|
+
HookEventType.BEFORE_MODEL: {
|
|
184
|
+
"claude": None,
|
|
185
|
+
"gemini": "BeforeModel",
|
|
186
|
+
"codex": None,
|
|
187
|
+
},
|
|
188
|
+
HookEventType.AFTER_MODEL: {
|
|
189
|
+
"claude": None,
|
|
190
|
+
"gemini": "AfterModel",
|
|
191
|
+
"codex": None,
|
|
192
|
+
},
|
|
193
|
+
HookEventType.PRE_COMPACT: {
|
|
194
|
+
"claude": "PreCompact",
|
|
195
|
+
"gemini": "PreCompress",
|
|
196
|
+
"codex": None,
|
|
197
|
+
},
|
|
198
|
+
HookEventType.SUBAGENT_START: {
|
|
199
|
+
"claude": "SubagentStart",
|
|
200
|
+
"gemini": None,
|
|
201
|
+
"codex": None,
|
|
202
|
+
},
|
|
203
|
+
HookEventType.SUBAGENT_STOP: {
|
|
204
|
+
"claude": "SubagentStop",
|
|
205
|
+
"gemini": None,
|
|
206
|
+
"codex": None,
|
|
207
|
+
},
|
|
208
|
+
HookEventType.PERMISSION_REQUEST: {
|
|
209
|
+
"claude": "PermissionRequest",
|
|
210
|
+
"gemini": None,
|
|
211
|
+
"codex": None,
|
|
212
|
+
},
|
|
213
|
+
HookEventType.NOTIFICATION: {
|
|
214
|
+
"claude": "Notification",
|
|
215
|
+
"gemini": "Notification",
|
|
216
|
+
"codex": None,
|
|
217
|
+
},
|
|
218
|
+
}
|
gobby/hooks/git.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Git-related hooks for merge operations.
|
|
2
|
+
|
|
3
|
+
Provides hooks for pre-merge and post-merge events that can be used
|
|
4
|
+
to integrate merge resolution with other systems.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Type aliases for hook callbacks
|
|
15
|
+
PreMergeHook = Callable[[str, str, str], bool] # (worktree_id, source, target) -> allow
|
|
16
|
+
PostMergeHook = Callable[[str, bool], None] # (resolution_id, success) -> None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MergeHookManager:
|
|
20
|
+
"""
|
|
21
|
+
Manager for merge-related hooks.
|
|
22
|
+
|
|
23
|
+
Allows registration of pre-merge and post-merge hooks that can be
|
|
24
|
+
used to integrate merge resolution with other systems (task status,
|
|
25
|
+
notifications, etc.).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
"""Initialize the hook manager."""
|
|
30
|
+
self._pre_merge_hooks: list[PreMergeHook] = []
|
|
31
|
+
self._post_merge_hooks: list[PostMergeHook] = []
|
|
32
|
+
|
|
33
|
+
def register_pre_merge(self, hook: PreMergeHook) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Register a pre-merge hook.
|
|
36
|
+
|
|
37
|
+
Pre-merge hooks are called before a merge operation starts.
|
|
38
|
+
They receive the worktree ID, source branch, and target branch.
|
|
39
|
+
If any hook returns False, the merge is blocked.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
hook: Callback function (worktree_id, source_branch, target_branch) -> bool
|
|
43
|
+
"""
|
|
44
|
+
self._pre_merge_hooks.append(hook)
|
|
45
|
+
logger.debug(f"Registered pre-merge hook: {hook.__name__}")
|
|
46
|
+
|
|
47
|
+
def register_post_merge(self, hook: PostMergeHook) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Register a post-merge hook.
|
|
50
|
+
|
|
51
|
+
Post-merge hooks are called after a merge operation completes.
|
|
52
|
+
They receive the resolution ID and success status.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
hook: Callback function (resolution_id, success) -> None
|
|
56
|
+
"""
|
|
57
|
+
self._post_merge_hooks.append(hook)
|
|
58
|
+
logger.debug(f"Registered post-merge hook: {hook.__name__}")
|
|
59
|
+
|
|
60
|
+
def unregister_pre_merge(self, hook: PreMergeHook) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Unregister a pre-merge hook.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
hook: Hook to unregister
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
True if hook was found and removed
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
self._pre_merge_hooks.remove(hook)
|
|
72
|
+
return True
|
|
73
|
+
except ValueError:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
def unregister_post_merge(self, hook: PostMergeHook) -> bool:
|
|
77
|
+
"""
|
|
78
|
+
Unregister a post-merge hook.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
hook: Hook to unregister
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
True if hook was found and removed
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
self._post_merge_hooks.remove(hook)
|
|
88
|
+
return True
|
|
89
|
+
except ValueError:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
def run_pre_merge_hooks(
|
|
93
|
+
self, worktree_id: str, source_branch: str, target_branch: str
|
|
94
|
+
) -> tuple[bool, str | None]:
|
|
95
|
+
"""
|
|
96
|
+
Run all pre-merge hooks.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
worktree_id: ID of the worktree
|
|
100
|
+
source_branch: Branch being merged
|
|
101
|
+
target_branch: Target branch
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Tuple of (allowed, blocking_reason)
|
|
105
|
+
If any hook returns False, returns (False, reason)
|
|
106
|
+
"""
|
|
107
|
+
for hook in self._pre_merge_hooks:
|
|
108
|
+
try:
|
|
109
|
+
result = hook(worktree_id, source_branch, target_branch)
|
|
110
|
+
if not result:
|
|
111
|
+
reason = f"Merge blocked by pre-merge hook: {hook.__name__}"
|
|
112
|
+
logger.warning(reason)
|
|
113
|
+
return (False, reason)
|
|
114
|
+
except Exception as e:
|
|
115
|
+
reason = f"Pre-merge hook {hook.__name__} raised exception: {e}"
|
|
116
|
+
logger.error(reason)
|
|
117
|
+
# Continue with other hooks on exception
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
return (True, None)
|
|
121
|
+
|
|
122
|
+
def run_post_merge_hooks(self, resolution_id: str, success: bool) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Run all post-merge hooks.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
resolution_id: ID of the merge resolution
|
|
128
|
+
success: Whether the merge was successful
|
|
129
|
+
"""
|
|
130
|
+
for hook in self._post_merge_hooks:
|
|
131
|
+
try:
|
|
132
|
+
hook(resolution_id, success)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.error(f"Post-merge hook {hook.__name__} raised exception: {e}")
|
|
135
|
+
# Continue with other hooks on exception
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def pre_merge_hook_count(self) -> int:
|
|
140
|
+
"""Get number of registered pre-merge hooks."""
|
|
141
|
+
return len(self._pre_merge_hooks)
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def post_merge_hook_count(self) -> int:
|
|
145
|
+
"""Get number of registered post-merge hooks."""
|
|
146
|
+
return len(self._post_merge_hooks)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# Singleton instance for global hook management
|
|
150
|
+
_default_manager: MergeHookManager | None = None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def get_merge_hook_manager() -> MergeHookManager:
|
|
154
|
+
"""
|
|
155
|
+
Get the default MergeHookManager instance.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
The global MergeHookManager singleton
|
|
159
|
+
"""
|
|
160
|
+
global _default_manager
|
|
161
|
+
if _default_manager is None:
|
|
162
|
+
_default_manager = MergeHookManager()
|
|
163
|
+
return _default_manager
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def reset_merge_hook_manager() -> None:
|
|
167
|
+
"""Reset the default MergeHookManager (for testing)."""
|
|
168
|
+
global _default_manager
|
|
169
|
+
_default_manager = None
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Health monitor module for daemon health check monitoring.
|
|
3
|
+
|
|
4
|
+
This module is extracted from hook_manager.py using Strangler Fig pattern.
|
|
5
|
+
It provides background health check monitoring for the Gobby daemon.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
HealthMonitor: Background daemon health check monitoring.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import threading
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from gobby.utils.daemon_client import DaemonClient
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HealthMonitor:
|
|
22
|
+
"""
|
|
23
|
+
Background daemon health check monitoring.
|
|
24
|
+
|
|
25
|
+
Periodically checks daemon health via DaemonClient and caches the result
|
|
26
|
+
for fast access without HTTP calls. Thread-safe.
|
|
27
|
+
|
|
28
|
+
Extracted from HookManager to separate health monitoring concerns.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
daemon_client: DaemonClient,
|
|
34
|
+
health_check_interval: float = 10.0,
|
|
35
|
+
logger: logging.Logger | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Initialize HealthMonitor.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
daemon_client: DaemonClient for health checks
|
|
42
|
+
health_check_interval: Interval between checks in seconds (must be >= 0)
|
|
43
|
+
logger: Optional logger instance
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If health_check_interval is negative
|
|
47
|
+
"""
|
|
48
|
+
if health_check_interval < 0:
|
|
49
|
+
raise ValueError("health_check_interval must be non-negative")
|
|
50
|
+
|
|
51
|
+
self._daemon_client = daemon_client
|
|
52
|
+
self._health_check_interval = health_check_interval
|
|
53
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
54
|
+
|
|
55
|
+
# Cached health status
|
|
56
|
+
self._cached_daemon_is_ready: bool = False
|
|
57
|
+
self._cached_daemon_message: str | None = None
|
|
58
|
+
self._cached_daemon_status: str = "not_running"
|
|
59
|
+
self._cached_daemon_error: str | None = None
|
|
60
|
+
|
|
61
|
+
# Threading state
|
|
62
|
+
self._health_check_timer: threading.Timer | None = None
|
|
63
|
+
self._health_check_lock = threading.Lock()
|
|
64
|
+
self._is_shutdown: bool = False
|
|
65
|
+
|
|
66
|
+
def start(self) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Start background health check monitoring.
|
|
69
|
+
|
|
70
|
+
Idempotent - safe to call multiple times.
|
|
71
|
+
"""
|
|
72
|
+
with self._health_check_lock:
|
|
73
|
+
if self._health_check_timer is not None:
|
|
74
|
+
return # Already running
|
|
75
|
+
if self._is_shutdown:
|
|
76
|
+
return # Already shutdown
|
|
77
|
+
|
|
78
|
+
def health_check_loop() -> None:
|
|
79
|
+
"""Background health check loop."""
|
|
80
|
+
try:
|
|
81
|
+
# Update daemon status cache
|
|
82
|
+
# check_status() returns tuple: (is_ready, message, status, error)
|
|
83
|
+
is_ready, message, status, error = self._daemon_client.check_status()
|
|
84
|
+
with self._health_check_lock:
|
|
85
|
+
self._cached_daemon_is_ready = is_ready
|
|
86
|
+
self._cached_daemon_message = message
|
|
87
|
+
self._cached_daemon_status = status
|
|
88
|
+
self._cached_daemon_error = error
|
|
89
|
+
except Exception as e:
|
|
90
|
+
# Daemon not responding is expected when stopped, log at debug level
|
|
91
|
+
self.logger.debug(f"Health check failed: {e}", exc_info=True)
|
|
92
|
+
with self._health_check_lock:
|
|
93
|
+
self._cached_daemon_is_ready = False
|
|
94
|
+
self._cached_daemon_status = "not_running"
|
|
95
|
+
self._cached_daemon_error = str(e)
|
|
96
|
+
finally:
|
|
97
|
+
# Schedule next check only if not shutting down
|
|
98
|
+
with self._health_check_lock:
|
|
99
|
+
if not self._is_shutdown:
|
|
100
|
+
self._health_check_timer = threading.Timer(
|
|
101
|
+
self._health_check_interval,
|
|
102
|
+
health_check_loop,
|
|
103
|
+
)
|
|
104
|
+
self._health_check_timer.daemon = True
|
|
105
|
+
self._health_check_timer.start()
|
|
106
|
+
|
|
107
|
+
# Start first check immediately
|
|
108
|
+
self._health_check_timer = threading.Timer(0, health_check_loop)
|
|
109
|
+
self._health_check_timer.daemon = True
|
|
110
|
+
self._health_check_timer.start()
|
|
111
|
+
|
|
112
|
+
def stop(self) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Stop background health check monitoring.
|
|
115
|
+
|
|
116
|
+
Cancels any pending timer and prevents new timers from being scheduled.
|
|
117
|
+
Safe to call multiple times.
|
|
118
|
+
"""
|
|
119
|
+
with self._health_check_lock:
|
|
120
|
+
self._is_shutdown = True
|
|
121
|
+
if self._health_check_timer is not None:
|
|
122
|
+
self._health_check_timer.cancel()
|
|
123
|
+
self._health_check_timer = None
|
|
124
|
+
|
|
125
|
+
def get_cached_status(self) -> tuple[bool, str | None, str, str | None]:
|
|
126
|
+
"""
|
|
127
|
+
Get cached daemon status without making HTTP call.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Tuple of (is_ready, message, status, error) where:
|
|
131
|
+
- is_ready: True if daemon is healthy
|
|
132
|
+
- message: Human-readable status message
|
|
133
|
+
- status: One of: "ready", "not_running", "cannot_access"
|
|
134
|
+
- error: Error details if status != "ready"
|
|
135
|
+
"""
|
|
136
|
+
with self._health_check_lock:
|
|
137
|
+
return (
|
|
138
|
+
self._cached_daemon_is_ready,
|
|
139
|
+
self._cached_daemon_message,
|
|
140
|
+
self._cached_daemon_status,
|
|
141
|
+
self._cached_daemon_error,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def check_now(self) -> bool:
|
|
145
|
+
"""
|
|
146
|
+
Perform immediate health check (not cached).
|
|
147
|
+
|
|
148
|
+
Makes a fresh HTTP call to check daemon status and updates the cache.
|
|
149
|
+
Used for retry logic when cached status indicates daemon is unavailable.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
True if daemon is healthy, False otherwise
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
is_ready, message, status, error = self._daemon_client.check_status()
|
|
156
|
+
with self._health_check_lock:
|
|
157
|
+
self._cached_daemon_is_ready = is_ready
|
|
158
|
+
self._cached_daemon_message = message
|
|
159
|
+
self._cached_daemon_status = status
|
|
160
|
+
self._cached_daemon_error = error
|
|
161
|
+
return is_ready
|
|
162
|
+
except Exception as e:
|
|
163
|
+
self.logger.debug(f"Immediate health check failed: {e}")
|
|
164
|
+
with self._health_check_lock:
|
|
165
|
+
self._cached_daemon_is_ready = False
|
|
166
|
+
self._cached_daemon_status = "not_running"
|
|
167
|
+
self._cached_daemon_error = str(e)
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
__all__ = ["HealthMonitor"]
|