hdsp-jupyter-extension 2.0.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 (121) hide show
  1. agent_server/__init__.py +8 -0
  2. agent_server/core/__init__.py +92 -0
  3. agent_server/core/api_key_manager.py +427 -0
  4. agent_server/core/code_validator.py +1238 -0
  5. agent_server/core/context_condenser.py +308 -0
  6. agent_server/core/embedding_service.py +254 -0
  7. agent_server/core/error_classifier.py +577 -0
  8. agent_server/core/llm_client.py +95 -0
  9. agent_server/core/llm_service.py +649 -0
  10. agent_server/core/notebook_generator.py +274 -0
  11. agent_server/core/prompt_builder.py +35 -0
  12. agent_server/core/rag_manager.py +742 -0
  13. agent_server/core/reflection_engine.py +489 -0
  14. agent_server/core/retriever.py +248 -0
  15. agent_server/core/state_verifier.py +452 -0
  16. agent_server/core/summary_generator.py +484 -0
  17. agent_server/core/task_manager.py +198 -0
  18. agent_server/knowledge/__init__.py +9 -0
  19. agent_server/knowledge/watchdog_service.py +352 -0
  20. agent_server/main.py +160 -0
  21. agent_server/prompts/__init__.py +60 -0
  22. agent_server/prompts/file_action_prompts.py +113 -0
  23. agent_server/routers/__init__.py +9 -0
  24. agent_server/routers/agent.py +591 -0
  25. agent_server/routers/chat.py +188 -0
  26. agent_server/routers/config.py +100 -0
  27. agent_server/routers/file_resolver.py +293 -0
  28. agent_server/routers/health.py +42 -0
  29. agent_server/routers/rag.py +163 -0
  30. agent_server/schemas/__init__.py +60 -0
  31. hdsp_agent_core/__init__.py +158 -0
  32. hdsp_agent_core/factory.py +252 -0
  33. hdsp_agent_core/interfaces.py +203 -0
  34. hdsp_agent_core/knowledge/__init__.py +31 -0
  35. hdsp_agent_core/knowledge/chunking.py +356 -0
  36. hdsp_agent_core/knowledge/libraries/dask.md +188 -0
  37. hdsp_agent_core/knowledge/libraries/matplotlib.md +164 -0
  38. hdsp_agent_core/knowledge/libraries/polars.md +68 -0
  39. hdsp_agent_core/knowledge/loader.py +337 -0
  40. hdsp_agent_core/llm/__init__.py +13 -0
  41. hdsp_agent_core/llm/service.py +556 -0
  42. hdsp_agent_core/managers/__init__.py +22 -0
  43. hdsp_agent_core/managers/config_manager.py +133 -0
  44. hdsp_agent_core/managers/session_manager.py +251 -0
  45. hdsp_agent_core/models/__init__.py +115 -0
  46. hdsp_agent_core/models/agent.py +316 -0
  47. hdsp_agent_core/models/chat.py +41 -0
  48. hdsp_agent_core/models/common.py +95 -0
  49. hdsp_agent_core/models/rag.py +368 -0
  50. hdsp_agent_core/prompts/__init__.py +63 -0
  51. hdsp_agent_core/prompts/auto_agent_prompts.py +1260 -0
  52. hdsp_agent_core/prompts/cell_action_prompts.py +98 -0
  53. hdsp_agent_core/services/__init__.py +18 -0
  54. hdsp_agent_core/services/agent_service.py +438 -0
  55. hdsp_agent_core/services/chat_service.py +205 -0
  56. hdsp_agent_core/services/rag_service.py +262 -0
  57. hdsp_agent_core/tests/__init__.py +1 -0
  58. hdsp_agent_core/tests/conftest.py +102 -0
  59. hdsp_agent_core/tests/test_factory.py +251 -0
  60. hdsp_agent_core/tests/test_services.py +326 -0
  61. hdsp_jupyter_extension-2.0.0.data/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
  62. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/build_log.json +738 -0
  63. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/install.json +5 -0
  64. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/package.json +134 -0
  65. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
  66. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
  67. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
  68. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
  69. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +94 -0
  70. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +1 -0
  71. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +94 -0
  72. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +1 -0
  73. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
  74. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
  75. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/style.js +4 -0
  76. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +507 -0
  77. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +1 -0
  78. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js +2071 -0
  79. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
  80. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +1059 -0
  81. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  82. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
  83. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
  84. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
  85. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
  86. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
  87. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
  88. hdsp_jupyter_extension-2.0.0.dist-info/METADATA +152 -0
  89. hdsp_jupyter_extension-2.0.0.dist-info/RECORD +121 -0
  90. hdsp_jupyter_extension-2.0.0.dist-info/WHEEL +4 -0
  91. hdsp_jupyter_extension-2.0.0.dist-info/licenses/LICENSE +21 -0
  92. jupyter_ext/__init__.py +233 -0
  93. jupyter_ext/_version.py +4 -0
  94. jupyter_ext/config.py +111 -0
  95. jupyter_ext/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
  96. jupyter_ext/handlers.py +632 -0
  97. jupyter_ext/labextension/build_log.json +738 -0
  98. jupyter_ext/labextension/package.json +134 -0
  99. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
  100. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
  101. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
  102. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
  103. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +94 -0
  104. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +1 -0
  105. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +94 -0
  106. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +1 -0
  107. jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
  108. jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
  109. jupyter_ext/labextension/static/style.js +4 -0
  110. jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +507 -0
  111. jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +1 -0
  112. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js +2071 -0
  113. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
  114. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +1059 -0
  115. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  116. jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
  117. jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
  118. jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
  119. jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
  120. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
  121. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
@@ -0,0 +1,352 @@
1
+ """
2
+ Watchdog Service - File system monitoring for automatic RAG re-indexing.
3
+
4
+ Features:
5
+ - Monitors knowledge base directory for changes
6
+ - Debounces rapid file changes
7
+ - Triggers incremental re-indexing
8
+ - Pattern-based file filtering
9
+ """
10
+
11
+ import asyncio
12
+ import logging
13
+ import threading
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import TYPE_CHECKING, Callable, Optional, Set
17
+
18
+ if TYPE_CHECKING:
19
+ from hdsp_agent_core.models.rag import WatchdogConfig
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class WatchdogService:
25
+ """
26
+ File system monitoring service for RAG knowledge base.
27
+
28
+ Features:
29
+ - Asynchronous file change detection
30
+ - Debouncing to prevent excessive re-indexing
31
+ - Pattern-based filtering (*.md, *.py, etc.)
32
+ - Ignore patterns for cache/build directories
33
+ - Callback-based notification
34
+
35
+ Usage:
36
+ service = WatchdogService(config, on_change_callback)
37
+ service.start("/path/to/knowledge")
38
+ # ... later
39
+ service.stop()
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ config: "WatchdogConfig",
45
+ on_change_callback: Optional[Callable[[Set[Path]], None]] = None,
46
+ ):
47
+ self._config = config
48
+ self._on_change = on_change_callback
49
+ self._observer = None
50
+ self._running = False
51
+ self._pending_changes: Set[Path] = set()
52
+ self._last_change_time: Optional[datetime] = None
53
+ self._debounce_task: Optional[asyncio.Task] = None
54
+ self._lock = threading.Lock()
55
+ self._loop: Optional[asyncio.AbstractEventLoop] = None
56
+
57
+ def start(self, watch_path: Path) -> bool:
58
+ """
59
+ Start monitoring the specified directory.
60
+
61
+ Args:
62
+ watch_path: Directory to monitor
63
+
64
+ Returns:
65
+ True if monitoring started successfully
66
+ """
67
+ if not self._config.enabled:
68
+ logger.info("Watchdog disabled by configuration")
69
+ return False
70
+
71
+ if self._running:
72
+ logger.warning("Watchdog already running")
73
+ return True
74
+
75
+ try:
76
+ from watchdog.events import FileSystemEvent, FileSystemEventHandler
77
+ from watchdog.observers import Observer
78
+ except ImportError:
79
+ logger.warning(
80
+ "watchdog not installed, file monitoring disabled. "
81
+ "Install with: pip install watchdog"
82
+ )
83
+ return False
84
+
85
+ if not watch_path.exists():
86
+ logger.warning(f"Watch path does not exist: {watch_path}")
87
+ return False
88
+
89
+ # Store event loop for async callbacks
90
+ try:
91
+ self._loop = asyncio.get_running_loop()
92
+ except RuntimeError:
93
+ self._loop = None
94
+
95
+ # Create event handler
96
+ handler = self._create_event_handler(FileSystemEventHandler)
97
+
98
+ # Start observer
99
+ self._observer = Observer()
100
+ self._observer.schedule(handler, str(watch_path), recursive=True)
101
+ self._observer.start()
102
+ self._running = True
103
+
104
+ logger.info(f"Watchdog started monitoring: {watch_path}")
105
+ return True
106
+
107
+ def _create_event_handler(self, base_class):
108
+ """Create a watchdog event handler class."""
109
+ service = self
110
+
111
+ class RAGEventHandler(base_class):
112
+ def on_any_event(self, event):
113
+ if event.is_directory:
114
+ return
115
+
116
+ # Get file path
117
+ file_path = Path(event.src_path)
118
+
119
+ # Check if file matches patterns
120
+ if not service._should_process(file_path):
121
+ return
122
+
123
+ # Queue the change
124
+ service._queue_change(file_path)
125
+
126
+ return RAGEventHandler()
127
+
128
+ def _should_process(self, file_path: Path) -> bool:
129
+ """Check if file should trigger re-indexing."""
130
+ file_name = file_path.name
131
+
132
+ # Check ignore patterns
133
+ for pattern in self._config.ignore_patterns:
134
+ if file_name.startswith(pattern.replace("*", "")):
135
+ return False
136
+ if pattern.startswith("*") and file_name.endswith(pattern[1:]):
137
+ return False
138
+
139
+ # Check include patterns
140
+ for pattern in self._config.patterns:
141
+ if pattern.startswith("*"):
142
+ suffix = pattern[1:]
143
+ if file_name.endswith(suffix):
144
+ return True
145
+ elif file_name == pattern:
146
+ return True
147
+
148
+ return False
149
+
150
+ def _queue_change(self, file_path: Path) -> None:
151
+ """Queue a file change for processing."""
152
+ with self._lock:
153
+ self._pending_changes.add(file_path)
154
+ self._last_change_time = datetime.now()
155
+
156
+ logger.debug(f"Queued change: {file_path}")
157
+
158
+ # Schedule debounced processing
159
+ if self._loop and self._loop.is_running():
160
+ self._loop.call_soon_threadsafe(self._schedule_debounce)
161
+
162
+ def _schedule_debounce(self) -> None:
163
+ """Schedule the debounced callback."""
164
+ if self._debounce_task and not self._debounce_task.done():
165
+ self._debounce_task.cancel()
166
+
167
+ self._debounce_task = asyncio.create_task(self._debounced_callback())
168
+
169
+ async def _debounced_callback(self) -> None:
170
+ """Wait for debounce period then trigger callback."""
171
+ try:
172
+ await asyncio.sleep(self._config.debounce_seconds)
173
+
174
+ # Get pending changes
175
+ with self._lock:
176
+ if not self._pending_changes:
177
+ return
178
+
179
+ changes = self._pending_changes.copy()
180
+ self._pending_changes.clear()
181
+
182
+ logger.info(f"Processing {len(changes)} file changes")
183
+
184
+ # Trigger callback
185
+ if self._on_change:
186
+ try:
187
+ self._on_change(changes)
188
+ except Exception as e:
189
+ logger.error(f"Error in change callback: {e}")
190
+
191
+ except asyncio.CancelledError:
192
+ # Debounce was reset, ignore
193
+ pass
194
+ except Exception as e:
195
+ logger.error(f"Error in debounced callback: {e}")
196
+
197
+ def stop(self) -> None:
198
+ """Stop file monitoring."""
199
+ if not self._running:
200
+ return
201
+
202
+ if self._observer:
203
+ self._observer.stop()
204
+ self._observer.join(timeout=5)
205
+ self._observer = None
206
+
207
+ if self._debounce_task and not self._debounce_task.done():
208
+ self._debounce_task.cancel()
209
+
210
+ self._running = False
211
+ self._pending_changes.clear()
212
+
213
+ logger.info("Watchdog stopped")
214
+
215
+ @property
216
+ def is_running(self) -> bool:
217
+ """Check if watchdog is currently running."""
218
+ return self._running
219
+
220
+ @property
221
+ def pending_count(self) -> int:
222
+ """Get number of pending changes."""
223
+ with self._lock:
224
+ return len(self._pending_changes)
225
+
226
+
227
+ class SimpleWatchdog:
228
+ """
229
+ Simplified watchdog for polling-based monitoring.
230
+
231
+ Use this when the watchdog library is not available
232
+ or for environments where filesystem events are unreliable.
233
+ """
234
+
235
+ def __init__(
236
+ self,
237
+ patterns: list[str] = None,
238
+ check_interval: float = 10.0,
239
+ on_change_callback: Optional[Callable[[Set[Path]], None]] = None,
240
+ ):
241
+ self._patterns = patterns or ["*.md", "*.py", "*.txt"]
242
+ self._check_interval = check_interval
243
+ self._on_change = on_change_callback
244
+ self._running = False
245
+ self._file_mtimes: dict[Path, float] = {}
246
+ self._check_task: Optional[asyncio.Task] = None
247
+
248
+ async def start(self, watch_path: Path) -> bool:
249
+ """Start polling-based monitoring."""
250
+ if self._running:
251
+ return True
252
+
253
+ if not watch_path.exists():
254
+ logger.warning(f"Watch path does not exist: {watch_path}")
255
+ return False
256
+
257
+ self._running = True
258
+ self._check_task = asyncio.create_task(self._poll_loop(watch_path))
259
+
260
+ logger.info(f"SimpleWatchdog started polling: {watch_path}")
261
+ return True
262
+
263
+ async def _poll_loop(self, watch_path: Path) -> None:
264
+ """Main polling loop."""
265
+ # Initial scan
266
+ self._scan_directory(watch_path)
267
+
268
+ while self._running:
269
+ try:
270
+ await asyncio.sleep(self._check_interval)
271
+
272
+ if not self._running:
273
+ break
274
+
275
+ # Check for changes
276
+ changes = self._check_changes(watch_path)
277
+
278
+ if changes and self._on_change:
279
+ try:
280
+ self._on_change(changes)
281
+ except Exception as e:
282
+ logger.error(f"Error in change callback: {e}")
283
+
284
+ except asyncio.CancelledError:
285
+ break
286
+ except Exception as e:
287
+ logger.error(f"Error in poll loop: {e}")
288
+
289
+ def _scan_directory(self, watch_path: Path) -> None:
290
+ """Scan directory and record file mtimes."""
291
+ for pattern in self._patterns:
292
+ for file_path in watch_path.rglob(pattern.lstrip("*")):
293
+ if file_path.is_file():
294
+ try:
295
+ self._file_mtimes[file_path] = file_path.stat().st_mtime
296
+ except OSError:
297
+ pass
298
+
299
+ def _check_changes(self, watch_path: Path) -> Set[Path]:
300
+ """Check for file changes since last scan."""
301
+ changes = set()
302
+ current_files: Set[Path] = set()
303
+
304
+ # Check existing files
305
+ for pattern in self._patterns:
306
+ suffix = pattern.lstrip("*")
307
+ for file_path in watch_path.rglob(f"*{suffix}"):
308
+ if not file_path.is_file():
309
+ continue
310
+
311
+ current_files.add(file_path)
312
+
313
+ try:
314
+ mtime = file_path.stat().st_mtime
315
+ except OSError:
316
+ continue
317
+
318
+ # Check if new or modified
319
+ if file_path not in self._file_mtimes:
320
+ changes.add(file_path)
321
+ self._file_mtimes[file_path] = mtime
322
+ elif self._file_mtimes[file_path] != mtime:
323
+ changes.add(file_path)
324
+ self._file_mtimes[file_path] = mtime
325
+
326
+ # Check for deleted files
327
+ deleted = set(self._file_mtimes.keys()) - current_files
328
+ for file_path in deleted:
329
+ del self._file_mtimes[file_path]
330
+ # Note: We don't add deleted files to changes
331
+ # The RAG system should handle missing files gracefully
332
+
333
+ return changes
334
+
335
+ async def stop(self) -> None:
336
+ """Stop polling."""
337
+ self._running = False
338
+
339
+ if self._check_task and not self._check_task.done():
340
+ self._check_task.cancel()
341
+ try:
342
+ await self._check_task
343
+ except asyncio.CancelledError:
344
+ pass
345
+
346
+ self._file_mtimes.clear()
347
+ logger.info("SimpleWatchdog stopped")
348
+
349
+ @property
350
+ def is_running(self) -> bool:
351
+ """Check if watchdog is currently running."""
352
+ return self._running
agent_server/main.py ADDED
@@ -0,0 +1,160 @@
1
+ """
2
+ HDSP Agent Server - FastAPI Entry Point
3
+
4
+ AI Agent Server for IDE integrations (JupyterLab, VS Code, PyCharm)
5
+
6
+ This server always runs in embedded mode (HDSP_AGENT_MODE=embedded)
7
+ since it's the actual implementation server that executes agent logic.
8
+ """
9
+
10
+ import logging
11
+ import os
12
+ from contextlib import asynccontextmanager
13
+
14
+ from fastapi import FastAPI
15
+ from fastapi.middleware.cors import CORSMiddleware
16
+
17
+ from agent_server.routers import agent, chat, config, file_resolver, health, rag
18
+
19
+ # Configure logging
20
+ logging.basicConfig(
21
+ level=logging.INFO,
22
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
23
+ )
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ @asynccontextmanager
28
+ async def lifespan(app: FastAPI):
29
+ """Application lifespan handler for startup/shutdown events"""
30
+ # Startup
31
+ logger.info("Starting HDSP Agent Server...")
32
+
33
+ # Force embedded mode for the server (it IS the implementation)
34
+ os.environ.setdefault("HDSP_AGENT_MODE", "embedded")
35
+
36
+ try:
37
+ from hdsp_agent_core.factory import get_service_factory
38
+
39
+ factory = get_service_factory()
40
+ await factory.initialize()
41
+
42
+ logger.info(f"ServiceFactory initialized in {factory.mode.value} mode")
43
+
44
+ # Log service status
45
+ rag_service = factory.get_rag_service()
46
+ if rag_service.is_ready():
47
+ logger.info("RAG service ready")
48
+ else:
49
+ logger.info("RAG service not ready (will start without RAG)")
50
+
51
+ except ImportError as e:
52
+ logger.warning(f"hdsp_agent_core not available: {e}")
53
+ # Fallback to legacy initialization
54
+ await _legacy_startup()
55
+ except Exception as e:
56
+ logger.warning(f"Failed to initialize ServiceFactory: {e}")
57
+ # Fallback to legacy initialization
58
+ await _legacy_startup()
59
+
60
+ yield
61
+
62
+ # Shutdown
63
+ logger.info("Shutting down HDSP Agent Server...")
64
+
65
+ try:
66
+ from hdsp_agent_core.factory import get_service_factory
67
+
68
+ factory = get_service_factory()
69
+ await factory.shutdown()
70
+ logger.info("ServiceFactory shutdown complete")
71
+ except Exception as e:
72
+ logger.warning(f"Error during ServiceFactory shutdown: {e}")
73
+ # Fallback to legacy shutdown
74
+ await _legacy_shutdown()
75
+
76
+
77
+ async def _legacy_startup():
78
+ """Legacy startup for backward compatibility (when hdsp_agent_core not available)"""
79
+ logger.info("Using legacy startup (hdsp_agent_core not available)")
80
+
81
+ try:
82
+ from hdsp_agent_core.managers.config_manager import ConfigManager
83
+
84
+ ConfigManager.get_instance()
85
+ logger.info("Configuration loaded successfully")
86
+ except Exception as e:
87
+ logger.warning(f"Failed to load configuration: {e}")
88
+
89
+ # Initialize RAG system
90
+ try:
91
+ from hdsp_agent_core.models.rag import get_default_rag_config
92
+
93
+ from agent_server.core.rag_manager import get_rag_manager
94
+
95
+ rag_config = get_default_rag_config()
96
+ if rag_config.is_enabled():
97
+ rag_manager = get_rag_manager(rag_config)
98
+ await rag_manager.initialize()
99
+ logger.info("RAG system initialized successfully")
100
+ else:
101
+ logger.info("RAG system disabled by configuration")
102
+ except Exception as e:
103
+ logger.warning(f"Failed to initialize RAG system: {e}")
104
+
105
+
106
+ async def _legacy_shutdown():
107
+ """Legacy shutdown for backward compatibility"""
108
+ logger.info("Using legacy shutdown")
109
+
110
+ try:
111
+ from agent_server.core.rag_manager import get_rag_manager, reset_rag_manager
112
+
113
+ rag_manager = get_rag_manager()
114
+ if rag_manager.is_ready:
115
+ await rag_manager.shutdown()
116
+ logger.info("RAG system shut down successfully")
117
+ reset_rag_manager()
118
+ except Exception as e:
119
+ logger.warning(f"Error during legacy RAG shutdown: {e}")
120
+
121
+
122
+ app = FastAPI(
123
+ title="HDSP Agent Server",
124
+ description="AI Agent Server for IDE integrations - provides intelligent code assistance",
125
+ version="1.0.0",
126
+ lifespan=lifespan,
127
+ )
128
+
129
+ # CORS middleware for cross-origin requests from IDE extensions
130
+ app.add_middleware(
131
+ CORSMiddleware,
132
+ allow_origins=["*"], # Development: allow all. Production: restrict
133
+ allow_credentials=True,
134
+ allow_methods=["*"],
135
+ allow_headers=["*"],
136
+ )
137
+
138
+ # Register routers
139
+ app.include_router(health.router, tags=["Health"])
140
+ app.include_router(config.router, prefix="/config", tags=["Configuration"])
141
+ app.include_router(agent.router, prefix="/agent", tags=["Agent"])
142
+ app.include_router(chat.router, prefix="/chat", tags=["Chat"])
143
+ app.include_router(rag.router, prefix="/rag", tags=["RAG"])
144
+ app.include_router(file_resolver.router, prefix="/file", tags=["File Resolution"])
145
+
146
+
147
+ def run():
148
+ """Entry point for `hdsp-agent-server` CLI command"""
149
+ import uvicorn
150
+
151
+ uvicorn.run(
152
+ "agent_server.main:app",
153
+ host="0.0.0.0",
154
+ port=8000,
155
+ reload=True,
156
+ )
157
+
158
+
159
+ if __name__ == "__main__":
160
+ run()
@@ -0,0 +1,60 @@
1
+ """
2
+ HDSP Agent Prompts Module
3
+
4
+ Re-export from hdsp_agent_core.prompts for backward compatibility.
5
+ """
6
+
7
+ # Auto-Agent Prompts
8
+ from hdsp_agent_core.prompts.auto_agent_prompts import (
9
+ ADAPTIVE_REPLAN_PROMPT,
10
+ CODE_GENERATION_PROMPT,
11
+ ERROR_REFINEMENT_PROMPT,
12
+ FINAL_ANSWER_PROMPT,
13
+ PLAN_GENERATION_PROMPT,
14
+ REFLECTION_PROMPT,
15
+ STRUCTURED_PLAN_PROMPT,
16
+ format_final_answer_prompt,
17
+ format_plan_prompt,
18
+ format_refine_prompt,
19
+ format_reflection_prompt,
20
+ format_replan_prompt,
21
+ format_structured_plan_prompt,
22
+ )
23
+
24
+ # Cell Action Prompts
25
+ from hdsp_agent_core.prompts.cell_action_prompts import (
26
+ CUSTOM_REQUEST_PROMPT,
27
+ DEFAULT_SYSTEM_PROMPT,
28
+ EXPLAIN_CODE_PROMPT,
29
+ FIX_CODE_PROMPT,
30
+ format_chat_prompt,
31
+ format_custom_prompt,
32
+ format_explain_prompt,
33
+ format_fix_prompt,
34
+ )
35
+
36
+ __all__ = [
37
+ # Auto-Agent Prompts
38
+ "PLAN_GENERATION_PROMPT",
39
+ "CODE_GENERATION_PROMPT",
40
+ "ERROR_REFINEMENT_PROMPT",
41
+ "ADAPTIVE_REPLAN_PROMPT",
42
+ "FINAL_ANSWER_PROMPT",
43
+ "STRUCTURED_PLAN_PROMPT",
44
+ "REFLECTION_PROMPT",
45
+ "format_plan_prompt",
46
+ "format_refine_prompt",
47
+ "format_final_answer_prompt",
48
+ "format_replan_prompt",
49
+ "format_structured_plan_prompt",
50
+ "format_reflection_prompt",
51
+ # Cell Action Prompts
52
+ "EXPLAIN_CODE_PROMPT",
53
+ "FIX_CODE_PROMPT",
54
+ "CUSTOM_REQUEST_PROMPT",
55
+ "DEFAULT_SYSTEM_PROMPT",
56
+ "format_explain_prompt",
57
+ "format_fix_prompt",
58
+ "format_custom_prompt",
59
+ "format_chat_prompt",
60
+ ]
@@ -0,0 +1,113 @@
1
+ """
2
+ File Action Prompts
3
+ Python 파일 분석 및 에러 수정용 LLM 프롬프트
4
+ """
5
+
6
+
7
+ def format_file_fix_prompt(
8
+ main_file: dict, error_output: str, related_files: list = None
9
+ ) -> str:
10
+ """Python 파일 에러 수정용 프롬프트 생성
11
+
12
+ Args:
13
+ main_file: { 'path': str, 'content': str }
14
+ error_output: 에러 메시지/트레이스백
15
+ related_files: [{ 'path': str, 'content': str }]
16
+ """
17
+ related_context = ""
18
+ if related_files:
19
+ related_context = "\n## 관련 파일들\n"
20
+ for rf in related_files:
21
+ if rf.get("content"):
22
+ related_context += f"""
23
+ ### {rf['path']}
24
+ ```python
25
+ {rf['content']}
26
+ ```
27
+ """
28
+
29
+ return f"""Python 파일에서 에러가 발생했습니다. 에러를 분석하고 수정된 코드를 제공하세요.
30
+
31
+ ## 에러 메시지
32
+ ```
33
+ {error_output}
34
+ ```
35
+
36
+ ## 메인 파일: {main_file['path']}
37
+ ```python
38
+ {main_file['content']}
39
+ ```
40
+ {related_context}
41
+
42
+ ## 지침
43
+ 1. 에러의 근본 원인을 분석하세요
44
+ 2. 수정이 필요한 파일의 **전체 코드**를 제공하세요 (부분 수정 아님)
45
+ 3. 여러 파일 수정이 필요하면 각각 제공하세요
46
+ 4. 수정 사항을 간단히 설명하세요
47
+ 5. **코드 내 주석과 문자열은 한글 또는 영어로만 작성하세요 (한자 사용 절대 금지)**
48
+
49
+ ## 출력 형식
50
+ ### 에러 원인
51
+ (에러가 발생한 원인 설명)
52
+
53
+ ### 수정 파일: [파일경로]
54
+ ```python
55
+ [전체 수정된 코드]
56
+ ```
57
+
58
+ ### 추가 수정 파일: [파일경로] (필요한 경우)
59
+ ```python
60
+ [전체 수정된 코드]
61
+ ```
62
+ """
63
+
64
+
65
+ def format_file_explain_prompt(file_path: str, file_content: str) -> str:
66
+ """Python 파일 설명용 프롬프트 생성"""
67
+ return f"""다음 Python 파일의 코드를 자세히 설명해주세요.
68
+
69
+ ## 파일: {file_path}
70
+ ```python
71
+ {file_content}
72
+ ```
73
+
74
+ ## 다음 형식으로 응답해주세요
75
+
76
+ ### 파일 개요
77
+ (이 파일의 전체적인 목적과 기능)
78
+
79
+ ### 주요 구성 요소
80
+ (클래스, 함수, 변수 등의 설명)
81
+
82
+ ### 코드 흐름
83
+ (코드의 실행 흐름 설명)
84
+
85
+ ### 의존성
86
+ (import하는 모듈과 그 용도)
87
+ """
88
+
89
+
90
+ def format_file_custom_prompt(
91
+ main_file: dict, custom_prompt: str, related_files: list = None
92
+ ) -> str:
93
+ """커스텀 질문용 프롬프트 생성"""
94
+ related_context = ""
95
+ if related_files:
96
+ related_context = "\n## 관련 파일들\n"
97
+ for rf in related_files:
98
+ if rf.get("content"):
99
+ related_context += f"""
100
+ ### {rf['path']}
101
+ ```python
102
+ {rf['content']}
103
+ ```
104
+ """
105
+
106
+ return f"""{custom_prompt}
107
+
108
+ ## 메인 파일: {main_file['path']}
109
+ ```python
110
+ {main_file['content']}
111
+ ```
112
+ {related_context}
113
+ """
@@ -0,0 +1,9 @@
1
+ """
2
+ Agent Server Routers
3
+
4
+ FastAPI routers for the HDSP Agent Server.
5
+ """
6
+
7
+ from . import agent, chat, config, health, rag
8
+
9
+ __all__ = ["agent", "chat", "config", "health", "rag"]