hdsp-jupyter-extension 2.0.8__py3-none-any.whl → 2.0.11__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 (88) hide show
  1. agent_server/core/notebook_generator.py +4 -4
  2. agent_server/core/rag_manager.py +12 -3
  3. agent_server/core/retriever.py +2 -1
  4. agent_server/core/vllm_embedding_service.py +8 -5
  5. agent_server/langchain/ARCHITECTURE.md +7 -51
  6. agent_server/langchain/agent.py +31 -20
  7. agent_server/langchain/custom_middleware.py +234 -31
  8. agent_server/langchain/hitl_config.py +5 -8
  9. agent_server/langchain/logging_utils.py +7 -7
  10. agent_server/langchain/prompts.py +106 -120
  11. agent_server/langchain/tools/__init__.py +1 -10
  12. agent_server/langchain/tools/file_tools.py +9 -61
  13. agent_server/langchain/tools/jupyter_tools.py +0 -1
  14. agent_server/langchain/tools/lsp_tools.py +8 -8
  15. agent_server/langchain/tools/resource_tools.py +12 -12
  16. agent_server/langchain/tools/search_tools.py +3 -158
  17. agent_server/prompts/file_action_prompts.py +8 -8
  18. agent_server/routers/langchain_agent.py +200 -125
  19. hdsp_agent_core/__init__.py +46 -47
  20. hdsp_agent_core/factory.py +6 -10
  21. hdsp_agent_core/interfaces.py +4 -2
  22. hdsp_agent_core/knowledge/__init__.py +5 -5
  23. hdsp_agent_core/knowledge/chunking.py +87 -61
  24. hdsp_agent_core/knowledge/loader.py +103 -101
  25. hdsp_agent_core/llm/service.py +192 -107
  26. hdsp_agent_core/managers/config_manager.py +16 -22
  27. hdsp_agent_core/managers/session_manager.py +5 -4
  28. hdsp_agent_core/models/__init__.py +12 -12
  29. hdsp_agent_core/models/agent.py +15 -8
  30. hdsp_agent_core/models/common.py +1 -2
  31. hdsp_agent_core/models/rag.py +48 -111
  32. hdsp_agent_core/prompts/__init__.py +12 -12
  33. hdsp_agent_core/prompts/cell_action_prompts.py +9 -7
  34. hdsp_agent_core/services/agent_service.py +10 -8
  35. hdsp_agent_core/services/chat_service.py +10 -6
  36. hdsp_agent_core/services/rag_service.py +3 -6
  37. hdsp_agent_core/tests/conftest.py +4 -1
  38. hdsp_agent_core/tests/test_factory.py +2 -2
  39. hdsp_agent_core/tests/test_services.py +12 -19
  40. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  41. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  42. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js +93 -4
  43. hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
  44. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js +153 -130
  45. hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
  46. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js +6 -6
  47. hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
  48. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/METADATA +1 -3
  49. hdsp_jupyter_extension-2.0.11.dist-info/RECORD +144 -0
  50. jupyter_ext/__init__.py +21 -11
  51. jupyter_ext/_version.py +1 -1
  52. jupyter_ext/handlers.py +69 -50
  53. jupyter_ext/labextension/build_log.json +1 -1
  54. jupyter_ext/labextension/package.json +2 -2
  55. jupyter_ext/labextension/static/{frontend_styles_index_js.8740a527757068814573.js → frontend_styles_index_js.2d9fb488c82498c45c2d.js} +93 -4
  56. jupyter_ext/labextension/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
  57. jupyter_ext/labextension/static/{lib_index_js.e4ff4b5779b5e049f84c.js → lib_index_js.58c1e128ba0b76f41f04.js} +153 -130
  58. jupyter_ext/labextension/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
  59. jupyter_ext/labextension/static/{remoteEntry.020cdb0b864cfaa4e41e.js → remoteEntry.9da31d1134a53b0c4af5.js} +6 -6
  60. jupyter_ext/labextension/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
  61. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
  62. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
  63. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
  64. hdsp_jupyter_extension-2.0.8.dist-info/RECORD +0 -144
  65. jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
  66. jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
  67. jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
  68. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  69. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  70. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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
  71. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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
  72. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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
  73. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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
  74. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  75. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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
  76. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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
  77. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
  78. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -0
  79. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
  80. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -0
  81. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  82. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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
  83. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  84. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  85. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
  86. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
  87. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/WHEEL +0 -0
  88. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/licenses/LICENSE +0 -0
@@ -1157,10 +1157,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1157
1157
  console.log('[AgentPanel] Captured agentThreadId from interrupt:', interrupt.threadId);
1158
1158
  }
1159
1159
  // Auto-approve search/file/resource tools - execute immediately without user interaction
1160
- if (interrupt.action === 'search_workspace_tool'
1161
- || interrupt.action === 'search_notebook_cells_tool'
1160
+ if (interrupt.action === 'search_notebook_cells_tool'
1162
1161
  || interrupt.action === 'check_resource_tool'
1163
- || interrupt.action === 'list_files_tool'
1164
1162
  || interrupt.action === 'read_file_tool') {
1165
1163
  void handleAutoToolInterrupt(interrupt);
1166
1164
  return;
@@ -2212,8 +2210,12 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2212
2210
  if (!pathCheck.valid) {
2213
2211
  return { success: false, error: pathCheck.error };
2214
2212
  }
2215
- const maxLines = typeof params.maxLines === 'number' ? params.maxLines : 1000;
2216
- const safeMaxLines = Math.max(0, maxLines);
2213
+ // Support both old (maxLines) and new (offset/limit) parameters
2214
+ const offset = typeof params.offset === 'number' ? Math.max(0, params.offset) : 0;
2215
+ const limit = typeof params.limit === 'number'
2216
+ ? params.limit
2217
+ : (typeof params.maxLines === 'number' ? params.maxLines : 500);
2218
+ const safeLimit = Math.max(0, limit);
2217
2219
  const contentsResult = await fetchContentsModel(params.path, { content: true, format: 'text' });
2218
2220
  if (!contentsResult.success) {
2219
2221
  return { success: false, error: contentsResult.error };
@@ -2233,13 +2235,18 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2233
2235
  content = JSON.stringify(content, null, 2);
2234
2236
  }
2235
2237
  const lines = content.split('\n');
2236
- const sliced = lines.slice(0, safeMaxLines);
2238
+ const totalLines = lines.length;
2239
+ // Apply offset and limit
2240
+ const sliced = lines.slice(offset, offset + safeLimit);
2237
2241
  return {
2238
2242
  success: true,
2239
2243
  output: sliced.join('\n'),
2240
2244
  metadata: {
2241
2245
  lineCount: sliced.length,
2242
- truncated: lines.length > safeMaxLines
2246
+ totalLines,
2247
+ offset,
2248
+ limit: safeLimit,
2249
+ truncated: totalLines > offset + safeLimit
2243
2250
  }
2244
2251
  };
2245
2252
  };
@@ -2249,18 +2256,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2249
2256
  console.log('[AgentPanel] Auto-approving tool:', action, args);
2250
2257
  try {
2251
2258
  let executionResult;
2252
- if (action === 'search_workspace_tool') {
2253
- setDebugStatus(`🔍 검색 실행 중: ${args?.pattern || ''}`);
2254
- executionResult = await apiService.searchWorkspace({
2255
- pattern: args?.pattern || '',
2256
- file_types: args?.file_types || ['*.py', '*.ipynb'],
2257
- path: args?.path || '.',
2258
- max_results: args?.max_results || 50,
2259
- case_sensitive: args?.case_sensitive || false
2260
- });
2261
- console.log('[AgentPanel] search_workspace result:', executionResult);
2262
- }
2263
- else if (action === 'search_notebook_cells_tool') {
2259
+ if (action === 'search_notebook_cells_tool') {
2264
2260
  setDebugStatus(`🔍 노트북 검색 실행 중: ${args?.pattern || ''}`);
2265
2261
  executionResult = await apiService.searchNotebookCells({
2266
2262
  pattern: args?.pattern || '',
@@ -2282,22 +2278,13 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2282
2278
  });
2283
2279
  console.log('[AgentPanel] check_resource result:', executionResult);
2284
2280
  }
2285
- else if (action === 'list_files_tool') {
2286
- setDebugStatus('📂 파일 목록 조회 중...');
2287
- const listParams = {
2288
- path: typeof args?.path === 'string' ? args.path : '.',
2289
- recursive: args?.recursive ?? false,
2290
- pattern: args?.pattern ?? undefined
2291
- };
2292
- executionResult = await executeListFilesTool(listParams);
2293
- console.log('[AgentPanel] list_files result:', executionResult);
2294
- }
2295
2281
  else if (action === 'read_file_tool') {
2296
2282
  setDebugStatus('📄 파일 읽는 중...');
2297
2283
  const readParams = {
2298
2284
  path: typeof args?.path === 'string' ? args.path : '',
2299
2285
  encoding: typeof args?.encoding === 'string' ? args.encoding : undefined,
2300
- maxLines: args?.max_lines ?? args?.maxLines
2286
+ offset: typeof args?.offset === 'number' ? args.offset : 0,
2287
+ limit: typeof args?.limit === 'number' ? args.limit : (args?.max_lines ?? args?.maxLines ?? 500)
2301
2288
  };
2302
2289
  executionResult = await executeReadFileTool(readParams);
2303
2290
  console.log('[AgentPanel] read_file result:', executionResult);
@@ -2341,10 +2328,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2341
2328
  approvalPendingRef.current = true;
2342
2329
  const autoApproveEnabled = getAutoApproveEnabled(llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)());
2343
2330
  // Handle next interrupt (could be another search or code execution)
2344
- if (nextInterrupt.action === 'search_workspace_tool'
2345
- || nextInterrupt.action === 'search_notebook_cells_tool'
2331
+ if (nextInterrupt.action === 'search_notebook_cells_tool'
2346
2332
  || nextInterrupt.action === 'check_resource_tool'
2347
- || nextInterrupt.action === 'list_files_tool'
2348
2333
  || nextInterrupt.action === 'read_file_tool') {
2349
2334
  void handleAutoToolInterrupt(nextInterrupt);
2350
2335
  return;
@@ -2673,7 +2658,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2673
2658
  };
2674
2659
  }
2675
2660
  else {
2676
- // LSP not available - suggest using search_workspace_tool
2661
+ // LSP not available - suggest using execute_command_tool with grep
2677
2662
  console.log('[AgentPanel] LSP not available for references, suggesting grep fallback');
2678
2663
  resumeDecision = 'approve';
2679
2664
  resumeArgs = {
@@ -2839,10 +2824,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2839
2824
  approvalPendingRef.current = true;
2840
2825
  const autoApproveEnabled = getAutoApproveEnabled(llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)());
2841
2826
  // Auto-approve search/file/resource tools
2842
- if (nextInterrupt.action === 'search_workspace_tool'
2843
- || nextInterrupt.action === 'search_notebook_cells_tool'
2827
+ if (nextInterrupt.action === 'search_notebook_cells_tool'
2844
2828
  || nextInterrupt.action === 'check_resource_tool'
2845
- || nextInterrupt.action === 'list_files_tool'
2846
2829
  || nextInterrupt.action === 'read_file_tool') {
2847
2830
  void handleAutoToolInterrupt(nextInterrupt);
2848
2831
  return;
@@ -3278,9 +3261,10 @@ SyntaxError: '(' was never closed
3278
3261
  const interruptAction = msg.metadata?.interrupt?.action;
3279
3262
  const isWriteFile = interruptAction === 'write_file_tool';
3280
3263
  const isEditFile = interruptAction === 'edit_file_tool';
3264
+ const isMultiEditFile = interruptAction === 'multiedit_file_tool';
3281
3265
  const writePath = (isWriteFile
3282
3266
  && typeof msg.metadata?.interrupt?.args?.path === 'string') ? msg.metadata?.interrupt?.args?.path : '';
3283
- const editPath = (isEditFile
3267
+ const editPath = ((isEditFile || isMultiEditFile)
3284
3268
  && typeof msg.metadata?.interrupt?.args?.path === 'string') ? msg.metadata?.interrupt?.args?.path : '';
3285
3269
  const autoApproved = msg.metadata?.interrupt?.autoApproved;
3286
3270
  const headerRole = msg.role === 'user'
@@ -3300,10 +3284,26 @@ SyntaxError: '(' was never closed
3300
3284
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-action-args" }, (() => {
3301
3285
  const command = msg.metadata?.interrupt?.args?.command;
3302
3286
  const code = msg.metadata?.interrupt?.args?.code || msg.metadata?.interrupt?.args?.content || '';
3303
- // Handle edit_file_tool with diff preview
3287
+ // Handle edit_file_tool and multiedit_file_tool with diff preview
3304
3288
  let snippet;
3305
3289
  let language;
3306
- if (isEditFile) {
3290
+ if (isMultiEditFile) {
3291
+ // Handle multiedit_file_tool - show all edits
3292
+ const edits = msg.metadata?.interrupt?.args?.edits || [];
3293
+ const editsPreview = edits.slice(0, 5).map((edit, idx) => {
3294
+ const oldStr = edit.old_string || '';
3295
+ const newStr = edit.new_string || '';
3296
+ const oldPreview = oldStr.length > 100 ? oldStr.slice(0, 100) + '...' : oldStr;
3297
+ const newPreview = newStr.length > 100 ? newStr.slice(0, 100) + '...' : newStr;
3298
+ return `# Edit ${idx + 1}${edit.replace_all ? ' (replace_all)' : ''}\n` +
3299
+ oldPreview.split('\n').map((line) => `-${line}`).join('\n') + '\n' +
3300
+ newPreview.split('\n').map((line) => `+${line}`).join('\n');
3301
+ }).join('\n\n');
3302
+ const moreEdits = edits.length > 5 ? `\n\n... and ${edits.length - 5} more edits` : '';
3303
+ snippet = `--- ${editPath} (${edits.length} edits)\n+++ ${editPath} (after)\n\n${editsPreview}${moreEdits}`;
3304
+ language = 'diff';
3305
+ }
3306
+ else if (isEditFile) {
3307
3307
  const oldStr = msg.metadata?.interrupt?.args?.old_string || '';
3308
3308
  const newStr = msg.metadata?.interrupt?.args?.new_string || '';
3309
3309
  const replaceAll = msg.metadata?.interrupt?.args?.replace_all;
@@ -3342,9 +3342,10 @@ SyntaxError: '(' was never closed
3342
3342
  const safePath = escapeHtml(writePath);
3343
3343
  html = html.replace(/<span class="code-block-language">[^<]*<\/span>/, `<span class="code-block-language jp-agent-interrupt-path">${safePath}</span>`);
3344
3344
  }
3345
- if (isEditFile && editPath) {
3345
+ if ((isEditFile || isMultiEditFile) && editPath) {
3346
3346
  const safePath = escapeHtml(editPath);
3347
- html = html.replace(/<span class="code-block-language">[^<]*<\/span>/, `<span class="code-block-language jp-agent-interrupt-path">✏️ ${safePath}</span>`);
3347
+ const editIcon = isMultiEditFile ? '📝' : '✏️';
3348
+ html = html.replace(/<span class="code-block-language">[^<]*<\/span>/, `<span class="code-block-language jp-agent-interrupt-path">${editIcon} ${safePath}</span>`);
3348
3349
  }
3349
3350
  // actionHtml이 비어있지 않을 때만 추가
3350
3351
  return actionHtml ? html.replace('</div>', `${actionHtml}</div>`) : html;
@@ -5699,10 +5700,10 @@ class IdleMonitorService {
5699
5700
  console.log('[IdleMonitor] Idle timeout reached. Triggering shutdown...');
5700
5701
  // Hide countdown
5701
5702
  this.hideCountdown();
5702
- // Show alert popup
5703
+ // Call shutdown API FIRST (non-blocking) - shutdown starts immediately
5704
+ this.callShutdownApi().catch(e => console.error('[IdleMonitor] Shutdown API error:', e));
5705
+ // Show notification popup (for user info only - shutdown already initiated)
5703
5706
  alert(`${this.idleTimeoutMinutes}분 동안 활동이 없어 세션이 종료됩니다.`);
5704
- // Call shutdown API
5705
- await this.callShutdownApi();
5706
5707
  }
5707
5708
  /**
5708
5709
  * Call HDSP shutdown API via Jupyter server endpoint
@@ -6059,7 +6060,7 @@ class LSPBridgeService {
6059
6060
  };
6060
6061
  }
6061
6062
  // TODO: jupyterlab-lsp 연결 시 구현
6062
- // 현재는 빈 결과 반환 (Agent가 search_workspace_tool 사용하도록)
6063
+ // 현재는 빈 결과 반환 (Agent가 execute_command_tool with grep 사용하도록)
6063
6064
  return {
6064
6065
  success: false,
6065
6066
  locations: []
@@ -9051,72 +9052,63 @@ __webpack_require__.r(__webpack_exports__);
9051
9052
  * - Key rotation on rate limit (429) is handled by frontend
9052
9053
  */
9053
9054
  const STORAGE_KEY = 'hdsp-agent-llm-config';
9054
- const DEFAULT_LANGCHAIN_SYSTEM_PROMPT = `You are an expert Python data scientist and Jupyter notebook assistant.
9055
- Your role is to help users with data analysis, visualization, and Python coding tasks in Jupyter notebooks. You can use only Korean
9056
-
9057
- # Core Behavior
9058
- Be concise and direct. Answer in fewer than 4 lines unless the user asks for detail.
9059
- After working on a file, just stop - don't explain what you did unless asked.
9060
- Avoid unnecessary introductions or conclusions.
9061
-
9062
- ## Task Management
9063
- Use write_todos for complex multi-step tasks (3+ steps). Mark tasks in_progress before starting, completed immediately after finishing.
9064
- For simple 1-2 step tasks, just do them directly without todos.
9065
-
9066
- You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
9067
- 1. Check your todo list - are there pending or in_progress items?
9068
- 2. If YES call the next appropriate tool (jupyter_cell_tool, markdown_tool, etc.)
9069
- 3. When you suggest next steps for todo item '다음 단계 제시', you MUST create next steps in json format matching this schema:
9070
- {
9071
- "next_items": [
9072
- {
9073
- "subject": "<subject for next step>",
9074
- "description": "<detailed description for the next step>"
9075
- }, ...
9076
- ]
9077
- }
9078
- 4. If ALL todos are completed → call final_answer_tool with a summary
9079
-
9080
- ## 🔴 MANDATORY: Resource Check Before Data Hanlding
9081
- **ALWAYS call check_resource_tool FIRST** when the task involves:
9082
- - Loading files: .csv, .parquet, .json, .xlsx, .pickle, .h5, .feather
9083
- - Handling datasets(dataframe) with pandas, polars, dask, or similar libraries
9084
- - Training ML models on data files
9085
-
9086
- ## Mandatory Workflow
9087
- 1. After EVERY tool result, immediately call the next tool
9088
- 2. Continue until ALL todos show status: "completed"
9089
- 3. ONLY THEN call final_answer_tool to summarize
9090
- 4. Only use jupyter_cell_tool for Python code or when the user explicitly asks to run in a notebook cell
9091
- 5. For plots and charts, use English text only.
9092
-
9093
- ## FORBIDDEN (will break the workflow)
9094
- - Producing an empty response (no tool call, no content)
9095
- - Stopping after any tool without calling the next tool
9096
- - Ending without calling final_answer_tool
9097
- - Leaving todos in "in_progress" or "pending" state without continuing
9098
-
9099
- ## 📖 File Reading Best Practices
9100
- **CRITICAL**: When exploring codebases or reading files, use pagination to prevent context overflow.
9101
-
9102
- **Pattern for codebase exploration:**
9103
- 1. First scan: read_file_tool(path, limit=100) - See file structure and key sections
9104
- 2. Targeted read: read_file_tool(path, offset=100, limit=200) - Read specific sections if needed
9105
- 3. Full read: Only read without limit when necessary for immediate editing
9106
-
9107
- **When to paginate (use offset/limit):**
9108
- - Reading any file >500 lines
9109
- - Exploring unfamiliar codebases (always start with limit=100)
9110
- - Reading multiple files in sequence
9111
- - Any research or investigation task
9112
-
9113
- **When full read is OK:**
9114
- - Small files (<500 lines)
9115
- - Files you need to edit immediately after reading
9116
- - After confirming file size with first scan
9117
-
9118
- ## 🔧 Code Development
9119
- For code generation/refactoring, use LSP tools (diagnostics_tool, references_tool) to check errors and find symbol usages. Use multiedit_file_tool for multiple changes in one file.
9055
+ const DEFAULT_LANGCHAIN_SYSTEM_PROMPT = `You are an expert Python data scientist and Jupyter notebook assistant. Respond in Korean only.
9056
+
9057
+ # Core Rules
9058
+ 1. Be concise (≤4 lines unless detail requested)
9059
+ 2. ALWAYS call a tool in every response - never respond with text only
9060
+ 3. ALWAYS include a brief Korean explanation before tool calls
9061
+
9062
+ # Task Workflow
9063
+
9064
+ ## Simple Tasks (1-2 steps)
9065
+ Execute directly without todos.
9066
+
9067
+ ## Complex Tasks (3+ steps)
9068
+ 1. Create todos with write_todos (all items in Korean)
9069
+ 2. ALWAYS include "작업 요약 다음단계 제시" as the LAST item
9070
+ 3. After each tool result: check todos call next tool repeat
9071
+ 4. **Final todo ("작업 요약 및 다음단계 제시")**:
9072
+ - FIRST: Output summary JSON in your content (REQUIRED!)
9073
+ - THEN: Call write_todos to mark all as completed
9074
+ - Both must be in the SAME response
9075
+
9076
+ ### Summary JSON Format (MUST output before marking complete)
9077
+ \`\`\`json
9078
+ {"summary": "실행된 작업 요약", "next_items": [{"subject": "제목", "description": "설명"}]}
9079
+ \`\`\`
9080
+ Suggest 3-5 next items. **You CANNOT mark "작업 요약" as completed without outputting this JSON first.**
9081
+
9082
+ # Mandatory Checks
9083
+
9084
+ ## Resource Check (BEFORE data operations)
9085
+ Call \`check_resource_tool\` FIRST when:
9086
+ - Loading files (.csv, .parquet, .json, .xlsx, .pickle, .h5, .feather)
9087
+ - Using pandas/polars/dask for dataframes
9088
+ - Training ML models
9089
+
9090
+ # Tool Usage
9091
+
9092
+ ## File Search (execute_command_tool)
9093
+ \`\`\`bash
9094
+ find . -iname '*filename*.csv' 2>/dev/null # Find by name
9095
+ grep -rn 'pattern' --include='*.py' . # Search contents
9096
+ \`\`\`
9097
+
9098
+ ## File Reading (read_file_tool)
9099
+ - Large files: \`read_file_tool(path, limit=100)\` first
9100
+ - Use \`offset\` for pagination
9101
+ - Small files (<500 lines): Read without limit
9102
+
9103
+ ## Code Output
9104
+ - For plots/charts: Use English labels only
9105
+ - Use LSP tools for error checking and symbol lookup
9106
+ - Use multiedit_file_tool for multiple changes
9107
+
9108
+ # Forbidden
9109
+ - Empty responses (no tool call AND no content)
9110
+ - Tool calls without Korean explanation
9111
+ - Stopping with pending/in_progress todos
9120
9112
  `;
9121
9113
  // ═══════════════════════════════════════════════════════════════════════════
9122
9114
  // Key Rotation State (in-memory, not persisted)
@@ -12183,7 +12175,7 @@ class ToolExecutor {
12183
12175
  return { text, truncated: lines.length > maxLines };
12184
12176
  }
12185
12177
  /**
12186
- * read_file 도구: 파일 읽기
12178
+ * read_file 도구: 파일 읽기 (offset/limit 지원)
12187
12179
  */
12188
12180
  async executeReadFile(params) {
12189
12181
  console.log('[ToolExecutor] executeReadFile:', params);
@@ -12193,15 +12185,29 @@ class ToolExecutor {
12193
12185
  return { success: false, error: pathCheck.error };
12194
12186
  }
12195
12187
  const encoding = params.encoding || 'utf-8';
12196
- const maxLines = params.maxLines || 1000;
12197
- // Python 코드로 파일 읽기 (커널에서 실행)
12188
+ // Support both old (maxLines) and new (offset/limit) parameters
12189
+ const offset = typeof params.offset === 'number' ? Math.max(0, params.offset) : 0;
12190
+ const limit = typeof params.limit === 'number'
12191
+ ? params.limit
12192
+ : (typeof params.maxLines === 'number' ? params.maxLines : 500);
12193
+ // Python 코드로 파일 읽기 (커널에서 실행) - offset/limit 적용
12198
12194
  const pythonCode = `
12199
12195
  import json
12200
12196
  try:
12201
12197
  with open(${JSON.stringify(params.path)}, 'r', encoding=${JSON.stringify(encoding)}) as f:
12202
- lines = f.readlines()[:${maxLines}]
12203
- content = ''.join(lines)
12204
- result = {'success': True, 'content': content, 'lineCount': len(lines), 'truncated': len(lines) >= ${maxLines}}
12198
+ all_lines = f.readlines()
12199
+ total_lines = len(all_lines)
12200
+ sliced_lines = all_lines[${offset}:${offset + limit}]
12201
+ content = ''.join(sliced_lines)
12202
+ result = {
12203
+ 'success': True,
12204
+ 'content': content,
12205
+ 'lineCount': len(sliced_lines),
12206
+ 'totalLines': total_lines,
12207
+ 'offset': ${offset},
12208
+ 'limit': ${limit},
12209
+ 'truncated': total_lines > ${offset + limit}
12210
+ }
12205
12211
  except FileNotFoundError:
12206
12212
  result = {'success': False, 'error': f'File not found: ${params.path}'}
12207
12213
  except PermissionError:
@@ -15068,19 +15074,19 @@ function extractNextItemsBlock(text) {
15068
15074
  if (lang && lang !== 'json' && !content.includes('"next_items"')) {
15069
15075
  continue;
15070
15076
  }
15071
- const items = parseNextItemsPayload(content);
15072
- if (items) {
15077
+ const parsed = parseNextItemsPayload(content);
15078
+ if (parsed) {
15073
15079
  const placeholder = `__NEXT_ITEMS_${Math.random().toString(36).slice(2, 11)}__`;
15074
15080
  const updated = text.slice(0, match.index) + placeholder + text.slice(match.index + match[0].length);
15075
- return { items, placeholder, text: updated };
15081
+ return { items: parsed.items, summary: parsed.summary, placeholder, text: updated };
15076
15082
  }
15077
15083
  }
15078
15084
  const range = findNextItemsJsonRange(text);
15079
15085
  if (!range)
15080
15086
  return null;
15081
15087
  const candidate = text.slice(range.start, range.end + 1);
15082
- const items = parseNextItemsPayload(candidate);
15083
- if (!items)
15088
+ const parsed = parseNextItemsPayload(candidate);
15089
+ if (!parsed)
15084
15090
  return null;
15085
15091
  let replacementStart = range.start;
15086
15092
  const lineStart = text.lastIndexOf('\n', range.start - 1) + 1;
@@ -15091,7 +15097,7 @@ function extractNextItemsBlock(text) {
15091
15097
  }
15092
15098
  const placeholder = `__NEXT_ITEMS_${Math.random().toString(36).slice(2, 11)}__`;
15093
15099
  const updated = text.slice(0, replacementStart) + placeholder + text.slice(range.end + 1);
15094
- return { items, placeholder, text: updated };
15100
+ return { items: parsed.items, summary: parsed.summary, placeholder, text: updated };
15095
15101
  }
15096
15102
  function findNextItemsJsonRange(text) {
15097
15103
  const key = '"next_items"';
@@ -15169,17 +15175,25 @@ function parseNextItemsPayload(payload) {
15169
15175
  return { subject, description };
15170
15176
  })
15171
15177
  .filter((item) => Boolean(item));
15172
- return items.length > 0 ? items : null;
15178
+ // Extract summary field if present
15179
+ const summaryRaw = parsed.summary;
15180
+ const summary = typeof summaryRaw === 'string' && summaryRaw.trim() ? summaryRaw.trim() : null;
15181
+ return items.length > 0 ? { items, summary } : null;
15173
15182
  }
15174
15183
  catch {
15175
15184
  return null;
15176
15185
  }
15177
15186
  }
15178
- function renderNextItemsList(items) {
15179
- // Simple arrow icon
15187
+ function renderNextItemsList(items, summary) {
15188
+ // Simple arrow icon for next items
15180
15189
  const arrowSvg = `
15181
15190
  <svg class="jp-next-items-icon" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
15182
15191
  <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"/>
15192
+ </svg>`;
15193
+ // Checkmark icon for summary
15194
+ const checkSvg = `
15195
+ <svg class="jp-summary-icon" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
15196
+ <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
15183
15197
  </svg>`;
15184
15198
  const listItems = items.map((item) => {
15185
15199
  const subject = escapeHtml(item.subject);
@@ -15195,7 +15209,16 @@ function renderNextItemsList(items) {
15195
15209
  ${arrowSvg}
15196
15210
  </li>`;
15197
15211
  }).join('');
15198
- return `
15212
+ // Render summary as separate block above next items
15213
+ const summaryHtml = summary ? `
15214
+ <div class="jp-summary-block">
15215
+ <div class="jp-summary-header">작업 요약</div>
15216
+ <div class="jp-summary-body">
15217
+ ${checkSvg}
15218
+ <div class="jp-summary-content">${escapeHtml(summary)}</div>
15219
+ </div>
15220
+ </div>` : '';
15221
+ return `${summaryHtml}
15199
15222
  <div class="jp-next-items" data-next-items="true">
15200
15223
  <div class="jp-next-items-header">다음 단계 제안</div>
15201
15224
  <ul class="jp-next-items-list" role="list">
@@ -15821,7 +15844,7 @@ function formatMarkdownToHtml(text) {
15821
15844
  });
15822
15845
  // Step 8.5: Restore next items list placeholders
15823
15846
  if (nextItemsBlock) {
15824
- html = html.split(nextItemsBlock.placeholder).join(renderNextItemsList(nextItemsBlock.items));
15847
+ html = html.split(nextItemsBlock.placeholder).join(renderNextItemsList(nextItemsBlock.items, nextItemsBlock.summary));
15825
15848
  }
15826
15849
  return html;
15827
15850
  }
@@ -15984,4 +16007,4 @@ __webpack_require__.r(__webpack_exports__);
15984
16007
  /***/ }
15985
16008
 
15986
16009
  }]);
15987
- //# sourceMappingURL=lib_index_js.e4ff4b5779b5e049f84c.js.map
16010
+ //# sourceMappingURL=lib_index_js.58c1e128ba0b76f41f04.js.map