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
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session message processor.
|
|
3
|
+
|
|
4
|
+
Handles asynchronous, incremental processing of session transcripts.
|
|
5
|
+
Tracks file offsets and updates the database with new messages.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from gobby.servers.websocket import WebSocketServer
|
|
15
|
+
|
|
16
|
+
from gobby.sessions.transcripts import get_parser
|
|
17
|
+
from gobby.sessions.transcripts.base import TranscriptParser
|
|
18
|
+
from gobby.storage.database import DatabaseProtocol
|
|
19
|
+
from gobby.storage.session_messages import LocalSessionMessageManager
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SessionMessageProcessor:
|
|
25
|
+
"""
|
|
26
|
+
Processes session transcripts in the background.
|
|
27
|
+
|
|
28
|
+
- Watches active session transcript files
|
|
29
|
+
- incrementally reads new content
|
|
30
|
+
- parses messages using TranscriptParser
|
|
31
|
+
- stores normalized messages in the database
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
db: DatabaseProtocol,
|
|
37
|
+
poll_interval: float = 2.0,
|
|
38
|
+
websocket_server: "WebSocketServer | None" = None,
|
|
39
|
+
):
|
|
40
|
+
self.db = db
|
|
41
|
+
self.message_manager = LocalSessionMessageManager(db)
|
|
42
|
+
self.poll_interval = poll_interval
|
|
43
|
+
self.websocket_server: WebSocketServer | None = websocket_server
|
|
44
|
+
|
|
45
|
+
# Track active sessions: session_id -> transcript_path
|
|
46
|
+
self._active_sessions: dict[str, str] = {}
|
|
47
|
+
|
|
48
|
+
# Track parsers: session_id -> TranscriptParser
|
|
49
|
+
# Currently hardcoded to ClaudeTranscriptParser, but could support others
|
|
50
|
+
self._parsers: dict[str, TranscriptParser] = {}
|
|
51
|
+
|
|
52
|
+
self._running = False
|
|
53
|
+
self._task: asyncio.Task[None] | None = None
|
|
54
|
+
|
|
55
|
+
async def start(self) -> None:
|
|
56
|
+
"""Start the processing loop."""
|
|
57
|
+
if self._running:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
self._running = True
|
|
61
|
+
self._task = asyncio.create_task(self._loop())
|
|
62
|
+
logger.info("SessionMessageProcessor started")
|
|
63
|
+
|
|
64
|
+
async def stop(self) -> None:
|
|
65
|
+
"""Stop the processing loop."""
|
|
66
|
+
self._running = False
|
|
67
|
+
if self._task:
|
|
68
|
+
self._task.cancel()
|
|
69
|
+
try:
|
|
70
|
+
await self._task
|
|
71
|
+
except asyncio.CancelledError:
|
|
72
|
+
pass
|
|
73
|
+
self._task = None
|
|
74
|
+
logger.info("SessionMessageProcessor stopped")
|
|
75
|
+
|
|
76
|
+
def register_session(
|
|
77
|
+
self, session_id: str, transcript_path: str, source: str = "claude"
|
|
78
|
+
) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Register a session for monitoring.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
session_id: Session ID
|
|
84
|
+
transcript_path: Absolute path to the transcript JSONL file
|
|
85
|
+
source: CLI source name (default: "claude")
|
|
86
|
+
"""
|
|
87
|
+
if session_id in self._active_sessions:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
if not os.path.exists(transcript_path):
|
|
91
|
+
logger.warning(f"Transcript file not found: {transcript_path}")
|
|
92
|
+
# We still register it, hoping it appears later (or we could fail)
|
|
93
|
+
# For now, let's assume it might be created shortly.
|
|
94
|
+
|
|
95
|
+
self._active_sessions[session_id] = transcript_path
|
|
96
|
+
self._parsers[session_id] = get_parser(source)
|
|
97
|
+
logger.debug(f"Registered session {session_id} for processing ({source})")
|
|
98
|
+
|
|
99
|
+
def unregister_session(self, session_id: str) -> None:
|
|
100
|
+
"""Stop monitoring a session."""
|
|
101
|
+
if session_id in self._active_sessions:
|
|
102
|
+
del self._active_sessions[session_id]
|
|
103
|
+
if session_id in self._parsers:
|
|
104
|
+
del self._parsers[session_id]
|
|
105
|
+
logger.debug(f"Unregistered session {session_id}")
|
|
106
|
+
|
|
107
|
+
async def _loop(self) -> None:
|
|
108
|
+
"""Main processing loop."""
|
|
109
|
+
while self._running:
|
|
110
|
+
try:
|
|
111
|
+
await self._process_all_sessions()
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(f"Error in SessionMessageProcessor loop: {e}")
|
|
114
|
+
|
|
115
|
+
await asyncio.sleep(self.poll_interval)
|
|
116
|
+
|
|
117
|
+
async def _process_all_sessions(self) -> None:
|
|
118
|
+
"""Process all registered sessions."""
|
|
119
|
+
# Create list copy to avoid concurrent modification issues
|
|
120
|
+
sessions = list(self._active_sessions.items())
|
|
121
|
+
|
|
122
|
+
for session_id, transcript_path in sessions:
|
|
123
|
+
try:
|
|
124
|
+
await self._process_session(session_id, transcript_path)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Failed to process session {session_id}: {e}")
|
|
127
|
+
|
|
128
|
+
async def _process_session(self, session_id: str, transcript_path: str) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Process a single session.
|
|
131
|
+
|
|
132
|
+
Reads new lines from the transcript file from the last known byte offset.
|
|
133
|
+
"""
|
|
134
|
+
if not os.path.exists(transcript_path):
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
# Get current processing state
|
|
138
|
+
state = await self.message_manager.get_state(session_id)
|
|
139
|
+
|
|
140
|
+
last_offset = 0
|
|
141
|
+
last_index = -1
|
|
142
|
+
|
|
143
|
+
if state:
|
|
144
|
+
last_offset = state.get("last_byte_offset", 0)
|
|
145
|
+
last_index = state.get("last_message_index", -1)
|
|
146
|
+
|
|
147
|
+
# Read new content
|
|
148
|
+
new_lines = []
|
|
149
|
+
valid_offset = last_offset
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
# Note: synchronous file I/O for simplicity; could use aiofiles if blocking is an issue
|
|
153
|
+
# but reading incremental logs is usually fast.
|
|
154
|
+
with open(transcript_path, encoding="utf-8") as f:
|
|
155
|
+
# Seek to last known position
|
|
156
|
+
f.seek(last_offset)
|
|
157
|
+
|
|
158
|
+
# Read line by line
|
|
159
|
+
while True:
|
|
160
|
+
line = f.readline()
|
|
161
|
+
if not line:
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
# Only process complete lines
|
|
165
|
+
if line.endswith("\n"):
|
|
166
|
+
new_lines.append(line)
|
|
167
|
+
valid_offset = f.tell()
|
|
168
|
+
else:
|
|
169
|
+
# Incomplete line (write in progress), stop reading
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.error(f"Error reading transcript {transcript_path}: {e}")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
if not new_lines:
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
# Parse new lines
|
|
180
|
+
parser = self._parsers.get(session_id)
|
|
181
|
+
if not parser:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
parsed_messages = parser.parse_lines(new_lines, start_index=last_index + 1)
|
|
185
|
+
|
|
186
|
+
if not parsed_messages:
|
|
187
|
+
# We read lines but found no valid messages (maybe parse errors or skipped types)
|
|
188
|
+
# We still update the offset so we don't re-read them endlessly
|
|
189
|
+
await self.message_manager.update_state(
|
|
190
|
+
session_id=session_id,
|
|
191
|
+
byte_offset=valid_offset,
|
|
192
|
+
message_index=last_index,
|
|
193
|
+
)
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# Store messages
|
|
197
|
+
await self.message_manager.store_messages(session_id, parsed_messages)
|
|
198
|
+
|
|
199
|
+
# Broadcast new messages
|
|
200
|
+
if self.websocket_server:
|
|
201
|
+
for msg in parsed_messages:
|
|
202
|
+
payload = {
|
|
203
|
+
"type": "session_message",
|
|
204
|
+
"session_id": session_id,
|
|
205
|
+
"message": {
|
|
206
|
+
"index": msg.index,
|
|
207
|
+
"role": msg.role,
|
|
208
|
+
"content": msg.content,
|
|
209
|
+
"content_type": msg.content_type,
|
|
210
|
+
"tool_name": msg.tool_name,
|
|
211
|
+
"timestamp": msg.timestamp.isoformat(),
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
await self.websocket_server.broadcast(payload)
|
|
215
|
+
|
|
216
|
+
# Update state
|
|
217
|
+
new_last_index = parsed_messages[-1].index
|
|
218
|
+
|
|
219
|
+
await self.message_manager.update_state(
|
|
220
|
+
session_id=session_id,
|
|
221
|
+
byte_offset=valid_offset,
|
|
222
|
+
message_index=new_last_index,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
logger.debug(f"Processed {len(parsed_messages)} messages for {session_id}")
|