gobby 0.2.8__py3-none-any.whl → 0.2.11__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 +1 -1
- gobby/adapters/__init__.py +6 -0
- gobby/adapters/base.py +11 -2
- gobby/adapters/claude_code.py +5 -28
- gobby/adapters/codex_impl/adapter.py +38 -43
- gobby/adapters/copilot.py +324 -0
- gobby/adapters/cursor.py +373 -0
- gobby/adapters/gemini.py +2 -26
- gobby/adapters/windsurf.py +359 -0
- gobby/agents/definitions.py +162 -2
- gobby/agents/isolation.py +33 -1
- gobby/agents/pty_reader.py +192 -0
- gobby/agents/registry.py +10 -1
- gobby/agents/runner.py +24 -8
- gobby/agents/sandbox.py +8 -3
- gobby/agents/session.py +4 -0
- gobby/agents/spawn.py +9 -2
- gobby/agents/spawn_executor.py +49 -61
- gobby/agents/spawners/command_builder.py +4 -4
- gobby/app_context.py +64 -0
- gobby/cli/__init__.py +4 -0
- gobby/cli/install.py +259 -4
- gobby/cli/installers/__init__.py +12 -0
- gobby/cli/installers/copilot.py +242 -0
- gobby/cli/installers/cursor.py +244 -0
- gobby/cli/installers/shared.py +3 -0
- gobby/cli/installers/windsurf.py +242 -0
- gobby/cli/pipelines.py +639 -0
- gobby/cli/sessions.py +3 -1
- gobby/cli/skills.py +209 -0
- gobby/cli/tasks/crud.py +6 -5
- gobby/cli/tasks/search.py +1 -1
- gobby/cli/ui.py +116 -0
- gobby/cli/utils.py +5 -17
- gobby/cli/workflows.py +38 -17
- gobby/config/app.py +5 -0
- gobby/config/features.py +0 -20
- gobby/config/skills.py +23 -2
- gobby/config/tasks.py +4 -0
- gobby/hooks/broadcaster.py +9 -0
- gobby/hooks/event_handlers/__init__.py +155 -0
- gobby/hooks/event_handlers/_agent.py +175 -0
- gobby/hooks/event_handlers/_base.py +92 -0
- gobby/hooks/event_handlers/_misc.py +66 -0
- gobby/hooks/event_handlers/_session.py +487 -0
- gobby/hooks/event_handlers/_tool.py +196 -0
- gobby/hooks/events.py +48 -0
- gobby/hooks/hook_manager.py +27 -3
- gobby/install/copilot/hooks/hook_dispatcher.py +203 -0
- gobby/install/cursor/hooks/hook_dispatcher.py +203 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +8 -0
- gobby/install/windsurf/hooks/hook_dispatcher.py +205 -0
- gobby/llm/__init__.py +14 -1
- gobby/llm/claude.py +594 -43
- gobby/llm/service.py +149 -0
- gobby/mcp_proxy/importer.py +4 -41
- gobby/mcp_proxy/instructions.py +9 -27
- gobby/mcp_proxy/manager.py +13 -3
- gobby/mcp_proxy/models.py +1 -0
- gobby/mcp_proxy/registries.py +66 -5
- gobby/mcp_proxy/server.py +6 -2
- gobby/mcp_proxy/services/recommendation.py +2 -28
- gobby/mcp_proxy/services/tool_filter.py +7 -0
- gobby/mcp_proxy/services/tool_proxy.py +19 -1
- gobby/mcp_proxy/stdio.py +37 -21
- gobby/mcp_proxy/tools/agents.py +7 -0
- gobby/mcp_proxy/tools/artifacts.py +3 -3
- gobby/mcp_proxy/tools/hub.py +30 -1
- gobby/mcp_proxy/tools/orchestration/cleanup.py +5 -5
- gobby/mcp_proxy/tools/orchestration/monitor.py +1 -1
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +8 -3
- gobby/mcp_proxy/tools/orchestration/review.py +17 -4
- gobby/mcp_proxy/tools/orchestration/wait.py +7 -7
- gobby/mcp_proxy/tools/pipelines/__init__.py +254 -0
- gobby/mcp_proxy/tools/pipelines/_discovery.py +67 -0
- gobby/mcp_proxy/tools/pipelines/_execution.py +281 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +4 -4
- gobby/mcp_proxy/tools/sessions/_handoff.py +1 -1
- gobby/mcp_proxy/tools/skills/__init__.py +184 -30
- gobby/mcp_proxy/tools/spawn_agent.py +229 -14
- gobby/mcp_proxy/tools/task_readiness.py +27 -4
- gobby/mcp_proxy/tools/tasks/_context.py +8 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +27 -1
- gobby/mcp_proxy/tools/tasks/_helpers.py +1 -1
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +125 -8
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +2 -1
- gobby/mcp_proxy/tools/tasks/_search.py +1 -1
- gobby/mcp_proxy/tools/workflows/__init__.py +273 -0
- gobby/mcp_proxy/tools/workflows/_artifacts.py +225 -0
- gobby/mcp_proxy/tools/workflows/_import.py +112 -0
- gobby/mcp_proxy/tools/workflows/_lifecycle.py +332 -0
- gobby/mcp_proxy/tools/workflows/_query.py +226 -0
- gobby/mcp_proxy/tools/workflows/_resolution.py +78 -0
- gobby/mcp_proxy/tools/workflows/_terminal.py +175 -0
- gobby/mcp_proxy/tools/worktrees.py +54 -15
- gobby/memory/components/__init__.py +0 -0
- gobby/memory/components/ingestion.py +98 -0
- gobby/memory/components/search.py +108 -0
- gobby/memory/context.py +5 -5
- gobby/memory/manager.py +16 -25
- gobby/paths.py +51 -0
- gobby/prompts/loader.py +1 -35
- gobby/runner.py +131 -16
- gobby/servers/http.py +193 -150
- gobby/servers/routes/__init__.py +2 -0
- gobby/servers/routes/admin.py +56 -0
- gobby/servers/routes/mcp/endpoints/execution.py +33 -32
- gobby/servers/routes/mcp/endpoints/registry.py +8 -8
- gobby/servers/routes/mcp/hooks.py +10 -1
- gobby/servers/routes/pipelines.py +227 -0
- gobby/servers/websocket.py +314 -1
- gobby/sessions/analyzer.py +89 -3
- gobby/sessions/manager.py +5 -5
- gobby/sessions/transcripts/__init__.py +3 -0
- gobby/sessions/transcripts/claude.py +5 -0
- gobby/sessions/transcripts/codex.py +5 -0
- gobby/sessions/transcripts/gemini.py +5 -0
- gobby/skills/hubs/__init__.py +25 -0
- gobby/skills/hubs/base.py +234 -0
- gobby/skills/hubs/claude_plugins.py +328 -0
- gobby/skills/hubs/clawdhub.py +289 -0
- gobby/skills/hubs/github_collection.py +465 -0
- gobby/skills/hubs/manager.py +263 -0
- gobby/skills/hubs/skillhub.py +342 -0
- gobby/skills/parser.py +23 -0
- gobby/skills/sync.py +5 -4
- gobby/storage/artifacts.py +19 -0
- gobby/storage/memories.py +4 -4
- gobby/storage/migrations.py +118 -3
- gobby/storage/pipelines.py +367 -0
- gobby/storage/sessions.py +23 -4
- gobby/storage/skills.py +48 -8
- gobby/storage/tasks/_aggregates.py +2 -2
- gobby/storage/tasks/_lifecycle.py +4 -4
- gobby/storage/tasks/_models.py +7 -1
- gobby/storage/tasks/_queries.py +3 -3
- gobby/sync/memories.py +4 -3
- gobby/tasks/commits.py +48 -17
- gobby/tasks/external_validator.py +4 -17
- gobby/tasks/validation.py +13 -87
- gobby/tools/summarizer.py +18 -51
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +80 -0
- gobby/workflows/context_actions.py +265 -27
- gobby/workflows/definitions.py +119 -1
- gobby/workflows/detection_helpers.py +23 -11
- gobby/workflows/enforcement/__init__.py +11 -1
- gobby/workflows/enforcement/blocking.py +96 -0
- gobby/workflows/enforcement/handlers.py +35 -1
- gobby/workflows/enforcement/task_policy.py +18 -0
- gobby/workflows/engine.py +26 -4
- gobby/workflows/evaluator.py +8 -5
- gobby/workflows/lifecycle_evaluator.py +59 -27
- gobby/workflows/loader.py +567 -30
- gobby/workflows/lobster_compat.py +147 -0
- gobby/workflows/pipeline_executor.py +801 -0
- gobby/workflows/pipeline_state.py +172 -0
- gobby/workflows/pipeline_webhooks.py +206 -0
- gobby/workflows/premature_stop.py +5 -0
- gobby/worktrees/git.py +135 -20
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/METADATA +56 -22
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/RECORD +166 -122
- gobby/hooks/event_handlers.py +0 -1008
- gobby/mcp_proxy/tools/workflows.py +0 -1023
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/WHEEL +0 -0
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Component for handling Memory Manager's multimodal ingestion logic.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from gobby.memory.ingestion import MultimodalIngestor
|
|
11
|
+
from gobby.memory.protocol import MemoryBackendProtocol
|
|
12
|
+
from gobby.storage.memories import LocalMemoryManager, Memory
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from gobby.llm.service import LLMService
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class IngestionService:
|
|
21
|
+
"""Service for handling memory ingestion, particularly multimodal content."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
storage: LocalMemoryManager,
|
|
26
|
+
backend: MemoryBackendProtocol,
|
|
27
|
+
llm_service: LLMService | None = None,
|
|
28
|
+
):
|
|
29
|
+
self.storage = storage
|
|
30
|
+
self._backend = backend
|
|
31
|
+
self._llm_service = llm_service
|
|
32
|
+
|
|
33
|
+
self._multimodal_ingestor = MultimodalIngestor(
|
|
34
|
+
storage=storage,
|
|
35
|
+
backend=backend,
|
|
36
|
+
llm_service=llm_service,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def llm_service(self) -> LLMService | None:
|
|
41
|
+
"""Get the LLM service."""
|
|
42
|
+
return self._llm_service
|
|
43
|
+
|
|
44
|
+
@llm_service.setter
|
|
45
|
+
def llm_service(self, service: LLMService | None) -> None:
|
|
46
|
+
"""Set the LLM service and propagate to ingestor."""
|
|
47
|
+
self._llm_service = service
|
|
48
|
+
self._multimodal_ingestor.llm_service = service
|
|
49
|
+
|
|
50
|
+
async def remember_with_image(
|
|
51
|
+
self,
|
|
52
|
+
image_path: str,
|
|
53
|
+
context: str | None = None,
|
|
54
|
+
memory_type: str = "fact",
|
|
55
|
+
importance: float = 0.5,
|
|
56
|
+
project_id: str | None = None,
|
|
57
|
+
source_type: str = "user",
|
|
58
|
+
source_session_id: str | None = None,
|
|
59
|
+
tags: list[str] | None = None,
|
|
60
|
+
) -> Memory:
|
|
61
|
+
"""
|
|
62
|
+
Store a memory with an image attachment.
|
|
63
|
+
"""
|
|
64
|
+
return await self._multimodal_ingestor.remember_with_image(
|
|
65
|
+
image_path=image_path,
|
|
66
|
+
context=context,
|
|
67
|
+
memory_type=memory_type,
|
|
68
|
+
importance=importance,
|
|
69
|
+
project_id=project_id,
|
|
70
|
+
source_type=source_type,
|
|
71
|
+
source_session_id=source_session_id,
|
|
72
|
+
tags=tags,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async def remember_screenshot(
|
|
76
|
+
self,
|
|
77
|
+
screenshot_bytes: bytes,
|
|
78
|
+
context: str | None = None,
|
|
79
|
+
memory_type: str = "observation",
|
|
80
|
+
importance: float = 0.5,
|
|
81
|
+
project_id: str | None = None,
|
|
82
|
+
source_type: str = "user",
|
|
83
|
+
source_session_id: str | None = None,
|
|
84
|
+
tags: list[str] | None = None,
|
|
85
|
+
) -> Memory:
|
|
86
|
+
"""
|
|
87
|
+
Store a memory from raw screenshot bytes.
|
|
88
|
+
"""
|
|
89
|
+
return await self._multimodal_ingestor.remember_screenshot(
|
|
90
|
+
screenshot_bytes=screenshot_bytes,
|
|
91
|
+
context=context,
|
|
92
|
+
memory_type=memory_type,
|
|
93
|
+
importance=importance,
|
|
94
|
+
project_id=project_id,
|
|
95
|
+
source_type=source_type,
|
|
96
|
+
source_session_id=source_session_id,
|
|
97
|
+
tags=tags,
|
|
98
|
+
)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Component for handling Memory Manager's search and cross-referencing logic.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from gobby.config.persistence import MemoryConfig
|
|
11
|
+
from gobby.memory.search.coordinator import SearchCoordinator
|
|
12
|
+
from gobby.memory.services.crossref import CrossrefService
|
|
13
|
+
from gobby.storage.database import DatabaseProtocol
|
|
14
|
+
from gobby.storage.memories import LocalMemoryManager, Memory
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SearchService:
|
|
20
|
+
"""Service for handling memory search and cross-referencing."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
storage: LocalMemoryManager,
|
|
25
|
+
config: MemoryConfig,
|
|
26
|
+
db: DatabaseProtocol,
|
|
27
|
+
):
|
|
28
|
+
self.storage = storage
|
|
29
|
+
self.config = config
|
|
30
|
+
|
|
31
|
+
self._search_coordinator = SearchCoordinator(
|
|
32
|
+
storage=storage,
|
|
33
|
+
config=config,
|
|
34
|
+
db=db,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
self._crossref_service = CrossrefService(
|
|
38
|
+
storage=storage,
|
|
39
|
+
config=config,
|
|
40
|
+
search_backend_getter=lambda: self._search_coordinator.search_backend,
|
|
41
|
+
ensure_fitted=self._search_coordinator.ensure_fitted,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def backend(self) -> Any:
|
|
46
|
+
"""Get the underlying search backend."""
|
|
47
|
+
return self._search_coordinator.search_backend
|
|
48
|
+
|
|
49
|
+
def ensure_fitted(self) -> None:
|
|
50
|
+
"""Ensure the search backend is fitted with current memories."""
|
|
51
|
+
self._search_coordinator.ensure_fitted()
|
|
52
|
+
|
|
53
|
+
def mark_refit_needed(self) -> None:
|
|
54
|
+
"""Mark that the search backend needs to be refitted."""
|
|
55
|
+
self._search_coordinator.mark_refit_needed()
|
|
56
|
+
|
|
57
|
+
def reindex(self) -> dict[str, Any]:
|
|
58
|
+
"""Force rebuild of the search index."""
|
|
59
|
+
return self._search_coordinator.reindex()
|
|
60
|
+
|
|
61
|
+
def search(
|
|
62
|
+
self,
|
|
63
|
+
query: str,
|
|
64
|
+
project_id: str | None = None,
|
|
65
|
+
limit: int = 10,
|
|
66
|
+
min_importance: float | None = None,
|
|
67
|
+
search_mode: str | None = None,
|
|
68
|
+
tags_all: list[str] | None = None,
|
|
69
|
+
tags_any: list[str] | None = None,
|
|
70
|
+
tags_none: list[str] | None = None,
|
|
71
|
+
) -> list[Memory]:
|
|
72
|
+
"""Perform search using the configured search backend."""
|
|
73
|
+
return self._search_coordinator.search(
|
|
74
|
+
query=query,
|
|
75
|
+
project_id=project_id,
|
|
76
|
+
limit=limit,
|
|
77
|
+
min_importance=min_importance,
|
|
78
|
+
search_mode=search_mode,
|
|
79
|
+
tags_all=tags_all,
|
|
80
|
+
tags_any=tags_any,
|
|
81
|
+
tags_none=tags_none,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
async def create_crossrefs(
|
|
85
|
+
self,
|
|
86
|
+
memory: Memory,
|
|
87
|
+
threshold: float | None = None,
|
|
88
|
+
max_links: int | None = None,
|
|
89
|
+
) -> int:
|
|
90
|
+
"""Find and link similar memories."""
|
|
91
|
+
return await self._crossref_service.create_crossrefs(
|
|
92
|
+
memory=memory,
|
|
93
|
+
threshold=threshold,
|
|
94
|
+
max_links=max_links,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
async def get_related(
|
|
98
|
+
self,
|
|
99
|
+
memory_id: str,
|
|
100
|
+
limit: int = 5,
|
|
101
|
+
min_similarity: float = 0.0,
|
|
102
|
+
) -> list[Memory]:
|
|
103
|
+
"""Get memories linked to this one via cross-references."""
|
|
104
|
+
return await self._crossref_service.get_related(
|
|
105
|
+
memory_id=memory_id,
|
|
106
|
+
limit=limit,
|
|
107
|
+
min_similarity=min_similarity,
|
|
108
|
+
)
|
gobby/memory/context.py
CHANGED
|
@@ -50,14 +50,14 @@ def build_memory_context(memories: list[Memory]) -> str:
|
|
|
50
50
|
|
|
51
51
|
# 1. Project Context
|
|
52
52
|
if context_memories:
|
|
53
|
-
parts.append("## Project Context
|
|
53
|
+
parts.append("## Project Context")
|
|
54
54
|
for mem in context_memories:
|
|
55
|
-
parts.append(
|
|
55
|
+
parts.append(mem.content)
|
|
56
56
|
parts.append("")
|
|
57
57
|
|
|
58
58
|
# 2. Preferences
|
|
59
59
|
if pref_memories:
|
|
60
|
-
parts.append("## Preferences
|
|
60
|
+
parts.append("## Preferences")
|
|
61
61
|
for mem in pref_memories:
|
|
62
62
|
content = _strip_leading_bullet(mem.content)
|
|
63
63
|
if content: # Skip empty content
|
|
@@ -66,7 +66,7 @@ def build_memory_context(memories: list[Memory]) -> str:
|
|
|
66
66
|
|
|
67
67
|
# 3. Patterns
|
|
68
68
|
if pattern_memories:
|
|
69
|
-
parts.append("## Patterns
|
|
69
|
+
parts.append("## Patterns")
|
|
70
70
|
for mem in pattern_memories:
|
|
71
71
|
content = _strip_leading_bullet(mem.content)
|
|
72
72
|
if content: # Skip empty content
|
|
@@ -75,7 +75,7 @@ def build_memory_context(memories: list[Memory]) -> str:
|
|
|
75
75
|
|
|
76
76
|
# 4. Facts/Other
|
|
77
77
|
if fact_memories:
|
|
78
|
-
parts.append("## Facts
|
|
78
|
+
parts.append("## Facts")
|
|
79
79
|
for mem in fact_memories:
|
|
80
80
|
content = _strip_leading_bullet(mem.content)
|
|
81
81
|
if content: # Skip empty content
|
gobby/memory/manager.py
CHANGED
|
@@ -6,11 +6,10 @@ from typing import TYPE_CHECKING, Any
|
|
|
6
6
|
|
|
7
7
|
from gobby.config.persistence import MemoryConfig
|
|
8
8
|
from gobby.memory.backends import get_backend
|
|
9
|
+
from gobby.memory.components.ingestion import IngestionService
|
|
10
|
+
from gobby.memory.components.search import SearchService
|
|
9
11
|
from gobby.memory.context import build_memory_context
|
|
10
|
-
from gobby.memory.ingestion import MultimodalIngestor
|
|
11
12
|
from gobby.memory.protocol import MemoryBackendProtocol
|
|
12
|
-
from gobby.memory.search.coordinator import SearchCoordinator
|
|
13
|
-
from gobby.memory.services.crossref import CrossrefService
|
|
14
13
|
from gobby.storage.database import DatabaseProtocol
|
|
15
14
|
from gobby.storage.memories import LocalMemoryManager, Memory
|
|
16
15
|
|
|
@@ -46,20 +45,13 @@ class MemoryManager:
|
|
|
46
45
|
self.storage = LocalMemoryManager(db)
|
|
47
46
|
|
|
48
47
|
# Initialize extracted components
|
|
49
|
-
self.
|
|
48
|
+
self._search_service = SearchService(
|
|
50
49
|
storage=self.storage,
|
|
51
50
|
config=config,
|
|
52
51
|
db=db,
|
|
53
52
|
)
|
|
54
53
|
|
|
55
|
-
self.
|
|
56
|
-
storage=self.storage,
|
|
57
|
-
config=config,
|
|
58
|
-
search_backend_getter=lambda: self._search_coordinator.search_backend,
|
|
59
|
-
ensure_fitted=self._search_coordinator.ensure_fitted,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
self._multimodal_ingestor = MultimodalIngestor(
|
|
54
|
+
self._ingestion_service = IngestionService(
|
|
63
55
|
storage=self.storage,
|
|
64
56
|
backend=self._backend,
|
|
65
57
|
llm_service=llm_service,
|
|
@@ -68,14 +60,13 @@ class MemoryManager:
|
|
|
68
60
|
@property
|
|
69
61
|
def llm_service(self) -> LLMService | None:
|
|
70
62
|
"""Get the LLM service for image description."""
|
|
71
|
-
return self.
|
|
63
|
+
return self._ingestion_service.llm_service
|
|
72
64
|
|
|
73
65
|
@llm_service.setter
|
|
74
66
|
def llm_service(self, service: LLMService | None) -> None:
|
|
75
67
|
"""Set the LLM service for image description."""
|
|
76
68
|
self._llm_service = service
|
|
77
|
-
|
|
78
|
-
self._multimodal_ingestor.llm_service = service
|
|
69
|
+
self._ingestion_service.llm_service = service
|
|
79
70
|
|
|
80
71
|
@property
|
|
81
72
|
def search_backend(self) -> Any:
|
|
@@ -86,15 +77,15 @@ class MemoryManager:
|
|
|
86
77
|
- "tfidf" (default): Zero-dependency TF-IDF search
|
|
87
78
|
- "text": Simple text substring matching
|
|
88
79
|
"""
|
|
89
|
-
return self.
|
|
80
|
+
return self._search_service.backend
|
|
90
81
|
|
|
91
82
|
def _ensure_search_backend_fitted(self) -> None:
|
|
92
83
|
"""Ensure the search backend is fitted with current memories."""
|
|
93
|
-
self.
|
|
84
|
+
self._search_service.ensure_fitted()
|
|
94
85
|
|
|
95
86
|
def mark_search_refit_needed(self) -> None:
|
|
96
87
|
"""Mark that the search backend needs to be refitted."""
|
|
97
|
-
self.
|
|
88
|
+
self._search_service.mark_refit_needed()
|
|
98
89
|
|
|
99
90
|
def reindex_search(self) -> dict[str, Any]:
|
|
100
91
|
"""
|
|
@@ -109,7 +100,7 @@ class MemoryManager:
|
|
|
109
100
|
Returns:
|
|
110
101
|
Dict with index statistics including memory_count, backend_type, etc.
|
|
111
102
|
"""
|
|
112
|
-
return self.
|
|
103
|
+
return self._search_service.reindex()
|
|
113
104
|
|
|
114
105
|
async def remember(
|
|
115
106
|
self,
|
|
@@ -161,7 +152,7 @@ class MemoryManager:
|
|
|
161
152
|
# Auto cross-reference if enabled
|
|
162
153
|
if getattr(self.config, "auto_crossref", False):
|
|
163
154
|
try:
|
|
164
|
-
await self.
|
|
155
|
+
await self._search_service.create_crossrefs(memory)
|
|
165
156
|
except Exception as e:
|
|
166
157
|
# Don't fail the remember if crossref fails
|
|
167
158
|
logger.warning(f"Auto-crossref failed for {memory.id}: {e}")
|
|
@@ -202,7 +193,7 @@ class MemoryManager:
|
|
|
202
193
|
Raises:
|
|
203
194
|
ValueError: If LLM service is not configured or image not found
|
|
204
195
|
"""
|
|
205
|
-
memory = await self.
|
|
196
|
+
memory = await self._ingestion_service.remember_with_image(
|
|
206
197
|
image_path=image_path,
|
|
207
198
|
context=context,
|
|
208
199
|
memory_type=memory_type,
|
|
@@ -249,7 +240,7 @@ class MemoryManager:
|
|
|
249
240
|
Raises:
|
|
250
241
|
ValueError: If LLM service is not configured or screenshot bytes are empty
|
|
251
242
|
"""
|
|
252
|
-
memory = await self.
|
|
243
|
+
memory = await self._ingestion_service.remember_screenshot(
|
|
253
244
|
screenshot_bytes=screenshot_bytes,
|
|
254
245
|
context=context,
|
|
255
246
|
memory_type=memory_type,
|
|
@@ -283,7 +274,7 @@ class MemoryManager:
|
|
|
283
274
|
Returns:
|
|
284
275
|
Number of cross-references created
|
|
285
276
|
"""
|
|
286
|
-
return await self.
|
|
277
|
+
return await self._search_service.create_crossrefs(
|
|
287
278
|
memory=memory,
|
|
288
279
|
threshold=threshold,
|
|
289
280
|
max_links=max_links,
|
|
@@ -306,7 +297,7 @@ class MemoryManager:
|
|
|
306
297
|
Returns:
|
|
307
298
|
List of related Memory objects, sorted by similarity
|
|
308
299
|
"""
|
|
309
|
-
return await self.
|
|
300
|
+
return await self._search_service.get_related(
|
|
310
301
|
memory_id=memory_id,
|
|
311
302
|
limit=limit,
|
|
312
303
|
min_similarity=min_similarity,
|
|
@@ -398,7 +389,7 @@ class MemoryManager:
|
|
|
398
389
|
if use_semantic is not None:
|
|
399
390
|
logger.warning("use_semantic argument is deprecated and ignored")
|
|
400
391
|
|
|
401
|
-
return self.
|
|
392
|
+
return self._search_service.search(
|
|
402
393
|
query=query,
|
|
403
394
|
project_id=project_id,
|
|
404
395
|
limit=limit,
|
gobby/paths.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core path utilities for Gobby package.
|
|
3
|
+
|
|
4
|
+
This module provides stable path resolution utilities that work in both
|
|
5
|
+
development (source) and installed (package) modes without CLI dependencies.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
__all__ = ["get_package_root", "get_install_dir"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_package_root() -> Path:
|
|
14
|
+
"""Get the root directory of the gobby package.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Path to src/gobby/ (the package root directory)
|
|
18
|
+
"""
|
|
19
|
+
import gobby
|
|
20
|
+
|
|
21
|
+
return Path(gobby.__file__).parent
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_install_dir() -> Path:
|
|
25
|
+
"""Get the gobby install directory.
|
|
26
|
+
|
|
27
|
+
Checks for source directory (development mode) first,
|
|
28
|
+
falls back to package directory. This handles both:
|
|
29
|
+
- Development: src/gobby/install/
|
|
30
|
+
- Installed package: <site-packages>/gobby/install/
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Path to the install directory
|
|
34
|
+
"""
|
|
35
|
+
import gobby
|
|
36
|
+
|
|
37
|
+
package_install_dir = Path(gobby.__file__).parent / "install"
|
|
38
|
+
|
|
39
|
+
# Try to find source directory (project root) for development mode
|
|
40
|
+
current = Path(gobby.__file__).resolve()
|
|
41
|
+
source_install_dir = None
|
|
42
|
+
|
|
43
|
+
for parent in current.parents:
|
|
44
|
+
potential_source = parent / "src" / "gobby" / "install"
|
|
45
|
+
if potential_source.exists():
|
|
46
|
+
source_install_dir = potential_source
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
if source_install_dir and source_install_dir.exists():
|
|
50
|
+
return source_install_dir
|
|
51
|
+
return package_install_dir
|
gobby/prompts/loader.py
CHANGED
|
@@ -12,7 +12,6 @@ Implements prompt loading with precedence:
|
|
|
12
12
|
|
|
13
13
|
import logging
|
|
14
14
|
import re
|
|
15
|
-
from collections.abc import Callable
|
|
16
15
|
from functools import lru_cache
|
|
17
16
|
from pathlib import Path
|
|
18
17
|
from typing import Any
|
|
@@ -66,21 +65,6 @@ class PromptLoader:
|
|
|
66
65
|
# Template cache
|
|
67
66
|
self._cache: dict[str, PromptTemplate] = {}
|
|
68
67
|
|
|
69
|
-
# Fallback registry for strangler fig pattern
|
|
70
|
-
self._fallbacks: dict[str, Callable[[], str]] = {}
|
|
71
|
-
|
|
72
|
-
def register_fallback(self, path: str, getter: Callable[[], str]) -> None:
|
|
73
|
-
"""Register a Python constant fallback for a template path.
|
|
74
|
-
|
|
75
|
-
Used for strangler fig pattern - if template file doesn't exist,
|
|
76
|
-
fall back to the original Python constant.
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
path: Template path (e.g., "expansion/system")
|
|
80
|
-
getter: Callable that returns the fallback string
|
|
81
|
-
"""
|
|
82
|
-
self._fallbacks[path] = getter
|
|
83
|
-
|
|
84
68
|
def clear_cache(self) -> None:
|
|
85
69
|
"""Clear the template cache."""
|
|
86
70
|
self._cache.clear()
|
|
@@ -162,19 +146,6 @@ class PromptLoader:
|
|
|
162
146
|
logger.debug(f"Loaded prompt template '{path}' from {template_file}")
|
|
163
147
|
return template
|
|
164
148
|
|
|
165
|
-
# Fall back to registered Python constant
|
|
166
|
-
if path in self._fallbacks:
|
|
167
|
-
fallback_content = self._fallbacks[path]()
|
|
168
|
-
template = PromptTemplate(
|
|
169
|
-
name=path,
|
|
170
|
-
description=f"Fallback for {path}",
|
|
171
|
-
content=fallback_content,
|
|
172
|
-
source_path=None,
|
|
173
|
-
)
|
|
174
|
-
self._cache[path] = template
|
|
175
|
-
logger.debug(f"Using fallback for prompt template '{path}'")
|
|
176
|
-
return template
|
|
177
|
-
|
|
178
149
|
raise FileNotFoundError(f"Prompt template not found: {path}")
|
|
179
150
|
|
|
180
151
|
def render(
|
|
@@ -280,7 +251,7 @@ class PromptLoader:
|
|
|
280
251
|
Returns:
|
|
281
252
|
True if template exists (file or fallback)
|
|
282
253
|
"""
|
|
283
|
-
return self._find_template_file(path) is not None
|
|
254
|
+
return self._find_template_file(path) is not None
|
|
284
255
|
|
|
285
256
|
def list_templates(self, category: str | None = None) -> list[str]:
|
|
286
257
|
"""List available template paths.
|
|
@@ -305,11 +276,6 @@ class PromptLoader:
|
|
|
305
276
|
if category is None or template_path.startswith(f"{category}/"):
|
|
306
277
|
templates.add(template_path)
|
|
307
278
|
|
|
308
|
-
# Add registered fallbacks
|
|
309
|
-
for fallback_path in self._fallbacks:
|
|
310
|
-
if category is None or fallback_path.startswith(f"{category}/"):
|
|
311
|
-
templates.add(fallback_path)
|
|
312
|
-
|
|
313
279
|
return sorted(templates)
|
|
314
280
|
|
|
315
281
|
|