agentpool 2.1.9__py3-none-any.whl → 2.5.0__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.
- acp/__init__.py +13 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +20 -50
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/stdio.py +39 -9
- acp/task/supervisor.py +2 -2
- acp/transports.py +362 -2
- acp/utils.py +17 -4
- agentpool/__init__.py +6 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +407 -277
- agentpool/agents/acp_agent/acp_converters.py +196 -38
- agentpool/agents/acp_agent/client_handler.py +191 -26
- agentpool/agents/acp_agent/session_state.py +17 -6
- agentpool/agents/agent.py +607 -572
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +176 -110
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +632 -17
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
- agentpool/agents/claude_code_agent/converters.py +74 -143
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +24 -0
- agentpool/agents/events/builtin_handlers.py +67 -1
- agentpool/agents/events/event_emitter.py +32 -2
- agentpool/agents/events/events.py +104 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +67 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +56 -21
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +10 -6
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +136 -289
- agentpool/delegation/team.py +58 -57
- agentpool/delegation/teamrun.py +51 -55
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +76 -32
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +34 -54
- agentpool/mcp_server/registries/official_registry_client.py +35 -1
- agentpool/mcp_server/tool_bridge.py +186 -139
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +99 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +54 -35
- agentpool/messaging/processing.py +12 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -24
- agentpool/models/acp_agents/mcp_capable.py +126 -157
- agentpool/models/acp_agents/non_mcp.py +129 -95
- agentpool/models/agents.py +98 -76
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +144 -19
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +113 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +11 -1
- agentpool/resource_providers/aggregating.py +56 -5
- agentpool/resource_providers/base.py +70 -4
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +89 -12
- agentpool/resource_providers/plan_provider.py +228 -46
- agentpool/resource_providers/pool.py +7 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +4 -2
- agentpool/sessions/__init__.py +4 -1
- agentpool/sessions/manager.py +33 -5
- agentpool/sessions/models.py +59 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +572 -49
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +538 -20
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +616 -2
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
- agentpool-2.5.0.dist-info/RECORD +579 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +24 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +100 -21
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +42 -5
- agentpool_commands/agents.py +75 -2
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +80 -30
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +132 -6
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +82 -38
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -22
- agentpool_config/toolsets.py +109 -233
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +234 -181
- agentpool_server/acp_server/commands/acp_commands.py +151 -156
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +24 -90
- agentpool_server/acp_server/session.py +173 -331
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +401 -0
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +19 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +975 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +421 -0
- agentpool_server/opencode_server/models/__init__.py +250 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +72 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +821 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +44 -0
- agentpool_server/opencode_server/models/message.py +179 -0
- agentpool_server/opencode_server/models/parts.py +323 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +111 -0
- agentpool_server/opencode_server/routes/__init__.py +29 -0
- agentpool_server/opencode_server/routes/agent_routes.py +473 -0
- agentpool_server/opencode_server/routes/app_routes.py +202 -0
- agentpool_server/opencode_server/routes/config_routes.py +302 -0
- agentpool_server/opencode_server/routes/file_routes.py +571 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +761 -0
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +300 -0
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +1276 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +475 -0
- agentpool_server/opencode_server/state.py +151 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +12 -0
- agentpool_storage/base.py +184 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/claude_provider/provider.py +1089 -0
- agentpool_storage/file_provider.py +278 -15
- agentpool_storage/memory_provider.py +193 -12
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +26 -6
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +269 -3
- agentpool_storage/sql_provider/utils.py +12 -13
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -12
- agentpool_toolsets/builtin/code.py +96 -57
- agentpool_toolsets/builtin/debug.py +118 -48
- agentpool_toolsets/builtin/execution_environment.py +115 -230
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +9 -4
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +99 -7
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +511 -0
- agentpool_toolsets/mcp_run_toolset.py +87 -12
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool-2.1.9.dist-info/RECORD +0 -474
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/text_log_provider.py +0 -275
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""High-level project store with auto-detection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from agentpool.log import get_logger
|
|
10
|
+
from agentpool.sessions.models import ProjectData
|
|
11
|
+
from agentpool.utils.now import get_now
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from agentpool.storage.manager import StorageManager
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
# Config file names to search for in .agentpool/ directory
|
|
20
|
+
CONFIG_FILENAMES = ["config.yml", "config.yaml", "config.json", "config.toml"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def resolve_config(project: ProjectData | None = None, cwd: str | None = None) -> str | None:
|
|
24
|
+
"""Resolve config path using project settings and global fallback.
|
|
25
|
+
|
|
26
|
+
Resolution order:
|
|
27
|
+
1. Project's explicit config_path (if set)
|
|
28
|
+
2. .agentpool/config.yml in project worktree (auto-discovered)
|
|
29
|
+
3. Global default from ConfigStore (CLI fallback)
|
|
30
|
+
4. None (use built-in defaults)
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
project: Project data (optional)
|
|
34
|
+
cwd: Current working directory for discovery if no project
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Path to config file, or None if no config found
|
|
38
|
+
"""
|
|
39
|
+
# 1. Project-specific explicit config
|
|
40
|
+
if project and project.config_path:
|
|
41
|
+
config_path = Path(project.config_path)
|
|
42
|
+
if config_path.is_file():
|
|
43
|
+
return str(config_path)
|
|
44
|
+
logger.warning("Project config not found", path=project.config_path)
|
|
45
|
+
|
|
46
|
+
# 2. Auto-discover in project worktree
|
|
47
|
+
worktree = project.worktree if project else cwd
|
|
48
|
+
if worktree:
|
|
49
|
+
local_config = discover_config_path(worktree)
|
|
50
|
+
if local_config:
|
|
51
|
+
return local_config
|
|
52
|
+
|
|
53
|
+
# 3. Global default from ConfigStore
|
|
54
|
+
try:
|
|
55
|
+
from agentpool_cli.store import ConfigStore
|
|
56
|
+
|
|
57
|
+
config_store = ConfigStore()
|
|
58
|
+
if active := config_store.get_active():
|
|
59
|
+
config_path = Path(active.path)
|
|
60
|
+
if config_path.is_file():
|
|
61
|
+
return str(config_path)
|
|
62
|
+
logger.warning("Active config not found", path=active.path)
|
|
63
|
+
except ImportError:
|
|
64
|
+
# CLI not installed, skip fallback
|
|
65
|
+
pass
|
|
66
|
+
except Exception:
|
|
67
|
+
logger.exception("Error loading ConfigStore")
|
|
68
|
+
|
|
69
|
+
# 4. No config found
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def detect_project_root(cwd: str) -> tuple[str, str | None]:
|
|
74
|
+
"""Walk up directory tree to find VCS root or use cwd.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
cwd: Current working directory
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Tuple of (worktree_path, vcs_type).
|
|
81
|
+
vcs_type is "git", "hg", or None if no VCS found.
|
|
82
|
+
"""
|
|
83
|
+
path = Path(cwd).resolve()
|
|
84
|
+
for parent in [path, *path.parents]:
|
|
85
|
+
if (parent / ".git").exists():
|
|
86
|
+
return str(parent), "git"
|
|
87
|
+
if (parent / ".hg").exists():
|
|
88
|
+
return str(parent), "hg"
|
|
89
|
+
# No VCS found, use the original directory
|
|
90
|
+
return str(path), None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def generate_project_id(worktree: str) -> str:
|
|
94
|
+
"""Generate stable hash of canonical worktree path.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
worktree: Absolute path to project root
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
40-character hex string (SHA1 hash)
|
|
101
|
+
"""
|
|
102
|
+
canonical = str(Path(worktree).resolve())
|
|
103
|
+
return hashlib.sha1(canonical.encode()).hexdigest()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def discover_config_path(worktree: str) -> str | None:
|
|
107
|
+
"""Search for config file in .agentpool/ directory.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
worktree: Project root path
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Path to config file if found, None otherwise
|
|
114
|
+
"""
|
|
115
|
+
config_dir = Path(worktree) / ".agentpool"
|
|
116
|
+
if not config_dir.is_dir():
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
for filename in CONFIG_FILENAMES:
|
|
120
|
+
config_path = config_dir / filename
|
|
121
|
+
if config_path.is_file():
|
|
122
|
+
return str(config_path)
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ProjectStore:
|
|
127
|
+
"""High-level API for project management.
|
|
128
|
+
|
|
129
|
+
Provides:
|
|
130
|
+
- Auto-detection of project root from cwd
|
|
131
|
+
- Auto-creation of projects
|
|
132
|
+
- Config file discovery
|
|
133
|
+
- Convenient lookup methods
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(self, storage: StorageManager) -> None:
|
|
137
|
+
"""Initialize project store.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
storage: Storage manager for persistence
|
|
141
|
+
"""
|
|
142
|
+
self.storage = storage
|
|
143
|
+
|
|
144
|
+
async def get_or_create(self, cwd: str) -> ProjectData:
|
|
145
|
+
"""Get existing project or create one for the given directory.
|
|
146
|
+
|
|
147
|
+
Detects VCS root, generates project ID, discovers config,
|
|
148
|
+
and creates/updates the project.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
cwd: Current working directory
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
ProjectData for the detected/created project
|
|
155
|
+
"""
|
|
156
|
+
# Detect project root and VCS
|
|
157
|
+
worktree, vcs = detect_project_root(cwd)
|
|
158
|
+
|
|
159
|
+
# Check if project already exists
|
|
160
|
+
existing = await self.storage.get_project_by_worktree(worktree)
|
|
161
|
+
if existing:
|
|
162
|
+
# Update last_active and return
|
|
163
|
+
await self.storage.touch_project(existing.project_id)
|
|
164
|
+
return existing.touch()
|
|
165
|
+
|
|
166
|
+
# Create new project
|
|
167
|
+
project_id = generate_project_id(worktree)
|
|
168
|
+
config_path = discover_config_path(worktree)
|
|
169
|
+
|
|
170
|
+
project = ProjectData(
|
|
171
|
+
project_id=project_id,
|
|
172
|
+
worktree=worktree,
|
|
173
|
+
name=None, # Can be set later
|
|
174
|
+
vcs=vcs,
|
|
175
|
+
config_path=config_path,
|
|
176
|
+
created_at=get_now(),
|
|
177
|
+
last_active=get_now(),
|
|
178
|
+
settings={},
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
await self.storage.save_project(project)
|
|
182
|
+
logger.info(
|
|
183
|
+
"Created project",
|
|
184
|
+
project_id=project_id,
|
|
185
|
+
worktree=worktree,
|
|
186
|
+
vcs=vcs,
|
|
187
|
+
config_path=config_path,
|
|
188
|
+
)
|
|
189
|
+
return project
|
|
190
|
+
|
|
191
|
+
async def get_by_cwd(self, cwd: str) -> ProjectData | None:
|
|
192
|
+
"""Get project for the given directory without creating.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
cwd: Current working directory
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
ProjectData if found, None otherwise
|
|
199
|
+
"""
|
|
200
|
+
worktree, _ = detect_project_root(cwd)
|
|
201
|
+
return await self.storage.get_project_by_worktree(worktree)
|
|
202
|
+
|
|
203
|
+
async def get_by_name(self, name: str) -> ProjectData | None:
|
|
204
|
+
"""Get project by friendly name.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
name: Project name
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
ProjectData if found, None otherwise
|
|
211
|
+
"""
|
|
212
|
+
return await self.storage.get_project_by_name(name)
|
|
213
|
+
|
|
214
|
+
async def get_by_id(self, project_id: str) -> ProjectData | None:
|
|
215
|
+
"""Get project by ID.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
project_id: Project identifier
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
ProjectData if found, None otherwise
|
|
222
|
+
"""
|
|
223
|
+
return await self.storage.get_project(project_id)
|
|
224
|
+
|
|
225
|
+
async def list_recent(self, limit: int = 20) -> list[ProjectData]:
|
|
226
|
+
"""List recent projects ordered by last_active.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
limit: Maximum number of projects to return
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
List of projects
|
|
233
|
+
"""
|
|
234
|
+
projects = await self.storage.list_projects(limit=limit)
|
|
235
|
+
return projects if projects is not None else []
|
|
236
|
+
|
|
237
|
+
async def set_name(self, project_id: str, name: str) -> ProjectData | None:
|
|
238
|
+
"""Set friendly name for a project.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
project_id: Project identifier
|
|
242
|
+
name: New name for the project
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Updated ProjectData, or None if project not found
|
|
246
|
+
"""
|
|
247
|
+
project = await self.storage.get_project(project_id)
|
|
248
|
+
if not project:
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
updated = project.model_copy(update={"name": name, "last_active": get_now()})
|
|
252
|
+
await self.storage.save_project(updated)
|
|
253
|
+
return updated
|
|
254
|
+
|
|
255
|
+
async def set_config_path(self, project_id: str, config_path: str | None) -> ProjectData | None:
|
|
256
|
+
"""Set or clear config path for a project.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
project_id: Project identifier
|
|
260
|
+
config_path: Path to config file, or None to use auto-discovery
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Updated ProjectData, or None if project not found
|
|
264
|
+
"""
|
|
265
|
+
project = await self.storage.get_project(project_id)
|
|
266
|
+
if not project:
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
updated = project.model_copy(update={"config_path": config_path, "last_active": get_now()})
|
|
270
|
+
await self.storage.save_project(updated)
|
|
271
|
+
return updated
|
|
272
|
+
|
|
273
|
+
async def update_settings(self, project_id: str, **settings: object) -> ProjectData | None:
|
|
274
|
+
"""Update project-specific settings.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
project_id: Project identifier
|
|
278
|
+
**settings: Settings to update
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Updated ProjectData, or None if project not found
|
|
282
|
+
"""
|
|
283
|
+
project = await self.storage.get_project(project_id)
|
|
284
|
+
if not project:
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
updated = project.with_settings(**settings)
|
|
288
|
+
await self.storage.save_project(updated)
|
|
289
|
+
return updated
|
|
290
|
+
|
|
291
|
+
async def delete(self, project_id: str) -> bool:
|
|
292
|
+
"""Delete a project.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
project_id: Project identifier
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
True if deleted, False if not found
|
|
299
|
+
"""
|
|
300
|
+
result = await self.storage.delete_project(project_id)
|
|
301
|
+
return bool(result)
|
|
302
|
+
|
|
303
|
+
async def refresh_config(self, project_id: str) -> ProjectData | None:
|
|
304
|
+
"""Re-discover config file for a project.
|
|
305
|
+
|
|
306
|
+
Useful after user adds .agentpool/config.yml to their project.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
project_id: Project identifier
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Updated ProjectData, or None if project not found
|
|
313
|
+
"""
|
|
314
|
+
project = await self.storage.get_project(project_id)
|
|
315
|
+
if not project:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
config_path = discover_config_path(project.worktree)
|
|
319
|
+
if config_path != project.config_path:
|
|
320
|
+
updated = project.model_copy(
|
|
321
|
+
update={"config_path": config_path, "last_active": get_now()}
|
|
322
|
+
)
|
|
323
|
+
await self.storage.save_project(updated)
|
|
324
|
+
return updated
|
|
325
|
+
return project
|
|
@@ -5,10 +5,6 @@ from __future__ import annotations
|
|
|
5
5
|
from datetime import timedelta
|
|
6
6
|
from typing import TYPE_CHECKING, Any, Self
|
|
7
7
|
|
|
8
|
-
from sqlalchemy import delete, select
|
|
9
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
|
-
from sqlmodel import SQLModel
|
|
11
|
-
|
|
12
8
|
from agentpool.log import get_logger
|
|
13
9
|
from agentpool.sessions.models import SessionData
|
|
14
10
|
from agentpool.utils.now import get_now
|
|
@@ -45,6 +41,8 @@ class SQLSessionStore:
|
|
|
45
41
|
|
|
46
42
|
async def __aenter__(self) -> Self:
|
|
47
43
|
"""Initialize database connection and create tables."""
|
|
44
|
+
from sqlmodel import SQLModel
|
|
45
|
+
|
|
48
46
|
self._engine = self._config.get_engine()
|
|
49
47
|
|
|
50
48
|
async with self._engine.begin() as conn:
|
|
@@ -95,7 +93,9 @@ class SQLSessionStore:
|
|
|
95
93
|
agent_name=data.agent_name,
|
|
96
94
|
conversation_id=data.conversation_id,
|
|
97
95
|
pool_id=data.pool_id,
|
|
98
|
-
|
|
96
|
+
project_id=data.project_id,
|
|
97
|
+
parent_id=data.parent_id,
|
|
98
|
+
version=data.version,
|
|
99
99
|
cwd=data.cwd,
|
|
100
100
|
created_at=data.created_at,
|
|
101
101
|
last_active=data.last_active,
|
|
@@ -109,7 +109,9 @@ class SQLSessionStore:
|
|
|
109
109
|
agent_name=row.agent_name,
|
|
110
110
|
conversation_id=row.conversation_id,
|
|
111
111
|
pool_id=row.pool_id,
|
|
112
|
-
|
|
112
|
+
project_id=row.project_id,
|
|
113
|
+
parent_id=row.parent_id,
|
|
114
|
+
version=row.version,
|
|
113
115
|
cwd=row.cwd,
|
|
114
116
|
created_at=row.created_at,
|
|
115
117
|
last_active=row.last_active,
|
|
@@ -124,6 +126,9 @@ class SQLSessionStore:
|
|
|
124
126
|
Args:
|
|
125
127
|
data: Session data to persist
|
|
126
128
|
"""
|
|
129
|
+
from sqlalchemy import delete
|
|
130
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
131
|
+
|
|
127
132
|
engine = self._get_engine()
|
|
128
133
|
|
|
129
134
|
async with AsyncSession(engine) as session:
|
|
@@ -146,6 +151,9 @@ class SQLSessionStore:
|
|
|
146
151
|
Returns:
|
|
147
152
|
Session data if found, None otherwise
|
|
148
153
|
"""
|
|
154
|
+
from sqlalchemy import select
|
|
155
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
156
|
+
|
|
149
157
|
engine = self._get_engine()
|
|
150
158
|
|
|
151
159
|
async with AsyncSession(engine) as session:
|
|
@@ -167,6 +175,9 @@ class SQLSessionStore:
|
|
|
167
175
|
Returns:
|
|
168
176
|
True if session was deleted, False if not found
|
|
169
177
|
"""
|
|
178
|
+
from sqlalchemy import delete
|
|
179
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
180
|
+
|
|
170
181
|
engine = self._get_engine()
|
|
171
182
|
|
|
172
183
|
async with AsyncSession(engine) as session:
|
|
@@ -193,6 +204,9 @@ class SQLSessionStore:
|
|
|
193
204
|
Returns:
|
|
194
205
|
List of session IDs
|
|
195
206
|
"""
|
|
207
|
+
from sqlalchemy import select
|
|
208
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
209
|
+
|
|
196
210
|
engine = self._get_engine()
|
|
197
211
|
|
|
198
212
|
async with AsyncSession(engine) as session:
|
|
@@ -217,6 +231,9 @@ class SQLSessionStore:
|
|
|
217
231
|
Returns:
|
|
218
232
|
Number of sessions removed
|
|
219
233
|
"""
|
|
234
|
+
from sqlalchemy import delete
|
|
235
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
236
|
+
|
|
220
237
|
engine = self._get_engine()
|
|
221
238
|
cutoff = get_now() - timedelta(hours=max_age_hours)
|
|
222
239
|
|
|
@@ -244,6 +261,9 @@ class SQLSessionStore:
|
|
|
244
261
|
Returns:
|
|
245
262
|
List of session data objects
|
|
246
263
|
"""
|
|
264
|
+
from sqlalchemy import select
|
|
265
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
266
|
+
|
|
247
267
|
engine = self._get_engine()
|
|
248
268
|
|
|
249
269
|
async with AsyncSession(engine) as session:
|
|
@@ -4,11 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from agentpool_storage.sql_provider.sql_provider import SQLModelProvider
|
|
6
6
|
from agentpool_storage.sql_provider.models import (
|
|
7
|
+
CommandHistory,
|
|
7
8
|
Conversation,
|
|
9
|
+
ConversationLog,
|
|
8
10
|
Message,
|
|
9
|
-
CommandHistory,
|
|
10
11
|
MessageLog,
|
|
11
|
-
|
|
12
|
+
Project,
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
__all__ = [
|
|
@@ -17,5 +18,6 @@ __all__ = [
|
|
|
17
18
|
"ConversationLog",
|
|
18
19
|
"Message",
|
|
19
20
|
"MessageLog",
|
|
21
|
+
"Project",
|
|
20
22
|
"SQLModelProvider",
|
|
21
23
|
]
|
|
@@ -127,6 +127,9 @@ class Message(AsyncAttrs, SQLModel, table=True):
|
|
|
127
127
|
conversation_id: str = Field(index=True)
|
|
128
128
|
"""ID of the conversation this message belongs to"""
|
|
129
129
|
|
|
130
|
+
parent_id: str | None = Field(default=None, index=True)
|
|
131
|
+
"""ID of the parent message for tree-structured conversations."""
|
|
132
|
+
|
|
130
133
|
timestamp: datetime = Field(
|
|
131
134
|
sa_column=Column(UTCDateTime, default=get_now), default_factory=get_now
|
|
132
135
|
)
|
|
@@ -201,6 +204,15 @@ class Session(AsyncAttrs, SQLModel, table=True):
|
|
|
201
204
|
pool_id: str | None = Field(default=None, index=True)
|
|
202
205
|
"""Optional pool/manifest identifier for multi-pool setups."""
|
|
203
206
|
|
|
207
|
+
project_id: str | None = Field(default=None, index=True)
|
|
208
|
+
"""Project identifier (e.g., for OpenCode compatibility)."""
|
|
209
|
+
|
|
210
|
+
parent_id: str | None = Field(default=None, index=True)
|
|
211
|
+
"""Parent session ID for forked sessions."""
|
|
212
|
+
|
|
213
|
+
version: str = Field(default="1")
|
|
214
|
+
"""Session version string."""
|
|
215
|
+
|
|
204
216
|
title: str | None = Field(default=None, index=True)
|
|
205
217
|
"""AI-generated or user-provided title for the conversation."""
|
|
206
218
|
|
|
@@ -225,6 +237,42 @@ class Session(AsyncAttrs, SQLModel, table=True):
|
|
|
225
237
|
model_config = SQLModelConfig(use_attribute_docstrings=True) # pyright: ignore[reportCallIssue]
|
|
226
238
|
|
|
227
239
|
|
|
240
|
+
class Project(AsyncAttrs, SQLModel, table=True):
|
|
241
|
+
"""Database model for project/worktree tracking."""
|
|
242
|
+
|
|
243
|
+
project_id: str = Field(primary_key=True)
|
|
244
|
+
"""Unique identifier (hash of canonical worktree path)."""
|
|
245
|
+
|
|
246
|
+
worktree: str = Field(sa_column=Column(Text, index=True, unique=True))
|
|
247
|
+
"""Absolute path to the project root/worktree."""
|
|
248
|
+
|
|
249
|
+
name: str | None = Field(default=None, index=True)
|
|
250
|
+
"""Optional friendly name for the project."""
|
|
251
|
+
|
|
252
|
+
vcs: str | None = Field(default=None)
|
|
253
|
+
"""Version control system type (git, hg, or None)."""
|
|
254
|
+
|
|
255
|
+
config_path: str | None = Field(default=None, sa_column=Column(Text))
|
|
256
|
+
"""Path to the project's config file, or None for auto-discovery."""
|
|
257
|
+
|
|
258
|
+
created_at: datetime = Field(
|
|
259
|
+
sa_column=Column(UTCDateTime, index=True),
|
|
260
|
+
default_factory=get_now,
|
|
261
|
+
)
|
|
262
|
+
"""When the project was first registered."""
|
|
263
|
+
|
|
264
|
+
last_active: datetime = Field(
|
|
265
|
+
sa_column=Column(UTCDateTime, index=True),
|
|
266
|
+
default_factory=get_now,
|
|
267
|
+
)
|
|
268
|
+
"""Last activity timestamp."""
|
|
269
|
+
|
|
270
|
+
settings_json: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
271
|
+
"""Project-specific settings overrides."""
|
|
272
|
+
|
|
273
|
+
model_config = SQLModelConfig(use_attribute_docstrings=True) # pyright: ignore[reportCallIssue]
|
|
274
|
+
|
|
275
|
+
|
|
228
276
|
class Conversation(AsyncAttrs, SQLModel, table=True):
|
|
229
277
|
"""Database model for conversations."""
|
|
230
278
|
|