hdsp-jupyter-extension 2.0.1__py3-none-any.whl → 2.0.3__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 (78) hide show
  1. agent_server/langchain/__init__.py +18 -0
  2. agent_server/langchain/agent.py +694 -0
  3. agent_server/langchain/executors/__init__.py +15 -0
  4. agent_server/langchain/executors/jupyter_executor.py +429 -0
  5. agent_server/langchain/executors/notebook_searcher.py +477 -0
  6. agent_server/langchain/middleware/__init__.py +36 -0
  7. agent_server/langchain/middleware/code_search_middleware.py +278 -0
  8. agent_server/langchain/middleware/error_handling_middleware.py +338 -0
  9. agent_server/langchain/middleware/jupyter_execution_middleware.py +301 -0
  10. agent_server/langchain/middleware/rag_middleware.py +227 -0
  11. agent_server/langchain/middleware/validation_middleware.py +240 -0
  12. agent_server/langchain/state.py +159 -0
  13. agent_server/langchain/tools/__init__.py +39 -0
  14. agent_server/langchain/tools/file_tools.py +279 -0
  15. agent_server/langchain/tools/jupyter_tools.py +143 -0
  16. agent_server/langchain/tools/search_tools.py +309 -0
  17. agent_server/main.py +13 -0
  18. agent_server/routers/health.py +14 -0
  19. agent_server/routers/langchain_agent.py +1368 -0
  20. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  21. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  22. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js +408 -4
  23. hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js.map +1 -0
  24. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.1366019c413f1d68467f.js +753 -65
  25. hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.1366019c413f1d68467f.js.map +1 -0
  26. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.729f933de01ad5620730.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.b6d91b150c0800bddfa4.js +8 -8
  27. hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.b6d91b150c0800bddfa4.js.map +1 -0
  28. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
  29. hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
  30. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
  31. hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
  32. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
  33. hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
  34. {hdsp_jupyter_extension-2.0.1.dist-info → hdsp_jupyter_extension-2.0.3.dist-info}/METADATA +6 -1
  35. {hdsp_jupyter_extension-2.0.1.dist-info → hdsp_jupyter_extension-2.0.3.dist-info}/RECORD +66 -49
  36. jupyter_ext/_version.py +1 -1
  37. jupyter_ext/handlers.py +126 -1
  38. jupyter_ext/labextension/build_log.json +1 -1
  39. jupyter_ext/labextension/package.json +2 -2
  40. jupyter_ext/labextension/static/{frontend_styles_index_js.2607ff74c74acfa83158.js → frontend_styles_index_js.634cf0ae0f3592d0882f.js} +408 -4
  41. jupyter_ext/labextension/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js.map +1 -0
  42. jupyter_ext/labextension/static/{lib_index_js.622c1a5918b3aafb2315.js → lib_index_js.1366019c413f1d68467f.js} +753 -65
  43. jupyter_ext/labextension/static/lib_index_js.1366019c413f1d68467f.js.map +1 -0
  44. jupyter_ext/labextension/static/{remoteEntry.729f933de01ad5620730.js → remoteEntry.b6d91b150c0800bddfa4.js} +8 -8
  45. jupyter_ext/labextension/static/remoteEntry.b6d91b150c0800bddfa4.js.map +1 -0
  46. hdsp_jupyter_extension-2.0.1.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 → jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
  47. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
  48. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
  49. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
  50. jupyter_ext/labextension/static/{vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js} +212 -3
  51. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
  52. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +0 -1
  53. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js.map +0 -1
  54. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.729f933de01ad5620730.js.map +0 -1
  55. hdsp_jupyter_extension-2.0.1.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 +0 -1
  56. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
  57. hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
  58. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +0 -1
  59. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js.map +0 -1
  60. jupyter_ext/labextension/static/remoteEntry.729f933de01ad5620730.js.map +0 -1
  61. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
  62. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
  63. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
  64. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  65. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  66. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.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 +0 -0
  67. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.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 +0 -0
  68. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.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 +0 -0
  69. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.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 +0 -0
  70. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  71. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.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 +0 -0
  72. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.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 +0 -0
  73. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  74. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +0 -0
  75. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  76. {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  77. {hdsp_jupyter_extension-2.0.1.dist-info → hdsp_jupyter_extension-2.0.3.dist-info}/WHEEL +0 -0
  78. {hdsp_jupyter_extension-2.0.1.dist-info → hdsp_jupyter_extension-2.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -15,13 +15,15 @@ __webpack_require__.r(__webpack_exports__);
15
15
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
16
16
  /* harmony import */ var _jupyterlab_ui_components__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @jupyterlab/ui-components */ "webpack/sharing/consume/default/@jupyterlab/ui-components");
17
17
  /* harmony import */ var _jupyterlab_ui_components__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_ui_components__WEBPACK_IMPORTED_MODULE_1__);
18
- /* harmony import */ var _SettingsPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./SettingsPanel */ "./lib/components/SettingsPanel.js");
19
- /* harmony import */ var _services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../services/ApiKeyManager */ "./lib/services/ApiKeyManager.js");
20
- /* harmony import */ var _services_AgentOrchestrator__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../services/AgentOrchestrator */ "./lib/services/AgentOrchestrator.js");
21
- /* harmony import */ var _types_auto_agent__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../types/auto-agent */ "./lib/types/auto-agent.js");
22
- /* harmony import */ var _utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../utils/markdownRenderer */ "./lib/utils/markdownRenderer.js");
23
- /* harmony import */ var _FileSelectionDialog__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./FileSelectionDialog */ "./lib/components/FileSelectionDialog.js");
24
- /* harmony import */ var _logoSvg__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../logoSvg */ "./lib/logoSvg.js");
18
+ /* harmony import */ var _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @jupyterlab/notebook */ "webpack/sharing/consume/default/@jupyterlab/notebook");
19
+ /* harmony import */ var _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_2__);
20
+ /* harmony import */ var _SettingsPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./SettingsPanel */ "./lib/components/SettingsPanel.js");
21
+ /* harmony import */ var _services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../services/ApiKeyManager */ "./lib/services/ApiKeyManager.js");
22
+ /* harmony import */ var _services_AgentOrchestrator__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../services/AgentOrchestrator */ "./lib/services/AgentOrchestrator.js");
23
+ /* harmony import */ var _types_auto_agent__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../types/auto-agent */ "./lib/types/auto-agent.js");
24
+ /* harmony import */ var _utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../utils/markdownRenderer */ "./lib/utils/markdownRenderer.js");
25
+ /* harmony import */ var _FileSelectionDialog__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./FileSelectionDialog */ "./lib/components/FileSelectionDialog.js");
26
+ /* harmony import */ var _logoSvg__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../logoSvg */ "./lib/logoSvg.js");
25
27
  /**
26
28
  * Agent Panel - Main sidebar panel for Jupyter Agent
27
29
  * Cursor AI Style: Unified Chat + Agent Interface
@@ -34,12 +36,13 @@ __webpack_require__.r(__webpack_exports__);
34
36
 
35
37
 
36
38
 
39
+
37
40
  // 로고 이미지 (SVG) - TypeScript 모듈에서 인라인 문자열로 import
38
41
 
39
42
  // 탭바 아이콘 생성
40
43
  const hdspTabIcon = new _jupyterlab_ui_components__WEBPACK_IMPORTED_MODULE_1__.LabIcon({
41
44
  name: 'hdsp-agent:tab-icon',
42
- svgstr: _logoSvg__WEBPACK_IMPORTED_MODULE_8__.tabbarLogoSvg
45
+ svgstr: _logoSvg__WEBPACK_IMPORTED_MODULE_9__.tabbarLogoSvg
43
46
  });
44
47
  // Agent 명령어 감지 함수
45
48
  const isAgentCommand = (input) => {
@@ -196,6 +199,194 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
196
199
  // File selection state
197
200
  const [fileSelectionMetadata, setFileSelectionMetadata] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
198
201
  const [pendingAgentRequest, setPendingAgentRequest] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
202
+ // Human-in-the-Loop state
203
+ const [debugStatus, setDebugStatus] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
204
+ const [interruptData, setInterruptData] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
205
+ // Todo list state (from TodoListMiddleware)
206
+ const [todos, setTodos] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
207
+ const [isTodoExpanded, setIsTodoExpanded] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
208
+ const interruptMessageIdRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
209
+ const approvalPendingRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(false);
210
+ const pendingToolCallsRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)([]);
211
+ const handledToolCallKeysRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(new Set());
212
+ const getActiveNotebookPanel = () => {
213
+ const app = window.jupyterapp;
214
+ if (app?.shell?.currentWidget) {
215
+ const currentWidget = app.shell.currentWidget;
216
+ if (currentWidget instanceof _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_2__.NotebookPanel) {
217
+ return currentWidget;
218
+ }
219
+ if ('content' in currentWidget && currentWidget.content?.model) {
220
+ return currentWidget;
221
+ }
222
+ }
223
+ return notebookTracker?.currentWidget || null;
224
+ };
225
+ const insertCell = (notebook, cellType, source) => {
226
+ const model = notebook.content?.model;
227
+ if (!model?.sharedModel) {
228
+ console.warn('[AgentPanel] Notebook model not ready for insert');
229
+ return null;
230
+ }
231
+ const insertIndex = model.cells.length;
232
+ model.sharedModel.insertCell(insertIndex, {
233
+ cell_type: cellType,
234
+ source
235
+ });
236
+ notebook.content.activeCellIndex = insertIndex;
237
+ return insertIndex;
238
+ };
239
+ const executeCell = async (notebook, cellIndex) => {
240
+ try {
241
+ await notebook.sessionContext.ready;
242
+ notebook.content.activeCellIndex = cellIndex;
243
+ await _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_2__.NotebookActions.run(notebook.content, notebook.sessionContext);
244
+ }
245
+ catch (error) {
246
+ console.error('[AgentPanel] Cell execution failed:', error);
247
+ }
248
+ };
249
+ const captureExecutionResult = (notebook, cellIndex) => {
250
+ const cell = notebook.content.widgets[cellIndex];
251
+ const model = cell?.model;
252
+ const outputs = model?.outputs;
253
+ const rawState = model?.executionState;
254
+ const executionState = typeof rawState === 'string'
255
+ ? rawState
256
+ : rawState?.get?.() ?? rawState?.value ?? null;
257
+ const result = {
258
+ success: true,
259
+ output: '',
260
+ error: undefined,
261
+ error_type: undefined,
262
+ traceback: undefined,
263
+ execution_count: model?.executionCount ?? null,
264
+ execution_state: executionState,
265
+ kernel_status: notebook.sessionContext?.session?.kernel?.status ?? null,
266
+ cell_type: model?.type ?? null,
267
+ outputs: [],
268
+ };
269
+ if (!outputs || outputs.length === 0) {
270
+ return result;
271
+ }
272
+ const stripImageData = (data) => {
273
+ const filtered = {};
274
+ Object.keys(data || {}).forEach((key) => {
275
+ if (!key.toLowerCase().startsWith('image/')) {
276
+ filtered[key] = data[key];
277
+ }
278
+ });
279
+ return filtered;
280
+ };
281
+ const errorSignatures = [
282
+ 'command not found',
283
+ 'ModuleNotFoundError',
284
+ 'No module named',
285
+ 'Traceback (most recent call last)',
286
+ 'Error:',
287
+ ];
288
+ for (let i = 0; i < outputs.length; i++) {
289
+ const output = outputs.get(i);
290
+ const json = output.toJSON?.() || output;
291
+ let sanitizedOutput = json;
292
+ if (output.type === 'error') {
293
+ result.outputs.push(sanitizedOutput);
294
+ result.success = false;
295
+ result.error_type = json.ename;
296
+ result.error = json.evalue;
297
+ if (Array.isArray(json.traceback)) {
298
+ result.traceback = json.traceback;
299
+ }
300
+ }
301
+ else if (output.type === 'stream' && json.text) {
302
+ result.outputs.push(sanitizedOutput);
303
+ result.output += json.text;
304
+ const lowerText = json.text.toLowerCase();
305
+ if (errorSignatures.some(signature => lowerText.includes(signature.toLowerCase()))) {
306
+ result.success = false;
307
+ result.error_type = 'runtime_error';
308
+ result.error = json.text.trim();
309
+ }
310
+ }
311
+ else if ((output.type === 'execute_result' || output.type === 'display_data') && json.data) {
312
+ const filteredData = stripImageData(json.data);
313
+ if (Object.keys(filteredData).length > 0) {
314
+ sanitizedOutput = { ...json, data: filteredData };
315
+ result.outputs.push(sanitizedOutput);
316
+ result.output += JSON.stringify(filteredData);
317
+ }
318
+ }
319
+ }
320
+ return result;
321
+ };
322
+ const executePendingApproval = async () => {
323
+ const notebook = getActiveNotebookPanel();
324
+ if (!notebook) {
325
+ console.warn('[AgentPanel] No active notebook to execute cell');
326
+ return null;
327
+ }
328
+ const pending = pendingToolCallsRef.current;
329
+ if (pending.length === 0) {
330
+ approvalPendingRef.current = false;
331
+ return null;
332
+ }
333
+ const next = pending.shift();
334
+ if (!next) {
335
+ approvalPendingRef.current = false;
336
+ return null;
337
+ }
338
+ if (next.tool === 'jupyter_cell' && next.code && typeof next.cellIndex === 'number') {
339
+ await executeCell(notebook, next.cellIndex);
340
+ const execResult = captureExecutionResult(notebook, next.cellIndex);
341
+ execResult.code = next.code;
342
+ next.execution_result = execResult;
343
+ console.log('[AgentPanel] Executed approved code cell from tool call');
344
+ approvalPendingRef.current = pendingToolCallsRef.current.length > 0;
345
+ return next;
346
+ }
347
+ approvalPendingRef.current = pendingToolCallsRef.current.length > 0;
348
+ return next;
349
+ };
350
+ const queueApprovalCell = (code) => {
351
+ const notebook = getActiveNotebookPanel();
352
+ if (!notebook) {
353
+ console.warn('[AgentPanel] No active notebook to add approval cell');
354
+ return;
355
+ }
356
+ const key = `jupyter_cell:${code}`;
357
+ if (handledToolCallKeysRef.current.has(key)) {
358
+ return;
359
+ }
360
+ const index = insertCell(notebook, 'code', code);
361
+ if (index === null) {
362
+ return;
363
+ }
364
+ handledToolCallKeysRef.current.add(key);
365
+ approvalPendingRef.current = true;
366
+ pendingToolCallsRef.current.push({ tool: 'jupyter_cell', code, cellIndex: index });
367
+ console.log('[AgentPanel] Added code cell pending approval via interrupt');
368
+ };
369
+ const handleToolCall = (toolCall) => {
370
+ const key = `${toolCall.tool}:${toolCall.code || toolCall.content || ''}`;
371
+ if (handledToolCallKeysRef.current.has(key)) {
372
+ return;
373
+ }
374
+ if (toolCall.tool === 'jupyter_cell') {
375
+ return;
376
+ }
377
+ if (toolCall.tool === 'markdown' && toolCall.content) {
378
+ const notebook = getActiveNotebookPanel();
379
+ if (!notebook) {
380
+ console.warn('[AgentPanel] No active notebook to add markdown');
381
+ return;
382
+ }
383
+ const index = insertCell(notebook, 'markdown', toolCall.content);
384
+ if (index !== null) {
385
+ handledToolCallKeysRef.current.add(key);
386
+ console.log('[AgentPanel] Added markdown cell from tool call');
387
+ }
388
+ }
389
+ };
199
390
  // 모드 변경 시 로컬 스토리지에 저장
200
391
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
201
392
  try {
@@ -211,6 +402,10 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
211
402
  const currentCellIdRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
212
403
  const currentCellIndexRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
213
404
  const orchestratorRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
405
+ const makeMessageId = (suffix) => {
406
+ const base = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
407
+ return suffix ? `${base}-${suffix}` : base;
408
+ };
214
409
  // Expose handleSendMessage via ref
215
410
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useImperativeHandle)(ref, () => ({
216
411
  handleSendMessage: async () => {
@@ -342,7 +537,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
342
537
  // CRITICAL: Prevent concurrent agent executions
343
538
  if (isAgentRunning) {
344
539
  const errorMessage = {
345
- id: Date.now().toString(),
540
+ id: makeMessageId(),
346
541
  role: 'assistant',
347
542
  content: '⚠️ 이전 작업이 아직 실행 중입니다. 완료될 때까지 기다려주세요.',
348
543
  timestamp: Date.now(),
@@ -373,7 +568,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
373
568
  if (!notebook || !sessionContext) {
374
569
  // 노트북이 없으면 에러 메시지 표시
375
570
  const errorMessage = {
376
- id: Date.now().toString(),
571
+ id: makeMessageId(),
377
572
  role: 'assistant',
378
573
  content: '노트북을 먼저 열어주세요. Agent 실행은 활성 노트북이 필요합니다.',
379
574
  timestamp: Date.now(),
@@ -382,8 +577,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
382
577
  return;
383
578
  }
384
579
  // ★ 현재 활성화된 노트북으로 AgentOrchestrator 재생성 (탭 전환 대응)
385
- const config = { ..._types_auto_agent__WEBPACK_IMPORTED_MODULE_5__.DEFAULT_AUTO_AGENT_CONFIG, executionSpeed };
386
- const orchestrator = new _services_AgentOrchestrator__WEBPACK_IMPORTED_MODULE_4__.AgentOrchestrator(notebook, sessionContext, apiService, config);
580
+ const config = { ..._types_auto_agent__WEBPACK_IMPORTED_MODULE_6__.DEFAULT_AUTO_AGENT_CONFIG, executionSpeed };
581
+ const orchestrator = new _services_AgentOrchestrator__WEBPACK_IMPORTED_MODULE_5__.AgentOrchestrator(notebook, sessionContext, apiService, config);
387
582
  // Agent 실행 메시지 생성
388
583
  const agentMessageId = `agent-${Date.now()}`;
389
584
  const agentMessage = {
@@ -402,7 +597,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
402
597
  setIsAgentRunning(true);
403
598
  try {
404
599
  // Get current llmConfig for the agent execution
405
- const currentLlmConfig = llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__.getDefaultLLMConfig)();
600
+ const currentLlmConfig = llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)();
406
601
  const result = await orchestrator.executeTask(request, notebook, (newStatus) => {
407
602
  // 실시간 상태 업데이트
408
603
  setMessages(prev => prev.map(msg => msg.id === agentMessageId && 'type' in msg && msg.type === 'agent_execution'
@@ -470,10 +665,10 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
470
665
  }, [notebookTracker, apiService]);
471
666
  const loadConfig = () => {
472
667
  // Load from localStorage using ApiKeyManager
473
- const config = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__.getLLMConfig)();
668
+ const config = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)();
474
669
  if (!config) {
475
670
  console.log('[AgentPanel] No config in localStorage, using default');
476
- const defaultConfig = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__.getDefaultLLMConfig)();
671
+ const defaultConfig = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)();
477
672
  setLlmConfig(defaultConfig);
478
673
  return;
479
674
  }
@@ -639,7 +834,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
639
834
  console.log('[AgentPanel] File fix response received, fixed files:', fixedFiles.length);
640
835
  // Assistant 메시지로 응답 표시
641
836
  const assistantMessage = {
642
- id: Date.now().toString() + '-file-fix',
837
+ id: makeMessageId('file-fix'),
643
838
  role: 'assistant',
644
839
  content: response,
645
840
  timestamp: Date.now(),
@@ -658,7 +853,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
658
853
  if (success) {
659
854
  // 성공 메시지
660
855
  const successMessage = {
661
- id: Date.now().toString() + '-apply-success',
856
+ id: makeMessageId('apply-success'),
662
857
  role: 'assistant',
663
858
  content: `✅ **${fix.path}** 파일이 수정되었습니다.\n\n파일 에디터에서 변경사항을 확인하세요.`,
664
859
  timestamp: Date.now(),
@@ -674,7 +869,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
674
869
  // 에러 메시지 추가 헬퍼
675
870
  const addErrorMessage = (message) => {
676
871
  const errorMessage = {
677
- id: Date.now().toString() + '-error',
872
+ id: makeMessageId('error'),
678
873
  role: 'assistant',
679
874
  content: `⚠️ ${message}`,
680
875
  timestamp: Date.now(),
@@ -684,9 +879,9 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
684
879
  const handleSaveConfig = (config) => {
685
880
  console.log('[AgentPanel] Saving config to localStorage');
686
881
  console.log('Provider:', config.provider);
687
- console.log('API Key configured:', (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__.hasValidApiKey)(config) ? '✓ Yes' : '✗ No');
882
+ console.log('API Key configured:', (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.hasValidApiKey)(config) ? '✓ Yes' : '✗ No');
688
883
  // Save to localStorage using ApiKeyManager
689
- (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__.saveLLMConfig)(config);
884
+ (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.saveLLMConfig)(config);
690
885
  // Update state
691
886
  setLlmConfig(config);
692
887
  console.log('[AgentPanel] Config saved successfully');
@@ -766,10 +961,17 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
766
961
  setIsAgentRunning(false);
767
962
  setCurrentAgentMessageId(null);
768
963
  };
769
- // Auto-scroll to bottom when messages change
964
+ // Auto-scroll to bottom when messages change or streaming
770
965
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
771
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
772
- }, [messages]);
966
+ if (isStreaming) {
967
+ // 스트리밍 중에는 즉시 스크롤
968
+ messagesEndRef.current?.scrollIntoView({ behavior: 'auto' });
969
+ }
970
+ else {
971
+ // 일반 메시지 추가 시 부드러운 스크롤
972
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
973
+ }
974
+ }, [messages, isStreaming]);
773
975
  // Extract and store code blocks from messages, setup button listeners
774
976
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
775
977
  // Use a small delay to ensure DOM is updated after message rendering
@@ -1360,6 +1562,171 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1360
1562
  div.textContent = text;
1361
1563
  return div.innerHTML;
1362
1564
  };
1565
+ const isAutoApprovedCode = (code) => {
1566
+ const lines = code
1567
+ .split('\n')
1568
+ .map(line => line.trim())
1569
+ .filter(line => line.length > 0 && !line.startsWith('#'));
1570
+ if (lines.length === 0) {
1571
+ return true;
1572
+ }
1573
+ const disallowedPatterns = [
1574
+ /(^|[^=!<>])=([^=]|$)/,
1575
+ /\.read_[a-zA-Z0-9_]*\s*\(/,
1576
+ /\bread_[a-zA-Z0-9_]*\s*\(/,
1577
+ /\.to_[a-zA-Z0-9_]*\s*\(/,
1578
+ ];
1579
+ if (lines.some(line => disallowedPatterns.some(pattern => pattern.test(line)))) {
1580
+ return false;
1581
+ }
1582
+ const allowedPatterns = [
1583
+ /^print\(.+\)$/,
1584
+ /^display\(.+\)$/,
1585
+ /^df\.(head|info|describe)\s*\(.*\)$/,
1586
+ /^display\(df\.(head|info|describe)\s*\(.*\)\)$/,
1587
+ /^df\.(tail|sample)\s*\(.*\)$/,
1588
+ /^display\(df\.(tail|sample)\s*\(.*\)\)$/,
1589
+ /^df\.(shape|columns|dtypes)$/,
1590
+ /^import\s+pandas\s+as\s+pd$/,
1591
+ ];
1592
+ return lines.every(line => allowedPatterns.some(pattern => pattern.test(line)));
1593
+ };
1594
+ const upsertInterruptMessage = (interrupt) => {
1595
+ const interruptMessageId = interruptMessageIdRef.current || makeMessageId('interrupt');
1596
+ interruptMessageIdRef.current = interruptMessageId;
1597
+ const interruptMessage = {
1598
+ id: interruptMessageId,
1599
+ role: 'system',
1600
+ content: interrupt.description || '코드 실행 승인이 필요합니다.',
1601
+ timestamp: Date.now(),
1602
+ metadata: { interrupt }
1603
+ };
1604
+ setMessages(prev => {
1605
+ const hasExisting = prev.some(msg => msg.id === interruptMessageId);
1606
+ if (hasExisting) {
1607
+ return prev.map(msg => msg.id === interruptMessageId ? interruptMessage : msg);
1608
+ }
1609
+ return [...prev, interruptMessage];
1610
+ });
1611
+ };
1612
+ const clearInterruptMessage = () => {
1613
+ if (!interruptMessageIdRef.current)
1614
+ return;
1615
+ const messageId = interruptMessageIdRef.current;
1616
+ interruptMessageIdRef.current = null;
1617
+ setMessages(prev => prev.map(msg => {
1618
+ // Only IChatMessage has metadata property (check via 'role' property)
1619
+ if (msg.id === messageId && 'role' in msg) {
1620
+ const chatMsg = msg;
1621
+ return {
1622
+ ...chatMsg,
1623
+ metadata: {
1624
+ ...chatMsg.metadata,
1625
+ interrupt: {
1626
+ ...(chatMsg.metadata?.interrupt || {}),
1627
+ resolved: true
1628
+ }
1629
+ }
1630
+ };
1631
+ }
1632
+ return msg;
1633
+ }));
1634
+ };
1635
+ const resumeFromInterrupt = async (interrupt, decision) => {
1636
+ const { threadId } = interrupt;
1637
+ setInterruptData(null);
1638
+ setDebugStatus(null);
1639
+ clearInterruptMessage();
1640
+ let resumeDecision = decision;
1641
+ let resumeArgs = undefined;
1642
+ if (decision === 'approve') {
1643
+ const executed = await executePendingApproval();
1644
+ if (executed && executed.tool === 'jupyter_cell') {
1645
+ resumeDecision = 'edit';
1646
+ resumeArgs = {
1647
+ code: executed.code,
1648
+ execution_result: executed.execution_result
1649
+ };
1650
+ }
1651
+ else if (executed && executed.tool === 'markdown') {
1652
+ resumeDecision = 'edit';
1653
+ resumeArgs = {
1654
+ content: executed.content
1655
+ };
1656
+ }
1657
+ }
1658
+ else {
1659
+ pendingToolCallsRef.current.shift();
1660
+ approvalPendingRef.current = pendingToolCallsRef.current.length > 0;
1661
+ }
1662
+ setIsLoading(true);
1663
+ setIsStreaming(true);
1664
+ let interrupted = false;
1665
+ // 항상 새 메시지 생성 - 승인 UI 아래에 append되도록
1666
+ const assistantMessageId = makeMessageId('assistant');
1667
+ setStreamingMessageId(assistantMessageId);
1668
+ // 새 메시지 추가 (맨 아래에 append)
1669
+ setMessages(prev => [
1670
+ ...prev,
1671
+ {
1672
+ id: assistantMessageId,
1673
+ role: 'assistant',
1674
+ content: '',
1675
+ timestamp: Date.now()
1676
+ }
1677
+ ]);
1678
+ let streamedContent = '';
1679
+ try {
1680
+ await apiService.resumeAgent(threadId, resumeDecision, resumeArgs, resumeDecision === 'reject' ? 'User rejected this action' : undefined, llmConfig || undefined, (chunk) => {
1681
+ streamedContent += chunk;
1682
+ setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)
1683
+ ? { ...msg, content: streamedContent }
1684
+ : msg));
1685
+ }, (status) => {
1686
+ setDebugStatus(status);
1687
+ }, (nextInterrupt) => {
1688
+ interrupted = true;
1689
+ approvalPendingRef.current = true;
1690
+ if (nextInterrupt.action === 'jupyter_cell_tool' && nextInterrupt.args?.code) {
1691
+ if (isAutoApprovedCode(nextInterrupt.args.code)) {
1692
+ queueApprovalCell(nextInterrupt.args.code);
1693
+ void resumeFromInterrupt(nextInterrupt, 'approve');
1694
+ return;
1695
+ }
1696
+ queueApprovalCell(nextInterrupt.args.code);
1697
+ }
1698
+ setInterruptData(nextInterrupt);
1699
+ upsertInterruptMessage(nextInterrupt);
1700
+ setIsLoading(false);
1701
+ setIsStreaming(false);
1702
+ }, (newTodos) => {
1703
+ setTodos(newTodos);
1704
+ }, () => {
1705
+ setDebugStatus(null);
1706
+ }, handleToolCall);
1707
+ }
1708
+ catch (error) {
1709
+ const message = error instanceof Error ? error.message : 'Failed to resume';
1710
+ setDebugStatus(`오류: ${message}`);
1711
+ console.error('Resume failed:', error);
1712
+ setMessages(prev => [
1713
+ ...prev,
1714
+ {
1715
+ id: makeMessageId(),
1716
+ role: 'assistant',
1717
+ content: `Error: ${message}`,
1718
+ timestamp: Date.now()
1719
+ }
1720
+ ]);
1721
+ }
1722
+ finally {
1723
+ setIsLoading(false);
1724
+ setIsStreaming(false);
1725
+ if (!interrupted) {
1726
+ approvalPendingRef.current = false;
1727
+ }
1728
+ }
1729
+ };
1363
1730
  const handleSendMessage = async () => {
1364
1731
  // Check if there's an LLM prompt stored (from cell action)
1365
1732
  const textarea = messagesEndRef.current?.parentElement?.querySelector('.jp-agent-input');
@@ -1389,7 +1756,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1389
1756
  console.log('[AgentPanel] Agent mode: No notebook, but Python error detected - switching to file fix mode');
1390
1757
  // User 메시지 추가
1391
1758
  const userMessage = {
1392
- id: Date.now().toString(),
1759
+ id: makeMessageId(),
1393
1760
  role: 'user',
1394
1761
  content: currentInput,
1395
1762
  timestamp: Date.now(),
@@ -1421,7 +1788,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1421
1788
  console.log('[AgentPanel] Agent mode: No notebook, file fix request detected - prompting for error details');
1422
1789
  // User 메시지 추가
1423
1790
  const userMessage = {
1424
- id: Date.now().toString(),
1791
+ id: makeMessageId(),
1425
1792
  role: 'user',
1426
1793
  content: currentInput,
1427
1794
  timestamp: Date.now(),
@@ -1430,7 +1797,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1430
1797
  setInput('');
1431
1798
  // 에러 메시지 요청 안내
1432
1799
  const guideMessage = {
1433
- id: Date.now().toString() + '-guide',
1800
+ id: makeMessageId('guide'),
1434
1801
  role: 'assistant',
1435
1802
  content: `파일 에러 수정을 도와드리겠습니다! 🔧
1436
1803
 
@@ -1462,7 +1829,7 @@ SyntaxError: '(' was never closed
1462
1829
  // 노트북이 있으면 Agent 실행
1463
1830
  // User 메시지 추가
1464
1831
  const userMessage = {
1465
- id: Date.now().toString(),
1832
+ id: makeMessageId(),
1466
1833
  role: 'user',
1467
1834
  content: `@agent ${currentInput}`,
1468
1835
  timestamp: Date.now(),
@@ -1480,7 +1847,7 @@ SyntaxError: '(' was never closed
1480
1847
  if (agentRequest) {
1481
1848
  // User 메시지 추가
1482
1849
  const userMessage = {
1483
- id: Date.now().toString(),
1850
+ id: makeMessageId(),
1484
1851
  role: 'user',
1485
1852
  content: currentInput,
1486
1853
  timestamp: Date.now(),
@@ -1497,7 +1864,7 @@ SyntaxError: '(' was never closed
1497
1864
  console.log('[AgentPanel] Python error detected in message');
1498
1865
  // User 메시지 추가
1499
1866
  const userMessage = {
1500
- id: Date.now().toString(),
1867
+ id: makeMessageId(),
1501
1868
  role: 'user',
1502
1869
  content: currentInput,
1503
1870
  timestamp: Date.now(),
@@ -1512,16 +1879,16 @@ SyntaxError: '(' was never closed
1512
1879
  let currentConfig = llmConfig;
1513
1880
  if (!currentConfig) {
1514
1881
  // Config not loaded yet, try to load from localStorage
1515
- currentConfig = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__.getDefaultLLMConfig)();
1882
+ currentConfig = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)();
1516
1883
  setLlmConfig(currentConfig);
1517
1884
  }
1518
1885
  // Check API key using ApiKeyManager
1519
- const hasApiKey = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_3__.hasValidApiKey)(currentConfig);
1886
+ const hasApiKey = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.hasValidApiKey)(currentConfig);
1520
1887
  if (!hasApiKey) {
1521
1888
  // Show error message and open settings
1522
1889
  const providerName = llmConfig?.provider || 'LLM';
1523
1890
  const errorMessage = {
1524
- id: Date.now().toString(),
1891
+ id: makeMessageId(),
1525
1892
  role: 'assistant',
1526
1893
  content: `API Key가 설정되지 않았습니다.\n\n${providerName === 'gemini' ? 'Gemini' : providerName === 'openai' ? 'OpenAI' : 'vLLM'} API Key를 먼저 설정해주세요.\n\n설정 버튼을 클릭하여 API Key를 입력하세요.`,
1527
1894
  timestamp: Date.now()
@@ -1533,7 +1900,7 @@ SyntaxError: '(' was never closed
1533
1900
  // Use the display prompt (input) for the user message, or use a fallback if input is empty
1534
1901
  const displayContent = currentInput || (llmPrompt ? '셀 분석 요청' : '');
1535
1902
  const userMessage = {
1536
- id: Date.now().toString(),
1903
+ id: makeMessageId(),
1537
1904
  role: 'user',
1538
1905
  content: displayContent,
1539
1906
  timestamp: Date.now()
@@ -1551,7 +1918,7 @@ SyntaxError: '(' was never closed
1551
1918
  pendingLlmPromptRef.current = null;
1552
1919
  }
1553
1920
  // Create assistant message ID for streaming updates
1554
- const assistantMessageId = Date.now().toString() + '-assistant';
1921
+ const assistantMessageId = makeMessageId('assistant');
1555
1922
  let streamedContent = '';
1556
1923
  setStreamingMessageId(assistantMessageId);
1557
1924
  // Add empty assistant message that will be updated during streaming
@@ -1562,6 +1929,7 @@ SyntaxError: '(' was never closed
1562
1929
  timestamp: Date.now()
1563
1930
  };
1564
1931
  setMessages(prev => [...prev, initialAssistantMessage]);
1932
+ let interrupted = false;
1565
1933
  try {
1566
1934
  // Use LLM prompt if available, otherwise use the display content
1567
1935
  const messageToSend = llmPrompt || displayContent;
@@ -1594,14 +1962,47 @@ SyntaxError: '(' was never closed
1594
1962
  }
1595
1963
  : msg));
1596
1964
  }
1597
- });
1965
+ },
1966
+ // onDebug callback - show debug status in gray
1967
+ (status) => {
1968
+ setDebugStatus(status);
1969
+ },
1970
+ // onInterrupt callback - show approval dialog
1971
+ (interrupt) => {
1972
+ interrupted = true;
1973
+ approvalPendingRef.current = true;
1974
+ if (interrupt.action === 'jupyter_cell_tool' && interrupt.args?.code) {
1975
+ if (isAutoApprovedCode(interrupt.args.code)) {
1976
+ queueApprovalCell(interrupt.args.code);
1977
+ void resumeFromInterrupt(interrupt, 'approve');
1978
+ return;
1979
+ }
1980
+ queueApprovalCell(interrupt.args.code);
1981
+ }
1982
+ setInterruptData(interrupt);
1983
+ upsertInterruptMessage(interrupt);
1984
+ setIsLoading(false);
1985
+ setIsStreaming(false);
1986
+ },
1987
+ // onTodos callback - update todo list UI
1988
+ (newTodos) => {
1989
+ setTodos(newTodos);
1990
+ },
1991
+ // onDebugClear callback - clear debug status
1992
+ () => {
1993
+ setDebugStatus(null);
1994
+ },
1995
+ // onToolCall callback - add cells to notebook
1996
+ handleToolCall);
1598
1997
  }
1599
1998
  catch (error) {
1999
+ const message = error instanceof Error ? error.message : 'Failed to send message';
2000
+ setDebugStatus(`오류: ${message}`);
1600
2001
  // Update the assistant message with error
1601
2002
  setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)
1602
2003
  ? {
1603
2004
  ...msg,
1604
- content: streamedContent + `\n\nError: ${error instanceof Error ? error.message : 'Failed to send message'}`
2005
+ content: streamedContent + `\n\nError: ${message}`
1605
2006
  }
1606
2007
  : msg));
1607
2008
  }
@@ -1609,8 +2010,15 @@ SyntaxError: '(' was never closed
1609
2010
  setIsLoading(false);
1610
2011
  setIsStreaming(false);
1611
2012
  setStreamingMessageId(null);
2013
+ // Keep completed todos visible after the run
1612
2014
  }
1613
2015
  };
2016
+ // Handle resume after user approval/rejection
2017
+ const handleResumeAgent = async (decision) => {
2018
+ if (!interruptData)
2019
+ return;
2020
+ await resumeFromInterrupt(interruptData, decision);
2021
+ };
1614
2022
  const handleKeyDown = (e) => {
1615
2023
  // Enter: 전송
1616
2024
  if (e.key === 'Enter' && !e.shiftKey) {
@@ -1707,7 +2115,7 @@ SyntaxError: '(' was never closed
1707
2115
  .map((tc, i) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { key: i, className: "jp-agent-execution-tool-tag" }, tc.tool)))))));
1708
2116
  })))),
1709
2117
  result && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `jp-agent-execution-result jp-agent-execution-result--${result.success ? 'success' : 'error'}` },
1710
- result.finalAnswer && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-execution-result-message jp-RenderedHTMLCommon", dangerouslySetInnerHTML: { __html: (0,_utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_6__.formatMarkdownToHtml)(result.finalAnswer) } })),
2118
+ result.finalAnswer && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-execution-result-message jp-RenderedHTMLCommon", dangerouslySetInnerHTML: { __html: (0,_utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_7__.formatMarkdownToHtml)(result.finalAnswer) } })),
1711
2119
  result.error && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("p", { className: "jp-agent-execution-result-error" }, result.error)),
1712
2120
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-execution-result-stats" },
1713
2121
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null,
@@ -1724,10 +2132,40 @@ SyntaxError: '(' was never closed
1724
2132
  const isChatMessage = (msg) => {
1725
2133
  return !('type' in msg) || msg.type !== 'agent_execution';
1726
2134
  };
2135
+ const mapStatusText = (raw) => {
2136
+ const normalized = raw.toLowerCase();
2137
+ if (normalized.includes('calling tool')) {
2138
+ return raw.replace(/Calling tool:/gi, '도구 호출 중:').trim();
2139
+ }
2140
+ if (normalized.includes('waiting for user approval')) {
2141
+ return '승인 대기 중...';
2142
+ }
2143
+ if (normalized.includes('resuming execution')) {
2144
+ return '승인 반영 후 실행 재개 중...';
2145
+ }
2146
+ if (normalized.includes('tool')) {
2147
+ return '도구 실행 중...';
2148
+ }
2149
+ return raw;
2150
+ };
2151
+ const getStatusText = () => {
2152
+ if (interruptData) {
2153
+ return `승인 대기: ${interruptData.action}`;
2154
+ }
2155
+ if (debugStatus) {
2156
+ return mapStatusText(debugStatus);
2157
+ }
2158
+ if (isLoading || isStreaming) {
2159
+ // 기본 상태 메시지 - todo 내용은 compact todo UI에서 표시
2160
+ return '요청 처리 중';
2161
+ }
2162
+ return null;
2163
+ };
2164
+ const statusText = getStatusText();
1727
2165
  return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-panel" },
1728
- showSettings && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_SettingsPanel__WEBPACK_IMPORTED_MODULE_2__.SettingsPanel, { onClose: () => setShowSettings(false), onSave: handleSaveConfig, currentConfig: llmConfig || undefined })),
2166
+ showSettings && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_SettingsPanel__WEBPACK_IMPORTED_MODULE_3__.SettingsPanel, { onClose: () => setShowSettings(false), onSave: handleSaveConfig, currentConfig: llmConfig || undefined })),
1729
2167
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header" },
1730
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header-logo", dangerouslySetInnerHTML: { __html: _logoSvg__WEBPACK_IMPORTED_MODULE_8__.headerLogoSvg } }),
2168
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header-logo", dangerouslySetInnerHTML: { __html: _logoSvg__WEBPACK_IMPORTED_MODULE_9__.headerLogoSvg } }),
1731
2169
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header-buttons" },
1732
2170
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-clear-button", onClick: clearChat, title: "\uB300\uD654 \uCD08\uAE30\uD654" },
1733
2171
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
@@ -1754,13 +2192,46 @@ SyntaxError: '(' was never closed
1754
2192
  : '메시지를 입력하거나 아래 버튼으로 Agent 모드를 선택하세요.'))) : (messages.map(msg => {
1755
2193
  if (isChatMessage(msg)) {
1756
2194
  // 일반 Chat 메시지
1757
- return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { key: msg.id, className: `jp-agent-message jp-agent-message-${msg.role}` },
1758
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-message-header" },
1759
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-message-role" }, msg.role === 'user' ? '사용자' : 'Agent'),
1760
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-message-time" }, new Date(msg.timestamp).toLocaleTimeString())),
1761
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `jp-agent-message-content${streamingMessageId === msg.id ? ' streaming' : ''}` }, msg.role === 'assistant' ? (
2195
+ const isAssistant = msg.role === 'assistant';
2196
+ return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { key: msg.id, className: isAssistant
2197
+ ? 'jp-agent-message jp-agent-message-assistant-inline'
2198
+ : `jp-agent-message jp-agent-message-${msg.role}` },
2199
+ !isAssistant && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-message-header" },
2200
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-message-role" }, msg.role === 'user' ? '사용자' : msg.role === 'system' ? '승인 요청' : 'Agent'),
2201
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-message-time" }, new Date(msg.timestamp).toLocaleTimeString()))),
2202
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `jp-agent-message-content${streamingMessageId === msg.id ? ' streaming' : ''}` }, msg.role === 'system' && msg.metadata?.interrupt ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-inline" },
2203
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-description" }, msg.content),
2204
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-action" },
2205
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-action-args" }, (() => {
2206
+ const code = msg.metadata?.interrupt?.args?.code || msg.metadata?.interrupt?.args?.content || '';
2207
+ const lines = code.split('\n');
2208
+ const preview = lines.slice(0, 8).join('\n');
2209
+ const suffix = lines.length > 8 ? '\n...' : '';
2210
+ const resolved = msg.metadata?.interrupt?.resolved;
2211
+ const actionHtml = resolved
2212
+ ? '<div class="jp-agent-interrupt-actions jp-agent-interrupt-actions--resolved">승인됨</div>'
2213
+ : `
2214
+ <div class="code-block-actions jp-agent-interrupt-actions">
2215
+ <button class="jp-agent-interrupt-approve-btn" data-action="approve">승인</button>
2216
+ <button class="jp-agent-interrupt-reject-btn" data-action="reject">거부</button>
2217
+ </div>
2218
+ `;
2219
+ return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-RenderedHTMLCommon", style: { padding: '0 4px' }, dangerouslySetInnerHTML: { __html: (0,_utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_7__.formatMarkdownToHtml)(`\n\`\`\`python\n${preview}${suffix}\n\`\`\``).replace('</div>', `${actionHtml}</div>`) }, onClick: (event) => {
2220
+ const target = event.target;
2221
+ const action = target?.getAttribute?.('data-action');
2222
+ if (msg.metadata?.interrupt?.resolved) {
2223
+ return;
2224
+ }
2225
+ if (action === 'approve') {
2226
+ handleResumeAgent('approve');
2227
+ }
2228
+ else if (action === 'reject') {
2229
+ handleResumeAgent('reject');
2230
+ }
2231
+ } }));
2232
+ })())))) : msg.role === 'assistant' ? (
1762
2233
  // Assistant(AI) 메시지: 마크다운 HTML 렌더링 + Jupyter 스타일 적용
1763
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-RenderedHTMLCommon", style: { padding: '0 5px' }, dangerouslySetInnerHTML: { __html: (0,_utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_6__.formatMarkdownToHtml)(msg.content) } })) : (
2234
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-RenderedHTMLCommon", style: { padding: '0 5px' }, dangerouslySetInnerHTML: { __html: (0,_utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_7__.formatMarkdownToHtml)(msg.content) } })) : (
1764
2235
  // User(사용자) 메시지: 텍스트 그대로 줄바꿈만 처리
1765
2236
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { style: { whiteSpace: 'pre-wrap' } }, msg.content)))));
1766
2237
  }
@@ -1769,13 +2240,6 @@ SyntaxError: '(' was never closed
1769
2240
  return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { key: msg.id, className: "jp-agent-message jp-agent-message-agent-execution" }, renderAgentExecutionMessage(msg)));
1770
2241
  }
1771
2242
  })),
1772
- isLoading && !isStreaming && !isAgentRunning && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-message jp-agent-message-assistant" },
1773
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-message-header" },
1774
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-message-role" }, "Agent")),
1775
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-message-content jp-agent-loading" },
1776
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-loading-dot" }, "."),
1777
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-loading-dot" }, "."),
1778
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-loading-dot" }, ".")))),
1779
2243
  showConsoleErrorNotification && lastConsoleError && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-console-error-notification" },
1780
2244
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-console-error-header" },
1781
2245
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", width: "16", height: "16" },
@@ -1812,6 +2276,38 @@ SyntaxError: '(' was never closed
1812
2276
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-file-fix-apply", onClick: () => applyFileFix(fix), title: `${fix.path} 파일에 수정 적용` }, "\uC801\uC6A9\uD558\uAE30"))))),
1813
2277
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-file-fixes-dismiss", onClick: () => setPendingFileFixes([]), title: "\uC218\uC815 \uC81C\uC548 \uB2EB\uAE30" }, "\uB2EB\uAE30"))),
1814
2278
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { ref: messagesEndRef })),
2279
+ statusText && todos.length === 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `jp-agent-message jp-agent-message-debug${statusText.startsWith('오류:') ? ' jp-agent-message-debug-error' : ''}` },
2280
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-debug-content" },
2281
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-debug-branch", "aria-hidden": "true" }),
2282
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-text" }, statusText),
2283
+ !statusText.startsWith('오류:') && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-ellipsis", "aria-hidden": "true" }))))),
2284
+ todos.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-compact" },
2285
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-compact-header", onClick: () => setIsTodoExpanded(!isTodoExpanded) },
2286
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-compact-left" },
2287
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { className: `jp-agent-todo-expand-icon ${isTodoExpanded ? 'jp-agent-todo-expand-icon--expanded' : ''}`, viewBox: "0 0 16 16", fill: "currentColor", width: "12", height: "12" },
2288
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M6 12l4-4-4-4" })),
2289
+ (() => {
2290
+ const currentTodo = todos.find(t => t.status === 'in_progress') || todos.find(t => t.status === 'pending');
2291
+ return currentTodo ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
2292
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-compact-spinner" }),
2293
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-compact-current" }, currentTodo.content))) : (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-compact-current" }, "\u2713 \uBAA8\uB4E0 \uC791\uC5C5 \uC644\uB8CC"));
2294
+ })()),
2295
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-compact-progress" },
2296
+ todos.filter(t => t.status === 'completed').length,
2297
+ "/",
2298
+ todos.length)),
2299
+ statusText && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `jp-agent-message jp-agent-message-debug jp-agent-message-debug--inline${statusText.startsWith('오류:') ? ' jp-agent-message-debug-error' : ''}` },
2300
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-debug-content" },
2301
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-debug-branch", "aria-hidden": "true" }),
2302
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-text" }, statusText),
2303
+ !statusText.startsWith('오류:') && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-ellipsis", "aria-hidden": "true" }))))),
2304
+ isTodoExpanded && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-expanded" }, todos.map((todo, index) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { key: index, className: `jp-agent-todo-item jp-agent-todo-item--${todo.status}` },
2305
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-item-indicator" },
2306
+ todo.status === 'completed' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", width: "12", height: "12" },
2307
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z" }))),
2308
+ todo.status === 'in_progress' && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-item-spinner" }),
2309
+ todo.status === 'pending' && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-item-number" }, index + 1)),
2310
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: `jp-agent-todo-item-text ${todo.status === 'completed' ? 'jp-agent-todo-item-text--done' : ''}` }, todo.content)))))))),
1815
2311
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-input-container" },
1816
2312
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-input-wrapper" },
1817
2313
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { className: `jp-agent-input ${inputMode === 'agent' ? 'jp-agent-input--agent-mode' : ''}`, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: handleKeyDown, placeholder: inputMode === 'agent'
@@ -1842,7 +2338,7 @@ SyntaxError: '(' was never closed
1842
2338
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-shortcut" }, "\uB178\uD2B8\uBD81 \uC790\uB3D9 \uC2E4\uD589"))))),
1843
2339
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-hints" },
1844
2340
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-hint" }, "\u21E7Tab \uBAA8\uB4DC \uC804\uD658")))),
1845
- fileSelectionMetadata && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_FileSelectionDialog__WEBPACK_IMPORTED_MODULE_7__.FileSelectionDialog, { filename: fileSelectionMetadata.pattern, options: fileSelectionMetadata.options, message: fileSelectionMetadata.message, onSelect: handleFileSelect, onCancel: handleFileSelectCancel }))));
2341
+ fileSelectionMetadata && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_FileSelectionDialog__WEBPACK_IMPORTED_MODULE_8__.FileSelectionDialog, { filename: fileSelectionMetadata.pattern, options: fileSelectionMetadata.options, message: fileSelectionMetadata.message, onSelect: handleFileSelect, onCancel: handleFileSelectCancel }))));
1846
2342
  });
1847
2343
  ChatPanel.displayName = 'ChatPanel';
1848
2344
  /**
@@ -6857,7 +7353,7 @@ class ApiService {
6857
7353
  * - Server receives ONLY ONE key per request
6858
7354
  * - On 429 rate limit, frontend rotates key and retries with next key
6859
7355
  */
6860
- async sendMessageStream(request, onChunk, onMetadata) {
7356
+ async sendMessageStream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall) {
6861
7357
  // Maximum retry attempts (should match number of keys)
6862
7358
  const MAX_RETRIES = 10;
6863
7359
  let currentConfig = request.llmConfig;
@@ -6868,7 +7364,7 @@ class ApiService {
6868
7364
  ? { ...request, llmConfig: (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.buildSingleKeyConfig)(currentConfig) }
6869
7365
  : request;
6870
7366
  try {
6871
- await this.sendMessageStreamInternal(requestToSend, onChunk, onMetadata);
7367
+ await this.sendMessageStreamInternal(requestToSend, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall);
6872
7368
  // Success - reset key rotation state
6873
7369
  (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.resetKeyRotation)();
6874
7370
  return;
@@ -6916,13 +7412,29 @@ class ApiService {
6916
7412
  }
6917
7413
  /**
6918
7414
  * Internal streaming implementation (without retry logic)
6919
- */
6920
- async sendMessageStreamInternal(request, onChunk, onMetadata) {
6921
- const response = await fetch(`${this.baseUrl}/chat/stream`, {
7415
+ * Uses LangChain agent endpoint for improved middleware support
7416
+ */
7417
+ async sendMessageStreamInternal(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall) {
7418
+ // Convert IChatRequest to LangChain AgentRequest format
7419
+ // Frontend's context has limited fields, map what's available
7420
+ const langchainRequest = {
7421
+ request: request.message,
7422
+ notebookContext: request.context ? {
7423
+ notebook_path: request.context.notebookPath,
7424
+ cell_count: 0,
7425
+ imported_libraries: [],
7426
+ defined_variables: [],
7427
+ recent_cells: request.context.selectedCells?.map(cell => ({ source: cell })) || []
7428
+ } : undefined,
7429
+ llmConfig: request.llmConfig,
7430
+ workspaceRoot: '.'
7431
+ };
7432
+ // Use LangChain streaming endpoint
7433
+ const response = await fetch(`${this.baseUrl}/agent/langchain/stream`, {
6922
7434
  method: 'POST',
6923
7435
  headers: this.getHeaders(),
6924
7436
  credentials: 'include',
6925
- body: JSON.stringify(request)
7437
+ body: JSON.stringify(langchainRequest)
6926
7438
  });
6927
7439
  if (!response.ok) {
6928
7440
  const error = await response.text();
@@ -6943,15 +7455,78 @@ class ApiService {
6943
7455
  // Process complete SSE messages
6944
7456
  const lines = buffer.split('\n');
6945
7457
  buffer = lines.pop() || ''; // Keep incomplete line in buffer
7458
+ let currentEventType = '';
6946
7459
  for (const line of lines) {
7460
+ // Handle SSE event type: "event: type"
7461
+ if (line.startsWith('event: ')) {
7462
+ currentEventType = line.slice(7).trim();
7463
+ continue;
7464
+ }
7465
+ // Handle SSE data: "data: json"
6947
7466
  if (line.startsWith('data: ')) {
6948
7467
  try {
6949
7468
  const data = JSON.parse(line.slice(6));
7469
+ // Handle based on event type
7470
+ if (currentEventType === 'todos' && data.todos && onTodos) {
7471
+ onTodos(data.todos);
7472
+ currentEventType = '';
7473
+ continue;
7474
+ }
7475
+ if (currentEventType === 'debug_clear' && onDebugClear) {
7476
+ onDebugClear();
7477
+ currentEventType = '';
7478
+ continue;
7479
+ }
7480
+ if (currentEventType === 'complete') {
7481
+ if (onDebugClear) {
7482
+ onDebugClear();
7483
+ }
7484
+ return;
7485
+ }
6950
7486
  // Handle errors
6951
7487
  if (data.error) {
7488
+ if (onDebug) {
7489
+ onDebug(`오류: ${data.error}`);
7490
+ }
6952
7491
  throw new Error(data.error);
6953
7492
  }
6954
- // Handle metadata (conversationId, messageId, etc.)
7493
+ // Handle debug events (display in gray)
7494
+ if (data.status && onDebug) {
7495
+ onDebug(data.status);
7496
+ }
7497
+ // Handle interrupt events (Human-in-the-Loop)
7498
+ if (data.thread_id && data.action && onInterrupt) {
7499
+ onInterrupt({
7500
+ threadId: data.thread_id,
7501
+ action: data.action,
7502
+ args: data.args || {},
7503
+ description: data.description || ''
7504
+ });
7505
+ return; // Stop processing, wait for user decision
7506
+ }
7507
+ // Handle token events (streaming LLM response)
7508
+ if (data.content) {
7509
+ onChunk(data.content);
7510
+ }
7511
+ // Handle tool_call events - pass to handler for cell creation
7512
+ if (currentEventType === 'tool_call' && data.tool && onToolCall) {
7513
+ onToolCall({
7514
+ tool: data.tool,
7515
+ code: data.code,
7516
+ content: data.content
7517
+ });
7518
+ currentEventType = '';
7519
+ continue;
7520
+ }
7521
+ // Handle tool_result events - skip displaying raw output
7522
+ if (data.output) {
7523
+ // Don't add raw tool output to chat
7524
+ }
7525
+ // Handle completion
7526
+ if (data.final_answer) {
7527
+ onChunk(data.final_answer);
7528
+ }
7529
+ // Handle metadata
6955
7530
  if (data.conversationId && onMetadata) {
6956
7531
  onMetadata({
6957
7532
  conversationId: data.conversationId,
@@ -6960,10 +7535,6 @@ class ApiService {
6960
7535
  model: data.metadata?.model
6961
7536
  });
6962
7537
  }
6963
- // Handle content chunks
6964
- if (data.content) {
6965
- onChunk(data.content);
6966
- }
6967
7538
  // Final metadata update
6968
7539
  if (data.done && data.metadata && onMetadata) {
6969
7540
  onMetadata({
@@ -6971,6 +7542,7 @@ class ApiService {
6971
7542
  model: data.metadata.model
6972
7543
  });
6973
7544
  }
7545
+ currentEventType = '';
6974
7546
  }
6975
7547
  catch (e) {
6976
7548
  if (e instanceof SyntaxError) {
@@ -6988,6 +7560,122 @@ class ApiService {
6988
7560
  reader.releaseLock();
6989
7561
  }
6990
7562
  }
7563
+ /**
7564
+ * Resume interrupted agent execution with user decision
7565
+ */
7566
+ async resumeAgent(threadId, decision, args, feedback, llmConfig, onChunk, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall) {
7567
+ const resumeRequest = {
7568
+ threadId,
7569
+ decisions: [{
7570
+ type: decision,
7571
+ args,
7572
+ feedback
7573
+ }],
7574
+ llmConfig,
7575
+ workspaceRoot: '.'
7576
+ };
7577
+ const response = await fetch(`${this.baseUrl}/agent/langchain/resume`, {
7578
+ method: 'POST',
7579
+ headers: this.getHeaders(),
7580
+ credentials: 'include',
7581
+ body: JSON.stringify(resumeRequest)
7582
+ });
7583
+ if (!response.ok) {
7584
+ const error = await response.text();
7585
+ throw new Error(`Failed to resume agent: ${error}`);
7586
+ }
7587
+ // Process SSE stream (same as sendMessageStream)
7588
+ const reader = response.body?.getReader();
7589
+ if (!reader) {
7590
+ throw new Error('Response body is not readable');
7591
+ }
7592
+ const decoder = new TextDecoder();
7593
+ let buffer = '';
7594
+ try {
7595
+ while (true) {
7596
+ const { done, value } = await reader.read();
7597
+ if (done)
7598
+ break;
7599
+ buffer += decoder.decode(value, { stream: true });
7600
+ const lines = buffer.split('\n');
7601
+ buffer = lines.pop() || '';
7602
+ let currentEventType = '';
7603
+ for (const line of lines) {
7604
+ // Handle SSE event type
7605
+ if (line.startsWith('event: ')) {
7606
+ currentEventType = line.slice(7).trim();
7607
+ continue;
7608
+ }
7609
+ if (line.startsWith('data: ')) {
7610
+ try {
7611
+ const data = JSON.parse(line.slice(6));
7612
+ if (data.error) {
7613
+ if (onDebug) {
7614
+ onDebug(`오류: ${data.error}`);
7615
+ }
7616
+ throw new Error(data.error);
7617
+ }
7618
+ // Handle todos event
7619
+ if (currentEventType === 'todos' && data.todos && onTodos) {
7620
+ onTodos(data.todos);
7621
+ currentEventType = '';
7622
+ continue;
7623
+ }
7624
+ // Handle debug_clear event
7625
+ if (currentEventType === 'debug_clear' && onDebugClear) {
7626
+ onDebugClear();
7627
+ currentEventType = '';
7628
+ continue;
7629
+ }
7630
+ if (currentEventType === 'complete') {
7631
+ if (onDebugClear) {
7632
+ onDebugClear();
7633
+ }
7634
+ return;
7635
+ }
7636
+ // Debug events
7637
+ if (data.status && onDebug) {
7638
+ onDebug(data.status);
7639
+ }
7640
+ // Another interrupt
7641
+ if (data.thread_id && data.action && onInterrupt) {
7642
+ onInterrupt({
7643
+ threadId: data.thread_id,
7644
+ action: data.action,
7645
+ args: data.args || {},
7646
+ description: data.description || ''
7647
+ });
7648
+ return;
7649
+ }
7650
+ // Content chunks
7651
+ if (data.content && onChunk) {
7652
+ onChunk(data.content);
7653
+ }
7654
+ // Tool call events during resume
7655
+ if (currentEventType === 'tool_call' && data.tool && onToolCall) {
7656
+ onToolCall({
7657
+ tool: data.tool,
7658
+ code: data.code,
7659
+ content: data.content
7660
+ });
7661
+ currentEventType = '';
7662
+ continue;
7663
+ }
7664
+ currentEventType = '';
7665
+ }
7666
+ catch (e) {
7667
+ if (!(e instanceof SyntaxError)) {
7668
+ throw e;
7669
+ }
7670
+ }
7671
+ }
7672
+ }
7673
+ }
7674
+ }
7675
+ finally {
7676
+ reader.releaseLock();
7677
+ }
7678
+ }
6991
7679
  /**
6992
7680
  * Save configuration
6993
7681
  */
@@ -12493,4 +13181,4 @@ __webpack_require__.r(__webpack_exports__);
12493
13181
  /***/ }
12494
13182
 
12495
13183
  }]);
12496
- //# sourceMappingURL=lib_index_js.622c1a5918b3aafb2315.js.map
13184
+ //# sourceMappingURL=lib_index_js.1366019c413f1d68467f.js.map