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/sync/memories.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Memory backup utilities for filesystem export.
|
|
2
|
+
|
|
3
|
+
This module provides JSONL backup functionality for memories. It is NOT a
|
|
4
|
+
bidirectional sync mechanism - memories are stored in the database via
|
|
5
|
+
MemoryBackendProtocol. This module handles:
|
|
6
|
+
|
|
7
|
+
- Backup export to .gobby/memories.jsonl for disaster recovery
|
|
8
|
+
- One-time migration import from existing JSONL files
|
|
9
|
+
- Debounced auto-backup on memory changes
|
|
10
|
+
|
|
11
|
+
Classes:
|
|
12
|
+
MemoryBackupManager: Main backup manager (formerly MemorySyncManager)
|
|
13
|
+
MemorySyncManager: Backward-compatible alias for MemoryBackupManager
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"MemoryBackupManager",
|
|
24
|
+
"MemorySyncManager", # Backward compatibility alias
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# TODO: Rename MemorySyncConfig to MemoryBackupConfig in gobby.config.persistence
|
|
28
|
+
# for consistency with MemoryBackupManager naming. Keeping current name for now
|
|
29
|
+
# to minimize breaking changes across the codebase.
|
|
30
|
+
from gobby.config.app import MemorySyncConfig
|
|
31
|
+
from gobby.memory.manager import MemoryManager
|
|
32
|
+
from gobby.storage.database import DatabaseProtocol
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MemoryBackupManager:
|
|
38
|
+
"""
|
|
39
|
+
Manages backup of memories from the database to filesystem.
|
|
40
|
+
|
|
41
|
+
This is a backup/export utility, NOT a sync mechanism. Memories are stored
|
|
42
|
+
in the database (via the configured backend) and this class provides:
|
|
43
|
+
- JSONL backup export (to .gobby/memories.jsonl)
|
|
44
|
+
- One-time migration import from existing JSONL files
|
|
45
|
+
- Debounced auto-backup on changes
|
|
46
|
+
|
|
47
|
+
For actual memory storage, see gobby.memory.backends.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
db: DatabaseProtocol,
|
|
53
|
+
memory_manager: MemoryManager | None,
|
|
54
|
+
config: MemorySyncConfig,
|
|
55
|
+
):
|
|
56
|
+
self.db = db
|
|
57
|
+
self.memory_manager = memory_manager
|
|
58
|
+
self.config = config
|
|
59
|
+
self.export_path = config.export_path
|
|
60
|
+
|
|
61
|
+
# Debounce state
|
|
62
|
+
self._export_task: asyncio.Task[None] | None = None
|
|
63
|
+
self._last_change_time: float = 0
|
|
64
|
+
self._shutdown_requested = False
|
|
65
|
+
|
|
66
|
+
def trigger_export(self) -> None:
|
|
67
|
+
"""Trigger a debounced export."""
|
|
68
|
+
if not self.config.enabled:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
self._last_change_time = time.time()
|
|
72
|
+
|
|
73
|
+
if self._export_task is None or self._export_task.done():
|
|
74
|
+
try:
|
|
75
|
+
loop = asyncio.get_running_loop()
|
|
76
|
+
self._export_task = loop.create_task(self._process_export_queue())
|
|
77
|
+
except RuntimeError:
|
|
78
|
+
# No running event loop (e.g. CLI usage) - run sync immediately
|
|
79
|
+
# We skip the debounce loop and just export
|
|
80
|
+
memories_file = self._get_export_path()
|
|
81
|
+
try:
|
|
82
|
+
self._export_to_files_sync(memories_file)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.warning(f"Failed to sync memory export: {e}")
|
|
85
|
+
|
|
86
|
+
async def shutdown(self) -> None:
|
|
87
|
+
"""Gracefully shutdown the export task."""
|
|
88
|
+
self._shutdown_requested = True
|
|
89
|
+
if self._export_task:
|
|
90
|
+
if not self._export_task.done():
|
|
91
|
+
try:
|
|
92
|
+
await self._export_task
|
|
93
|
+
except asyncio.CancelledError:
|
|
94
|
+
pass
|
|
95
|
+
self._export_task = None
|
|
96
|
+
|
|
97
|
+
async def _process_export_queue(self) -> None:
|
|
98
|
+
"""Process export task with debounce."""
|
|
99
|
+
if not self.config.enabled:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
while not self._shutdown_requested:
|
|
103
|
+
# Check if debounce time has passed
|
|
104
|
+
now = time.time()
|
|
105
|
+
elapsed = now - self._last_change_time
|
|
106
|
+
|
|
107
|
+
if elapsed >= self.config.export_debounce:
|
|
108
|
+
try:
|
|
109
|
+
await self.export_to_files()
|
|
110
|
+
return
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Error during memory sync export: {e}")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
# Wait for remaining debounce time
|
|
116
|
+
wait_time = max(0.1, self.config.export_debounce - elapsed)
|
|
117
|
+
await asyncio.sleep(wait_time)
|
|
118
|
+
|
|
119
|
+
def _get_export_path(self) -> Path:
|
|
120
|
+
"""Get the path for the memories.jsonl file.
|
|
121
|
+
|
|
122
|
+
Returns the export_path, resolving relative paths against the project context.
|
|
123
|
+
"""
|
|
124
|
+
if self.export_path.is_absolute():
|
|
125
|
+
return self.export_path
|
|
126
|
+
|
|
127
|
+
# Try to get project path from project context
|
|
128
|
+
try:
|
|
129
|
+
from gobby.utils.project_context import get_project_context
|
|
130
|
+
|
|
131
|
+
project_ctx = get_project_context()
|
|
132
|
+
if project_ctx and project_ctx.get("path"):
|
|
133
|
+
project_path = Path(project_ctx["path"]).expanduser().resolve()
|
|
134
|
+
return project_path / self.export_path
|
|
135
|
+
except Exception:
|
|
136
|
+
pass # nosec B110 - fall back to cwd if project context unavailable
|
|
137
|
+
|
|
138
|
+
# Fall back to current working directory
|
|
139
|
+
return Path.cwd() / self.export_path
|
|
140
|
+
|
|
141
|
+
async def import_from_files(self) -> int:
|
|
142
|
+
"""
|
|
143
|
+
Import memories from filesystem (one-time migration).
|
|
144
|
+
|
|
145
|
+
This is intended for migrating existing JSONL backup files into the
|
|
146
|
+
database. For ongoing memory storage, use the memory backend directly.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Count of imported memories
|
|
150
|
+
"""
|
|
151
|
+
if not self.config.enabled:
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
if not self.memory_manager:
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
memories_file = self._get_export_path()
|
|
158
|
+
if not memories_file.exists():
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
return await asyncio.to_thread(self._import_memories_sync, memories_file)
|
|
162
|
+
|
|
163
|
+
def backup_sync(self) -> int:
|
|
164
|
+
"""
|
|
165
|
+
Backup memories to filesystem synchronously (blocking).
|
|
166
|
+
|
|
167
|
+
Used to force a backup write before the async loop starts.
|
|
168
|
+
This is a one-way export for backup purposes only.
|
|
169
|
+
"""
|
|
170
|
+
if not self.config.enabled:
|
|
171
|
+
return 0
|
|
172
|
+
|
|
173
|
+
if not self.memory_manager:
|
|
174
|
+
return 0
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
memories_file = self._get_export_path()
|
|
178
|
+
return self._export_to_files_sync(memories_file)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.warning(f"Failed to backup memories: {e}")
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
# Backward compatibility alias
|
|
184
|
+
export_sync = backup_sync
|
|
185
|
+
|
|
186
|
+
async def export_to_files(self) -> int:
|
|
187
|
+
"""
|
|
188
|
+
Backup memories to filesystem as JSONL.
|
|
189
|
+
|
|
190
|
+
This exports all memories to a JSONL file for backup purposes.
|
|
191
|
+
The file can be used for disaster recovery or migration.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Count of backed up memories
|
|
195
|
+
"""
|
|
196
|
+
if not self.config.enabled:
|
|
197
|
+
return 0
|
|
198
|
+
|
|
199
|
+
if not self.memory_manager:
|
|
200
|
+
return 0
|
|
201
|
+
|
|
202
|
+
memories_file = self._get_export_path()
|
|
203
|
+
return await asyncio.to_thread(self._export_to_files_sync, memories_file)
|
|
204
|
+
|
|
205
|
+
def _export_to_files_sync(self, memories_file: Path) -> int:
|
|
206
|
+
"""Synchronous implementation of export."""
|
|
207
|
+
memories_file.parent.mkdir(parents=True, exist_ok=True)
|
|
208
|
+
return self._export_memories_sync(memories_file)
|
|
209
|
+
|
|
210
|
+
def _import_memories_sync(self, file_path: Path) -> int:
|
|
211
|
+
"""Import memories from JSONL file (sync)."""
|
|
212
|
+
if not self.memory_manager:
|
|
213
|
+
return 0
|
|
214
|
+
|
|
215
|
+
count = 0
|
|
216
|
+
skipped = 0
|
|
217
|
+
try:
|
|
218
|
+
with open(file_path, encoding="utf-8") as f:
|
|
219
|
+
for line in f:
|
|
220
|
+
if not line.strip():
|
|
221
|
+
continue
|
|
222
|
+
try:
|
|
223
|
+
data = json.loads(line)
|
|
224
|
+
content = data.get("content", "")
|
|
225
|
+
|
|
226
|
+
# Skip if memory with identical content already exists
|
|
227
|
+
if self.memory_manager.content_exists(content):
|
|
228
|
+
skipped += 1
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
# Use storage directly for sync import (skip auto-embedding)
|
|
232
|
+
self.memory_manager.storage.create_memory(
|
|
233
|
+
content=content,
|
|
234
|
+
memory_type=data.get("type", "fact"),
|
|
235
|
+
tags=data.get("tags", []),
|
|
236
|
+
importance=data.get("importance", 0.5),
|
|
237
|
+
source_type=data.get("source", "import"),
|
|
238
|
+
source_session_id=data.get("source_id"),
|
|
239
|
+
)
|
|
240
|
+
count += 1
|
|
241
|
+
except json.JSONDecodeError:
|
|
242
|
+
logger.warning(f"Invalid JSON in memories file: {line[:50]}...")
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.debug(f"Skipping memory import: {e}")
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
logger.error(f"Failed to import memories: {e}")
|
|
248
|
+
|
|
249
|
+
if skipped > 0:
|
|
250
|
+
logger.debug(f"Skipped {skipped} duplicate memories during import")
|
|
251
|
+
|
|
252
|
+
return count
|
|
253
|
+
|
|
254
|
+
def _export_memories_sync(self, file_path: Path) -> int:
|
|
255
|
+
"""Export memories to JSONL file (sync)."""
|
|
256
|
+
if not self.memory_manager:
|
|
257
|
+
return 0
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
memories = self.memory_manager.list_memories()
|
|
261
|
+
|
|
262
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
263
|
+
for memory in memories:
|
|
264
|
+
data = {
|
|
265
|
+
"id": memory.id,
|
|
266
|
+
"content": memory.content,
|
|
267
|
+
"type": memory.memory_type,
|
|
268
|
+
"importance": memory.importance,
|
|
269
|
+
"tags": memory.tags,
|
|
270
|
+
"created_at": memory.created_at,
|
|
271
|
+
"updated_at": memory.updated_at,
|
|
272
|
+
"source": memory.source_type,
|
|
273
|
+
"source_id": memory.source_session_id,
|
|
274
|
+
}
|
|
275
|
+
f.write(json.dumps(data, ensure_ascii=False) + "\n")
|
|
276
|
+
|
|
277
|
+
return len(memories)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
logger.error(f"Failed to export memories: {e}")
|
|
280
|
+
return 0
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# Backward compatibility alias
|
|
284
|
+
MemorySyncManager = MemoryBackupManager
|