alita-sdk 0.3.462__py3-none-any.whl → 0.3.627__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.
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +15 -3
- alita_sdk/cli/agent_loader.py +56 -8
- alita_sdk/cli/agent_ui.py +93 -31
- alita_sdk/cli/agents.py +2274 -230
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +162 -9
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +36 -2
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +910 -64
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +17 -5
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +353 -48
- alita_sdk/runtime/clients/sandbox_client.py +0 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +123 -26
- alita_sdk/runtime/langchain/constants.py +642 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
- alita_sdk/runtime/langchain/langraph_agent.py +279 -73
- alita_sdk/runtime/langchain/utils.py +82 -15
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +7 -0
- alita_sdk/runtime/toolkits/application.py +21 -9
- alita_sdk/runtime/toolkits/artifact.py +15 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +139 -251
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +238 -32
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/application.py +20 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +43 -15
- alita_sdk/runtime/tools/image_generation.py +50 -44
- alita_sdk/runtime/tools/llm.py +852 -67
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
- alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +9 -6
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +7 -2
- alita_sdk/runtime/tools/vectorstore_base.py +51 -11
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +202 -5
- alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +16 -5
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +113 -29
- alita_sdk/tools/ado/repos/__init__.py +51 -33
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -9
- alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +170 -45
- alita_sdk/tools/bitbucket/__init__.py +17 -12
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +10 -7
- alita_sdk/tools/code_indexer_toolkit.py +73 -23
- alita_sdk/tools/confluence/__init__.py +21 -15
- alita_sdk/tools/confluence/api_wrapper.py +78 -23
- alita_sdk/tools/confluence/loader.py +4 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +13 -14
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +15 -11
- alita_sdk/tools/gitlab/api_wrapper.py +207 -41
- alita_sdk/tools/gitlab_org/__init__.py +10 -8
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +10 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -11
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +11 -3
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +490 -114
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +11 -11
- alita_sdk/tools/pptx/__init__.py +10 -9
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +30 -10
- alita_sdk/tools/qtest/api_wrapper.py +430 -13
- alita_sdk/tools/rally/__init__.py +10 -8
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -9
- alita_sdk/tools/salesforce/__init__.py +10 -9
- alita_sdk/tools/servicenow/__init__.py +17 -14
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -8
- alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
- alita_sdk/tools/slack/__init__.py +10 -8
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +11 -9
- alita_sdk/tools/testio/__init__.py +10 -8
- alita_sdk/tools/testrail/__init__.py +11 -8
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +77 -3
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
- alita_sdk/tools/xray/__init__.py +12 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +9 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
- alita_sdk/tools/zephyr_essential/__init__.py +10 -8
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -9
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -8
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.462.dist-info/RECORD +0 -384
- alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Planning tools for CLI agents.
|
|
3
|
+
|
|
4
|
+
Provides plan management for multi-step task execution with progress tracking.
|
|
5
|
+
Sessions are persisted to $ALITA_DIR/sessions/<session_id>/
|
|
6
|
+
- plan.json: Execution plan with steps
|
|
7
|
+
- memory.db: SQLite database for conversation memory
|
|
8
|
+
- session.json: Session metadata (agent, model, etc.)
|
|
9
|
+
|
|
10
|
+
This module re-exports the runtime PlanningToolkit for unified usage across
|
|
11
|
+
CLI, indexer_worker, and SDK agents. The runtime toolkit supports both
|
|
12
|
+
PostgreSQL (production) and filesystem (local) storage backends.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import json
|
|
17
|
+
import uuid
|
|
18
|
+
import sqlite3
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Optional, List, Dict, Any, Callable
|
|
21
|
+
from langchain_core.tools import BaseTool
|
|
22
|
+
from pydantic import BaseModel, Field
|
|
23
|
+
import logging
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ============================================================================
|
|
29
|
+
# Session Management Functions
|
|
30
|
+
# ============================================================================
|
|
31
|
+
|
|
32
|
+
def get_sessions_dir() -> Path:
|
|
33
|
+
"""Get the sessions directory path (relative to $ALITA_DIR or .alita)."""
|
|
34
|
+
alita_dir = os.environ.get('ALITA_DIR', '.alita')
|
|
35
|
+
return Path(alita_dir) / 'sessions'
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def generate_session_id() -> str:
|
|
39
|
+
"""Generate a new unique session ID."""
|
|
40
|
+
return uuid.uuid4().hex[:12]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_session_dir(session_id: str) -> Path:
|
|
44
|
+
"""Get the directory for a specific session."""
|
|
45
|
+
return get_sessions_dir() / session_id
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_session_memory_path(session_id: str) -> Path:
|
|
49
|
+
"""Get the path to the memory database for a session."""
|
|
50
|
+
session_dir = get_session_dir(session_id)
|
|
51
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
return session_dir / "memory.db"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_session_metadata_path(session_id: str) -> Path:
|
|
56
|
+
"""Get the path to the session metadata file."""
|
|
57
|
+
session_dir = get_session_dir(session_id)
|
|
58
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
return session_dir / "session.json"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def create_session_memory(session_id: str):
|
|
63
|
+
"""
|
|
64
|
+
Create a SQLite-based memory saver for the session.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
session_id: The session ID
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
SqliteSaver instance connected to the session's memory.db
|
|
71
|
+
"""
|
|
72
|
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
73
|
+
|
|
74
|
+
memory_path = get_session_memory_path(session_id)
|
|
75
|
+
conn = sqlite3.connect(str(memory_path), check_same_thread=False)
|
|
76
|
+
logger.debug(f"Created session memory at {memory_path}")
|
|
77
|
+
return SqliteSaver(conn)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def save_session_metadata(session_id: str, metadata: Dict[str, Any]) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Save session metadata (agent name, model, etc.).
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
session_id: The session ID
|
|
86
|
+
metadata: Dictionary with session metadata
|
|
87
|
+
"""
|
|
88
|
+
metadata_path = get_session_metadata_path(session_id)
|
|
89
|
+
metadata['session_id'] = session_id
|
|
90
|
+
metadata_path.write_text(json.dumps(metadata, indent=2))
|
|
91
|
+
logger.debug(f"Saved session metadata to {metadata_path}")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def load_session_metadata(session_id: str) -> Optional[Dict[str, Any]]:
|
|
95
|
+
"""
|
|
96
|
+
Load session metadata.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
session_id: The session ID
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Session metadata dict or None if not found
|
|
103
|
+
"""
|
|
104
|
+
metadata_path = get_session_metadata_path(session_id)
|
|
105
|
+
if metadata_path.exists():
|
|
106
|
+
try:
|
|
107
|
+
return json.loads(metadata_path.read_text())
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.warning(f"Failed to load session metadata: {e}")
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def update_session_metadata(session_id: str, updates: Dict[str, Any]) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Update session metadata by merging new fields into existing metadata.
|
|
116
|
+
|
|
117
|
+
This preserves existing fields while updating/adding new ones.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
session_id: The session ID
|
|
121
|
+
updates: Dictionary with fields to update/add
|
|
122
|
+
"""
|
|
123
|
+
existing = load_session_metadata(session_id) or {}
|
|
124
|
+
existing.update(updates)
|
|
125
|
+
save_session_metadata(session_id, existing)
|
|
126
|
+
logger.debug(f"Updated session metadata with: {list(updates.keys())}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_alita_dir() -> Path:
|
|
130
|
+
"""Get the ALITA_DIR path (relative to $ALITA_DIR or .alita)."""
|
|
131
|
+
return Path(os.environ.get('ALITA_DIR', '.alita'))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def to_portable_path(path: str) -> str:
|
|
135
|
+
"""
|
|
136
|
+
Convert an absolute path to a portable path for session storage.
|
|
137
|
+
|
|
138
|
+
If the path is under $ALITA_DIR, store as relative path (e.g., 'agents/my-agent.yaml').
|
|
139
|
+
Otherwise, store the absolute path.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
path: Absolute file path
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Portable path string (relative to ALITA_DIR if applicable, else absolute)
|
|
146
|
+
"""
|
|
147
|
+
if not path:
|
|
148
|
+
return path
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
path_obj = Path(path).resolve()
|
|
152
|
+
alita_dir = get_alita_dir().resolve()
|
|
153
|
+
|
|
154
|
+
# Check if path is under ALITA_DIR
|
|
155
|
+
if str(path_obj).startswith(str(alita_dir)):
|
|
156
|
+
relative = path_obj.relative_to(alita_dir)
|
|
157
|
+
return str(relative)
|
|
158
|
+
except (ValueError, OSError):
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
return str(path)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def from_portable_path(portable_path: str) -> str:
|
|
165
|
+
"""
|
|
166
|
+
Convert a portable path back to an absolute path.
|
|
167
|
+
|
|
168
|
+
If the path is relative, resolve it against $ALITA_DIR.
|
|
169
|
+
Otherwise, return as-is.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
portable_path: Portable path string from session storage
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Absolute file path
|
|
176
|
+
"""
|
|
177
|
+
if not portable_path:
|
|
178
|
+
return portable_path
|
|
179
|
+
|
|
180
|
+
path_obj = Path(portable_path)
|
|
181
|
+
|
|
182
|
+
# If already absolute, return as-is
|
|
183
|
+
if path_obj.is_absolute():
|
|
184
|
+
return str(path_obj)
|
|
185
|
+
|
|
186
|
+
# Resolve relative path against ALITA_DIR
|
|
187
|
+
alita_dir = get_alita_dir()
|
|
188
|
+
return str(alita_dir / portable_path)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ============================================================================
|
|
192
|
+
# PlanState - For CLI UI compatibility and session listing
|
|
193
|
+
# ============================================================================
|
|
194
|
+
|
|
195
|
+
class PlanStep(BaseModel):
|
|
196
|
+
"""A single step in a plan."""
|
|
197
|
+
description: str = Field(description="Step description")
|
|
198
|
+
completed: bool = Field(default=False, description="Whether step is completed")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class PlanState(BaseModel):
|
|
202
|
+
"""
|
|
203
|
+
Current plan state for CLI display.
|
|
204
|
+
|
|
205
|
+
This is used for CLI UI rendering and backwards compatibility.
|
|
206
|
+
The actual plan storage is handled by the runtime PlanningWrapper.
|
|
207
|
+
"""
|
|
208
|
+
title: str = Field(default="", description="Plan title")
|
|
209
|
+
steps: List[PlanStep] = Field(default_factory=list, description="List of steps")
|
|
210
|
+
session_id: str = Field(default="", description="Session ID for persistence")
|
|
211
|
+
|
|
212
|
+
def render(self) -> str:
|
|
213
|
+
"""Render plan as formatted string with checkboxes."""
|
|
214
|
+
if not self.steps:
|
|
215
|
+
return ""
|
|
216
|
+
|
|
217
|
+
lines = []
|
|
218
|
+
if self.title:
|
|
219
|
+
lines.append(f"📋 {self.title}")
|
|
220
|
+
|
|
221
|
+
for i, step in enumerate(self.steps, 1):
|
|
222
|
+
checkbox = "☑" if step.completed else "☐"
|
|
223
|
+
status = " (completed)" if step.completed else ""
|
|
224
|
+
lines.append(f" {checkbox} {i}. {step.description}{status}")
|
|
225
|
+
|
|
226
|
+
return "\n".join(lines)
|
|
227
|
+
|
|
228
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
229
|
+
"""Convert to dictionary for serialization."""
|
|
230
|
+
return {
|
|
231
|
+
"title": self.title,
|
|
232
|
+
"steps": [{"description": s.description, "completed": s.completed} for s in self.steps],
|
|
233
|
+
"session_id": self.session_id
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def from_dict(cls, data: Dict[str, Any]) -> "PlanState":
|
|
238
|
+
"""Create from dictionary."""
|
|
239
|
+
steps = [PlanStep(**s) for s in data.get("steps", [])]
|
|
240
|
+
return cls(
|
|
241
|
+
title=data.get("title", ""),
|
|
242
|
+
steps=steps,
|
|
243
|
+
session_id=data.get("session_id", "")
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def load(cls, session_id: str) -> Optional["PlanState"]:
|
|
248
|
+
"""Load plan state from session file."""
|
|
249
|
+
try:
|
|
250
|
+
plan_file = get_sessions_dir() / session_id / "plan.json"
|
|
251
|
+
if plan_file.exists():
|
|
252
|
+
data = json.loads(plan_file.read_text())
|
|
253
|
+
state = cls.from_dict(data)
|
|
254
|
+
state.session_id = session_id
|
|
255
|
+
logger.debug(f"Loaded plan from {plan_file}")
|
|
256
|
+
return state
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.warning(f"Failed to load plan: {e}")
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def list_sessions() -> List[Dict[str, Any]]:
|
|
263
|
+
"""List all sessions with their metadata and plans."""
|
|
264
|
+
sessions = []
|
|
265
|
+
sessions_dir = get_sessions_dir()
|
|
266
|
+
|
|
267
|
+
if not sessions_dir.exists():
|
|
268
|
+
return sessions
|
|
269
|
+
|
|
270
|
+
for session_dir in sessions_dir.iterdir():
|
|
271
|
+
if session_dir.is_dir():
|
|
272
|
+
session_info = {
|
|
273
|
+
"session_id": session_dir.name,
|
|
274
|
+
"title": None,
|
|
275
|
+
"steps_total": 0,
|
|
276
|
+
"steps_completed": 0,
|
|
277
|
+
"agent_name": None,
|
|
278
|
+
"model": None,
|
|
279
|
+
"modified": 0,
|
|
280
|
+
"has_memory": False,
|
|
281
|
+
"has_plan": False,
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# Load session metadata
|
|
285
|
+
metadata_file = session_dir / "session.json"
|
|
286
|
+
if metadata_file.exists():
|
|
287
|
+
try:
|
|
288
|
+
metadata = json.loads(metadata_file.read_text())
|
|
289
|
+
session_info["agent_name"] = metadata.get("agent_name")
|
|
290
|
+
session_info["model"] = metadata.get("model")
|
|
291
|
+
session_info["modified"] = metadata_file.stat().st_mtime
|
|
292
|
+
except Exception:
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
# Check for memory database
|
|
296
|
+
memory_file = session_dir / "memory.db"
|
|
297
|
+
if memory_file.exists():
|
|
298
|
+
session_info["has_memory"] = True
|
|
299
|
+
# Use memory file mtime if newer
|
|
300
|
+
mem_mtime = memory_file.stat().st_mtime
|
|
301
|
+
if mem_mtime > session_info["modified"]:
|
|
302
|
+
session_info["modified"] = mem_mtime
|
|
303
|
+
|
|
304
|
+
# Load plan info
|
|
305
|
+
plan_file = session_dir / "plan.json"
|
|
306
|
+
if plan_file.exists():
|
|
307
|
+
try:
|
|
308
|
+
data = json.loads(plan_file.read_text())
|
|
309
|
+
session_info["has_plan"] = True
|
|
310
|
+
session_info["title"] = data.get("title", "(untitled)")
|
|
311
|
+
session_info["steps_total"] = len(data.get("steps", []))
|
|
312
|
+
session_info["steps_completed"] = sum(1 for s in data.get("steps", []) if s.get("completed"))
|
|
313
|
+
# Use plan file mtime if newer
|
|
314
|
+
plan_mtime = plan_file.stat().st_mtime
|
|
315
|
+
if plan_mtime > session_info["modified"]:
|
|
316
|
+
session_info["modified"] = plan_mtime
|
|
317
|
+
except Exception:
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
# Only include sessions that have some content
|
|
321
|
+
if session_info["has_memory"] or session_info["has_plan"]:
|
|
322
|
+
sessions.append(session_info)
|
|
323
|
+
|
|
324
|
+
# Sort by modified time, newest first
|
|
325
|
+
sessions.sort(key=lambda x: x.get("modified", 0), reverse=True)
|
|
326
|
+
return sessions
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# ============================================================================
|
|
330
|
+
# Planning Tools - Using Runtime PlanningToolkit
|
|
331
|
+
# ============================================================================
|
|
332
|
+
|
|
333
|
+
def get_planning_tools(
|
|
334
|
+
plan_state: Optional[PlanState] = None,
|
|
335
|
+
plan_callback: Optional[Callable] = None,
|
|
336
|
+
session_id: Optional[str] = None
|
|
337
|
+
) -> tuple[List[BaseTool], PlanState]:
|
|
338
|
+
"""
|
|
339
|
+
Get planning tools using the runtime PlanningToolkit.
|
|
340
|
+
|
|
341
|
+
Uses the runtime PlanningToolkit which supports both PostgreSQL
|
|
342
|
+
and filesystem storage. For CLI, it uses filesystem storage with
|
|
343
|
+
session_id as the thread identifier.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
plan_state: Optional existing plan state (for backwards compatibility)
|
|
347
|
+
plan_callback: Optional callback function called when plan changes (for CLI UI)
|
|
348
|
+
session_id: Optional session ID for persistence. If None, generates a new one.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Tuple of (list of tools, plan state object)
|
|
352
|
+
"""
|
|
353
|
+
from alita_sdk.runtime.toolkits.planning import PlanningToolkit
|
|
354
|
+
from alita_sdk.runtime.tools.planning.wrapper import PlanState as RuntimePlanState
|
|
355
|
+
|
|
356
|
+
# Generate session_id if not provided
|
|
357
|
+
if not session_id:
|
|
358
|
+
session_id = generate_session_id()
|
|
359
|
+
|
|
360
|
+
# Create adapter callback that converts between PlanState types
|
|
361
|
+
def adapter_callback(runtime_plan: RuntimePlanState):
|
|
362
|
+
if plan_callback:
|
|
363
|
+
# Convert runtime PlanState to CLI PlanState for UI
|
|
364
|
+
cli_plan = PlanState(
|
|
365
|
+
title=runtime_plan.title,
|
|
366
|
+
steps=[PlanStep(description=s.description, completed=s.completed) for s in runtime_plan.steps],
|
|
367
|
+
session_id=session_id
|
|
368
|
+
)
|
|
369
|
+
plan_callback(cli_plan)
|
|
370
|
+
|
|
371
|
+
# Create toolkit with filesystem storage (no pgvector_configuration)
|
|
372
|
+
# Use session_id as conversation_id so tools don't need it passed explicitly
|
|
373
|
+
toolkit = PlanningToolkit.get_toolkit(
|
|
374
|
+
toolkit_name=None, # No prefix - tools are called directly
|
|
375
|
+
selected_tools=['update_plan', 'complete_step', 'get_plan_status', 'delete_plan'],
|
|
376
|
+
pgvector_configuration=None, # Uses filesystem storage
|
|
377
|
+
storage_dir=str(get_sessions_dir() / session_id), # Use session-specific directory
|
|
378
|
+
plan_callback=adapter_callback if plan_callback else None,
|
|
379
|
+
conversation_id=session_id # Use session_id as conversation_id
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
tools = toolkit.get_tools()
|
|
383
|
+
|
|
384
|
+
# Create local state for return (for backward compatibility)
|
|
385
|
+
loaded = PlanState.load(session_id)
|
|
386
|
+
state = loaded if loaded else (plan_state or PlanState())
|
|
387
|
+
state.session_id = session_id
|
|
388
|
+
|
|
389
|
+
return tools, state
|