kweaver-dolphin 0.1.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.
- DolphinLanguageSDK/__init__.py +58 -0
- dolphin/__init__.py +62 -0
- dolphin/cli/__init__.py +20 -0
- dolphin/cli/args/__init__.py +9 -0
- dolphin/cli/args/parser.py +567 -0
- dolphin/cli/builtin_agents/__init__.py +22 -0
- dolphin/cli/commands/__init__.py +4 -0
- dolphin/cli/interrupt/__init__.py +8 -0
- dolphin/cli/interrupt/handler.py +205 -0
- dolphin/cli/interrupt/keyboard.py +82 -0
- dolphin/cli/main.py +49 -0
- dolphin/cli/multimodal/__init__.py +34 -0
- dolphin/cli/multimodal/clipboard.py +327 -0
- dolphin/cli/multimodal/handler.py +249 -0
- dolphin/cli/multimodal/image_processor.py +214 -0
- dolphin/cli/multimodal/input_parser.py +149 -0
- dolphin/cli/runner/__init__.py +8 -0
- dolphin/cli/runner/runner.py +989 -0
- dolphin/cli/ui/__init__.py +10 -0
- dolphin/cli/ui/console.py +2795 -0
- dolphin/cli/ui/input.py +340 -0
- dolphin/cli/ui/layout.py +425 -0
- dolphin/cli/ui/stream_renderer.py +302 -0
- dolphin/cli/utils/__init__.py +8 -0
- dolphin/cli/utils/helpers.py +135 -0
- dolphin/cli/utils/version.py +49 -0
- dolphin/core/__init__.py +107 -0
- dolphin/core/agent/__init__.py +10 -0
- dolphin/core/agent/agent_state.py +69 -0
- dolphin/core/agent/base_agent.py +970 -0
- dolphin/core/code_block/__init__.py +0 -0
- dolphin/core/code_block/agent_init_block.py +0 -0
- dolphin/core/code_block/assign_block.py +98 -0
- dolphin/core/code_block/basic_code_block.py +1865 -0
- dolphin/core/code_block/explore_block.py +1327 -0
- dolphin/core/code_block/explore_block_v2.py +712 -0
- dolphin/core/code_block/explore_strategy.py +672 -0
- dolphin/core/code_block/judge_block.py +220 -0
- dolphin/core/code_block/prompt_block.py +32 -0
- dolphin/core/code_block/skill_call_deduplicator.py +291 -0
- dolphin/core/code_block/tool_block.py +129 -0
- dolphin/core/common/__init__.py +17 -0
- dolphin/core/common/constants.py +176 -0
- dolphin/core/common/enums.py +1173 -0
- dolphin/core/common/exceptions.py +133 -0
- dolphin/core/common/multimodal.py +539 -0
- dolphin/core/common/object_type.py +165 -0
- dolphin/core/common/output_format.py +432 -0
- dolphin/core/common/types.py +36 -0
- dolphin/core/config/__init__.py +16 -0
- dolphin/core/config/global_config.py +1289 -0
- dolphin/core/config/ontology_config.py +133 -0
- dolphin/core/context/__init__.py +12 -0
- dolphin/core/context/context.py +1580 -0
- dolphin/core/context/context_manager.py +161 -0
- dolphin/core/context/var_output.py +82 -0
- dolphin/core/context/variable_pool.py +356 -0
- dolphin/core/context_engineer/__init__.py +41 -0
- dolphin/core/context_engineer/config/__init__.py +5 -0
- dolphin/core/context_engineer/config/settings.py +402 -0
- dolphin/core/context_engineer/core/__init__.py +7 -0
- dolphin/core/context_engineer/core/budget_manager.py +327 -0
- dolphin/core/context_engineer/core/context_assembler.py +583 -0
- dolphin/core/context_engineer/core/context_manager.py +637 -0
- dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
- dolphin/core/context_engineer/example/incremental_example.py +267 -0
- dolphin/core/context_engineer/example/traditional_example.py +334 -0
- dolphin/core/context_engineer/services/__init__.py +5 -0
- dolphin/core/context_engineer/services/compressor.py +399 -0
- dolphin/core/context_engineer/utils/__init__.py +6 -0
- dolphin/core/context_engineer/utils/context_utils.py +441 -0
- dolphin/core/context_engineer/utils/message_formatter.py +270 -0
- dolphin/core/context_engineer/utils/token_utils.py +139 -0
- dolphin/core/coroutine/__init__.py +15 -0
- dolphin/core/coroutine/context_snapshot.py +154 -0
- dolphin/core/coroutine/context_snapshot_profile.py +922 -0
- dolphin/core/coroutine/context_snapshot_store.py +268 -0
- dolphin/core/coroutine/execution_frame.py +145 -0
- dolphin/core/coroutine/execution_state_registry.py +161 -0
- dolphin/core/coroutine/resume_handle.py +101 -0
- dolphin/core/coroutine/step_result.py +101 -0
- dolphin/core/executor/__init__.py +18 -0
- dolphin/core/executor/debug_controller.py +630 -0
- dolphin/core/executor/dolphin_executor.py +1063 -0
- dolphin/core/executor/executor.py +624 -0
- dolphin/core/flags/__init__.py +27 -0
- dolphin/core/flags/definitions.py +49 -0
- dolphin/core/flags/manager.py +113 -0
- dolphin/core/hook/__init__.py +95 -0
- dolphin/core/hook/expression_evaluator.py +499 -0
- dolphin/core/hook/hook_dispatcher.py +380 -0
- dolphin/core/hook/hook_types.py +248 -0
- dolphin/core/hook/isolated_variable_pool.py +284 -0
- dolphin/core/interfaces.py +53 -0
- dolphin/core/llm/__init__.py +0 -0
- dolphin/core/llm/llm.py +495 -0
- dolphin/core/llm/llm_call.py +100 -0
- dolphin/core/llm/llm_client.py +1285 -0
- dolphin/core/llm/message_sanitizer.py +120 -0
- dolphin/core/logging/__init__.py +20 -0
- dolphin/core/logging/logger.py +526 -0
- dolphin/core/message/__init__.py +8 -0
- dolphin/core/message/compressor.py +749 -0
- dolphin/core/parser/__init__.py +8 -0
- dolphin/core/parser/parser.py +405 -0
- dolphin/core/runtime/__init__.py +10 -0
- dolphin/core/runtime/runtime_graph.py +926 -0
- dolphin/core/runtime/runtime_instance.py +446 -0
- dolphin/core/skill/__init__.py +14 -0
- dolphin/core/skill/context_retention.py +157 -0
- dolphin/core/skill/skill_function.py +686 -0
- dolphin/core/skill/skill_matcher.py +282 -0
- dolphin/core/skill/skillkit.py +700 -0
- dolphin/core/skill/skillset.py +72 -0
- dolphin/core/trajectory/__init__.py +10 -0
- dolphin/core/trajectory/recorder.py +189 -0
- dolphin/core/trajectory/trajectory.py +522 -0
- dolphin/core/utils/__init__.py +9 -0
- dolphin/core/utils/cache_kv.py +212 -0
- dolphin/core/utils/tools.py +340 -0
- dolphin/lib/__init__.py +93 -0
- dolphin/lib/debug/__init__.py +8 -0
- dolphin/lib/debug/visualizer.py +409 -0
- dolphin/lib/memory/__init__.py +28 -0
- dolphin/lib/memory/async_processor.py +220 -0
- dolphin/lib/memory/llm_calls.py +195 -0
- dolphin/lib/memory/manager.py +78 -0
- dolphin/lib/memory/sandbox.py +46 -0
- dolphin/lib/memory/storage.py +245 -0
- dolphin/lib/memory/utils.py +51 -0
- dolphin/lib/ontology/__init__.py +12 -0
- dolphin/lib/ontology/basic/__init__.py +0 -0
- dolphin/lib/ontology/basic/base.py +102 -0
- dolphin/lib/ontology/basic/concept.py +130 -0
- dolphin/lib/ontology/basic/object.py +11 -0
- dolphin/lib/ontology/basic/relation.py +63 -0
- dolphin/lib/ontology/datasource/__init__.py +27 -0
- dolphin/lib/ontology/datasource/datasource.py +66 -0
- dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
- dolphin/lib/ontology/datasource/sql.py +845 -0
- dolphin/lib/ontology/mapping.py +177 -0
- dolphin/lib/ontology/ontology.py +733 -0
- dolphin/lib/ontology/ontology_context.py +16 -0
- dolphin/lib/ontology/ontology_manager.py +107 -0
- dolphin/lib/skill_results/__init__.py +31 -0
- dolphin/lib/skill_results/cache_backend.py +559 -0
- dolphin/lib/skill_results/result_processor.py +181 -0
- dolphin/lib/skill_results/result_reference.py +179 -0
- dolphin/lib/skill_results/skillkit_hook.py +324 -0
- dolphin/lib/skill_results/strategies.py +328 -0
- dolphin/lib/skill_results/strategy_registry.py +150 -0
- dolphin/lib/skillkits/__init__.py +44 -0
- dolphin/lib/skillkits/agent_skillkit.py +155 -0
- dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
- dolphin/lib/skillkits/env_skillkit.py +250 -0
- dolphin/lib/skillkits/mcp_adapter.py +616 -0
- dolphin/lib/skillkits/mcp_skillkit.py +771 -0
- dolphin/lib/skillkits/memory_skillkit.py +650 -0
- dolphin/lib/skillkits/noop_skillkit.py +31 -0
- dolphin/lib/skillkits/ontology_skillkit.py +89 -0
- dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
- dolphin/lib/skillkits/resource/__init__.py +52 -0
- dolphin/lib/skillkits/resource/models/__init__.py +6 -0
- dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
- dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
- dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
- dolphin/lib/skillkits/resource/skill_cache.py +215 -0
- dolphin/lib/skillkits/resource/skill_loader.py +395 -0
- dolphin/lib/skillkits/resource/skill_validator.py +406 -0
- dolphin/lib/skillkits/resource_skillkit.py +11 -0
- dolphin/lib/skillkits/search_skillkit.py +163 -0
- dolphin/lib/skillkits/sql_skillkit.py +274 -0
- dolphin/lib/skillkits/system_skillkit.py +509 -0
- dolphin/lib/skillkits/vm_skillkit.py +65 -0
- dolphin/lib/utils/__init__.py +9 -0
- dolphin/lib/utils/data_process.py +207 -0
- dolphin/lib/utils/handle_progress.py +178 -0
- dolphin/lib/utils/security.py +139 -0
- dolphin/lib/utils/text_retrieval.py +462 -0
- dolphin/lib/vm/__init__.py +11 -0
- dolphin/lib/vm/env_executor.py +895 -0
- dolphin/lib/vm/python_session_manager.py +453 -0
- dolphin/lib/vm/vm.py +610 -0
- dolphin/sdk/__init__.py +60 -0
- dolphin/sdk/agent/__init__.py +12 -0
- dolphin/sdk/agent/agent_factory.py +236 -0
- dolphin/sdk/agent/dolphin_agent.py +1106 -0
- dolphin/sdk/api/__init__.py +4 -0
- dolphin/sdk/runtime/__init__.py +8 -0
- dolphin/sdk/runtime/env.py +363 -0
- dolphin/sdk/skill/__init__.py +10 -0
- dolphin/sdk/skill/global_skills.py +706 -0
- dolphin/sdk/skill/traditional_toolkit.py +260 -0
- kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
- kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
- kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
- kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
- kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
- kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python Session Manager with Pickle Support
|
|
3
|
+
|
|
4
|
+
This module manages stateful Python execution sessions, allowing variables
|
|
5
|
+
and state to persist across multiple executions like Jupyter notebooks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Dict, Any, Optional
|
|
11
|
+
import string
|
|
12
|
+
import random
|
|
13
|
+
from datetime import datetime, timedelta
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class PythonSession:
|
|
21
|
+
"""Represents a Python execution session with persistent state"""
|
|
22
|
+
|
|
23
|
+
session_id: str
|
|
24
|
+
created_at: datetime
|
|
25
|
+
last_accessed: datetime
|
|
26
|
+
namespace: Dict[str, Any] = field(default_factory=dict)
|
|
27
|
+
execution_count: int = 0
|
|
28
|
+
remote_pickle_path: Optional[str] = None
|
|
29
|
+
|
|
30
|
+
def is_expired(self, timeout_hours: int = 24) -> bool:
|
|
31
|
+
"""Check if session has expired"""
|
|
32
|
+
return datetime.now() - self.last_accessed > timedelta(hours=timeout_hours)
|
|
33
|
+
|
|
34
|
+
def touch(self):
|
|
35
|
+
"""Update last accessed time"""
|
|
36
|
+
self.last_accessed = datetime.now()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PythonSessionManager:
|
|
40
|
+
"""
|
|
41
|
+
Manages Python sessions with state persistence using pickle.
|
|
42
|
+
|
|
43
|
+
Sessions maintain variable state across multiple executions,
|
|
44
|
+
similar to Jupyter notebook cells.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, remote_session_dir: str = "/tmp/python_sessions"):
|
|
48
|
+
"""
|
|
49
|
+
Initialize the session manager.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
remote_session_dir: Directory on remote VM to store session pickles
|
|
53
|
+
"""
|
|
54
|
+
self.remote_session_dir = remote_session_dir
|
|
55
|
+
self.sessions: Dict[str, PythonSession] = {}
|
|
56
|
+
self._cleanup_interval_calls = 0
|
|
57
|
+
|
|
58
|
+
def get_or_create_session(self, session_id: str = None) -> PythonSession:
|
|
59
|
+
"""
|
|
60
|
+
Get an existing session or create a new one.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
session_id: Session identifier. If None, creates a new session.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
PythonSession object
|
|
67
|
+
"""
|
|
68
|
+
# Periodic cleanup every 100 calls
|
|
69
|
+
self._cleanup_interval_calls += 1
|
|
70
|
+
if self._cleanup_interval_calls >= 100:
|
|
71
|
+
self.cleanup_expired_sessions()
|
|
72
|
+
self._cleanup_interval_calls = 0
|
|
73
|
+
|
|
74
|
+
if session_id is None:
|
|
75
|
+
session_id = self._generate_session_id()
|
|
76
|
+
|
|
77
|
+
if session_id not in self.sessions:
|
|
78
|
+
session = PythonSession(
|
|
79
|
+
session_id=session_id,
|
|
80
|
+
created_at=datetime.now(),
|
|
81
|
+
last_accessed=datetime.now(),
|
|
82
|
+
remote_pickle_path=f"{self.remote_session_dir}/session_{session_id}.pkl",
|
|
83
|
+
)
|
|
84
|
+
self.sessions[session_id] = session
|
|
85
|
+
logger.info(f"Created new Python session: {session_id}")
|
|
86
|
+
else:
|
|
87
|
+
session = self.sessions[session_id]
|
|
88
|
+
session.touch()
|
|
89
|
+
|
|
90
|
+
return session
|
|
91
|
+
|
|
92
|
+
def _auto_capture_last_expr(self, code: str) -> str:
|
|
93
|
+
"""
|
|
94
|
+
Automatically capture the last expression's value as return_value.
|
|
95
|
+
|
|
96
|
+
If the last statement in the code is a pure expression (not an assignment,
|
|
97
|
+
function definition, etc.), wrap it to assign to return_value.
|
|
98
|
+
This makes the behavior similar to Jupyter notebooks.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
code: User's Python code
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Modified code with last expression captured, or original code if not applicable
|
|
105
|
+
"""
|
|
106
|
+
if not code or not code.strip():
|
|
107
|
+
return code
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
tree = ast.parse(code)
|
|
111
|
+
if not tree.body:
|
|
112
|
+
return code
|
|
113
|
+
|
|
114
|
+
last_stmt = tree.body[-1]
|
|
115
|
+
|
|
116
|
+
# Only process if the last statement is an expression (not assignment, etc.)
|
|
117
|
+
if isinstance(last_stmt, ast.Expr):
|
|
118
|
+
# Get the source lines
|
|
119
|
+
lines = code.split('\n')
|
|
120
|
+
|
|
121
|
+
# Find the start line of the last expression (1-indexed in AST)
|
|
122
|
+
last_expr_start = last_stmt.lineno - 1 # Convert to 0-indexed
|
|
123
|
+
|
|
124
|
+
# Get the expression text (may span multiple lines)
|
|
125
|
+
expr_lines = lines[last_expr_start:]
|
|
126
|
+
expr_text = '\n'.join(expr_lines).strip()
|
|
127
|
+
|
|
128
|
+
# Build new code: everything before + wrapped expression
|
|
129
|
+
prefix_lines = lines[:last_expr_start]
|
|
130
|
+
prefix = '\n'.join(prefix_lines)
|
|
131
|
+
|
|
132
|
+
# Wrap the expression to capture its value
|
|
133
|
+
wrapped_expr = f"return_value = ({expr_text})"
|
|
134
|
+
|
|
135
|
+
if prefix.strip():
|
|
136
|
+
return f"{prefix}\n{wrapped_expr}"
|
|
137
|
+
else:
|
|
138
|
+
return wrapped_expr
|
|
139
|
+
|
|
140
|
+
except SyntaxError:
|
|
141
|
+
# If code has syntax errors, return as-is and let the execution handle it
|
|
142
|
+
pass
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.debug(f"Could not auto-capture last expression: {e}")
|
|
145
|
+
|
|
146
|
+
return code
|
|
147
|
+
|
|
148
|
+
def prepare_session_code(
|
|
149
|
+
self,
|
|
150
|
+
code: str,
|
|
151
|
+
session: PythonSession,
|
|
152
|
+
varDict: Optional[Dict[str, Any]] = None,
|
|
153
|
+
) -> str:
|
|
154
|
+
"""
|
|
155
|
+
Prepare Python code with session management.
|
|
156
|
+
|
|
157
|
+
This wraps the user code with pickle load/save operations
|
|
158
|
+
to maintain state across executions.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
code: User's Python code
|
|
162
|
+
session: Session object
|
|
163
|
+
varDict: Optional variables to inject
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Complete Python code with session management
|
|
167
|
+
"""
|
|
168
|
+
session.execution_count += 1
|
|
169
|
+
|
|
170
|
+
session_code = []
|
|
171
|
+
|
|
172
|
+
# Header
|
|
173
|
+
session_code.append("# -*- coding: utf-8 -*-")
|
|
174
|
+
session_code.append("import pickle")
|
|
175
|
+
session_code.append("import os")
|
|
176
|
+
session_code.append("import sys")
|
|
177
|
+
session_code.append("import itertools")
|
|
178
|
+
session_code.append("import warnings")
|
|
179
|
+
session_code.append("")
|
|
180
|
+
session_code.append(
|
|
181
|
+
"# Suppress DeprecationWarning for itertools pickle support"
|
|
182
|
+
)
|
|
183
|
+
session_code.append(
|
|
184
|
+
"warnings.filterwarnings('ignore', category=DeprecationWarning, module='.*itertools.*')"
|
|
185
|
+
)
|
|
186
|
+
session_code.append("")
|
|
187
|
+
|
|
188
|
+
# Session info
|
|
189
|
+
session_code.append(f"# Session: {session.session_id}")
|
|
190
|
+
session_code.append(f"# Execution: {session.execution_count}")
|
|
191
|
+
session_code.append("")
|
|
192
|
+
|
|
193
|
+
# Initialize namespace
|
|
194
|
+
session_code.append("# Initialize session namespace")
|
|
195
|
+
session_code.append("__session_namespace = {}")
|
|
196
|
+
session_code.append(
|
|
197
|
+
"__session_pickle_path = '{}'".format(session.remote_pickle_path)
|
|
198
|
+
)
|
|
199
|
+
session_code.append("")
|
|
200
|
+
|
|
201
|
+
# Load existing session state if available
|
|
202
|
+
session_code.append("# Load existing session state")
|
|
203
|
+
session_code.append("if os.path.exists(__session_pickle_path):")
|
|
204
|
+
session_code.append(" try:")
|
|
205
|
+
session_code.append(" with open(__session_pickle_path, 'rb') as f:")
|
|
206
|
+
session_code.append(" __session_data = pickle.load(f)")
|
|
207
|
+
session_code.append(" ")
|
|
208
|
+
session_code.append(
|
|
209
|
+
" # Handle both old format (dict) and new format (dict with variables/modules)"
|
|
210
|
+
)
|
|
211
|
+
session_code.append(
|
|
212
|
+
" if isinstance(__session_data, dict) and 'variables' in __session_data:"
|
|
213
|
+
)
|
|
214
|
+
session_code.append(
|
|
215
|
+
" __session_namespace = __session_data['variables']"
|
|
216
|
+
)
|
|
217
|
+
session_code.append(
|
|
218
|
+
" __session_modules = __session_data.get('modules', {})"
|
|
219
|
+
)
|
|
220
|
+
session_code.append(" else:")
|
|
221
|
+
session_code.append(" # Old format - treat as variables only")
|
|
222
|
+
session_code.append(" __session_namespace = __session_data")
|
|
223
|
+
session_code.append(" __session_modules = {}")
|
|
224
|
+
session_code.append(" ")
|
|
225
|
+
session_code.append(" # Restore modules first")
|
|
226
|
+
session_code.append(" __failed_modules = []")
|
|
227
|
+
session_code.append(
|
|
228
|
+
" for __mod_name, __mod_import_name in __session_modules.items():"
|
|
229
|
+
)
|
|
230
|
+
session_code.append(" try:")
|
|
231
|
+
session_code.append(" __module = __import__(__mod_import_name)")
|
|
232
|
+
session_code.append(" globals()[__mod_name] = __module")
|
|
233
|
+
session_code.append(" except ImportError:")
|
|
234
|
+
session_code.append(" __failed_modules.append(__mod_name)")
|
|
235
|
+
session_code.append(" ")
|
|
236
|
+
session_code.append(" if __failed_modules:")
|
|
237
|
+
session_code.append(
|
|
238
|
+
" print(f'Note: Some modules could not be auto-restored: {\", \".join(__failed_modules)}. Please re-import if needed.')"
|
|
239
|
+
)
|
|
240
|
+
session_code.append(" ")
|
|
241
|
+
session_code.append(" # Restore variables to global namespace")
|
|
242
|
+
session_code.append(
|
|
243
|
+
" for __key, __value in __session_namespace.items():"
|
|
244
|
+
)
|
|
245
|
+
session_code.append(" if not __key.startswith('__'):")
|
|
246
|
+
session_code.append(" globals()[__key] = __value")
|
|
247
|
+
session_code.append(
|
|
248
|
+
" print(f'Session restored: {len(__session_namespace)} variables, {len(__session_modules)} modules loaded')"
|
|
249
|
+
)
|
|
250
|
+
session_code.append(" except Exception as e:")
|
|
251
|
+
session_code.append(
|
|
252
|
+
" print(f'Warning: Could not load session state: {e}')"
|
|
253
|
+
)
|
|
254
|
+
session_code.append(" __session_namespace = {}")
|
|
255
|
+
session_code.append(" __session_modules = {}")
|
|
256
|
+
session_code.append("else:")
|
|
257
|
+
session_code.append(" print('Starting new session')")
|
|
258
|
+
session_code.append("")
|
|
259
|
+
|
|
260
|
+
# Inject varDict if provided
|
|
261
|
+
if varDict:
|
|
262
|
+
session_code.append("# Inject provided variables")
|
|
263
|
+
for key, value in varDict.items():
|
|
264
|
+
# Serialize the value safely
|
|
265
|
+
session_code.append(f"{key} = {repr(value)}")
|
|
266
|
+
session_code.append("")
|
|
267
|
+
|
|
268
|
+
# Add execution counter
|
|
269
|
+
session_code.append("# Execution counter")
|
|
270
|
+
session_code.append(f"__execution_count = {session.execution_count}")
|
|
271
|
+
session_code.append(
|
|
272
|
+
"print(f'[Session {}: Execution #{{__execution_count}}]')".format(
|
|
273
|
+
session.session_id[:8]
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
session_code.append("")
|
|
277
|
+
|
|
278
|
+
# Auto-capture last expression value (like Jupyter)
|
|
279
|
+
processed_code = self._auto_capture_last_expr(code)
|
|
280
|
+
|
|
281
|
+
# Execute user code
|
|
282
|
+
session_code.append("# User code execution")
|
|
283
|
+
session_code.append("try:")
|
|
284
|
+
# Indent user code
|
|
285
|
+
for line in processed_code.split("\n"):
|
|
286
|
+
if line.strip():
|
|
287
|
+
session_code.append(f" {line}")
|
|
288
|
+
else:
|
|
289
|
+
session_code.append("")
|
|
290
|
+
session_code.append("except Exception as e:")
|
|
291
|
+
session_code.append(" print(f'Error in user code: {e}')")
|
|
292
|
+
session_code.append(" import traceback")
|
|
293
|
+
session_code.append(" traceback.print_exc()")
|
|
294
|
+
session_code.append("")
|
|
295
|
+
|
|
296
|
+
# Save session state
|
|
297
|
+
session_code.append("# Save session state")
|
|
298
|
+
session_code.append("try:")
|
|
299
|
+
session_code.append(" # Ensure directory exists")
|
|
300
|
+
session_code.append(
|
|
301
|
+
" os.makedirs(os.path.dirname(__session_pickle_path), exist_ok=True)"
|
|
302
|
+
)
|
|
303
|
+
session_code.append(" ")
|
|
304
|
+
session_code.append(" # Collect all non-private variables and modules")
|
|
305
|
+
session_code.append(" __vars_to_save = {}")
|
|
306
|
+
session_code.append(" __modules_to_save = {}")
|
|
307
|
+
session_code.append(" for __key, __value in list(globals().items()):")
|
|
308
|
+
session_code.append(
|
|
309
|
+
" if not __key.startswith('__') and __key not in ['pickle', 'os', 'sys']:"
|
|
310
|
+
)
|
|
311
|
+
session_code.append(" # Handle modules separately")
|
|
312
|
+
session_code.append(
|
|
313
|
+
" if hasattr(__value, '__name__') and hasattr(__value, '__package__'):"
|
|
314
|
+
)
|
|
315
|
+
session_code.append(
|
|
316
|
+
" __modules_to_save[__key] = __value.__name__"
|
|
317
|
+
)
|
|
318
|
+
session_code.append(" else:")
|
|
319
|
+
session_code.append(
|
|
320
|
+
" # Skip user-defined functions as they cause pickle issues"
|
|
321
|
+
)
|
|
322
|
+
session_code.append(
|
|
323
|
+
" if callable(__value) and hasattr(__value, '__name__') and getattr(__value, '__module__', None) == '__main__':"
|
|
324
|
+
)
|
|
325
|
+
session_code.append(" # Skip user-defined functions")
|
|
326
|
+
session_code.append(" pass")
|
|
327
|
+
session_code.append(" else:")
|
|
328
|
+
session_code.append(" # Check if it's an itertools object")
|
|
329
|
+
session_code.append(
|
|
330
|
+
" if type(__value).__module__ == 'itertools':"
|
|
331
|
+
)
|
|
332
|
+
session_code.append(
|
|
333
|
+
" # Skip itertools objects to avoid deprecation warnings"
|
|
334
|
+
)
|
|
335
|
+
session_code.append(" pass")
|
|
336
|
+
session_code.append(" else:")
|
|
337
|
+
session_code.append(" try:")
|
|
338
|
+
session_code.append(
|
|
339
|
+
" # Test if object is pickleable"
|
|
340
|
+
)
|
|
341
|
+
session_code.append(
|
|
342
|
+
" with warnings.catch_warnings():"
|
|
343
|
+
)
|
|
344
|
+
session_code.append(
|
|
345
|
+
" warnings.simplefilter('ignore', DeprecationWarning)"
|
|
346
|
+
)
|
|
347
|
+
session_code.append(" pickle.dumps(__value)")
|
|
348
|
+
session_code.append(
|
|
349
|
+
" __vars_to_save[__key] = __value"
|
|
350
|
+
)
|
|
351
|
+
session_code.append(" except Exception:")
|
|
352
|
+
session_code.append(" # Skip non-pickleable objects")
|
|
353
|
+
session_code.append(" pass")
|
|
354
|
+
session_code.append(" ")
|
|
355
|
+
session_code.append(" # Save variables and modules info")
|
|
356
|
+
session_code.append(" __session_data = {")
|
|
357
|
+
session_code.append(" 'variables': __vars_to_save,")
|
|
358
|
+
session_code.append(" 'modules': __modules_to_save")
|
|
359
|
+
session_code.append(" }")
|
|
360
|
+
session_code.append(" with warnings.catch_warnings():")
|
|
361
|
+
session_code.append(
|
|
362
|
+
" warnings.simplefilter('ignore', DeprecationWarning)"
|
|
363
|
+
)
|
|
364
|
+
session_code.append(" with open(__session_pickle_path, 'wb') as f:")
|
|
365
|
+
session_code.append(" pickle.dump(__session_data, f)")
|
|
366
|
+
session_code.append(
|
|
367
|
+
" print(f'\\nSession saved: {len(__vars_to_save)} variables, {len(__modules_to_save)} modules persisted')"
|
|
368
|
+
)
|
|
369
|
+
session_code.append("except Exception as e:")
|
|
370
|
+
session_code.append(" print(f'Warning: Could not save session state: {e}')")
|
|
371
|
+
session_code.append("")
|
|
372
|
+
|
|
373
|
+
# Print final return value if it exists
|
|
374
|
+
session_code.append("# Output return value if defined")
|
|
375
|
+
session_code.append("if 'return_value' in globals():")
|
|
376
|
+
session_code.append(" print(f'\\nReturn value: {return_value}')")
|
|
377
|
+
|
|
378
|
+
return "\n".join(session_code)
|
|
379
|
+
|
|
380
|
+
def clear_session(self, session_id: str) -> bool:
|
|
381
|
+
"""
|
|
382
|
+
Clear a specific session.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
session_id: Session to clear
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
True if session was cleared, False otherwise
|
|
389
|
+
"""
|
|
390
|
+
if session_id in self.sessions:
|
|
391
|
+
del self.sessions[session_id]
|
|
392
|
+
logger.info(f"Cleared session: {session_id}")
|
|
393
|
+
return True
|
|
394
|
+
return False
|
|
395
|
+
|
|
396
|
+
def cleanup_expired_sessions(self, timeout_hours: int = 6):
|
|
397
|
+
"""
|
|
398
|
+
Remove expired sessions.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
timeout_hours: Sessions older than this are removed
|
|
402
|
+
"""
|
|
403
|
+
expired = [
|
|
404
|
+
sid
|
|
405
|
+
for sid, session in self.sessions.items()
|
|
406
|
+
if session.is_expired(timeout_hours)
|
|
407
|
+
]
|
|
408
|
+
|
|
409
|
+
for sid in expired:
|
|
410
|
+
self.clear_session(sid)
|
|
411
|
+
|
|
412
|
+
if expired:
|
|
413
|
+
logger.info(f"Cleaned up {len(expired)} expired sessions")
|
|
414
|
+
|
|
415
|
+
def get_session_info(self, session_id: str) -> Optional[Dict[str, Any]]:
|
|
416
|
+
"""
|
|
417
|
+
Get information about a session.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
session_id: Session identifier
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Session information dict or None if not found
|
|
424
|
+
"""
|
|
425
|
+
if session_id not in self.sessions:
|
|
426
|
+
return None
|
|
427
|
+
|
|
428
|
+
session = self.sessions[session_id]
|
|
429
|
+
return {
|
|
430
|
+
"session_id": session.session_id,
|
|
431
|
+
"created_at": session.created_at.isoformat(),
|
|
432
|
+
"last_accessed": session.last_accessed.isoformat(),
|
|
433
|
+
"execution_count": session.execution_count,
|
|
434
|
+
"variable_count": len(session.namespace),
|
|
435
|
+
"variables": list(session.namespace.keys()),
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
def list_sessions(self) -> Dict[str, Dict[str, Any]]:
|
|
439
|
+
"""
|
|
440
|
+
List all active sessions.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
Dict of session information
|
|
444
|
+
"""
|
|
445
|
+
return {sid: self.get_session_info(sid) for sid in self.sessions.keys()}
|
|
446
|
+
|
|
447
|
+
def _generate_session_id(self) -> str:
|
|
448
|
+
"""Generate a unique session ID"""
|
|
449
|
+
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
450
|
+
random_suffix = "".join(
|
|
451
|
+
random.choices(string.ascii_lowercase + string.digits, k=6)
|
|
452
|
+
)
|
|
453
|
+
return f"ps_{timestamp}_{random_suffix}"
|