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.
Files changed (261) hide show
  1. alita_sdk/cli/agent/__init__.py +5 -0
  2. alita_sdk/cli/agent/default.py +258 -0
  3. alita_sdk/cli/agent_executor.py +15 -3
  4. alita_sdk/cli/agent_loader.py +56 -8
  5. alita_sdk/cli/agent_ui.py +93 -31
  6. alita_sdk/cli/agents.py +2274 -230
  7. alita_sdk/cli/callbacks.py +96 -25
  8. alita_sdk/cli/cli.py +10 -1
  9. alita_sdk/cli/config.py +162 -9
  10. alita_sdk/cli/context/__init__.py +30 -0
  11. alita_sdk/cli/context/cleanup.py +198 -0
  12. alita_sdk/cli/context/manager.py +731 -0
  13. alita_sdk/cli/context/message.py +285 -0
  14. alita_sdk/cli/context/strategies.py +289 -0
  15. alita_sdk/cli/context/token_estimation.py +127 -0
  16. alita_sdk/cli/input_handler.py +419 -0
  17. alita_sdk/cli/inventory.py +1073 -0
  18. alita_sdk/cli/testcases/__init__.py +94 -0
  19. alita_sdk/cli/testcases/data_generation.py +119 -0
  20. alita_sdk/cli/testcases/discovery.py +96 -0
  21. alita_sdk/cli/testcases/executor.py +84 -0
  22. alita_sdk/cli/testcases/logger.py +85 -0
  23. alita_sdk/cli/testcases/parser.py +172 -0
  24. alita_sdk/cli/testcases/prompts.py +91 -0
  25. alita_sdk/cli/testcases/reporting.py +125 -0
  26. alita_sdk/cli/testcases/setup.py +108 -0
  27. alita_sdk/cli/testcases/test_runner.py +282 -0
  28. alita_sdk/cli/testcases/utils.py +39 -0
  29. alita_sdk/cli/testcases/validation.py +90 -0
  30. alita_sdk/cli/testcases/workflow.py +196 -0
  31. alita_sdk/cli/toolkit.py +14 -17
  32. alita_sdk/cli/toolkit_loader.py +35 -5
  33. alita_sdk/cli/tools/__init__.py +36 -2
  34. alita_sdk/cli/tools/approval.py +224 -0
  35. alita_sdk/cli/tools/filesystem.py +910 -64
  36. alita_sdk/cli/tools/planning.py +389 -0
  37. alita_sdk/cli/tools/terminal.py +414 -0
  38. alita_sdk/community/__init__.py +72 -12
  39. alita_sdk/community/inventory/__init__.py +236 -0
  40. alita_sdk/community/inventory/config.py +257 -0
  41. alita_sdk/community/inventory/enrichment.py +2137 -0
  42. alita_sdk/community/inventory/extractors.py +1469 -0
  43. alita_sdk/community/inventory/ingestion.py +3172 -0
  44. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  45. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  46. alita_sdk/community/inventory/parsers/base.py +295 -0
  47. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  48. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  49. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  50. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  51. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  52. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  53. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  54. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  55. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  56. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  57. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  58. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  59. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  60. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  61. alita_sdk/community/inventory/patterns/loader.py +348 -0
  62. alita_sdk/community/inventory/patterns/registry.py +198 -0
  63. alita_sdk/community/inventory/presets.py +535 -0
  64. alita_sdk/community/inventory/retrieval.py +1403 -0
  65. alita_sdk/community/inventory/toolkit.py +173 -0
  66. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  67. alita_sdk/community/inventory/visualize.py +1370 -0
  68. alita_sdk/configurations/__init__.py +1 -1
  69. alita_sdk/configurations/ado.py +141 -20
  70. alita_sdk/configurations/bitbucket.py +0 -3
  71. alita_sdk/configurations/confluence.py +76 -42
  72. alita_sdk/configurations/figma.py +76 -0
  73. alita_sdk/configurations/gitlab.py +17 -5
  74. alita_sdk/configurations/openapi.py +329 -0
  75. alita_sdk/configurations/qtest.py +72 -1
  76. alita_sdk/configurations/report_portal.py +96 -0
  77. alita_sdk/configurations/sharepoint.py +148 -0
  78. alita_sdk/configurations/testio.py +83 -0
  79. alita_sdk/runtime/clients/artifact.py +3 -3
  80. alita_sdk/runtime/clients/client.py +353 -48
  81. alita_sdk/runtime/clients/sandbox_client.py +0 -21
  82. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  83. alita_sdk/runtime/langchain/assistant.py +123 -26
  84. alita_sdk/runtime/langchain/constants.py +642 -1
  85. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  86. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  87. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
  88. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  89. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  90. alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
  91. alita_sdk/runtime/langchain/langraph_agent.py +279 -73
  92. alita_sdk/runtime/langchain/utils.py +82 -15
  93. alita_sdk/runtime/llms/preloaded.py +2 -6
  94. alita_sdk/runtime/skills/__init__.py +91 -0
  95. alita_sdk/runtime/skills/callbacks.py +498 -0
  96. alita_sdk/runtime/skills/discovery.py +540 -0
  97. alita_sdk/runtime/skills/executor.py +610 -0
  98. alita_sdk/runtime/skills/input_builder.py +371 -0
  99. alita_sdk/runtime/skills/models.py +330 -0
  100. alita_sdk/runtime/skills/registry.py +355 -0
  101. alita_sdk/runtime/skills/skill_runner.py +330 -0
  102. alita_sdk/runtime/toolkits/__init__.py +7 -0
  103. alita_sdk/runtime/toolkits/application.py +21 -9
  104. alita_sdk/runtime/toolkits/artifact.py +15 -5
  105. alita_sdk/runtime/toolkits/datasource.py +13 -6
  106. alita_sdk/runtime/toolkits/mcp.py +139 -251
  107. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  108. alita_sdk/runtime/toolkits/planning.py +178 -0
  109. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  110. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  111. alita_sdk/runtime/toolkits/tools.py +238 -32
  112. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  113. alita_sdk/runtime/tools/__init__.py +3 -1
  114. alita_sdk/runtime/tools/application.py +20 -6
  115. alita_sdk/runtime/tools/artifact.py +511 -28
  116. alita_sdk/runtime/tools/data_analysis.py +183 -0
  117. alita_sdk/runtime/tools/function.py +43 -15
  118. alita_sdk/runtime/tools/image_generation.py +50 -44
  119. alita_sdk/runtime/tools/llm.py +852 -67
  120. alita_sdk/runtime/tools/loop.py +3 -1
  121. alita_sdk/runtime/tools/loop_output.py +3 -1
  122. alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
  123. alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
  124. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  125. alita_sdk/runtime/tools/planning/models.py +246 -0
  126. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  127. alita_sdk/runtime/tools/router.py +2 -4
  128. alita_sdk/runtime/tools/sandbox.py +9 -6
  129. alita_sdk/runtime/tools/skill_router.py +776 -0
  130. alita_sdk/runtime/tools/tool.py +3 -1
  131. alita_sdk/runtime/tools/vectorstore.py +7 -2
  132. alita_sdk/runtime/tools/vectorstore_base.py +51 -11
  133. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  134. alita_sdk/runtime/utils/constants.py +5 -1
  135. alita_sdk/runtime/utils/mcp_client.py +492 -0
  136. alita_sdk/runtime/utils/mcp_oauth.py +202 -5
  137. alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
  138. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  139. alita_sdk/runtime/utils/serialization.py +155 -0
  140. alita_sdk/runtime/utils/streamlit.py +6 -10
  141. alita_sdk/runtime/utils/toolkit_utils.py +16 -5
  142. alita_sdk/runtime/utils/utils.py +36 -0
  143. alita_sdk/tools/__init__.py +113 -29
  144. alita_sdk/tools/ado/repos/__init__.py +51 -33
  145. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  146. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  147. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  148. alita_sdk/tools/ado/utils.py +1 -18
  149. alita_sdk/tools/ado/wiki/__init__.py +25 -8
  150. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  151. alita_sdk/tools/ado/work_item/__init__.py +26 -9
  152. alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
  153. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  154. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  155. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  156. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  157. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  158. alita_sdk/tools/base/tool.py +5 -1
  159. alita_sdk/tools/base_indexer_toolkit.py +170 -45
  160. alita_sdk/tools/bitbucket/__init__.py +17 -12
  161. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  162. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  163. alita_sdk/tools/browser/__init__.py +5 -4
  164. alita_sdk/tools/carrier/__init__.py +5 -6
  165. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  166. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  167. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  168. alita_sdk/tools/chunkers/__init__.py +3 -1
  169. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  170. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  171. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  172. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  173. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  174. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  175. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  176. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  177. alita_sdk/tools/code/linter/__init__.py +10 -8
  178. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  179. alita_sdk/tools/code/sonar/__init__.py +10 -7
  180. alita_sdk/tools/code_indexer_toolkit.py +73 -23
  181. alita_sdk/tools/confluence/__init__.py +21 -15
  182. alita_sdk/tools/confluence/api_wrapper.py +78 -23
  183. alita_sdk/tools/confluence/loader.py +4 -2
  184. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  185. alita_sdk/tools/elastic/__init__.py +11 -8
  186. alita_sdk/tools/elitea_base.py +493 -30
  187. alita_sdk/tools/figma/__init__.py +58 -11
  188. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  189. alita_sdk/tools/figma/figma_client.py +73 -0
  190. alita_sdk/tools/figma/toon_tools.py +2748 -0
  191. alita_sdk/tools/github/__init__.py +13 -14
  192. alita_sdk/tools/github/github_client.py +224 -100
  193. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  194. alita_sdk/tools/github/schemas.py +14 -5
  195. alita_sdk/tools/github/tool.py +5 -1
  196. alita_sdk/tools/github/tool_prompts.py +9 -22
  197. alita_sdk/tools/gitlab/__init__.py +15 -11
  198. alita_sdk/tools/gitlab/api_wrapper.py +207 -41
  199. alita_sdk/tools/gitlab_org/__init__.py +10 -8
  200. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  201. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  202. alita_sdk/tools/google/bigquery/tool.py +5 -1
  203. alita_sdk/tools/google_places/__init__.py +10 -8
  204. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  205. alita_sdk/tools/jira/__init__.py +17 -11
  206. alita_sdk/tools/jira/api_wrapper.py +91 -40
  207. alita_sdk/tools/keycloak/__init__.py +11 -8
  208. alita_sdk/tools/localgit/__init__.py +9 -3
  209. alita_sdk/tools/localgit/local_git.py +62 -54
  210. alita_sdk/tools/localgit/tool.py +5 -1
  211. alita_sdk/tools/memory/__init__.py +11 -3
  212. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  213. alita_sdk/tools/ocr/__init__.py +11 -8
  214. alita_sdk/tools/openapi/__init__.py +490 -114
  215. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  216. alita_sdk/tools/openapi/tool.py +20 -0
  217. alita_sdk/tools/pandas/__init__.py +20 -12
  218. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  219. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  220. alita_sdk/tools/postman/__init__.py +11 -11
  221. alita_sdk/tools/pptx/__init__.py +10 -9
  222. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  223. alita_sdk/tools/qtest/__init__.py +30 -10
  224. alita_sdk/tools/qtest/api_wrapper.py +430 -13
  225. alita_sdk/tools/rally/__init__.py +10 -8
  226. alita_sdk/tools/rally/api_wrapper.py +1 -1
  227. alita_sdk/tools/report_portal/__init__.py +12 -9
  228. alita_sdk/tools/salesforce/__init__.py +10 -9
  229. alita_sdk/tools/servicenow/__init__.py +17 -14
  230. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  231. alita_sdk/tools/sharepoint/__init__.py +10 -8
  232. alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
  233. alita_sdk/tools/slack/__init__.py +10 -8
  234. alita_sdk/tools/slack/api_wrapper.py +2 -2
  235. alita_sdk/tools/sql/__init__.py +11 -9
  236. alita_sdk/tools/testio/__init__.py +10 -8
  237. alita_sdk/tools/testrail/__init__.py +11 -8
  238. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  239. alita_sdk/tools/utils/__init__.py +9 -4
  240. alita_sdk/tools/utils/content_parser.py +77 -3
  241. alita_sdk/tools/utils/text_operations.py +410 -0
  242. alita_sdk/tools/utils/tool_prompts.py +79 -0
  243. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
  244. alita_sdk/tools/xray/__init__.py +12 -9
  245. alita_sdk/tools/yagmail/__init__.py +9 -3
  246. alita_sdk/tools/zephyr/__init__.py +9 -7
  247. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
  248. alita_sdk/tools/zephyr_essential/__init__.py +10 -8
  249. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  250. alita_sdk/tools/zephyr_essential/client.py +2 -2
  251. alita_sdk/tools/zephyr_scale/__init__.py +11 -9
  252. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  253. alita_sdk/tools/zephyr_squad/__init__.py +10 -8
  254. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
  255. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  256. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  257. alita_sdk-0.3.462.dist-info/RECORD +0 -384
  258. alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
  259. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  260. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  261. {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