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.
Files changed (199) hide show
  1. DolphinLanguageSDK/__init__.py +58 -0
  2. dolphin/__init__.py +62 -0
  3. dolphin/cli/__init__.py +20 -0
  4. dolphin/cli/args/__init__.py +9 -0
  5. dolphin/cli/args/parser.py +567 -0
  6. dolphin/cli/builtin_agents/__init__.py +22 -0
  7. dolphin/cli/commands/__init__.py +4 -0
  8. dolphin/cli/interrupt/__init__.py +8 -0
  9. dolphin/cli/interrupt/handler.py +205 -0
  10. dolphin/cli/interrupt/keyboard.py +82 -0
  11. dolphin/cli/main.py +49 -0
  12. dolphin/cli/multimodal/__init__.py +34 -0
  13. dolphin/cli/multimodal/clipboard.py +327 -0
  14. dolphin/cli/multimodal/handler.py +249 -0
  15. dolphin/cli/multimodal/image_processor.py +214 -0
  16. dolphin/cli/multimodal/input_parser.py +149 -0
  17. dolphin/cli/runner/__init__.py +8 -0
  18. dolphin/cli/runner/runner.py +989 -0
  19. dolphin/cli/ui/__init__.py +10 -0
  20. dolphin/cli/ui/console.py +2795 -0
  21. dolphin/cli/ui/input.py +340 -0
  22. dolphin/cli/ui/layout.py +425 -0
  23. dolphin/cli/ui/stream_renderer.py +302 -0
  24. dolphin/cli/utils/__init__.py +8 -0
  25. dolphin/cli/utils/helpers.py +135 -0
  26. dolphin/cli/utils/version.py +49 -0
  27. dolphin/core/__init__.py +107 -0
  28. dolphin/core/agent/__init__.py +10 -0
  29. dolphin/core/agent/agent_state.py +69 -0
  30. dolphin/core/agent/base_agent.py +970 -0
  31. dolphin/core/code_block/__init__.py +0 -0
  32. dolphin/core/code_block/agent_init_block.py +0 -0
  33. dolphin/core/code_block/assign_block.py +98 -0
  34. dolphin/core/code_block/basic_code_block.py +1865 -0
  35. dolphin/core/code_block/explore_block.py +1327 -0
  36. dolphin/core/code_block/explore_block_v2.py +712 -0
  37. dolphin/core/code_block/explore_strategy.py +672 -0
  38. dolphin/core/code_block/judge_block.py +220 -0
  39. dolphin/core/code_block/prompt_block.py +32 -0
  40. dolphin/core/code_block/skill_call_deduplicator.py +291 -0
  41. dolphin/core/code_block/tool_block.py +129 -0
  42. dolphin/core/common/__init__.py +17 -0
  43. dolphin/core/common/constants.py +176 -0
  44. dolphin/core/common/enums.py +1173 -0
  45. dolphin/core/common/exceptions.py +133 -0
  46. dolphin/core/common/multimodal.py +539 -0
  47. dolphin/core/common/object_type.py +165 -0
  48. dolphin/core/common/output_format.py +432 -0
  49. dolphin/core/common/types.py +36 -0
  50. dolphin/core/config/__init__.py +16 -0
  51. dolphin/core/config/global_config.py +1289 -0
  52. dolphin/core/config/ontology_config.py +133 -0
  53. dolphin/core/context/__init__.py +12 -0
  54. dolphin/core/context/context.py +1580 -0
  55. dolphin/core/context/context_manager.py +161 -0
  56. dolphin/core/context/var_output.py +82 -0
  57. dolphin/core/context/variable_pool.py +356 -0
  58. dolphin/core/context_engineer/__init__.py +41 -0
  59. dolphin/core/context_engineer/config/__init__.py +5 -0
  60. dolphin/core/context_engineer/config/settings.py +402 -0
  61. dolphin/core/context_engineer/core/__init__.py +7 -0
  62. dolphin/core/context_engineer/core/budget_manager.py +327 -0
  63. dolphin/core/context_engineer/core/context_assembler.py +583 -0
  64. dolphin/core/context_engineer/core/context_manager.py +637 -0
  65. dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
  66. dolphin/core/context_engineer/example/incremental_example.py +267 -0
  67. dolphin/core/context_engineer/example/traditional_example.py +334 -0
  68. dolphin/core/context_engineer/services/__init__.py +5 -0
  69. dolphin/core/context_engineer/services/compressor.py +399 -0
  70. dolphin/core/context_engineer/utils/__init__.py +6 -0
  71. dolphin/core/context_engineer/utils/context_utils.py +441 -0
  72. dolphin/core/context_engineer/utils/message_formatter.py +270 -0
  73. dolphin/core/context_engineer/utils/token_utils.py +139 -0
  74. dolphin/core/coroutine/__init__.py +15 -0
  75. dolphin/core/coroutine/context_snapshot.py +154 -0
  76. dolphin/core/coroutine/context_snapshot_profile.py +922 -0
  77. dolphin/core/coroutine/context_snapshot_store.py +268 -0
  78. dolphin/core/coroutine/execution_frame.py +145 -0
  79. dolphin/core/coroutine/execution_state_registry.py +161 -0
  80. dolphin/core/coroutine/resume_handle.py +101 -0
  81. dolphin/core/coroutine/step_result.py +101 -0
  82. dolphin/core/executor/__init__.py +18 -0
  83. dolphin/core/executor/debug_controller.py +630 -0
  84. dolphin/core/executor/dolphin_executor.py +1063 -0
  85. dolphin/core/executor/executor.py +624 -0
  86. dolphin/core/flags/__init__.py +27 -0
  87. dolphin/core/flags/definitions.py +49 -0
  88. dolphin/core/flags/manager.py +113 -0
  89. dolphin/core/hook/__init__.py +95 -0
  90. dolphin/core/hook/expression_evaluator.py +499 -0
  91. dolphin/core/hook/hook_dispatcher.py +380 -0
  92. dolphin/core/hook/hook_types.py +248 -0
  93. dolphin/core/hook/isolated_variable_pool.py +284 -0
  94. dolphin/core/interfaces.py +53 -0
  95. dolphin/core/llm/__init__.py +0 -0
  96. dolphin/core/llm/llm.py +495 -0
  97. dolphin/core/llm/llm_call.py +100 -0
  98. dolphin/core/llm/llm_client.py +1285 -0
  99. dolphin/core/llm/message_sanitizer.py +120 -0
  100. dolphin/core/logging/__init__.py +20 -0
  101. dolphin/core/logging/logger.py +526 -0
  102. dolphin/core/message/__init__.py +8 -0
  103. dolphin/core/message/compressor.py +749 -0
  104. dolphin/core/parser/__init__.py +8 -0
  105. dolphin/core/parser/parser.py +405 -0
  106. dolphin/core/runtime/__init__.py +10 -0
  107. dolphin/core/runtime/runtime_graph.py +926 -0
  108. dolphin/core/runtime/runtime_instance.py +446 -0
  109. dolphin/core/skill/__init__.py +14 -0
  110. dolphin/core/skill/context_retention.py +157 -0
  111. dolphin/core/skill/skill_function.py +686 -0
  112. dolphin/core/skill/skill_matcher.py +282 -0
  113. dolphin/core/skill/skillkit.py +700 -0
  114. dolphin/core/skill/skillset.py +72 -0
  115. dolphin/core/trajectory/__init__.py +10 -0
  116. dolphin/core/trajectory/recorder.py +189 -0
  117. dolphin/core/trajectory/trajectory.py +522 -0
  118. dolphin/core/utils/__init__.py +9 -0
  119. dolphin/core/utils/cache_kv.py +212 -0
  120. dolphin/core/utils/tools.py +340 -0
  121. dolphin/lib/__init__.py +93 -0
  122. dolphin/lib/debug/__init__.py +8 -0
  123. dolphin/lib/debug/visualizer.py +409 -0
  124. dolphin/lib/memory/__init__.py +28 -0
  125. dolphin/lib/memory/async_processor.py +220 -0
  126. dolphin/lib/memory/llm_calls.py +195 -0
  127. dolphin/lib/memory/manager.py +78 -0
  128. dolphin/lib/memory/sandbox.py +46 -0
  129. dolphin/lib/memory/storage.py +245 -0
  130. dolphin/lib/memory/utils.py +51 -0
  131. dolphin/lib/ontology/__init__.py +12 -0
  132. dolphin/lib/ontology/basic/__init__.py +0 -0
  133. dolphin/lib/ontology/basic/base.py +102 -0
  134. dolphin/lib/ontology/basic/concept.py +130 -0
  135. dolphin/lib/ontology/basic/object.py +11 -0
  136. dolphin/lib/ontology/basic/relation.py +63 -0
  137. dolphin/lib/ontology/datasource/__init__.py +27 -0
  138. dolphin/lib/ontology/datasource/datasource.py +66 -0
  139. dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
  140. dolphin/lib/ontology/datasource/sql.py +845 -0
  141. dolphin/lib/ontology/mapping.py +177 -0
  142. dolphin/lib/ontology/ontology.py +733 -0
  143. dolphin/lib/ontology/ontology_context.py +16 -0
  144. dolphin/lib/ontology/ontology_manager.py +107 -0
  145. dolphin/lib/skill_results/__init__.py +31 -0
  146. dolphin/lib/skill_results/cache_backend.py +559 -0
  147. dolphin/lib/skill_results/result_processor.py +181 -0
  148. dolphin/lib/skill_results/result_reference.py +179 -0
  149. dolphin/lib/skill_results/skillkit_hook.py +324 -0
  150. dolphin/lib/skill_results/strategies.py +328 -0
  151. dolphin/lib/skill_results/strategy_registry.py +150 -0
  152. dolphin/lib/skillkits/__init__.py +44 -0
  153. dolphin/lib/skillkits/agent_skillkit.py +155 -0
  154. dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
  155. dolphin/lib/skillkits/env_skillkit.py +250 -0
  156. dolphin/lib/skillkits/mcp_adapter.py +616 -0
  157. dolphin/lib/skillkits/mcp_skillkit.py +771 -0
  158. dolphin/lib/skillkits/memory_skillkit.py +650 -0
  159. dolphin/lib/skillkits/noop_skillkit.py +31 -0
  160. dolphin/lib/skillkits/ontology_skillkit.py +89 -0
  161. dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
  162. dolphin/lib/skillkits/resource/__init__.py +52 -0
  163. dolphin/lib/skillkits/resource/models/__init__.py +6 -0
  164. dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
  165. dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
  166. dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
  167. dolphin/lib/skillkits/resource/skill_cache.py +215 -0
  168. dolphin/lib/skillkits/resource/skill_loader.py +395 -0
  169. dolphin/lib/skillkits/resource/skill_validator.py +406 -0
  170. dolphin/lib/skillkits/resource_skillkit.py +11 -0
  171. dolphin/lib/skillkits/search_skillkit.py +163 -0
  172. dolphin/lib/skillkits/sql_skillkit.py +274 -0
  173. dolphin/lib/skillkits/system_skillkit.py +509 -0
  174. dolphin/lib/skillkits/vm_skillkit.py +65 -0
  175. dolphin/lib/utils/__init__.py +9 -0
  176. dolphin/lib/utils/data_process.py +207 -0
  177. dolphin/lib/utils/handle_progress.py +178 -0
  178. dolphin/lib/utils/security.py +139 -0
  179. dolphin/lib/utils/text_retrieval.py +462 -0
  180. dolphin/lib/vm/__init__.py +11 -0
  181. dolphin/lib/vm/env_executor.py +895 -0
  182. dolphin/lib/vm/python_session_manager.py +453 -0
  183. dolphin/lib/vm/vm.py +610 -0
  184. dolphin/sdk/__init__.py +60 -0
  185. dolphin/sdk/agent/__init__.py +12 -0
  186. dolphin/sdk/agent/agent_factory.py +236 -0
  187. dolphin/sdk/agent/dolphin_agent.py +1106 -0
  188. dolphin/sdk/api/__init__.py +4 -0
  189. dolphin/sdk/runtime/__init__.py +8 -0
  190. dolphin/sdk/runtime/env.py +363 -0
  191. dolphin/sdk/skill/__init__.py +10 -0
  192. dolphin/sdk/skill/global_skills.py +706 -0
  193. dolphin/sdk/skill/traditional_toolkit.py +260 -0
  194. kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
  195. kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
  196. kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
  197. kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
  198. kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
  199. kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,268 @@
1
+ import os
2
+ import json
3
+ import threading
4
+ import time
5
+ from abc import ABC, abstractmethod
6
+ from typing import Dict, List, Optional
7
+
8
+ from dolphin.core.logging.logger import get_logger
9
+ from .context_snapshot import ContextSnapshot
10
+
11
+ logger = get_logger()
12
+
13
+
14
+ class ContextSnapshotStore(ABC):
15
+ """Abstract interface for context snapshot storage"""
16
+
17
+ @abstractmethod
18
+ def save_snapshot(self, snapshot: ContextSnapshot) -> str:
19
+ """Save snapshot, return snapshot ID"""
20
+ pass
21
+
22
+ @abstractmethod
23
+ def load_snapshot(self, snapshot_id: str) -> Optional[ContextSnapshot]:
24
+ """Load snapshot"""
25
+ pass
26
+
27
+ @abstractmethod
28
+ def delete_snapshot(self, snapshot_id: str) -> bool:
29
+ """Delete snapshot"""
30
+ pass
31
+
32
+ @abstractmethod
33
+ def list_snapshots(self, frame_id: Optional[str] = None) -> List[str]:
34
+ """List snapshots"""
35
+ pass
36
+
37
+ @abstractmethod
38
+ def save_pending_snapshot(self, snapshot: ContextSnapshot) -> str:
39
+ """Save pending snapshot (for transactional)"""
40
+ pass
41
+
42
+ @abstractmethod
43
+ def finalize_snapshot(self, snapshot_id: str) -> bool:
44
+ """Confirm Snapshot (Atomic Rename)"""
45
+ pass
46
+
47
+
48
+ class FileContextSnapshotStore(ContextSnapshotStore):
49
+ """Filesystem-based context snapshot storage"""
50
+
51
+ def __init__(self, base_path: str = "./data/snapshots"):
52
+ self.base_path = base_path
53
+ self._lock = threading.RLock()
54
+ self._ensure_directory()
55
+
56
+ def _ensure_directory(self):
57
+ """Ensure the storage directory exists"""
58
+ os.makedirs(self.base_path, exist_ok=True)
59
+
60
+ def _get_snapshot_path(self, snapshot_id: str) -> str:
61
+ """Get snapshot file path"""
62
+ return os.path.join(self.base_path, f"{snapshot_id}.json")
63
+
64
+ def _get_pending_path(self, snapshot_id: str) -> str:
65
+ """Get pending snapshot file path"""
66
+ return os.path.join(self.base_path, f"{snapshot_id}.pending.json")
67
+
68
+ def save_snapshot(self, snapshot: ContextSnapshot) -> str:
69
+ """Save snapshot"""
70
+ with self._lock:
71
+ file_path = self._get_snapshot_path(snapshot.snapshot_id)
72
+ try:
73
+ with open(file_path, "w", encoding="utf-8") as f:
74
+ json.dump(snapshot.encode(), f, ensure_ascii=False, indent=2)
75
+ return snapshot.snapshot_id
76
+ except Exception as e:
77
+ raise RuntimeError(
78
+ f"Failed to save snapshot {snapshot.snapshot_id}: {e}"
79
+ )
80
+
81
+ def load_snapshot(self, snapshot_id: str) -> Optional[ContextSnapshot]:
82
+ """Load snapshot"""
83
+ with self._lock:
84
+ file_path = self._get_snapshot_path(snapshot_id)
85
+ if not os.path.exists(file_path):
86
+ return None
87
+
88
+ try:
89
+ with open(file_path, "r", encoding="utf-8") as f:
90
+ data = json.load(f)
91
+ return ContextSnapshot.decode(data)
92
+ except Exception as e:
93
+ raise RuntimeError(f"Failed to load snapshot {snapshot_id}: {e}")
94
+
95
+ def delete_snapshot(self, snapshot_id: str) -> bool:
96
+ """Delete snapshot"""
97
+ with self._lock:
98
+ file_path = self._get_snapshot_path(snapshot_id)
99
+ if os.path.exists(file_path):
100
+ try:
101
+ os.remove(file_path)
102
+ return True
103
+ except Exception:
104
+ return False
105
+ return False
106
+
107
+ def list_snapshots(self, frame_id: Optional[str] = None) -> List[str]:
108
+ """List snapshots"""
109
+ with self._lock:
110
+ snapshots = []
111
+ if not os.path.exists(self.base_path):
112
+ return snapshots
113
+
114
+ for filename in os.listdir(self.base_path):
115
+ if filename.endswith(".json") and not filename.endswith(
116
+ ".pending.json"
117
+ ):
118
+ snapshot_id = filename[:-5] # Remove .json suffix
119
+
120
+ # If frame_id is specified, filter it.
121
+ if frame_id:
122
+ try:
123
+ snapshot = self.load_snapshot(snapshot_id)
124
+ if snapshot and snapshot.frame_id == frame_id:
125
+ snapshots.append(snapshot_id)
126
+ except Exception:
127
+ continue
128
+ else:
129
+ snapshots.append(snapshot_id)
130
+
131
+ return sorted(snapshots)
132
+
133
+ def save_pending_snapshot(self, snapshot: ContextSnapshot) -> str:
134
+ """Pending snapshot"""
135
+ with self._lock:
136
+ pending_path = self._get_pending_path(snapshot.snapshot_id)
137
+ try:
138
+ with open(pending_path, "w", encoding="utf-8") as f:
139
+ json.dump(snapshot.encode(), f, ensure_ascii=False, indent=2)
140
+ return snapshot.snapshot_id
141
+ except Exception as e:
142
+ raise RuntimeError(
143
+ f"Failed to save pending snapshot {snapshot.snapshot_id}: {e}"
144
+ )
145
+
146
+ def finalize_snapshot(self, snapshot_id: str) -> bool:
147
+ """Confirm Snapshot (Atomic Rename)"""
148
+ with self._lock:
149
+ pending_path = self._get_pending_path(snapshot_id)
150
+ final_path = self._get_snapshot_path(snapshot_id)
151
+
152
+ if not os.path.exists(pending_path):
153
+ return False
154
+
155
+ try:
156
+ os.rename(pending_path, final_path)
157
+ return True
158
+ except Exception:
159
+ return False
160
+
161
+ def cleanup_pending_snapshots(self, max_age_seconds: int = 3600) -> int:
162
+ """Clean up expired pending snapshots"""
163
+ with self._lock:
164
+ cleaned = 0
165
+ current_time = time.time()
166
+
167
+ if not os.path.exists(self.base_path):
168
+ return cleaned
169
+
170
+ for filename in os.listdir(self.base_path):
171
+ if filename.endswith(".pending.json"):
172
+ file_path = os.path.join(self.base_path, filename)
173
+ try:
174
+ file_mtime = os.path.getmtime(file_path)
175
+ if current_time - file_mtime > max_age_seconds:
176
+ os.remove(file_path)
177
+ cleaned += 1
178
+ except Exception:
179
+ continue
180
+
181
+ return cleaned
182
+
183
+ def get_snapshot_stats(self) -> Dict:
184
+ """Get snapshot storage statistics"""
185
+ with self._lock:
186
+ stats = {
187
+ "total_snapshots": 0,
188
+ "pending_snapshots": 0,
189
+ "total_size_bytes": 0,
190
+ }
191
+
192
+ if not os.path.exists(self.base_path):
193
+ return stats
194
+
195
+ for filename in os.listdir(self.base_path):
196
+ file_path = os.path.join(self.base_path, filename)
197
+ try:
198
+ file_size = os.path.getsize(file_path)
199
+ stats["total_size_bytes"] += file_size
200
+
201
+ if filename.endswith(".pending.json"):
202
+ stats["pending_snapshots"] += 1
203
+ elif filename.endswith(".json"):
204
+ stats["total_snapshots"] += 1
205
+ except Exception:
206
+ continue
207
+
208
+ return stats
209
+
210
+
211
+ class MemoryContextSnapshotStore(ContextSnapshotStore):
212
+ """In-memory context snapshot storage (for testing)"""
213
+
214
+ def __init__(self):
215
+ self._snapshots: Dict[str, ContextSnapshot] = {}
216
+ self._pending: Dict[str, ContextSnapshot] = {}
217
+ self._lock = threading.RLock()
218
+
219
+ def save_snapshot(self, snapshot: ContextSnapshot) -> str:
220
+ """Save snapshot"""
221
+ with self._lock:
222
+ self._snapshots[snapshot.snapshot_id] = snapshot
223
+ return snapshot.snapshot_id
224
+
225
+ def load_snapshot(self, snapshot_id: str) -> Optional[ContextSnapshot]:
226
+ """Load snapshot"""
227
+ with self._lock:
228
+ return self._snapshots.get(snapshot_id)
229
+
230
+ def delete_snapshot(self, snapshot_id: str) -> bool:
231
+ """Delete snapshot"""
232
+ with self._lock:
233
+ if snapshot_id in self._snapshots:
234
+ del self._snapshots[snapshot_id]
235
+ return True
236
+ return False
237
+
238
+ def list_snapshots(self, frame_id: Optional[str] = None) -> List[str]:
239
+ """List snapshots"""
240
+ with self._lock:
241
+ if frame_id:
242
+ return [
243
+ sid
244
+ for sid, snapshot in self._snapshots.items()
245
+ if snapshot.frame_id == frame_id
246
+ ]
247
+ return list(self._snapshots.keys())
248
+
249
+ def save_pending_snapshot(self, snapshot: ContextSnapshot) -> str:
250
+ """Pending snapshot"""
251
+ with self._lock:
252
+ self._pending[snapshot.snapshot_id] = snapshot
253
+ return snapshot.snapshot_id
254
+
255
+ def finalize_snapshot(self, snapshot_id: str) -> bool:
256
+ """Confirm Snapshot"""
257
+ with self._lock:
258
+ if snapshot_id in self._pending:
259
+ snapshot = self._pending.pop(snapshot_id)
260
+ self._snapshots[snapshot_id] = snapshot
261
+ return True
262
+ return False
263
+
264
+ def clear(self):
265
+ """Clear all snapshots"""
266
+ with self._lock:
267
+ self._snapshots.clear()
268
+ self._pending.clear()
@@ -0,0 +1,145 @@
1
+ import time
2
+ import uuid
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Optional, List, Dict
6
+
7
+
8
+ class FrameStatus(Enum):
9
+ RUNNING = "running"
10
+ PAUSED = "paused"
11
+ COMPLETED = "completed"
12
+ FAILED = "failed"
13
+ WAITING_FOR_INTERVENTION = "waiting_for_intervention"
14
+ TERMINATED = "terminated"
15
+
16
+
17
+ class WaitReason(Enum):
18
+ """Reason for WAITING status (entropy-reduction: reuse WAITING with discriminator).
19
+
20
+ This enum distinguishes different waiting scenarios without adding new frame statuses.
21
+ """
22
+ TOOL_REQUEST = "tool_request" # Tool requests user input (ToolInterrupt)
23
+ USER_INTERRUPT = "user_interrupt" # User actively interrupted (UserInterrupt)
24
+
25
+
26
+ @dataclass
27
+ class ExecutionFrame:
28
+ """Execution frame - saves execution state and control flow information"""
29
+
30
+ frame_id: str
31
+ parent_id: Optional[str] = None
32
+ agent_id: Optional[str] = None
33
+ block_pointer: int = 0
34
+ block_stack: List[Dict] = None
35
+ status: FrameStatus = FrameStatus.RUNNING
36
+ context_snapshot_id: str = ""
37
+ children: List[str] = None
38
+ created_at: float = None
39
+ updated_at: float = None
40
+ original_content: str = "" # Added: Save original dolphin script content
41
+ error: Optional[Dict] = None # Added: Error Messages
42
+ wait_reason: Optional[WaitReason] = None # Reason when in WAITING status
43
+
44
+ def __post_init__(self):
45
+ if self.block_stack is None:
46
+ self.block_stack = []
47
+ if self.children is None:
48
+ self.children = []
49
+ if self.created_at is None:
50
+ self.created_at = time.time()
51
+ if self.updated_at is None:
52
+ self.updated_at = time.time()
53
+
54
+ @classmethod
55
+ def create_root_frame(cls, agent_id: Optional[str] = None) -> "ExecutionFrame":
56
+ """Create root execution frame"""
57
+ return cls(
58
+ frame_id=str(uuid.uuid4()),
59
+ parent_id=None,
60
+ agent_id=agent_id,
61
+ block_pointer=0,
62
+ block_stack=[],
63
+ status=FrameStatus.RUNNING,
64
+ children=[],
65
+ )
66
+
67
+ @classmethod
68
+ def create_child_frame(
69
+ cls, parent_frame: "ExecutionFrame", agent_id: Optional[str] = None
70
+ ) -> "ExecutionFrame":
71
+ """Create sub-execution frame"""
72
+ return cls(
73
+ frame_id=str(uuid.uuid4()),
74
+ parent_id=parent_frame.frame_id,
75
+ agent_id=agent_id,
76
+ block_pointer=0,
77
+ block_stack=[],
78
+ status=FrameStatus.RUNNING,
79
+ children=[],
80
+ )
81
+
82
+ def update_timestamp(self):
83
+ """Update timestamp"""
84
+ self.updated_at = time.time()
85
+
86
+ def is_completed(self) -> bool:
87
+ """Check if completed"""
88
+ return self.status == FrameStatus.COMPLETED
89
+
90
+ def is_failed(self) -> bool:
91
+ """Check if failed"""
92
+ return self.status == FrameStatus.FAILED
93
+
94
+ def is_paused(self) -> bool:
95
+ """Check if paused"""
96
+ return self.status == FrameStatus.PAUSED
97
+
98
+ def is_running(self) -> bool:
99
+ """Check if running"""
100
+ return self.status == FrameStatus.RUNNING
101
+
102
+ def is_waiting_for_intervention(self) -> bool:
103
+ """Check if waiting for user intervention"""
104
+ return self.status == FrameStatus.WAITING_FOR_INTERVENTION
105
+
106
+ def is_terminated(self) -> bool:
107
+ """Check if already terminated"""
108
+ return self.status == FrameStatus.TERMINATED
109
+
110
+ def is_waiting_for_user_input(self) -> bool:
111
+ """Check if waiting for user input (due to UserInterrupt)"""
112
+ return (
113
+ self.status == FrameStatus.WAITING_FOR_INTERVENTION
114
+ and self.wait_reason == WaitReason.USER_INTERRUPT
115
+ )
116
+
117
+ def to_dict(self) -> Dict:
118
+ """Convert to dictionary format"""
119
+ return {
120
+ "frame_id": self.frame_id,
121
+ "parent_id": self.parent_id,
122
+ "agent_id": self.agent_id,
123
+ "block_pointer": self.block_pointer,
124
+ "block_stack": self.block_stack,
125
+ "status": self.status.value,
126
+ "context_snapshot_id": self.context_snapshot_id,
127
+ "children": self.children,
128
+ "created_at": self.created_at,
129
+ "updated_at": self.updated_at,
130
+ "original_content": self.original_content,
131
+ "error": self.error,
132
+ "wait_reason": self.wait_reason.value if self.wait_reason else None,
133
+ }
134
+
135
+ @classmethod
136
+ def from_dict(cls, data: Dict) -> "ExecutionFrame":
137
+ """Create execution frame from dictionary"""
138
+ data = data.copy()
139
+ data["status"] = FrameStatus(data["status"])
140
+ # Handle wait_reason if present
141
+ if data.get("wait_reason"):
142
+ data["wait_reason"] = WaitReason(data["wait_reason"])
143
+ else:
144
+ data["wait_reason"] = None
145
+ return cls(**data)
@@ -0,0 +1,161 @@
1
+ import time
2
+ import threading
3
+ from typing import Dict, List, Optional, Tuple
4
+ from .execution_frame import ExecutionFrame, FrameStatus
5
+
6
+
7
+ class ExecutionStateRegistry:
8
+ """Execution Status Registry - Manage the lifecycle of all execution frames"""
9
+
10
+ def __init__(self):
11
+ self._frames: Dict[str, ExecutionFrame] = {}
12
+ self._lock = threading.RLock() # Using Reentrant Lock
13
+
14
+ def register_frame(self, frame: ExecutionFrame) -> bool:
15
+ """Register Execution Frame"""
16
+ with self._lock:
17
+ if frame.frame_id in self._frames:
18
+ return False
19
+ self._frames[frame.frame_id] = frame
20
+ return True
21
+
22
+ def get_frame(self, frame_id: str) -> Optional[ExecutionFrame]:
23
+ """Get execution frame"""
24
+ with self._lock:
25
+ return self._frames.get(frame_id)
26
+
27
+ def update_frame(self, frame: ExecutionFrame) -> bool:
28
+ """Update execution frame"""
29
+ with self._lock:
30
+ if frame.frame_id not in self._frames:
31
+ return False
32
+ frame.update_timestamp()
33
+ self._frames[frame.frame_id] = frame
34
+ return True
35
+
36
+ def compare_and_swap(
37
+ self, frame_id: str, expected_version: int, updates: Dict
38
+ ) -> Tuple[bool, Optional[ExecutionFrame]]:
39
+ """Optimistic Concurrency Control - Compare and Swap"""
40
+ with self._lock:
41
+ frame = self._frames.get(frame_id)
42
+ if not frame:
43
+ return False, None
44
+
45
+ # Simplified version control - using update timestamp as version
46
+ current_version = int(frame.updated_at)
47
+ if current_version != expected_version:
48
+ return False, frame
49
+
50
+ # Application Update
51
+ for key, value in updates.items():
52
+ if hasattr(frame, key):
53
+ setattr(frame, key, value)
54
+
55
+ frame.update_timestamp()
56
+ self._frames[frame_id] = frame
57
+ return True, frame
58
+
59
+ def remove_frame(self, frame_id: str) -> bool:
60
+ """Remove execution frame"""
61
+ with self._lock:
62
+ if frame_id in self._frames:
63
+ del self._frames[frame_id]
64
+ return True
65
+ return False
66
+
67
+ def get_child_frames(self, parent_id: str) -> List[ExecutionFrame]:
68
+ """Get the list of child execution frames"""
69
+ with self._lock:
70
+ children = []
71
+ for frame in self._frames.values():
72
+ if frame.parent_id == parent_id:
73
+ children.append(frame)
74
+ return children
75
+
76
+ def get_frame_tree(self, root_id: str) -> Dict:
77
+ """Get the frame tree with the specified frame as root"""
78
+ with self._lock:
79
+ root_frame = self._frames.get(root_id)
80
+ if not root_frame:
81
+ return {}
82
+
83
+ def build_tree(frame_id: str) -> Dict:
84
+ frame = self._frames.get(frame_id)
85
+ if not frame:
86
+ return {}
87
+
88
+ tree = {"frame": frame, "children": []}
89
+
90
+ for child_id in frame.children:
91
+ child_tree = build_tree(child_id)
92
+ if child_tree:
93
+ tree["children"].append(child_tree)
94
+
95
+ return tree
96
+
97
+ return build_tree(root_id)
98
+
99
+ def list_frames_by_status(self, status: FrameStatus) -> List[ExecutionFrame]:
100
+ """List execution frames by status"""
101
+ with self._lock:
102
+ return [frame for frame in self._frames.values() if frame.status == status]
103
+
104
+ def list_all_frames(self) -> List[ExecutionFrame]:
105
+ """List all executed frames"""
106
+ with self._lock:
107
+ return list(self._frames.values())
108
+
109
+ def get_frame_count(self) -> int:
110
+ """Get the number of execution frames"""
111
+ with self._lock:
112
+ return len(self._frames)
113
+
114
+ def get_stats(self) -> Dict:
115
+ """Get registry statistics"""
116
+ with self._lock:
117
+ stats = {
118
+ "total_frames": len(self._frames),
119
+ "running": 0,
120
+ "paused": 0,
121
+ "completed": 0,
122
+ "failed": 0,
123
+ "waiting_for_intervention": 0,
124
+ }
125
+
126
+ for frame in self._frames.values():
127
+ if frame.status == FrameStatus.RUNNING:
128
+ stats["running"] += 1
129
+ elif frame.status == FrameStatus.PAUSED:
130
+ stats["paused"] += 1
131
+ elif frame.status == FrameStatus.COMPLETED:
132
+ stats["completed"] += 1
133
+ elif frame.status == FrameStatus.FAILED:
134
+ stats["failed"] += 1
135
+ elif frame.status == FrameStatus.WAITING_FOR_INTERVENTION:
136
+ stats["waiting_for_intervention"] += 1
137
+
138
+ return stats
139
+
140
+ def cleanup_completed_frames(self, max_age_seconds: int = 3600) -> int:
141
+ """Clean up completed old execution frames"""
142
+ with self._lock:
143
+ current_time = time.time()
144
+ to_remove = []
145
+
146
+ for frame_id, frame in self._frames.items():
147
+ if (
148
+ frame.status in [FrameStatus.COMPLETED, FrameStatus.FAILED]
149
+ and current_time - frame.updated_at > max_age_seconds
150
+ ):
151
+ to_remove.append(frame_id)
152
+
153
+ for frame_id in to_remove:
154
+ del self._frames[frame_id]
155
+
156
+ return len(to_remove)
157
+
158
+ def clear(self):
159
+ """Clear all execution frames"""
160
+ with self._lock:
161
+ self._frames.clear()
@@ -0,0 +1,101 @@
1
+ import uuid
2
+ from dataclasses import dataclass, field
3
+ from typing import Dict, Optional, Literal
4
+
5
+
6
+ @dataclass
7
+ class ResumeHandle:
8
+ """Resume handle - used to restore paused execution.
9
+
10
+ Supports two interrupt types:
11
+ - "tool_interrupt": Tool requested user input, resume from breakpoint
12
+ - "user_interrupt": User actively interrupted, restart block with new context
13
+
14
+ Attributes:
15
+ frame_id: ID of the execution frame
16
+ snapshot_id: ID of the context snapshot
17
+ resume_token: Unique token for this resume operation
18
+ interrupt_type: Type of interrupt ("tool_interrupt" or "user_interrupt")
19
+ current_block: Block pointer at interrupt time (for user_interrupt)
20
+ restart_block: Whether to restart the block (True for user_interrupt)
21
+ """
22
+
23
+ frame_id: str
24
+ snapshot_id: str
25
+ resume_token: str
26
+ interrupt_type: Literal["tool_interrupt", "user_interrupt"] = "tool_interrupt"
27
+ current_block: Optional[int] = None
28
+ restart_block: bool = False
29
+
30
+ @classmethod
31
+ def create_handle(cls, frame_id: str, snapshot_id: str) -> "ResumeHandle":
32
+ """Create recovery handle (for tool interrupt - backward compatible)"""
33
+ return cls(
34
+ frame_id=frame_id,
35
+ snapshot_id=snapshot_id,
36
+ resume_token=str(uuid.uuid4()),
37
+ interrupt_type="tool_interrupt",
38
+ restart_block=False,
39
+ )
40
+
41
+ @classmethod
42
+ def create_user_interrupt_handle(
43
+ cls,
44
+ frame_id: str,
45
+ snapshot_id: str,
46
+ current_block: Optional[int] = None,
47
+ ) -> "ResumeHandle":
48
+ """Create resume handle for user interrupt.
49
+
50
+ Args:
51
+ frame_id: ID of the execution frame
52
+ snapshot_id: ID of the context snapshot
53
+ current_block: Block pointer at interrupt time
54
+
55
+ Returns:
56
+ ResumeHandle configured for user interrupt (restart_block=True)
57
+ """
58
+ return cls(
59
+ frame_id=frame_id,
60
+ snapshot_id=snapshot_id,
61
+ resume_token=str(uuid.uuid4()),
62
+ interrupt_type="user_interrupt",
63
+ current_block=current_block,
64
+ restart_block=True,
65
+ )
66
+
67
+ def to_dict(self) -> Dict:
68
+ """Convert to dictionary format"""
69
+ return {
70
+ "frame_id": self.frame_id,
71
+ "snapshot_id": self.snapshot_id,
72
+ "resume_token": self.resume_token,
73
+ "interrupt_type": self.interrupt_type,
74
+ "current_block": self.current_block,
75
+ "restart_block": self.restart_block,
76
+ }
77
+
78
+ @classmethod
79
+ def from_dict(cls, data: Dict) -> "ResumeHandle":
80
+ """Create a restore handle from a dictionary"""
81
+ # Handle backward compatibility for old data without new fields
82
+ return cls(
83
+ frame_id=data["frame_id"],
84
+ snapshot_id=data["snapshot_id"],
85
+ resume_token=data["resume_token"],
86
+ interrupt_type=data.get("interrupt_type", "tool_interrupt"),
87
+ current_block=data.get("current_block"),
88
+ restart_block=data.get("restart_block", False),
89
+ )
90
+
91
+ def is_valid(self) -> bool:
92
+ """Check if the handle is valid"""
93
+ return bool(self.frame_id and self.snapshot_id and self.resume_token)
94
+
95
+ def is_user_interrupt(self) -> bool:
96
+ """Check if this handle is for a user interrupt"""
97
+ return self.interrupt_type == "user_interrupt"
98
+
99
+ def is_tool_interrupt(self) -> bool:
100
+ """Check if this handle is for a tool interrupt"""
101
+ return self.interrupt_type == "tool_interrupt"