hdsp-jupyter-extension 2.0.7__py3-none-any.whl → 2.0.8__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/core/embedding_service.py +67 -46
  2. agent_server/core/rag_manager.py +31 -17
  3. agent_server/core/retriever.py +13 -8
  4. agent_server/core/vllm_embedding_service.py +243 -0
  5. agent_server/langchain/agent.py +8 -0
  6. agent_server/langchain/custom_middleware.py +58 -31
  7. agent_server/langchain/hitl_config.py +6 -1
  8. agent_server/langchain/logging_utils.py +53 -14
  9. agent_server/langchain/prompts.py +47 -16
  10. agent_server/langchain/tools/__init__.py +13 -0
  11. agent_server/langchain/tools/file_tools.py +285 -7
  12. agent_server/langchain/tools/file_utils.py +334 -0
  13. agent_server/langchain/tools/lsp_tools.py +264 -0
  14. agent_server/main.py +7 -0
  15. agent_server/routers/langchain_agent.py +115 -19
  16. agent_server/routers/rag.py +8 -3
  17. hdsp_agent_core/models/rag.py +15 -1
  18. hdsp_agent_core/services/rag_service.py +6 -1
  19. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  20. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +3 -2
  21. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js +160 -3
  22. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
  23. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js +1759 -221
  24. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
  25. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js +14 -12
  26. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
  27. 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.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
  28. hdsp_jupyter_extension-2.0.8.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
  29. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
  30. hdsp_jupyter_extension-2.0.8.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
  31. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
  32. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
  33. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/METADATA +1 -1
  34. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/RECORD +66 -63
  35. jupyter_ext/__init__.py +18 -0
  36. jupyter_ext/_version.py +1 -1
  37. jupyter_ext/handlers.py +176 -1
  38. jupyter_ext/labextension/build_log.json +1 -1
  39. jupyter_ext/labextension/package.json +3 -2
  40. jupyter_ext/labextension/static/{frontend_styles_index_js.4770ec0fb2d173b6deb4.js → frontend_styles_index_js.8740a527757068814573.js} +160 -3
  41. jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
  42. jupyter_ext/labextension/static/{lib_index_js.29cf4312af19e86f82af.js → lib_index_js.e4ff4b5779b5e049f84c.js} +1759 -221
  43. jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
  44. jupyter_ext/labextension/static/{remoteEntry.61343eb4cf0577e74b50.js → remoteEntry.020cdb0b864cfaa4e41e.js} +14 -12
  45. jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
  46. hdsp_jupyter_extension-2.0.7.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.7.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. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → jupyter_ext/labextension/static/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.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +0 -1
  53. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
  54. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js.map +0 -1
  55. hdsp_jupyter_extension-2.0.7.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.7.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.7.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.4770ec0fb2d173b6deb4.js.map +0 -1
  59. jupyter_ext/labextension/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
  60. jupyter_ext/labextension/static/remoteEntry.61343eb4cf0577e74b50.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.7.data → hdsp_jupyter_extension-2.0.8.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  65. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  66. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  71. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/WHEEL +0 -0
  78. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/licenses/LICENSE +0 -0
@@ -1088,99 +1088,127 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1088
1088
  try {
1089
1089
  // Use LLM prompt if available, otherwise use the display content
1090
1090
  const messageToSend = llmPrompt || displayContentText;
1091
- console.log('[AgentPanel] Sending message with agentThreadId:', agentThreadId);
1092
- await apiService.sendMessageStream({
1093
- message: messageToSend,
1094
- conversationId: conversationId || undefined,
1095
- llmConfig: currentConfig // Include API keys with request
1096
- },
1097
- // onChunk callback - update message content incrementally
1098
- (chunk) => {
1099
- streamedContent += chunk;
1100
- setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)
1101
- ? { ...msg, content: streamedContent }
1102
- : msg));
1103
- },
1104
- // onMetadata callback - update conversationId and metadata
1105
- (metadata) => {
1106
- if (metadata.conversationId && !conversationId) {
1107
- setConversationId(metadata.conversationId);
1108
- }
1109
- if (metadata.provider || metadata.model) {
1091
+ console.log('[AgentPanel] Sending message with mode:', inputMode, 'agentThreadId:', agentThreadId);
1092
+ // Chat 모드: 단순 Q&A (sendChatStream)
1093
+ // Agent V2 모드 또는 그 외: LangChain Deep Agent (sendAgentV2Stream)
1094
+ if (inputMode === 'chat') {
1095
+ // 단순 Chat 모드 - /chat/stream 사용
1096
+ await apiService.sendChatStream({
1097
+ message: messageToSend,
1098
+ conversationId: conversationId || undefined,
1099
+ llmConfig: currentConfig
1100
+ },
1101
+ // onChunk callback
1102
+ (chunk) => {
1103
+ streamedContent += chunk;
1110
1104
  setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)
1111
- ? {
1112
- ...msg,
1113
- metadata: {
1114
- ...msg.metadata,
1115
- provider: metadata.provider,
1116
- model: metadata.model
1105
+ ? { ...msg, content: streamedContent }
1106
+ : msg));
1107
+ },
1108
+ // onMetadata callback
1109
+ (metadata) => {
1110
+ if (metadata.conversationId && !conversationId) {
1111
+ setConversationId(metadata.conversationId);
1112
+ }
1113
+ });
1114
+ }
1115
+ else {
1116
+ // Agent V2 모드 - /agent/langchain/stream 사용 (HITL, Todo, 도구 실행)
1117
+ await apiService.sendAgentV2Stream({
1118
+ message: messageToSend,
1119
+ conversationId: conversationId || undefined,
1120
+ llmConfig: currentConfig // Include API keys with request
1121
+ },
1122
+ // onChunk callback - update message content incrementally
1123
+ (chunk) => {
1124
+ streamedContent += chunk;
1125
+ setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)
1126
+ ? { ...msg, content: streamedContent }
1127
+ : msg));
1128
+ },
1129
+ // onMetadata callback - update conversationId and metadata
1130
+ (metadata) => {
1131
+ if (metadata.conversationId && !conversationId) {
1132
+ setConversationId(metadata.conversationId);
1133
+ }
1134
+ if (metadata.provider || metadata.model) {
1135
+ setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)
1136
+ ? {
1137
+ ...msg,
1138
+ metadata: {
1139
+ ...msg.metadata,
1140
+ provider: metadata.provider,
1141
+ model: metadata.model
1142
+ }
1117
1143
  }
1144
+ : msg));
1145
+ }
1146
+ },
1147
+ // onDebug callback - show debug status in gray
1148
+ (status) => {
1149
+ setDebugStatus(status);
1150
+ },
1151
+ // onInterrupt callback - show approval dialog
1152
+ (interrupt) => {
1153
+ approvalPendingRef.current = true;
1154
+ // Capture threadId from interrupt for context persistence
1155
+ if (interrupt.threadId && !agentThreadId) {
1156
+ setAgentThreadId(interrupt.threadId);
1157
+ console.log('[AgentPanel] Captured agentThreadId from interrupt:', interrupt.threadId);
1158
+ }
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'
1162
+ || interrupt.action === 'check_resource_tool'
1163
+ || interrupt.action === 'list_files_tool'
1164
+ || interrupt.action === 'read_file_tool') {
1165
+ void handleAutoToolInterrupt(interrupt);
1166
+ return;
1167
+ }
1168
+ if (autoApproveEnabled) {
1169
+ // 자동 승인 모드일 때도 인터럽트 메시지를 생성하여 코드블럭으로 표시
1170
+ upsertInterruptMessage(interrupt, true);
1171
+ void resumeFromInterrupt(interrupt, 'approve');
1172
+ return;
1173
+ }
1174
+ if (interrupt.action === 'jupyter_cell_tool' && interrupt.args?.code) {
1175
+ const shouldQueue = shouldExecuteInNotebook(interrupt.args.code);
1176
+ if (isAutoApprovedCode(interrupt.args.code)) {
1177
+ if (shouldQueue) {
1178
+ queueApprovalCell(interrupt.args.code);
1179
+ }
1180
+ void resumeFromInterrupt(interrupt, 'approve');
1181
+ return;
1118
1182
  }
1119
- : msg));
1120
- }
1121
- },
1122
- // onDebug callback - show debug status in gray
1123
- (status) => {
1124
- setDebugStatus(status);
1125
- },
1126
- // onInterrupt callback - show approval dialog
1127
- (interrupt) => {
1128
- approvalPendingRef.current = true;
1129
- // Capture threadId from interrupt for context persistence
1130
- if (interrupt.threadId && !agentThreadId) {
1131
- setAgentThreadId(interrupt.threadId);
1132
- console.log('[AgentPanel] Captured agentThreadId from interrupt:', interrupt.threadId);
1133
- }
1134
- // Auto-approve search/file/resource tools - execute immediately without user interaction
1135
- if (interrupt.action === 'search_workspace_tool'
1136
- || interrupt.action === 'search_notebook_cells_tool'
1137
- || interrupt.action === 'check_resource_tool'
1138
- || interrupt.action === 'list_files_tool'
1139
- || interrupt.action === 'read_file_tool') {
1140
- void handleAutoToolInterrupt(interrupt);
1141
- return;
1142
- }
1143
- if (autoApproveEnabled) {
1144
- void resumeFromInterrupt(interrupt, 'approve');
1145
- return;
1146
- }
1147
- if (interrupt.action === 'jupyter_cell_tool' && interrupt.args?.code) {
1148
- const shouldQueue = shouldExecuteInNotebook(interrupt.args.code);
1149
- if (isAutoApprovedCode(interrupt.args.code)) {
1150
1183
  if (shouldQueue) {
1151
1184
  queueApprovalCell(interrupt.args.code);
1152
1185
  }
1153
- void resumeFromInterrupt(interrupt, 'approve');
1154
- return;
1155
1186
  }
1156
- if (shouldQueue) {
1157
- queueApprovalCell(interrupt.args.code);
1187
+ setInterruptData(interrupt);
1188
+ upsertInterruptMessage(interrupt);
1189
+ setIsLoading(false);
1190
+ setIsStreaming(false);
1191
+ },
1192
+ // onTodos callback - update todo list UI
1193
+ (newTodos) => {
1194
+ setTodos(newTodos);
1195
+ },
1196
+ // onDebugClear callback - clear debug status
1197
+ () => {
1198
+ setDebugStatus(null);
1199
+ },
1200
+ // onToolCall callback - add cells to notebook
1201
+ handleToolCall,
1202
+ // onComplete callback - capture thread_id for context persistence
1203
+ (data) => {
1204
+ if (data.threadId) {
1205
+ setAgentThreadId(data.threadId);
1206
+ console.log('[AgentPanel] Captured agentThreadId for context persistence:', data.threadId);
1158
1207
  }
1159
- }
1160
- setInterruptData(interrupt);
1161
- upsertInterruptMessage(interrupt);
1162
- setIsLoading(false);
1163
- setIsStreaming(false);
1164
- },
1165
- // onTodos callback - update todo list UI
1166
- (newTodos) => {
1167
- setTodos(newTodos);
1168
- },
1169
- // onDebugClear callback - clear debug status
1170
- () => {
1171
- setDebugStatus(null);
1172
- },
1173
- // onToolCall callback - add cells to notebook
1174
- handleToolCall,
1175
- // onComplete callback - capture thread_id for context persistence
1176
- (data) => {
1177
- if (data.threadId) {
1178
- setAgentThreadId(data.threadId);
1179
- console.log('[AgentPanel] Captured agentThreadId for context persistence:', data.threadId);
1180
- }
1181
- },
1182
- // threadId - pass existing thread_id to continue context
1183
- agentThreadId || undefined);
1208
+ },
1209
+ // threadId - pass existing thread_id to continue context
1210
+ agentThreadId || undefined);
1211
+ }
1184
1212
  }
1185
1213
  catch (error) {
1186
1214
  const message = error instanceof Error ? error.message : 'Failed to send message';
@@ -2013,7 +2041,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2013
2041
  execution_method: 'subprocess'
2014
2042
  };
2015
2043
  };
2016
- const upsertInterruptMessage = (interrupt) => {
2044
+ const upsertInterruptMessage = (interrupt, autoApproved) => {
2017
2045
  const interruptMessageId = interruptMessageIdRef.current || makeMessageId('interrupt');
2018
2046
  interruptMessageIdRef.current = interruptMessageId;
2019
2047
  const interruptMessage = {
@@ -2021,7 +2049,14 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2021
2049
  role: 'system',
2022
2050
  content: interrupt.description || '코드 실행 승인이 필요합니다.',
2023
2051
  timestamp: Date.now(),
2024
- metadata: { interrupt }
2052
+ metadata: {
2053
+ interrupt: {
2054
+ ...interrupt,
2055
+ resolved: autoApproved || false,
2056
+ decision: autoApproved ? 'approve' : undefined,
2057
+ autoApproved: autoApproved || false
2058
+ }
2059
+ }
2025
2060
  };
2026
2061
  setMessages(prev => {
2027
2062
  const hasExisting = prev.some(msg => msg.id === interruptMessageId);
@@ -2237,7 +2272,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2237
2272
  console.log('[AgentPanel] search_notebook_cells result:', executionResult);
2238
2273
  }
2239
2274
  else if (action === 'check_resource_tool') {
2240
- const filesList = Array.isArray(args?.files) ? args.files : (args?.files ? [args.files] : []);
2275
+ const filesList = args?.files || [];
2241
2276
  setDebugStatus(`📊 리소스 체크 중: ${filesList.join(', ') || 'system'}`);
2242
2277
  executionResult = await apiService.checkResource({
2243
2278
  files: filesList,
@@ -2315,6 +2350,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2315
2350
  return;
2316
2351
  }
2317
2352
  if (autoApproveEnabled) {
2353
+ // 자동 승인 모드일 때도 인터럽트 메시지를 생성하여 코드블럭으로 표시
2354
+ upsertInterruptMessage(nextInterrupt, true);
2318
2355
  void resumeFromInterrupt(nextInterrupt, 'approve');
2319
2356
  return;
2320
2357
  }
@@ -2390,6 +2427,276 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2390
2427
  };
2391
2428
  }
2392
2429
  }
2430
+ else if (interrupt.action === 'edit_file_tool') {
2431
+ // Handle edit_file_tool - read file, perform replacement, write back
2432
+ try {
2433
+ setDebugStatus('✏️ 파일 수정 중...');
2434
+ const filePath = interrupt.args?.path || '';
2435
+ const oldString = interrupt.args?.old_string || '';
2436
+ const newString = interrupt.args?.new_string || '';
2437
+ const replaceAll = interrupt.args?.replace_all || false;
2438
+ // Read current file content
2439
+ const readResult = await apiService.readFile(filePath);
2440
+ if (!readResult.success || readResult.content === undefined) {
2441
+ throw new Error(readResult.error || 'Failed to read file');
2442
+ }
2443
+ const originalContent = readResult.content;
2444
+ const occurrences = (originalContent.match(new RegExp(oldString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
2445
+ if (occurrences === 0) {
2446
+ throw new Error(`String not found in file: '${oldString.slice(0, 100)}...'`);
2447
+ }
2448
+ if (occurrences > 1 && !replaceAll) {
2449
+ throw new Error(`String appears ${occurrences} times. Use replace_all=True or provide more context.`);
2450
+ }
2451
+ // Perform replacement
2452
+ const newContent = replaceAll
2453
+ ? originalContent.split(oldString).join(newString)
2454
+ : originalContent.replace(oldString, newString);
2455
+ // Generate diff for logging
2456
+ const diffLines = [];
2457
+ const originalLines = originalContent.split('\n');
2458
+ const newLines = newContent.split('\n');
2459
+ diffLines.push(`--- ${filePath} (before)`);
2460
+ diffLines.push(`+++ ${filePath} (after)`);
2461
+ // Simple diff generation
2462
+ let i = 0, j = 0;
2463
+ while (i < originalLines.length || j < newLines.length) {
2464
+ if (i < originalLines.length && j < newLines.length && originalLines[i] === newLines[j]) {
2465
+ i++;
2466
+ j++;
2467
+ }
2468
+ else if (i < originalLines.length && (j >= newLines.length || originalLines[i] !== newLines[j])) {
2469
+ diffLines.push(`-${originalLines[i]}`);
2470
+ i++;
2471
+ }
2472
+ else {
2473
+ diffLines.push(`+${newLines[j]}`);
2474
+ j++;
2475
+ }
2476
+ }
2477
+ const diff = diffLines.join('\n');
2478
+ // Write the modified content
2479
+ const writeResult = await apiService.writeFile(filePath, newContent, {
2480
+ encoding: 'utf-8',
2481
+ overwrite: true
2482
+ });
2483
+ console.log('[AgentPanel] edit_file result:', writeResult);
2484
+ resumeDecision = 'edit';
2485
+ resumeArgs = {
2486
+ ...interrupt.args,
2487
+ execution_result: {
2488
+ ...writeResult,
2489
+ diff,
2490
+ occurrences,
2491
+ lines_added: newLines.length - originalLines.length > 0 ? newLines.length - originalLines.length : 0,
2492
+ lines_removed: originalLines.length - newLines.length > 0 ? originalLines.length - newLines.length : 0,
2493
+ }
2494
+ };
2495
+ }
2496
+ catch (error) {
2497
+ const message = error instanceof Error ? error.message : 'File edit failed';
2498
+ console.error('[AgentPanel] edit_file error:', error);
2499
+ resumeDecision = 'edit';
2500
+ resumeArgs = {
2501
+ ...interrupt.args,
2502
+ execution_result: { success: false, error: message }
2503
+ };
2504
+ }
2505
+ }
2506
+ else if (interrupt.action === 'multiedit_file_tool') {
2507
+ // Handle multiedit_file_tool - apply multiple edits atomically (Crush 패턴)
2508
+ try {
2509
+ setDebugStatus('✏️ 다중 파일 수정 중...');
2510
+ const filePath = interrupt.args?.path || '';
2511
+ const edits = interrupt.args?.edits || [];
2512
+ if (!filePath || edits.length === 0) {
2513
+ throw new Error('File path and edits are required');
2514
+ }
2515
+ // Read current file content
2516
+ const readResult = await apiService.readFile(filePath);
2517
+ if (!readResult.success || readResult.content === undefined) {
2518
+ throw new Error(readResult.error || 'Failed to read file');
2519
+ }
2520
+ const originalContent = readResult.content;
2521
+ let currentContent = originalContent;
2522
+ let editsApplied = 0;
2523
+ let editsFailed = 0;
2524
+ const failedEdits = [];
2525
+ // Apply edits sequentially
2526
+ for (let i = 0; i < edits.length; i++) {
2527
+ const edit = edits[i];
2528
+ const oldString = edit.old_string || '';
2529
+ const newString = edit.new_string || '';
2530
+ const replaceAll = edit.replace_all || false;
2531
+ const occurrences = (currentContent.match(new RegExp(oldString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
2532
+ if (occurrences === 0) {
2533
+ failedEdits.push(`Edit ${i + 1}: String not found`);
2534
+ editsFailed++;
2535
+ continue;
2536
+ }
2537
+ if (occurrences > 1 && !replaceAll) {
2538
+ failedEdits.push(`Edit ${i + 1}: String appears ${occurrences} times (use replace_all)`);
2539
+ editsFailed++;
2540
+ continue;
2541
+ }
2542
+ // Apply this edit
2543
+ currentContent = replaceAll
2544
+ ? currentContent.split(oldString).join(newString)
2545
+ : currentContent.replace(oldString, newString);
2546
+ editsApplied++;
2547
+ }
2548
+ // Generate diff
2549
+ const diffLines = [];
2550
+ const originalLines = originalContent.split('\n');
2551
+ const newLines = currentContent.split('\n');
2552
+ diffLines.push(`--- ${filePath} (before)`);
2553
+ diffLines.push(`+++ ${filePath} (after)`);
2554
+ let i = 0, j = 0;
2555
+ while (i < originalLines.length || j < newLines.length) {
2556
+ if (i < originalLines.length && j < newLines.length && originalLines[i] === newLines[j]) {
2557
+ i++;
2558
+ j++;
2559
+ }
2560
+ else if (i < originalLines.length && (j >= newLines.length || originalLines[i] !== newLines[j])) {
2561
+ diffLines.push(`-${originalLines[i]}`);
2562
+ i++;
2563
+ }
2564
+ else {
2565
+ diffLines.push(`+${newLines[j]}`);
2566
+ j++;
2567
+ }
2568
+ }
2569
+ const diff = diffLines.join('\n');
2570
+ // Write the modified content if any edits succeeded
2571
+ if (editsApplied > 0) {
2572
+ const writeResult = await apiService.writeFile(filePath, currentContent, {
2573
+ encoding: 'utf-8',
2574
+ overwrite: true
2575
+ });
2576
+ console.log('[AgentPanel] multiedit_file result:', writeResult, 'applied:', editsApplied, 'failed:', editsFailed);
2577
+ resumeDecision = 'edit';
2578
+ resumeArgs = {
2579
+ ...interrupt.args,
2580
+ execution_result: {
2581
+ ...writeResult,
2582
+ diff,
2583
+ edits_applied: editsApplied,
2584
+ edits_failed: editsFailed,
2585
+ failed_details: failedEdits,
2586
+ lines_added: newLines.length - originalLines.length > 0 ? newLines.length - originalLines.length : 0,
2587
+ lines_removed: originalLines.length - newLines.length > 0 ? originalLines.length - newLines.length : 0,
2588
+ }
2589
+ };
2590
+ }
2591
+ else {
2592
+ throw new Error(`All ${edits.length} edits failed: ${failedEdits.join('; ')}`);
2593
+ }
2594
+ }
2595
+ catch (error) {
2596
+ const message = error instanceof Error ? error.message : 'Multi-edit failed';
2597
+ console.error('[AgentPanel] multiedit_file error:', error);
2598
+ resumeDecision = 'edit';
2599
+ resumeArgs = {
2600
+ ...interrupt.args,
2601
+ execution_result: { success: false, error: message, edits_applied: 0, edits_failed: 0 }
2602
+ };
2603
+ }
2604
+ }
2605
+ else if (interrupt.action === 'diagnostics_tool') {
2606
+ // Handle diagnostics_tool - get LSP diagnostics via LSP Bridge
2607
+ try {
2608
+ setDebugStatus('🔍 LSP 진단 조회 중...');
2609
+ const filePath = interrupt.args?.path || null;
2610
+ const severityFilter = interrupt.args?.severity_filter || null;
2611
+ // Get LSP Bridge instance
2612
+ const lspBridge = window._hdspLSPBridge;
2613
+ if (lspBridge && lspBridge.isLSPAvailable()) {
2614
+ const result = await lspBridge.getDiagnostics(filePath);
2615
+ // Apply severity filter if specified
2616
+ let diagnostics = result.diagnostics;
2617
+ if (severityFilter) {
2618
+ diagnostics = diagnostics.filter((d) => d.severity === severityFilter);
2619
+ }
2620
+ resumeDecision = 'approve';
2621
+ resumeArgs = {
2622
+ ...interrupt.args,
2623
+ execution_result: {
2624
+ success: true,
2625
+ lsp_available: true,
2626
+ diagnostics
2627
+ }
2628
+ };
2629
+ }
2630
+ else {
2631
+ // LSP not available - return empty result
2632
+ console.log('[AgentPanel] LSP not available, returning empty diagnostics');
2633
+ resumeDecision = 'approve';
2634
+ resumeArgs = {
2635
+ ...interrupt.args,
2636
+ execution_result: {
2637
+ success: true,
2638
+ lsp_available: false,
2639
+ diagnostics: []
2640
+ }
2641
+ };
2642
+ }
2643
+ }
2644
+ catch (error) {
2645
+ const message = error instanceof Error ? error.message : 'Diagnostics failed';
2646
+ console.error('[AgentPanel] diagnostics_tool error:', error);
2647
+ resumeDecision = 'approve';
2648
+ resumeArgs = {
2649
+ ...interrupt.args,
2650
+ execution_result: { success: false, lsp_available: false, error: message, diagnostics: [] }
2651
+ };
2652
+ }
2653
+ }
2654
+ else if (interrupt.action === 'references_tool') {
2655
+ // Handle references_tool - find symbol references
2656
+ try {
2657
+ setDebugStatus('🔗 참조 검색 중...');
2658
+ const symbol = interrupt.args?.symbol || '';
2659
+ const filePath = interrupt.args?.path || null;
2660
+ // Get LSP Bridge instance
2661
+ const lspBridge = window._hdspLSPBridge;
2662
+ if (lspBridge && lspBridge.isLSPAvailable()) {
2663
+ const result = await lspBridge.getReferences(symbol, filePath, interrupt.args?.line, interrupt.args?.character);
2664
+ resumeDecision = 'approve';
2665
+ resumeArgs = {
2666
+ ...interrupt.args,
2667
+ execution_result: {
2668
+ success: result.success,
2669
+ lsp_available: true,
2670
+ locations: result.locations,
2671
+ used_grep: false
2672
+ }
2673
+ };
2674
+ }
2675
+ else {
2676
+ // LSP not available - suggest using search_workspace_tool
2677
+ console.log('[AgentPanel] LSP not available for references, suggesting grep fallback');
2678
+ resumeDecision = 'approve';
2679
+ resumeArgs = {
2680
+ ...interrupt.args,
2681
+ execution_result: {
2682
+ success: false,
2683
+ lsp_available: false,
2684
+ locations: [],
2685
+ used_grep: false
2686
+ }
2687
+ };
2688
+ }
2689
+ }
2690
+ catch (error) {
2691
+ const message = error instanceof Error ? error.message : 'References search failed';
2692
+ console.error('[AgentPanel] references_tool error:', error);
2693
+ resumeDecision = 'approve';
2694
+ resumeArgs = {
2695
+ ...interrupt.args,
2696
+ execution_result: { success: false, lsp_available: false, error: message, locations: [] }
2697
+ };
2698
+ }
2699
+ }
2393
2700
  else if (interrupt.action === 'execute_command_tool') {
2394
2701
  const command = (interrupt.args?.command || '').trim();
2395
2702
  // Default stdin to "y\n" for interactive prompts (yes/no)
@@ -2541,6 +2848,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2541
2848
  return;
2542
2849
  }
2543
2850
  if (autoApproveEnabled) {
2851
+ // 자동 승인 모드일 때도 인터럽트 메시지를 생성하여 코드블럭으로 표시
2852
+ upsertInterruptMessage(nextInterrupt, true);
2544
2853
  void resumeFromInterrupt(nextInterrupt, 'approve');
2545
2854
  return;
2546
2855
  }
@@ -2633,26 +2942,24 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2633
2942
  }
2634
2943
  // 노트북이 없는 경우
2635
2944
  if (!notebook) {
2636
- // Python 에러가 감지되면 파일 수정 모드로 전환
2637
- if (detectPythonError(currentInput)) {
2638
- console.log('[AgentPanel] Agent mode: No notebook, but Python error detected - attempting file fix');
2639
- // Python 에러 수정 처리 시도
2640
- const handled = await handlePythonErrorFix(currentInput);
2641
- if (handled) {
2642
- // 파일 수정 모드로 처리됨
2643
- const userMessage = {
2644
- id: makeMessageId(),
2645
- role: 'user',
2646
- content: currentInput,
2647
- timestamp: Date.now(),
2648
- };
2649
- setMessages(prev => [...prev, userMessage]);
2650
- setInput('');
2651
- return;
2652
- }
2653
- // 파일 경로를 찾지 못함 - 아래 로직으로 계속 진행
2654
- console.log('[AgentPanel] Agent mode: No file path found, continuing...');
2655
- }
2945
+ // [DISABLED] Python 에러가 감지되면 파일 수정 모드로 전환
2946
+ // 레거시 기능은 /file/action API가 구현되지 않아 비활성화됨
2947
+ // if (detectPythonError(currentInput)) {
2948
+ // console.log('[AgentPanel] Agent mode: No notebook, but Python error detected - attempting file fix');
2949
+ // const handled = await handlePythonErrorFix(currentInput);
2950
+ // if (handled) {
2951
+ // const userMessage: IChatMessage = {
2952
+ // id: makeMessageId(),
2953
+ // role: 'user',
2954
+ // content: currentInput,
2955
+ // timestamp: Date.now(),
2956
+ // };
2957
+ // setMessages(prev => [...prev, userMessage]);
2958
+ // setInput('');
2959
+ // return;
2960
+ // }
2961
+ // console.log('[AgentPanel] Agent mode: No file path found, continuing...');
2962
+ // }
2656
2963
  // 파일 수정 관련 자연어 요청 감지 (에러, 고쳐, 수정, fix 등)
2657
2964
  const fileFixRequestPatterns = [
2658
2965
  /에러.*해결/i,
@@ -2745,29 +3052,25 @@ SyntaxError: '(' was never closed
2745
3052
  return;
2746
3053
  }
2747
3054
  }
2748
- // Python 에러 감지 및 파일 수정 모드 (Chat 모드에서만)
2749
- // 파일 경로를 추출할 있는 경우에만 파일 수정 모드 사용
2750
- // 그렇지 않으면 일반 LLM 스트림으로 처리
2751
- if (inputMode === 'chat' && detectPythonError(currentInput)) {
2752
- console.log('[AgentPanel] Python error detected in message, attempting file fix...');
2753
- // Python 에러 수정 처리 시도 (파일 경로를 찾을 수 있는 경우에만 처리됨)
2754
- const handled = await handlePythonErrorFix(currentInput);
2755
- if (handled) {
2756
- // 파일 수정 모드로 처리됨 - 사용자 메시지 추가 후 종료
2757
- const userMessage = {
2758
- id: makeMessageId(),
2759
- role: 'user',
2760
- content: currentInput,
2761
- timestamp: Date.now(),
2762
- };
2763
- setMessages(prev => [...prev, userMessage]);
2764
- setInput('');
2765
- return;
2766
- }
2767
- // 파일 경로를 찾지 못해 handled=false 반환됨
2768
- // 일반 스트림으로 폴백 (아래 로직에서 사용자 메시지 추가됨)
2769
- console.log('[AgentPanel] No file path found in error, using regular LLM stream');
2770
- }
3055
+ // [DISABLED] Python 에러 감지 및 파일 수정 모드 (Chat 모드에서만)
3056
+ // 레거시 기능은 /file/action API가 구현되지 않아 비활성화됨
3057
+ // LangChain agent의 edit_file_tool을 사용하도록 변경됨
3058
+ // if (inputMode === 'chat' && detectPythonError(currentInput)) {
3059
+ // console.log('[AgentPanel] Python error detected in message, attempting file fix...');
3060
+ // const handled = await handlePythonErrorFix(currentInput);
3061
+ // if (handled) {
3062
+ // const userMessage: IChatMessage = {
3063
+ // id: makeMessageId(),
3064
+ // role: 'user',
3065
+ // content: currentInput,
3066
+ // timestamp: Date.now(),
3067
+ // };
3068
+ // setMessages(prev => [...prev, userMessage]);
3069
+ // setInput('');
3070
+ // return;
3071
+ // }
3072
+ // console.log('[AgentPanel] No file path found in error, using regular LLM stream');
3073
+ // }
2771
3074
  // Use the display prompt (input) for the user message, or use a fallback if input is empty
2772
3075
  const displayContent = currentInput || (llmPrompt ? '셀 분석 요청' : '');
2773
3076
  await sendChatMessage({
@@ -2803,26 +3106,26 @@ SyntaxError: '(' was never closed
2803
3106
  e.preventDefault();
2804
3107
  handleSendMessage();
2805
3108
  }
2806
- // Shift+Tab: 모드 전환 (chat agent)
3109
+ // Shift+Tab: 모드 전환 (chat agent → agent_v2 → chat 순환)
2807
3110
  if (e.key === 'Tab' && e.shiftKey) {
2808
3111
  e.preventDefault();
2809
- setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');
3112
+ setInputMode(prev => prev === 'chat' ? 'agent' : prev === 'agent' ? 'agent_v2' : 'chat');
2810
3113
  return;
2811
3114
  }
2812
3115
  // Cmd/Ctrl + . : 모드 전환 (대체 단축키)
2813
3116
  if (e.key === '.' && (e.metaKey || e.ctrlKey)) {
2814
3117
  e.preventDefault();
2815
- setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');
3118
+ setInputMode(prev => prev === 'chat' ? 'agent' : prev === 'agent' ? 'agent_v2' : 'chat');
2816
3119
  }
2817
3120
  // Tab (without Shift): Agent 모드일 때 드롭다운 토글
2818
- if (e.key === 'Tab' && !e.shiftKey && inputMode === 'agent') {
3121
+ if (e.key === 'Tab' && !e.shiftKey && inputMode !== 'chat') {
2819
3122
  e.preventDefault();
2820
3123
  setShowModeDropdown(prev => !prev);
2821
3124
  }
2822
3125
  };
2823
- // 모드 토글 함수
3126
+ // 모드 토글 함수 (chat → agent → agent_v2 → chat 순환)
2824
3127
  const toggleMode = () => {
2825
- setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');
3128
+ setInputMode(prev => prev === 'chat' ? 'agent' : prev === 'agent' ? 'agent_v2' : 'chat');
2826
3129
  setShowModeDropdown(false);
2827
3130
  };
2828
3131
  const clearChat = () => {
@@ -2965,19 +3268,25 @@ SyntaxError: '(' was never closed
2965
3268
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("p", null, "\uC548\uB155\uD558\uC138\uC694! HDSP Agent\uC785\uB2C8\uB2E4."),
2966
3269
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("p", { className: "jp-agent-empty-hint" }, inputMode === 'agent'
2967
3270
  ? '노트북 작업을 자연어로 요청하세요. 예: "데이터 시각화 해줘"'
2968
- : '메시지를 입력하거나 아래 버튼으로 Agent 모드를 선택하세요.'))) : (messages.map(msg => {
3271
+ : inputMode === 'agent_v2'
3272
+ ? 'Deep Agent 모드입니다. HITL 승인을 통해 도구 실행을 제어할 수 있습니다.'
3273
+ : '메시지를 입력하거나 아래 버튼으로 Agent 모드를 선택하세요.'))) : (messages.map(msg => {
2969
3274
  if (isChatMessage(msg)) {
2970
3275
  // 일반 Chat 메시지
2971
3276
  const isAssistant = msg.role === 'assistant';
2972
3277
  const isShellOutput = msg.metadata?.kind === 'shell-output';
2973
3278
  const interruptAction = msg.metadata?.interrupt?.action;
2974
3279
  const isWriteFile = interruptAction === 'write_file_tool';
3280
+ const isEditFile = interruptAction === 'edit_file_tool';
2975
3281
  const writePath = (isWriteFile
2976
3282
  && typeof msg.metadata?.interrupt?.args?.path === 'string') ? msg.metadata?.interrupt?.args?.path : '';
3283
+ const editPath = (isEditFile
3284
+ && typeof msg.metadata?.interrupt?.args?.path === 'string') ? msg.metadata?.interrupt?.args?.path : '';
3285
+ const autoApproved = msg.metadata?.interrupt?.autoApproved;
2977
3286
  const headerRole = msg.role === 'user'
2978
3287
  ? '사용자'
2979
3288
  : msg.role === 'system'
2980
- ? (isShellOutput ? 'shell 실행' : '승인 요청')
3289
+ ? (isShellOutput ? 'shell 실행' : (autoApproved ? '자동 승인됨' : '승인 요청'))
2981
3290
  : 'Agent';
2982
3291
  return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { key: msg.id, className: isAssistant
2983
3292
  ? 'jp-agent-message jp-agent-message-assistant-inline'
@@ -2985,21 +3294,43 @@ SyntaxError: '(' was never closed
2985
3294
  !isAssistant && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-message-header" },
2986
3295
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-message-role" }, headerRole),
2987
3296
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-message-time" }, new Date(msg.timestamp).toLocaleTimeString()))),
2988
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `jp-agent-message-content${streamingMessageId === msg.id ? ' streaming' : ''}${isShellOutput ? ' jp-agent-message-content-shell' : ''}` }, msg.role === 'system' && msg.metadata?.interrupt ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-inline" },
2989
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-description" }, msg.content),
3297
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `jp-agent-message-content${streamingMessageId === msg.id ? ' streaming' : ''}${isShellOutput ? ' jp-agent-message-content-shell' : ''}` }, msg.role === 'system' && msg.metadata?.interrupt ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `jp-agent-interrupt-inline${msg.metadata?.interrupt?.autoApproved ? ' jp-agent-interrupt-auto-approved' : ''}` },
3298
+ !msg.metadata?.interrupt?.autoApproved && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-description" }, msg.content)),
2990
3299
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-action" },
2991
3300
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-interrupt-action-args" }, (() => {
2992
3301
  const command = msg.metadata?.interrupt?.args?.command;
2993
3302
  const code = msg.metadata?.interrupt?.args?.code || msg.metadata?.interrupt?.args?.content || '';
2994
- const snippet = (command || code || '(no details)');
2995
- const language = command ? 'bash' : 'python';
3303
+ // Handle edit_file_tool with diff preview
3304
+ let snippet;
3305
+ let language;
3306
+ if (isEditFile) {
3307
+ const oldStr = msg.metadata?.interrupt?.args?.old_string || '';
3308
+ const newStr = msg.metadata?.interrupt?.args?.new_string || '';
3309
+ const replaceAll = msg.metadata?.interrupt?.args?.replace_all;
3310
+ // Generate simple diff preview
3311
+ const oldPreview = oldStr.length > 500 ? oldStr.slice(0, 500) + '...' : oldStr;
3312
+ const newPreview = newStr.length > 500 ? newStr.slice(0, 500) + '...' : newStr;
3313
+ snippet = `--- ${editPath} (before)\n+++ ${editPath} (after)\n` +
3314
+ oldPreview.split('\n').map((line) => `-${line}`).join('\n') + '\n' +
3315
+ newPreview.split('\n').map((line) => `+${line}`).join('\n') +
3316
+ (replaceAll ? '\n\n(replace_all: true)' : '');
3317
+ language = 'diff';
3318
+ }
3319
+ else {
3320
+ snippet = (command || code || '(no details)');
3321
+ language = command ? 'bash' : 'python';
3322
+ }
2996
3323
  const resolved = msg.metadata?.interrupt?.resolved;
2997
3324
  const decision = msg.metadata?.interrupt?.decision;
2998
- const resolvedText = decision === 'reject' ? '거부됨' : '승인됨';
2999
- const resolvedClass = decision === 'reject' ? 'jp-agent-interrupt-actions--rejected' : 'jp-agent-interrupt-actions--resolved';
3000
- const actionHtml = resolved
3325
+ const autoApproved = msg.metadata?.interrupt?.autoApproved;
3326
+ // 자동 승인일 때는 코드블럭 헤더에 배지가 표시되므로 actionHtml에는 표시하지 않음
3327
+ const resolvedText = autoApproved ? '' : (decision === 'reject' ? '거부됨' : '승인됨');
3328
+ const resolvedClass = autoApproved ? '' : (decision === 'reject' ? 'jp-agent-interrupt-actions--rejected' : 'jp-agent-interrupt-actions--resolved');
3329
+ const actionHtml = resolved && !autoApproved
3001
3330
  ? `<div class="jp-agent-interrupt-actions ${resolvedClass}">${resolvedText}</div>`
3002
- : `
3331
+ : resolved && autoApproved
3332
+ ? '' // 자동 승인일 때는 actionHtml 비움 (코드블럭 헤더에 배지 표시)
3333
+ : `
3003
3334
  <div class="code-block-actions jp-agent-interrupt-actions">
3004
3335
  <button class="jp-agent-interrupt-approve-btn" data-action="approve">승인</button>
3005
3336
  <button class="jp-agent-interrupt-reject-btn" data-action="reject">거부</button>
@@ -3011,7 +3342,12 @@ SyntaxError: '(' was never closed
3011
3342
  const safePath = escapeHtml(writePath);
3012
3343
  html = html.replace(/<span class="code-block-language">[^<]*<\/span>/, `<span class="code-block-language jp-agent-interrupt-path">${safePath}</span>`);
3013
3344
  }
3014
- return html.replace('</div>', `${actionHtml}</div>`);
3345
+ if (isEditFile && editPath) {
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>`);
3348
+ }
3349
+ // actionHtml이 비어있지 않을 때만 추가
3350
+ return actionHtml ? html.replace('</div>', `${actionHtml}</div>`) : html;
3015
3351
  })();
3016
3352
  return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-RenderedHTMLCommon", style: { padding: '0 4px' }, dangerouslySetInnerHTML: { __html: renderedHtml }, onClick: (event) => {
3017
3353
  const target = event.target;
@@ -3073,7 +3409,7 @@ SyntaxError: '(' was never closed
3073
3409
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-file-fix-apply", onClick: () => applyFileFix(fix), title: `${fix.path} 파일에 수정 적용` }, "\uC801\uC6A9\uD558\uAE30"))))),
3074
3410
  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"))),
3075
3411
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { ref: messagesEndRef })),
3076
- todos.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-compact" },
3412
+ inputMode !== 'chat' && todos.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-compact" },
3077
3413
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-compact-header", onClick: () => setIsTodoExpanded(!isTodoExpanded) },
3078
3414
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-compact-left" },
3079
3415
  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" },
@@ -3117,21 +3453,25 @@ SyntaxError: '(' was never closed
3117
3453
  !statusText.startsWith('오류:') && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-ellipsis", "aria-hidden": "true" }))))),
3118
3454
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-input-container" },
3119
3455
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-input-wrapper" },
3120
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { className: `jp-agent-input ${inputMode === 'agent' ? 'jp-agent-input--agent-mode' : ''} ${isRejectionMode ? 'jp-agent-input--rejection-mode' : ''}`, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: handleKeyDown, placeholder: isRejectionMode
3456
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { className: `jp-agent-input ${inputMode !== 'chat' ? 'jp-agent-input--agent-mode' : ''} ${isRejectionMode ? 'jp-agent-input--rejection-mode' : ''}`, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: handleKeyDown, placeholder: isRejectionMode
3121
3457
  ? '다른 방향 제시'
3122
3458
  : (inputMode === 'agent'
3123
3459
  ? '노트북 작업을 입력하세요... (예: 데이터 시각화 해줘)'
3124
- : '메시지를 입력하세요...'), rows: 3, disabled: isLoading || isAgentRunning }),
3460
+ : inputMode === 'agent_v2'
3461
+ ? 'Deep Agent 요청을 입력하세요... (HITL 승인 모드)'
3462
+ : '메시지를 입력하세요...'), rows: 3, disabled: isLoading || isAgentRunning }),
3125
3463
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-send-button", onClick: handleSendMessage, disabled: (!input.trim() && !isRejectionMode) || isLoading || isStreaming || isAgentRunning, title: isRejectionMode ? "거부 전송 (Enter)" : "전송 (Enter)" }, isAgentRunning ? '실행 중...' : (isRejectionMode ? '거부' : '전송'))),
3126
3464
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-bar" },
3127
3465
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-toggle-container" },
3128
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: `jp-agent-mode-toggle ${inputMode === 'agent' ? 'jp-agent-mode-toggle--active' : ''}`, onClick: toggleMode, title: `${inputMode === 'agent' ? 'Agent' : 'Chat'} 모드 (⇧Tab)` },
3129
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { className: "jp-agent-mode-icon", viewBox: "0 0 16 16", fill: "currentColor", width: "14", height: "14" }, inputMode === 'agent' ? (
3466
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: `jp-agent-mode-toggle ${inputMode !== 'chat' ? 'jp-agent-mode-toggle--active' : ''}`, onClick: toggleMode, title: `${inputMode === 'chat' ? 'Chat' : inputMode === 'agent' ? 'Agent' : 'Agent V2'} 모드 (⇧Tab)` },
3467
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { className: "jp-agent-mode-icon", viewBox: "0 0 16 16", fill: "currentColor", width: "14", height: "14" }, inputMode === 'chat' ? (
3468
+ // 채팅 아이콘 (Chat 모드)
3469
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M8 1C3.58 1 0 4.13 0 8c0 1.5.5 2.88 1.34 4.04L.5 15l3.37-.92A8.56 8.56 0 008 15c4.42 0 8-3.13 8-7s-3.58-7-8-7zM4.5 9a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2z" })) : inputMode === 'agent' ? (
3130
3470
  // 무한대 아이콘 (Agent 모드)
3131
3471
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M4.5 8c0-1.38 1.12-2.5 2.5-2.5.9 0 1.68.48 2.12 1.2L8 8l1.12 1.3c-.44.72-1.22 1.2-2.12 1.2-1.38 0-2.5-1.12-2.5-2.5zm6.88 1.3c.44-.72 1.22-1.2 2.12-1.2 1.38 0 2.5 1.12 2.5 2.5s-1.12 2.5-2.5 2.5c-.9 0-1.68-.48-2.12-1.2L12.5 10.6c.3.24.68.4 1.1.4.83 0 1.5-.67 1.5-1.5S14.43 8 13.6 8c-.42 0-.8.16-1.1.4l-1.12 1.3zM7 9.5c-.42 0-.8-.16-1.1-.4L4.78 7.8c-.44.72-1.22 1.2-2.12 1.2C1.28 9 .17 7.88.17 6.5S1.29 4 2.67 4c.9 0 1.68.48 2.12 1.2L5.9 6.5c-.3-.24-.68-.4-1.1-.4C3.97 6.1 3.3 6.77 3.3 7.6s.67 1.5 1.5 1.5c.42 0 .8-.16 1.1-.4l1.12-1.3L8 8l-1 1.5z" })) : (
3132
- // 채팅 아이콘 (Chat 모드)
3133
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M8 1C3.58 1 0 4.13 0 8c0 1.5.5 2.88 1.34 4.04L.5 15l3.37-.92A8.56 8.56 0 008 15c4.42 0 8-3.13 8-7s-3.58-7-8-7zM4.5 9a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2z" }))),
3134
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-label" }, inputMode === 'agent' ? 'Agent' : 'Chat'),
3472
+ // 아이콘 (Agent V2 모드)
3473
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M8 0a8 8 0 100 16A8 8 0 008 0zm.5 11.5h-1v-1h1v1zm1.7-4.3c-.4.5-.7.8-.7 1.3v.5h-1v-.5c0-.8.4-1.3.8-1.8.4-.4.7-.7.7-1.2 0-.6-.4-1-1-1-.5 0-.9.3-1 .8l-1-.2c.2-.9 1-1.6 2-1.6 1.1 0 2 .8 2 1.9 0 .8-.4 1.3-.8 1.8z" }))),
3474
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-label" }, inputMode === 'chat' ? 'Chat' : inputMode === 'agent' ? 'Agent' : 'Agent V2'),
3135
3475
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { className: "jp-agent-mode-chevron", viewBox: "0 0 16 16", fill: "currentColor", width: "12", height: "12" },
3136
3476
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M4.47 5.47a.75.75 0 011.06 0L8 7.94l2.47-2.47a.75.75 0 111.06 1.06l-3 3a.75.75 0 01-1.06 0l-3-3a.75.75 0 010-1.06z" }))),
3137
3477
  showModeDropdown && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-dropdown" },
@@ -3144,7 +3484,12 @@ SyntaxError: '(' was never closed
3144
3484
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", width: "14", height: "14" },
3145
3485
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M4.5 8c0-1.38 1.12-2.5 2.5-2.5.9 0 1.68.48 2.12 1.2L8 8l1.12 1.3c-.44.72-1.22 1.2-2.12 1.2-1.38 0-2.5-1.12-2.5-2.5z" })),
3146
3486
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "Agent"),
3147
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-shortcut" }, "\uB178\uD2B8\uBD81 \uC790\uB3D9 \uC2E4\uD589"))))),
3487
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-shortcut" }, "\uB178\uD2B8\uBD81 \uC790\uB3D9 \uC2E4\uD589")),
3488
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: `jp-agent-mode-option ${inputMode === 'agent_v2' ? 'jp-agent-mode-option--selected' : ''}`, onClick: () => { setInputMode('agent_v2'); setShowModeDropdown(false); } },
3489
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", width: "14", height: "14" },
3490
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M8 0a8 8 0 100 16A8 8 0 008 0zm.5 11.5h-1v-1h1v1zm1.7-4.3c-.4.5-.7.8-.7 1.3v.5h-1v-.5c0-.8.4-1.3.8-1.8.4-.4.7-.7.7-1.2 0-.6-.4-1-1-1-.5 0-.9.3-1 .8l-1-.2c.2-.9 1-1.6 2-1.6 1.1 0 2 .8 2 1.9 0 .8-.4 1.3-.8 1.8z" })),
3491
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "Agent V2"),
3492
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-shortcut" }, "Deep Agent (HITL)"))))),
3148
3493
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-hints" },
3149
3494
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-hint" }, "\u21E7Tab \uBAA8\uB4DC \uC804\uD658")))),
3150
3495
  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 }))));
@@ -3641,6 +3986,7 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
3641
3986
  const [systemPrompt, setSystemPrompt] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(initConfig.systemPrompt || '');
3642
3987
  const [workspaceRoot, setWorkspaceRoot] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(initConfig.workspaceRoot || '');
3643
3988
  const [autoApprove, setAutoApprove] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(Boolean(initConfig.autoApprove));
3989
+ const [idleTimeoutMinutes, setIdleTimeoutMinutes] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(initConfig.idleTimeoutMinutes ?? 60);
3644
3990
  // Update state when currentConfig changes
3645
3991
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
3646
3992
  if (currentConfig) {
@@ -3664,6 +4010,7 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
3664
4010
  setSystemPrompt(currentConfig.systemPrompt || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_1__.getDefaultLLMConfig)().systemPrompt || '');
3665
4011
  setWorkspaceRoot(currentConfig.workspaceRoot || '');
3666
4012
  setAutoApprove(Boolean(currentConfig.autoApprove));
4013
+ setIdleTimeoutMinutes(currentConfig.idleTimeoutMinutes ?? 60);
3667
4014
  }
3668
4015
  }, [currentConfig]);
3669
4016
  // Helper: Build LLM config from state
@@ -3685,7 +4032,8 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
3685
4032
  },
3686
4033
  workspaceRoot: workspaceRoot.trim() ? workspaceRoot.trim() : undefined,
3687
4034
  systemPrompt: systemPrompt && systemPrompt.trim() ? systemPrompt : undefined,
3688
- autoApprove
4035
+ autoApprove,
4036
+ idleTimeoutMinutes
3689
4037
  });
3690
4038
  // Handlers for multiple API keys
3691
4039
  const handleAddKey = () => {
@@ -3853,6 +4201,11 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
3853
4201
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-checkbox" },
3854
4202
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "checkbox", checked: autoApprove, onChange: (e) => setAutoApprove(e.target.checked), "data-testid": "auto-approve-checkbox" }),
3855
4203
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\uC2B9\uC778 \uC5C6\uC774 \uBC14\uB85C \uC2E4\uD589 (\uCF54\uB4DC/\uD30C\uC77C/\uC178 \uD3EC\uD568)"))),
4204
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
4205
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label", htmlFor: "jp-agent-idle-timeout" },
4206
+ "Idle Timeout (\uBD84)",
4207
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, "\uBE44\uD65C\uB3D9 \uC2DC \uC790\uB3D9 \uC885\uB8CC (0 = \uBE44\uD65C\uC131\uD654)")),
4208
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { id: "jp-agent-idle-timeout", type: "number", className: "jp-agent-settings-input", value: idleTimeoutMinutes, onChange: (e) => setIdleTimeoutMinutes(Math.max(0, parseInt(e.target.value) || 0)), min: 0, max: 1440, placeholder: "60", style: { width: '120px' }, "data-testid": "idle-timeout-input" })),
3856
4209
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
3857
4210
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label" },
3858
4211
  "System Prompt (LangChain)",
@@ -4008,6 +4361,8 @@ __webpack_require__.r(__webpack_exports__);
4008
4361
  /* harmony import */ var _plugins_cell_buttons_plugin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./plugins/cell-buttons-plugin */ "./lib/plugins/cell-buttons-plugin.js");
4009
4362
  /* harmony import */ var _plugins_prompt_generation_plugin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./plugins/prompt-generation-plugin */ "./lib/plugins/prompt-generation-plugin.js");
4010
4363
  /* harmony import */ var _plugins_save_interceptor_plugin__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./plugins/save-interceptor-plugin */ "./lib/plugins/save-interceptor-plugin.js");
4364
+ /* harmony import */ var _plugins_idle_monitor_plugin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./plugins/idle-monitor-plugin */ "./lib/plugins/idle-monitor-plugin.js");
4365
+ /* harmony import */ var _plugins_lsp_bridge_plugin__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./plugins/lsp-bridge-plugin */ "./lib/plugins/lsp-bridge-plugin.js");
4011
4366
  /**
4012
4367
  * Jupyter Agent Extension Entry Point
4013
4368
  */
@@ -4016,6 +4371,8 @@ __webpack_require__.r(__webpack_exports__);
4016
4371
 
4017
4372
 
4018
4373
 
4374
+
4375
+
4019
4376
  // Import styles
4020
4377
  // import '../style/index.css';
4021
4378
  /**
@@ -4027,7 +4384,9 @@ const plugins = [
4027
4384
  _plugins_sidebar_plugin__WEBPACK_IMPORTED_MODULE_0__.sidebarPlugin,
4028
4385
  _plugins_cell_buttons_plugin__WEBPACK_IMPORTED_MODULE_1__.cellButtonsPlugin,
4029
4386
  _plugins_prompt_generation_plugin__WEBPACK_IMPORTED_MODULE_2__.promptGenerationPlugin,
4030
- _plugins_save_interceptor_plugin__WEBPACK_IMPORTED_MODULE_3__.saveInterceptorPlugin
4387
+ _plugins_save_interceptor_plugin__WEBPACK_IMPORTED_MODULE_3__.saveInterceptorPlugin,
4388
+ _plugins_idle_monitor_plugin__WEBPACK_IMPORTED_MODULE_4__.idleMonitorPlugin,
4389
+ _plugins_lsp_bridge_plugin__WEBPACK_IMPORTED_MODULE_5__.lspBridgePlugin
4031
4390
  ];
4032
4391
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (plugins);
4033
4392
 
@@ -4827,60 +5186,1020 @@ function showCustomPromptDialog(cell) {
4827
5186
  showNotification('질문 내용을 입력해주세요.', 'warning');
4828
5187
  return;
4829
5188
  }
4830
- dialogOverlay.remove();
4831
- // Get cell output
4832
- const cellOutput = getCellOutput(cell);
4833
- // Create display prompt: "Cell x: Custom request"
4834
- const displayPrompt = `${cellIndex}번째 셀: ${promptText}`;
4835
- // Create LLM prompt with code and output
4836
- let llmPrompt = `${promptText}\n\n셀 내용:\n\`\`\`\n${cellContent}\n\`\`\``;
4837
- if (cellOutput) {
4838
- llmPrompt += `\n\n실행 결과:\n\`\`\`\n${cellOutput}\n\`\`\``;
5189
+ dialogOverlay.remove();
5190
+ // Get cell output
5191
+ const cellOutput = getCellOutput(cell);
5192
+ // Create display prompt: "Cell x: Custom request"
5193
+ const displayPrompt = `${cellIndex}번째 셀: ${promptText}`;
5194
+ // Create LLM prompt with code and output
5195
+ let llmPrompt = `${promptText}\n\n셀 내용:\n\`\`\`\n${cellContent}\n\`\`\``;
5196
+ if (cellOutput) {
5197
+ llmPrompt += `\n\n실행 결과:\n\`\`\`\n${cellOutput}\n\`\`\``;
5198
+ }
5199
+ const agentPanel = window._hdspAgentPanel;
5200
+ if (agentPanel) {
5201
+ // Activate the sidebar panel
5202
+ const app = window.jupyterapp;
5203
+ if (app) {
5204
+ app.shell.activateById(agentPanel.id);
5205
+ }
5206
+ // Send both prompts with cell ID and cell index
5207
+ if (agentPanel.addCellActionMessage) {
5208
+ // cellIndex is 1-based (for display), convert to 0-based for array access
5209
+ const cellIndexZeroBased = cellIndex - 1;
5210
+ agentPanel.addCellActionMessage(_types__WEBPACK_IMPORTED_MODULE_1__.CellAction.CUSTOM_PROMPT, cellContent, displayPrompt, llmPrompt, cellId, cellIndexZeroBased);
5211
+ }
5212
+ }
5213
+ };
5214
+ submitBtn.addEventListener('click', handleSubmit);
5215
+ submitBtn.addEventListener('mouseenter', () => {
5216
+ submitBtn.style.background = 'rgba(25, 118, 210, 0.1)';
5217
+ });
5218
+ submitBtn.addEventListener('mouseleave', () => {
5219
+ submitBtn.style.background = 'transparent';
5220
+ });
5221
+ // Enter 키로 제출 (Shift+Enter는 줄바꿈)
5222
+ inputField?.addEventListener('keydown', (e) => {
5223
+ if (e.key === 'Enter' && !e.shiftKey) {
5224
+ e.preventDefault();
5225
+ handleSubmit();
5226
+ }
5227
+ });
5228
+ // 오버레이 클릭 시 다이얼로그 닫기
5229
+ dialogOverlay.addEventListener('click', (e) => {
5230
+ if (e.target === dialogOverlay) {
5231
+ dialogOverlay.remove();
5232
+ }
5233
+ });
5234
+ // ESC 키로 다이얼로그 닫기
5235
+ const handleEscapeKey = (e) => {
5236
+ if (e.key === 'Escape') {
5237
+ dialogOverlay.remove();
5238
+ document.removeEventListener('keydown', handleEscapeKey);
5239
+ }
5240
+ };
5241
+ document.addEventListener('keydown', handleEscapeKey);
5242
+ }
5243
+
5244
+
5245
+ /***/ },
5246
+
5247
+ /***/ "./lib/plugins/idle-monitor-plugin.js"
5248
+ /*!********************************************!*\
5249
+ !*** ./lib/plugins/idle-monitor-plugin.js ***!
5250
+ \********************************************/
5251
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
5252
+
5253
+ __webpack_require__.r(__webpack_exports__);
5254
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
5255
+ /* harmony export */ getIdleMonitor: () => (/* binding */ getIdleMonitor),
5256
+ /* harmony export */ idleMonitorPlugin: () => (/* binding */ idleMonitorPlugin)
5257
+ /* harmony export */ });
5258
+ /* harmony import */ var _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @jupyterlab/notebook */ "webpack/sharing/consume/default/@jupyterlab/notebook");
5259
+ /* harmony import */ var _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_0__);
5260
+ /* harmony import */ var _jupyterlab_terminal__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @jupyterlab/terminal */ "webpack/sharing/consume/default/@jupyterlab/terminal");
5261
+ /* harmony import */ var _jupyterlab_terminal__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_terminal__WEBPACK_IMPORTED_MODULE_1__);
5262
+ /* harmony import */ var _jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @jupyterlab/coreutils */ "webpack/sharing/consume/default/@jupyterlab/coreutils");
5263
+ /* harmony import */ var _jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_2__);
5264
+ /**
5265
+ * Idle Monitor Plugin
5266
+ *
5267
+ * Monitors user activity and triggers shutdown after idle timeout.
5268
+ * Activity is tracked by:
5269
+ * - Keyboard events
5270
+ * - Mouse events
5271
+ * - Notebook cell execution (kernel busy status)
5272
+ * - Terminal output
5273
+ */
5274
+
5275
+
5276
+
5277
+ /**
5278
+ * Plugin namespace
5279
+ */
5280
+ const PLUGIN_ID = '@hdsp-agent/idle-monitor';
5281
+ const CONFIG_STORAGE_KEY = 'hdsp-agent-llm-config';
5282
+ /**
5283
+ * Default idle timeout (in minutes)
5284
+ */
5285
+ const DEFAULT_IDLE_TIMEOUT_MINUTES = 60;
5286
+ // Check intervals - must be shorter than warning period to detect it
5287
+ const CHECK_INTERVAL_NORMAL_MS = 10 * 1000; // Check every 10 seconds (normal mode)
5288
+ const CHECK_INTERVAL_WARNING_MS = 1000; // Check every second (warning mode for countdown)
5289
+ // Warning period: show countdown before timeout (minimum 30 seconds, or 25% of timeout)
5290
+ const MIN_WARNING_PERIOD_MS = 30 * 1000; // Minimum 30 seconds warning
5291
+ /**
5292
+ * Idle Monitor Service
5293
+ */
5294
+ class IdleMonitorService {
5295
+ constructor() {
5296
+ this.checkIntervalId = null;
5297
+ this.countdownElement = null;
5298
+ this.isShuttingDown = false;
5299
+ this.notebookTracker = null;
5300
+ this.terminalTracker = null;
5301
+ this.isInWarningMode = false;
5302
+ this.isDisabled = false;
5303
+ /**
5304
+ * Throttle mousemove events to reduce performance impact
5305
+ */
5306
+ this.mouseMoveThrottleTime = 0;
5307
+ this.lastActivityTime = Date.now();
5308
+ // Initialize timeout from localStorage config
5309
+ this.idleTimeoutMinutes = this.loadTimeoutFromConfig();
5310
+ this.isDisabled = this.idleTimeoutMinutes === 0;
5311
+ this.idleTimeoutMs = this.idleTimeoutMinutes * 60 * 1000;
5312
+ // Warning period: 25% of timeout or minimum 30 seconds
5313
+ const warningPeriod = Math.max(MIN_WARNING_PERIOD_MS, this.idleTimeoutMs * 0.25);
5314
+ this.warningStartMs = this.idleTimeoutMs - warningPeriod;
5315
+ this.setupActivityListeners();
5316
+ this.setupStorageListener();
5317
+ this.createCountdownElement();
5318
+ if (!this.isDisabled) {
5319
+ this.startIdleCheck();
5320
+ }
5321
+ console.log('[IdleMonitor] Service initialized. Idle timeout:', this.idleTimeoutMinutes, 'minutes, warning at:', Math.round(this.warningStartMs / 1000), 'sec', this.isDisabled ? '(DISABLED)' : '');
5322
+ }
5323
+ /**
5324
+ * Load timeout configuration from localStorage
5325
+ */
5326
+ loadTimeoutFromConfig() {
5327
+ try {
5328
+ const stored = localStorage.getItem(CONFIG_STORAGE_KEY);
5329
+ if (stored) {
5330
+ const config = JSON.parse(stored);
5331
+ if (typeof config.idleTimeoutMinutes === 'number') {
5332
+ return config.idleTimeoutMinutes;
5333
+ }
5334
+ }
5335
+ }
5336
+ catch (e) {
5337
+ console.warn('[IdleMonitor] Failed to load config from localStorage:', e);
5338
+ }
5339
+ return DEFAULT_IDLE_TIMEOUT_MINUTES;
5340
+ }
5341
+ /**
5342
+ * Setup listener for localStorage changes (config updates from settings panel)
5343
+ */
5344
+ setupStorageListener() {
5345
+ window.addEventListener('storage', (e) => {
5346
+ if (e.key === CONFIG_STORAGE_KEY) {
5347
+ const newTimeout = this.loadTimeoutFromConfig();
5348
+ if (newTimeout !== this.idleTimeoutMinutes) {
5349
+ this.updateTimeout(newTimeout);
5350
+ }
5351
+ }
5352
+ });
5353
+ // Also listen for custom event (same-tab config changes)
5354
+ window.addEventListener('hdsp-config-updated', () => {
5355
+ const newTimeout = this.loadTimeoutFromConfig();
5356
+ if (newTimeout !== this.idleTimeoutMinutes) {
5357
+ this.updateTimeout(newTimeout);
5358
+ }
5359
+ });
5360
+ }
5361
+ /**
5362
+ * Update timeout dynamically
5363
+ */
5364
+ updateTimeout(minutes) {
5365
+ const wasDisabled = this.isDisabled;
5366
+ this.idleTimeoutMinutes = minutes;
5367
+ this.isDisabled = minutes === 0;
5368
+ this.idleTimeoutMs = minutes * 60 * 1000;
5369
+ // Warning period: 25% of timeout or minimum 30 seconds
5370
+ const warningPeriod = Math.max(MIN_WARNING_PERIOD_MS, this.idleTimeoutMs * 0.25);
5371
+ this.warningStartMs = this.idleTimeoutMs - warningPeriod;
5372
+ console.log('[IdleMonitor] Timeout updated to:', minutes, 'minutes', this.isDisabled ? '(DISABLED)' : '');
5373
+ // Stop idle check if disabled
5374
+ if (this.isDisabled) {
5375
+ this.stopIdleCheck();
5376
+ this.hideCountdown();
5377
+ }
5378
+ else if (wasDisabled) {
5379
+ // Re-enable if was disabled
5380
+ this.resetIdleTimer();
5381
+ this.startIdleCheck();
5382
+ }
5383
+ }
5384
+ /**
5385
+ * Stop idle check interval
5386
+ */
5387
+ stopIdleCheck() {
5388
+ if (this.checkIntervalId !== null) {
5389
+ clearInterval(this.checkIntervalId);
5390
+ this.checkIntervalId = null;
5391
+ }
5392
+ this.isInWarningMode = false;
5393
+ console.log('[IdleMonitor] Idle check stopped');
5394
+ }
5395
+ /**
5396
+ * Set notebook tracker for monitoring cell execution
5397
+ */
5398
+ setNotebookTracker(tracker) {
5399
+ this.notebookTracker = tracker;
5400
+ // Monitor cell execution changes
5401
+ tracker.currentChanged.connect(() => {
5402
+ this.resetIdleTimer();
5403
+ });
5404
+ // Monitor active cell changes
5405
+ tracker.activeCellChanged.connect(() => {
5406
+ this.resetIdleTimer();
5407
+ });
5408
+ console.log('[IdleMonitor] Notebook tracker connected');
5409
+ }
5410
+ /**
5411
+ * Set terminal tracker for monitoring terminal activity
5412
+ */
5413
+ setTerminalTracker(tracker) {
5414
+ this.terminalTracker = tracker;
5415
+ // Monitor terminal changes
5416
+ tracker.currentChanged.connect(() => {
5417
+ this.resetIdleTimer();
5418
+ });
5419
+ // Setup monitoring for existing terminals
5420
+ tracker.forEach(terminal => {
5421
+ this.setupTerminalMonitoring(terminal);
5422
+ });
5423
+ // Monitor new terminals
5424
+ tracker.widgetAdded.connect((_, terminal) => {
5425
+ this.setupTerminalMonitoring(terminal);
5426
+ });
5427
+ console.log('[IdleMonitor] Terminal tracker connected');
5428
+ }
5429
+ /**
5430
+ * Setup monitoring for a single terminal
5431
+ * Simply reset idle timer on any terminal output
5432
+ */
5433
+ setupTerminalMonitoring(terminal) {
5434
+ const terminalId = terminal.id || terminal.session?.name || 'unknown';
5435
+ const session = terminal.session;
5436
+ if (!session)
5437
+ return;
5438
+ // Listen for terminal output - any output resets idle timer
5439
+ session.messageReceived.connect(() => {
5440
+ this.resetIdleTimer();
5441
+ });
5442
+ console.log('[IdleMonitor] Terminal monitoring setup:', terminalId);
5443
+ }
5444
+ /**
5445
+ * Setup global activity listeners
5446
+ */
5447
+ setupActivityListeners() {
5448
+ // Keyboard events
5449
+ document.addEventListener('keydown', this.handleActivity.bind(this), true);
5450
+ document.addEventListener('keyup', this.handleActivity.bind(this), true);
5451
+ document.addEventListener('keypress', this.handleActivity.bind(this), true);
5452
+ // Mouse events
5453
+ document.addEventListener('mousedown', this.handleActivity.bind(this), true);
5454
+ document.addEventListener('mouseup', this.handleActivity.bind(this), true);
5455
+ document.addEventListener('mousemove', this.throttledMouseMove.bind(this), true);
5456
+ document.addEventListener('wheel', this.handleActivity.bind(this), true);
5457
+ document.addEventListener('click', this.handleActivity.bind(this), true);
5458
+ // Touch events (for tablet support)
5459
+ document.addEventListener('touchstart', this.handleActivity.bind(this), true);
5460
+ document.addEventListener('touchend', this.handleActivity.bind(this), true);
5461
+ console.log('[IdleMonitor] Activity listeners attached');
5462
+ }
5463
+ throttledMouseMove() {
5464
+ const now = Date.now();
5465
+ if (now - this.mouseMoveThrottleTime > 5000) { // Only update every 5 seconds for mouse move
5466
+ this.mouseMoveThrottleTime = now;
5467
+ this.handleActivity();
5468
+ }
5469
+ }
5470
+ /**
5471
+ * Handle any user activity
5472
+ */
5473
+ handleActivity() {
5474
+ this.resetIdleTimer();
5475
+ }
5476
+ /**
5477
+ * Reset the idle timer
5478
+ */
5479
+ resetIdleTimer() {
5480
+ this.lastActivityTime = Date.now();
5481
+ this.isShuttingDown = false;
5482
+ this.hideCountdown();
5483
+ // Switch back to normal mode if in warning mode
5484
+ this.switchToNormalMode();
5485
+ }
5486
+ /**
5487
+ * Create countdown display element (fixed position, top-right)
5488
+ */
5489
+ createCountdownElement() {
5490
+ // Create countdown container with fixed positioning
5491
+ this.countdownElement = document.createElement('div');
5492
+ this.countdownElement.id = 'hdsp-idle-countdown';
5493
+ this.countdownElement.className = 'hdsp-idle-countdown';
5494
+ this.countdownElement.style.cssText = `
5495
+ display: none;
5496
+ position: fixed;
5497
+ right: 20px;
5498
+ top: 10px;
5499
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%);
5500
+ color: white;
5501
+ padding: 8px 16px;
5502
+ border-radius: 20px;
5503
+ font-size: 14px;
5504
+ font-weight: 600;
5505
+ box-shadow: 0 4px 12px rgba(238, 90, 90, 0.5);
5506
+ z-index: 99999;
5507
+ white-space: nowrap;
5508
+ cursor: pointer;
5509
+ user-select: none;
5510
+ `;
5511
+ // Click to dismiss and reset timer
5512
+ this.countdownElement.addEventListener('click', () => {
5513
+ this.resetIdleTimer();
5514
+ console.log('[IdleMonitor] User clicked countdown - timer reset');
5515
+ });
5516
+ // Add pulse animation
5517
+ const style = document.createElement('style');
5518
+ style.id = 'hdsp-idle-countdown-style';
5519
+ style.textContent = `
5520
+ @keyframes hdsp-pulse {
5521
+ 0% { transform: scale(1); }
5522
+ 50% { transform: scale(1.03); }
5523
+ 100% { transform: scale(1); }
5524
+ }
5525
+
5526
+ .hdsp-idle-countdown {
5527
+ animation: hdsp-pulse 2s infinite;
5528
+ }
5529
+
5530
+ .hdsp-idle-countdown.warning {
5531
+ background: linear-gradient(135deg, #ffa726 0%, #fb8c00 100%) !important;
5532
+ box-shadow: 0 4px 12px rgba(251, 140, 0, 0.5) !important;
5533
+ }
5534
+
5535
+ .hdsp-idle-countdown.critical {
5536
+ background: linear-gradient(135deg, #ef5350 0%, #d32f2f 100%) !important;
5537
+ box-shadow: 0 4px 12px rgba(211, 47, 47, 0.6) !important;
5538
+ animation: hdsp-pulse-critical 0.5s infinite !important;
5539
+ }
5540
+
5541
+ @keyframes hdsp-pulse-critical {
5542
+ 0% { transform: scale(1); opacity: 1; }
5543
+ 50% { transform: scale(1.05); opacity: 0.9; }
5544
+ 100% { transform: scale(1); opacity: 1; }
5545
+ }
5546
+ `;
5547
+ // Remove existing style if any
5548
+ const existingStyle = document.getElementById('hdsp-idle-countdown-style');
5549
+ if (existingStyle) {
5550
+ existingStyle.remove();
5551
+ }
5552
+ document.head.appendChild(style);
5553
+ // Append to body for fixed positioning
5554
+ document.body.appendChild(this.countdownElement);
5555
+ console.log('[IdleMonitor] Countdown element created (fixed position)');
5556
+ }
5557
+ /**
5558
+ * Show countdown in toolbar
5559
+ */
5560
+ showCountdown(secondsRemaining) {
5561
+ console.log(`[IdleMonitor] showCountdown called: ${secondsRemaining}s remaining`);
5562
+ if (!this.countdownElement) {
5563
+ this.createCountdownElement();
5564
+ return;
5565
+ }
5566
+ this.countdownElement.style.display = 'block';
5567
+ this.countdownElement.textContent = `Idle Shutdown ${secondsRemaining}초 전`;
5568
+ // Update styling based on urgency
5569
+ this.countdownElement.classList.remove('warning', 'critical');
5570
+ if (secondsRemaining <= 10) {
5571
+ this.countdownElement.classList.add('critical');
5572
+ }
5573
+ else if (secondsRemaining <= 30) {
5574
+ this.countdownElement.classList.add('warning');
5575
+ }
5576
+ }
5577
+ /**
5578
+ * Hide countdown display
5579
+ */
5580
+ hideCountdown() {
5581
+ if (this.countdownElement) {
5582
+ this.countdownElement.style.display = 'none';
5583
+ }
5584
+ }
5585
+ /**
5586
+ * Check if any notebook cells are currently executing
5587
+ * Uses kernel status for reliability
5588
+ */
5589
+ hasRunningCells() {
5590
+ if (!this.notebookTracker)
5591
+ return false;
5592
+ // Check all open notebooks via kernel status
5593
+ const notebooks = this.notebookTracker.filter(() => true);
5594
+ for (const notebook of notebooks) {
5595
+ const session = notebook.sessionContext?.session;
5596
+ if (session) {
5597
+ const kernelStatus = session.kernel?.status;
5598
+ // Kernel is busy = cells are executing
5599
+ if (kernelStatus === 'busy') {
5600
+ return true;
5601
+ }
5602
+ }
5603
+ // Fallback: also check DOM for [*] indicator (for edge cases)
5604
+ if (notebook.content) {
5605
+ const cells = notebook.content.widgets;
5606
+ for (const cell of cells) {
5607
+ const model = cell.model;
5608
+ if (model && model.type === 'code') {
5609
+ const promptNode = cell.node.querySelector('.jp-InputPrompt');
5610
+ if (promptNode && promptNode.textContent?.includes('*')) {
5611
+ return true;
5612
+ }
5613
+ }
5614
+ }
5615
+ }
5616
+ }
5617
+ return false;
5618
+ }
5619
+ /**
5620
+ * Start idle checking interval
5621
+ * Starts in normal mode (10 min), switches to warning mode (1 sec) when needed
5622
+ */
5623
+ startIdleCheck() {
5624
+ this.checkIntervalId = window.setInterval(() => {
5625
+ this.checkIdleStatus();
5626
+ }, CHECK_INTERVAL_NORMAL_MS);
5627
+ console.log('[IdleMonitor] Started in normal mode: check every 10 seconds');
5628
+ }
5629
+ /**
5630
+ * Switch to warning mode (faster checks for countdown)
5631
+ */
5632
+ switchToWarningMode() {
5633
+ if (this.isInWarningMode)
5634
+ return;
5635
+ this.isInWarningMode = true;
5636
+ // Clear normal interval
5637
+ if (this.checkIntervalId !== null) {
5638
+ clearInterval(this.checkIntervalId);
5639
+ }
5640
+ // Start warning interval (1 second)
5641
+ this.checkIntervalId = window.setInterval(() => {
5642
+ this.checkIdleStatus();
5643
+ }, CHECK_INTERVAL_WARNING_MS);
5644
+ console.log('[IdleMonitor] Switched to warning mode: check every', CHECK_INTERVAL_WARNING_MS / 1000, 'seconds');
5645
+ }
5646
+ /**
5647
+ * Switch back to normal mode
5648
+ */
5649
+ switchToNormalMode() {
5650
+ if (!this.isInWarningMode)
5651
+ return;
5652
+ this.isInWarningMode = false;
5653
+ // Clear warning interval
5654
+ if (this.checkIntervalId !== null) {
5655
+ clearInterval(this.checkIntervalId);
5656
+ }
5657
+ // Start normal interval (10 minutes)
5658
+ this.checkIntervalId = window.setInterval(() => {
5659
+ this.checkIdleStatus();
5660
+ }, CHECK_INTERVAL_NORMAL_MS);
5661
+ console.log('[IdleMonitor] Switched to normal mode: check every 10 seconds');
5662
+ }
5663
+ /**
5664
+ * Check current idle status
5665
+ */
5666
+ checkIdleStatus() {
5667
+ // Skip if disabled
5668
+ if (this.isDisabled) {
5669
+ return;
5670
+ }
5671
+ // Skip if notebook cells are running (not idle)
5672
+ if (this.hasRunningCells()) {
5673
+ this.resetIdleTimer();
5674
+ return;
5675
+ }
5676
+ const now = Date.now();
5677
+ const idleTime = now - this.lastActivityTime;
5678
+ const remainingTime = this.idleTimeoutMs - idleTime;
5679
+ const secondsRemaining = Math.ceil(remainingTime / 1000);
5680
+ // Debug log every 10 seconds
5681
+ if (Math.floor(idleTime / 1000) % 10 === 0) {
5682
+ console.log(`[IdleMonitor] idle=${Math.round(idleTime / 1000)}s, warningAt=${Math.round(this.warningStartMs / 1000)}s, timeout=${Math.round(this.idleTimeoutMs / 1000)}s, remaining=${secondsRemaining}s`);
5683
+ }
5684
+ // Enter warning period - switch to fast checking for countdown
5685
+ if (idleTime >= this.warningStartMs && !this.isShuttingDown) {
5686
+ this.switchToWarningMode();
5687
+ this.showCountdown(secondsRemaining);
5688
+ }
5689
+ // Trigger shutdown if idle timeout reached
5690
+ if (idleTime >= this.idleTimeoutMs && !this.isShuttingDown) {
5691
+ this.isShuttingDown = true;
5692
+ this.triggerShutdown();
5693
+ }
5694
+ }
5695
+ /**
5696
+ * Trigger shutdown process
5697
+ */
5698
+ async triggerShutdown() {
5699
+ console.log('[IdleMonitor] Idle timeout reached. Triggering shutdown...');
5700
+ // Hide countdown
5701
+ this.hideCountdown();
5702
+ // Show alert popup
5703
+ alert(`${this.idleTimeoutMinutes}분 동안 활동이 없어 세션이 종료됩니다.`);
5704
+ // Call shutdown API
5705
+ await this.callShutdownApi();
5706
+ }
5707
+ /**
5708
+ * Call HDSP shutdown API via Jupyter server endpoint
5709
+ * Server-side call to avoid CORS issues
5710
+ */
5711
+ async callShutdownApi() {
5712
+ try {
5713
+ // Get base URL from PageConfig
5714
+ const baseUrl = _jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_2__.PageConfig.getBaseUrl();
5715
+ const apiUrl = `${baseUrl}hdsp-agent/idle-shutdown`;
5716
+ console.log('[IdleMonitor] Calling shutdown API via server:', apiUrl);
5717
+ // Call server endpoint (which will call HDSP API)
5718
+ const response = await fetch(apiUrl, {
5719
+ method: 'POST',
5720
+ headers: {
5721
+ 'Content-Type': 'application/json'
5722
+ }
5723
+ });
5724
+ const result = await response.json();
5725
+ if (response.ok && result.success) {
5726
+ console.log('[IdleMonitor] Shutdown API call successful:', result.message);
5727
+ }
5728
+ else {
5729
+ console.error('[IdleMonitor] Shutdown API call failed:', result.error || response.statusText);
5730
+ }
5731
+ }
5732
+ catch (error) {
5733
+ console.error('[IdleMonitor] Shutdown API call error:', error);
5734
+ }
5735
+ }
5736
+ /**
5737
+ * Get current idle time in milliseconds
5738
+ */
5739
+ getIdleTimeMs() {
5740
+ return Date.now() - this.lastActivityTime;
5741
+ }
5742
+ /**
5743
+ * Manually trigger activity (for external use)
5744
+ */
5745
+ triggerActivity() {
5746
+ this.resetIdleTimer();
5747
+ }
5748
+ /**
5749
+ * Cleanup service
5750
+ */
5751
+ dispose() {
5752
+ if (this.checkIntervalId !== null) {
5753
+ clearInterval(this.checkIntervalId);
5754
+ this.checkIntervalId = null;
5755
+ }
5756
+ if (this.countdownElement && this.countdownElement.parentNode) {
5757
+ this.countdownElement.parentNode.removeChild(this.countdownElement);
5758
+ }
5759
+ console.log('[IdleMonitor] Service disposed');
5760
+ }
5761
+ }
5762
+ /**
5763
+ * Global idle monitor instance
5764
+ */
5765
+ let idleMonitor = null;
5766
+ /**
5767
+ * Get idle monitor instance
5768
+ */
5769
+ function getIdleMonitor() {
5770
+ return idleMonitor;
5771
+ }
5772
+ /**
5773
+ * Idle Monitor Plugin
5774
+ */
5775
+ const idleMonitorPlugin = {
5776
+ id: PLUGIN_ID,
5777
+ autoStart: true,
5778
+ requires: [],
5779
+ optional: [_jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_0__.INotebookTracker, _jupyterlab_terminal__WEBPACK_IMPORTED_MODULE_1__.ITerminalTracker],
5780
+ activate: (app, notebookTracker, terminalTracker) => {
5781
+ console.log('[IdleMonitorPlugin] Activating Idle Monitor');
5782
+ try {
5783
+ // Create idle monitor service
5784
+ idleMonitor = new IdleMonitorService();
5785
+ // Connect notebook tracker if available
5786
+ if (notebookTracker) {
5787
+ idleMonitor.setNotebookTracker(notebookTracker);
5788
+ }
5789
+ // Connect terminal tracker if available
5790
+ if (terminalTracker) {
5791
+ idleMonitor.setTerminalTracker(terminalTracker);
5792
+ }
5793
+ // Store reference globally for debugging
5794
+ window._hdspIdleMonitor = idleMonitor;
5795
+ console.log('[IdleMonitorPlugin] Idle Monitor activated successfully');
5796
+ }
5797
+ catch (error) {
5798
+ console.error('[IdleMonitorPlugin] Failed to activate:', error);
5799
+ }
5800
+ }
5801
+ };
5802
+
5803
+
5804
+ /***/ },
5805
+
5806
+ /***/ "./lib/plugins/lsp-bridge-plugin.js"
5807
+ /*!******************************************!*\
5808
+ !*** ./lib/plugins/lsp-bridge-plugin.js ***!
5809
+ \******************************************/
5810
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
5811
+
5812
+ __webpack_require__.r(__webpack_exports__);
5813
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
5814
+ /* harmony export */ DiagnosticSeverity: () => (/* binding */ DiagnosticSeverity),
5815
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__),
5816
+ /* harmony export */ getLSPBridge: () => (/* binding */ getLSPBridge),
5817
+ /* harmony export */ lspBridgePlugin: () => (/* binding */ lspBridgePlugin)
5818
+ /* harmony export */ });
5819
+ /* harmony import */ var _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @jupyterlab/notebook */ "webpack/sharing/consume/default/@jupyterlab/notebook");
5820
+ /* harmony import */ var _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_0__);
5821
+ /**
5822
+ * LSP Bridge Plugin
5823
+ *
5824
+ * jupyterlab-lsp와 HDSP Agent를 연결하는 브릿지
5825
+ * Crush 패턴 적용: Version-based 캐싱, 이벤트 기반 업데이트
5826
+ *
5827
+ * 주요 기능:
5828
+ * - LSP 진단 결과 캐싱 및 조회
5829
+ * - 심볼 참조 검색
5830
+ * - Agent 도구와 연동
5831
+ */
5832
+
5833
+ /**
5834
+ * Plugin namespace
5835
+ */
5836
+ const PLUGIN_ID = '@hdsp-agent/lsp-bridge';
5837
+ /**
5838
+ * LSP Diagnostic severity levels
5839
+ */
5840
+ var DiagnosticSeverity;
5841
+ (function (DiagnosticSeverity) {
5842
+ DiagnosticSeverity[DiagnosticSeverity["Error"] = 1] = "Error";
5843
+ DiagnosticSeverity[DiagnosticSeverity["Warning"] = 2] = "Warning";
5844
+ DiagnosticSeverity[DiagnosticSeverity["Information"] = 3] = "Information";
5845
+ DiagnosticSeverity[DiagnosticSeverity["Hint"] = 4] = "Hint";
5846
+ })(DiagnosticSeverity || (DiagnosticSeverity = {}));
5847
+ /**
5848
+ * LSP State Cache (Crush의 VersionedMap 패턴)
5849
+ * 버전 기반 캐시 무효화로 불필요한 재계산 방지
5850
+ */
5851
+ class LSPStateCache {
5852
+ constructor() {
5853
+ this.diagnosticsVersion = 0;
5854
+ this.diagnosticsCache = new Map();
5855
+ this.summaryCache = null;
5856
+ this.summaryVersion = -1;
5857
+ }
5858
+ /**
5859
+ * Update diagnostics for a file
5860
+ */
5861
+ updateDiagnostics(uri, diagnostics) {
5862
+ this.diagnosticsCache.set(uri, diagnostics);
5863
+ this.diagnosticsVersion++;
5864
+ }
5865
+ /**
5866
+ * Get diagnostics for a specific file or all files
5867
+ */
5868
+ getDiagnostics(uri) {
5869
+ if (uri) {
5870
+ return {
5871
+ version: this.diagnosticsVersion,
5872
+ diagnostics: this.diagnosticsCache.get(uri) || []
5873
+ };
5874
+ }
5875
+ return {
5876
+ version: this.diagnosticsVersion,
5877
+ diagnostics: this.diagnosticsCache
5878
+ };
5879
+ }
5880
+ /**
5881
+ * Get diagnostic counts with caching (Crush 패턴)
5882
+ */
5883
+ getDiagnosticSummary() {
5884
+ // Return cached if version matches
5885
+ if (this.summaryVersion === this.diagnosticsVersion && this.summaryCache) {
5886
+ return this.summaryCache;
5887
+ }
5888
+ // Recalculate
5889
+ let errors = 0;
5890
+ let warnings = 0;
5891
+ let hints = 0;
5892
+ for (const diags of this.diagnosticsCache.values()) {
5893
+ for (const d of diags) {
5894
+ switch (d.severity) {
5895
+ case DiagnosticSeverity.Error:
5896
+ errors++;
5897
+ break;
5898
+ case DiagnosticSeverity.Warning:
5899
+ warnings++;
5900
+ break;
5901
+ default:
5902
+ hints++;
5903
+ }
5904
+ }
5905
+ }
5906
+ this.summaryCache = {
5907
+ errors,
5908
+ warnings,
5909
+ hints,
5910
+ total: errors + warnings + hints,
5911
+ version: this.diagnosticsVersion
5912
+ };
5913
+ this.summaryVersion = this.diagnosticsVersion;
5914
+ return this.summaryCache;
5915
+ }
5916
+ /**
5917
+ * Clear diagnostics for a file
5918
+ */
5919
+ clearDiagnostics(uri) {
5920
+ if (this.diagnosticsCache.has(uri)) {
5921
+ this.diagnosticsCache.delete(uri);
5922
+ this.diagnosticsVersion++;
5923
+ }
5924
+ }
5925
+ /**
5926
+ * Clear all diagnostics
5927
+ */
5928
+ clearAll() {
5929
+ this.diagnosticsCache.clear();
5930
+ this.diagnosticsVersion++;
5931
+ }
5932
+ /**
5933
+ * Get current version
5934
+ */
5935
+ getVersion() {
5936
+ return this.diagnosticsVersion;
5937
+ }
5938
+ }
5939
+ /**
5940
+ * LSP Bridge Service
5941
+ * Agent 도구에서 LSP 기능을 사용할 수 있게 해주는 서비스
5942
+ */
5943
+ class LSPBridgeService {
5944
+ constructor() {
5945
+ this.cache = new LSPStateCache();
5946
+ this.lspAvailable = false;
5947
+ this.notebookTracker = null;
5948
+ this.checkLSPAvailability();
5949
+ }
5950
+ /**
5951
+ * Check if jupyterlab-lsp is available
5952
+ */
5953
+ async checkLSPAvailability() {
5954
+ try {
5955
+ // jupyterlab-lsp가 설치되어 있는지 확인
5956
+ // 실제로는 ILSPDocumentConnectionManager 토큰을 통해 확인
5957
+ this.lspAvailable = false; // 기본값, 실제 연결 시 true로 변경
5958
+ console.log('[LSPBridge] LSP availability check - will be updated when connection established');
5959
+ }
5960
+ catch (error) {
5961
+ console.warn('[LSPBridge] LSP not available:', error);
5962
+ this.lspAvailable = false;
4839
5963
  }
4840
- const agentPanel = window._hdspAgentPanel;
4841
- if (agentPanel) {
4842
- // Activate the sidebar panel
4843
- const app = window.jupyterapp;
4844
- if (app) {
4845
- app.shell.activateById(agentPanel.id);
5964
+ }
5965
+ /**
5966
+ * Set notebook tracker for document access
5967
+ */
5968
+ setNotebookTracker(tracker) {
5969
+ this.notebookTracker = tracker;
5970
+ }
5971
+ /**
5972
+ * Mark LSP as available (called when connection is established)
5973
+ */
5974
+ setLSPAvailable(available) {
5975
+ this.lspAvailable = available;
5976
+ console.log('[LSPBridge] LSP available:', available);
5977
+ }
5978
+ /**
5979
+ * Check if LSP is available
5980
+ */
5981
+ isLSPAvailable() {
5982
+ return this.lspAvailable;
5983
+ }
5984
+ /**
5985
+ * Update diagnostics from external source (e.g., jupyterlab-lsp)
5986
+ */
5987
+ updateDiagnostics(uri, diagnostics) {
5988
+ this.cache.updateDiagnostics(uri, diagnostics);
5989
+ this.notifyDiagnosticsChanged(uri);
5990
+ }
5991
+ /**
5992
+ * Get diagnostics for Agent tool
5993
+ * Returns formatted diagnostics for a file or entire project
5994
+ */
5995
+ async getDiagnostics(filePath) {
5996
+ const result = this.cache.getDiagnostics(filePath ? this.pathToUri(filePath) : undefined);
5997
+ const summary = this.cache.getDiagnosticSummary();
5998
+ const formattedDiagnostics = [];
5999
+ if (filePath) {
6000
+ // Single file
6001
+ const diags = result.diagnostics;
6002
+ for (const d of diags) {
6003
+ formattedDiagnostics.push({
6004
+ severity: this.severityToString(d.severity),
6005
+ line: d.range.start.line + 1,
6006
+ character: d.range.start.character,
6007
+ message: d.message,
6008
+ source: d.source,
6009
+ code: d.code,
6010
+ file: filePath
6011
+ });
4846
6012
  }
4847
- // Send both prompts with cell ID and cell index
4848
- if (agentPanel.addCellActionMessage) {
4849
- // cellIndex is 1-based (for display), convert to 0-based for array access
4850
- const cellIndexZeroBased = cellIndex - 1;
4851
- agentPanel.addCellActionMessage(_types__WEBPACK_IMPORTED_MODULE_1__.CellAction.CUSTOM_PROMPT, cellContent, displayPrompt, llmPrompt, cellId, cellIndexZeroBased);
6013
+ }
6014
+ else {
6015
+ // All files
6016
+ const diagsMap = result.diagnostics;
6017
+ for (const [uri, diags] of diagsMap) {
6018
+ const file = this.uriToPath(uri);
6019
+ for (const d of diags) {
6020
+ formattedDiagnostics.push({
6021
+ severity: this.severityToString(d.severity),
6022
+ line: d.range.start.line + 1,
6023
+ character: d.range.start.character,
6024
+ message: d.message,
6025
+ source: d.source,
6026
+ code: d.code,
6027
+ file
6028
+ });
6029
+ }
4852
6030
  }
4853
6031
  }
4854
- };
4855
- submitBtn.addEventListener('click', handleSubmit);
4856
- submitBtn.addEventListener('mouseenter', () => {
4857
- submitBtn.style.background = 'rgba(25, 118, 210, 0.1)';
4858
- });
4859
- submitBtn.addEventListener('mouseleave', () => {
4860
- submitBtn.style.background = 'transparent';
4861
- });
4862
- // Enter 키로 제출 (Shift+Enter는 줄바꿈)
4863
- inputField?.addEventListener('keydown', (e) => {
4864
- if (e.key === 'Enter' && !e.shiftKey) {
4865
- e.preventDefault();
4866
- handleSubmit();
6032
+ // Sort: errors first, then by file and line (Crush 패턴)
6033
+ formattedDiagnostics.sort((a, b) => {
6034
+ const severityOrder = { error: 0, warning: 1, information: 2, hint: 3 };
6035
+ const severityDiff = (severityOrder[a.severity] || 3) - (severityOrder[b.severity] || 3);
6036
+ if (severityDiff !== 0)
6037
+ return severityDiff;
6038
+ const fileDiff = a.file.localeCompare(b.file);
6039
+ if (fileDiff !== 0)
6040
+ return fileDiff;
6041
+ return a.line - b.line;
6042
+ });
6043
+ return {
6044
+ success: true,
6045
+ diagnostics: formattedDiagnostics,
6046
+ summary
6047
+ };
6048
+ }
6049
+ /**
6050
+ * Find references for a symbol (Crush의 Grep-then-LSP 패턴)
6051
+ * 현재는 grep 기반으로 구현, LSP 연결 시 향상 가능
6052
+ */
6053
+ async getReferences(symbol, filePath, line, character) {
6054
+ // LSP가 없으면 grep 기반으로 검색하도록 안내
6055
+ if (!this.lspAvailable) {
6056
+ return {
6057
+ success: false,
6058
+ locations: []
6059
+ };
4867
6060
  }
4868
- });
4869
- // 오버레이 클릭 다이얼로그 닫기
4870
- dialogOverlay.addEventListener('click', (e) => {
4871
- if (e.target === dialogOverlay) {
4872
- dialogOverlay.remove();
6061
+ // TODO: jupyterlab-lsp 연결 시 구현
6062
+ // 현재는 결과 반환 (Agent가 search_workspace_tool 사용하도록)
6063
+ return {
6064
+ success: false,
6065
+ locations: []
6066
+ };
6067
+ }
6068
+ /**
6069
+ * Get diagnostic summary
6070
+ */
6071
+ getDiagnosticSummary() {
6072
+ return this.cache.getDiagnosticSummary();
6073
+ }
6074
+ /**
6075
+ * Notify diagnostics changed event
6076
+ */
6077
+ notifyDiagnosticsChanged(uri) {
6078
+ const summary = this.cache.getDiagnosticSummary();
6079
+ window.dispatchEvent(new CustomEvent('hdsp-lsp-diagnostics-changed', {
6080
+ detail: { uri, summary }
6081
+ }));
6082
+ }
6083
+ /**
6084
+ * Convert severity number to string
6085
+ */
6086
+ severityToString(severity) {
6087
+ switch (severity) {
6088
+ case DiagnosticSeverity.Error:
6089
+ return 'error';
6090
+ case DiagnosticSeverity.Warning:
6091
+ return 'warning';
6092
+ case DiagnosticSeverity.Information:
6093
+ return 'information';
6094
+ case DiagnosticSeverity.Hint:
6095
+ return 'hint';
6096
+ default:
6097
+ return 'hint';
4873
6098
  }
4874
- });
4875
- // ESC 키로 다이얼로그 닫기
4876
- const handleEscapeKey = (e) => {
4877
- if (e.key === 'Escape') {
4878
- dialogOverlay.remove();
4879
- document.removeEventListener('keydown', handleEscapeKey);
6099
+ }
6100
+ /**
6101
+ * Convert file path to URI
6102
+ */
6103
+ pathToUri(path) {
6104
+ if (path.startsWith('file://')) {
6105
+ return path;
4880
6106
  }
4881
- };
4882
- document.addEventListener('keydown', handleEscapeKey);
6107
+ return `file://${path.startsWith('/') ? '' : '/'}${path}`;
6108
+ }
6109
+ /**
6110
+ * Convert URI to file path
6111
+ */
6112
+ uriToPath(uri) {
6113
+ if (uri.startsWith('file://')) {
6114
+ return uri.replace('file://', '');
6115
+ }
6116
+ return uri;
6117
+ }
6118
+ /**
6119
+ * Dispose service
6120
+ */
6121
+ dispose() {
6122
+ this.cache.clearAll();
6123
+ console.log('[LSPBridge] Service disposed');
6124
+ }
6125
+ }
6126
+ /**
6127
+ * Global LSP Bridge instance
6128
+ */
6129
+ let lspBridge = null;
6130
+ /**
6131
+ * Get LSP Bridge instance
6132
+ */
6133
+ function getLSPBridge() {
6134
+ return lspBridge;
6135
+ }
6136
+ /**
6137
+ * LSP Bridge Plugin
6138
+ *
6139
+ * jupyterlab-lsp가 설치되어 있으면 연동하고,
6140
+ * 없으면 기본 기능만 제공
6141
+ */
6142
+ const lspBridgePlugin = {
6143
+ id: PLUGIN_ID,
6144
+ autoStart: true,
6145
+ requires: [],
6146
+ optional: [_jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_0__.INotebookTracker],
6147
+ activate: (app, notebookTracker) => {
6148
+ console.log('[LSPBridgePlugin] Activating LSP Bridge...');
6149
+ // Create LSP Bridge service
6150
+ lspBridge = new LSPBridgeService();
6151
+ // Connect notebook tracker if available
6152
+ if (notebookTracker) {
6153
+ lspBridge.setNotebookTracker(notebookTracker);
6154
+ }
6155
+ // Store reference globally for debugging and Agent access
6156
+ window._hdspLSPBridge = lspBridge;
6157
+ // Try to connect to jupyterlab-lsp if available
6158
+ // This is done dynamically to avoid hard dependency
6159
+ tryConnectToLSP(app, lspBridge);
6160
+ console.log('[LSPBridgePlugin] LSP Bridge activated');
6161
+ }
6162
+ };
6163
+ /**
6164
+ * Try to connect to jupyterlab-lsp extension
6165
+ */
6166
+ async function tryConnectToLSP(app, bridge) {
6167
+ try {
6168
+ // Check if ILSPDocumentConnectionManager is available
6169
+ // This token is provided by jupyterlab-lsp
6170
+ const lspToken = '@jupyterlab/lsp:ILSPDocumentConnectionManager';
6171
+ // Try to get the LSP manager from the application
6172
+ // Note: This requires jupyterlab-lsp to be installed
6173
+ const hasLSP = app.commands.hasCommand('lsp:show-diagnostics-panel');
6174
+ if (hasLSP) {
6175
+ console.log('[LSPBridgePlugin] jupyterlab-lsp detected');
6176
+ bridge.setLSPAvailable(true);
6177
+ // Listen for LSP diagnostic events
6178
+ // jupyterlab-lsp publishes diagnostics through its own mechanism
6179
+ setupLSPEventListeners(bridge);
6180
+ }
6181
+ else {
6182
+ console.log('[LSPBridgePlugin] jupyterlab-lsp not detected, using fallback mode');
6183
+ bridge.setLSPAvailable(false);
6184
+ }
6185
+ }
6186
+ catch (error) {
6187
+ console.warn('[LSPBridgePlugin] Failed to connect to jupyterlab-lsp:', error);
6188
+ bridge.setLSPAvailable(false);
6189
+ }
6190
+ }
6191
+ /**
6192
+ * Setup event listeners for LSP events
6193
+ */
6194
+ function setupLSPEventListeners(bridge) {
6195
+ // jupyterlab-lsp uses its own event system
6196
+ // We'll listen for changes through polling or direct integration
6197
+ // This is a placeholder for future implementation
6198
+ console.log('[LSPBridgePlugin] LSP event listeners setup (placeholder)');
6199
+ // For now, we can use a simple polling mechanism or
6200
+ // hook into JupyterLab's document change events
4883
6201
  }
6202
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (lspBridgePlugin);
4884
6203
 
4885
6204
 
4886
6205
  /***/ },
@@ -7735,7 +9054,14 @@ const STORAGE_KEY = 'hdsp-agent-llm-config';
7735
9054
  const DEFAULT_LANGCHAIN_SYSTEM_PROMPT = `You are an expert Python data scientist and Jupyter notebook assistant.
7736
9055
  Your role is to help users with data analysis, visualization, and Python coding tasks in Jupyter notebooks. You can use only Korean
7737
9056
 
7738
- ## ⚠️ CRITICAL RULE: NEVER produce an empty response
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.
7739
9065
 
7740
9066
  You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
7741
9067
  1. Check your todo list - are there pending or in_progress items?
@@ -7751,8 +9077,6 @@ You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
7751
9077
  }
7752
9078
  4. If ALL todos are completed → call final_answer_tool with a summary
7753
9079
 
7754
- NEVER end your turn without calling a tool. NEVER produce an empty response.
7755
-
7756
9080
  ## 🔴 MANDATORY: Resource Check Before Data Hanlding
7757
9081
  **ALWAYS call check_resource_tool FIRST** when the task involves:
7758
9082
  - Loading files: .csv, .parquet, .json, .xlsx, .pickle, .h5, .feather
@@ -7771,6 +9095,28 @@ NEVER end your turn without calling a tool. NEVER produce an empty response.
7771
9095
  - Stopping after any tool without calling the next tool
7772
9096
  - Ending without calling final_answer_tool
7773
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.
7774
9120
  `;
7775
9121
  // ═══════════════════════════════════════════════════════════════════════════
7776
9122
  // Key Rotation State (in-memory, not persisted)
@@ -7804,6 +9150,8 @@ function saveLLMConfig(config) {
7804
9150
  try {
7805
9151
  localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
7806
9152
  console.log('[ApiKeyManager] Config saved to localStorage');
9153
+ // Dispatch custom event for same-tab listeners (e.g., idle monitor)
9154
+ window.dispatchEvent(new CustomEvent('hdsp-config-updated', { detail: config }));
7807
9155
  }
7808
9156
  catch (e) {
7809
9157
  console.error('[ApiKeyManager] Failed to save config:', e);
@@ -8262,7 +9610,102 @@ class ApiService {
8262
9610
  * - Server receives ONLY ONE key per request
8263
9611
  * - On 429 rate limit, frontend rotates key and retries with next key
8264
9612
  */
8265
- async sendMessageStream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, // Callback to capture thread_id for context persistence
9613
+ /**
9614
+ * 단순 Chat용 스트리밍 - /chat/stream 사용
9615
+ * 원래 main 브랜치의 구현 복원 - LangChain 에이전트 없이 단순 Q&A
9616
+ */
9617
+ async sendChatStream(request, onChunk, onMetadata) {
9618
+ const MAX_RETRIES = 10;
9619
+ let currentConfig = request.llmConfig;
9620
+ let lastError = null;
9621
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
9622
+ const requestToSend = currentConfig
9623
+ ? { ...request, llmConfig: (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.buildSingleKeyConfig)(currentConfig) }
9624
+ : request;
9625
+ try {
9626
+ await this.sendChatStreamInternal(requestToSend, onChunk, onMetadata);
9627
+ (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.resetKeyRotation)();
9628
+ return;
9629
+ }
9630
+ catch (error) {
9631
+ const errorMsg = error instanceof Error ? error.message : String(error);
9632
+ lastError = error instanceof Error ? error : new Error(errorMsg);
9633
+ if ((0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.isRateLimitError)(errorMsg) && request.llmConfig) {
9634
+ console.log(`[ApiService] Chat rate limit on attempt ${attempt + 1}, trying next key...`);
9635
+ const rotatedConfig = (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.handleRateLimitError)(request.llmConfig);
9636
+ if (rotatedConfig) {
9637
+ currentConfig = request.llmConfig;
9638
+ continue;
9639
+ }
9640
+ else {
9641
+ throw new Error('모든 API 키가 Rate Limit 상태입니다. 잠시 후 다시 시도해주세요.');
9642
+ }
9643
+ }
9644
+ throw error;
9645
+ }
9646
+ }
9647
+ throw lastError || new Error('Maximum retry attempts exceeded');
9648
+ }
9649
+ /**
9650
+ * 단순 Chat 스트리밍 내부 구현 - /chat/stream 엔드포인트 사용
9651
+ */
9652
+ async sendChatStreamInternal(request, onChunk, onMetadata) {
9653
+ const response = await fetch(`${this.baseUrl}/chat/stream`, {
9654
+ method: 'POST',
9655
+ headers: this.getHeaders(),
9656
+ credentials: 'include',
9657
+ body: JSON.stringify(request)
9658
+ });
9659
+ if (!response.ok) {
9660
+ const error = await response.text();
9661
+ throw new Error(`Failed to send chat message: ${error}`);
9662
+ }
9663
+ const reader = response.body?.getReader();
9664
+ if (!reader) {
9665
+ throw new Error('Response body is not readable');
9666
+ }
9667
+ const decoder = new TextDecoder();
9668
+ let buffer = '';
9669
+ try {
9670
+ while (true) {
9671
+ const { done, value } = await reader.read();
9672
+ if (done)
9673
+ break;
9674
+ buffer += decoder.decode(value, { stream: true });
9675
+ const lines = buffer.split('\n');
9676
+ buffer = lines.pop() || '';
9677
+ for (const line of lines) {
9678
+ if (line.startsWith('data: ')) {
9679
+ try {
9680
+ const data = JSON.parse(line.slice(6));
9681
+ if (data.error) {
9682
+ throw new Error(data.error);
9683
+ }
9684
+ if (data.content) {
9685
+ onChunk(data.content);
9686
+ }
9687
+ if (data.done && data.conversationId && onMetadata) {
9688
+ onMetadata({ conversationId: data.conversationId });
9689
+ }
9690
+ }
9691
+ catch (e) {
9692
+ if (!(e instanceof SyntaxError)) {
9693
+ throw e;
9694
+ }
9695
+ }
9696
+ }
9697
+ }
9698
+ }
9699
+ }
9700
+ finally {
9701
+ reader.releaseLock();
9702
+ }
9703
+ }
9704
+ /**
9705
+ * Agent V2용 스트리밍 - /agent/langchain/stream 사용
9706
+ * LangChain Deep Agent (HITL, Todo, 도구 실행 등)
9707
+ */
9708
+ async sendAgentV2Stream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, // Callback to capture thread_id for context persistence
8266
9709
  threadId // Optional thread_id to continue existing conversation
8267
9710
  ) {
8268
9711
  // Maximum retry attempts (should match number of keys)
@@ -8275,7 +9718,7 @@ class ApiService {
8275
9718
  ? { ...request, llmConfig: (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.buildSingleKeyConfig)(currentConfig) }
8276
9719
  : request;
8277
9720
  try {
8278
- await this.sendMessageStreamInternal(requestToSend, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId);
9721
+ await this.sendAgentV2StreamInternal(requestToSend, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId);
8279
9722
  // Success - reset key rotation state
8280
9723
  (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.resetKeyRotation)();
8281
9724
  return;
@@ -8322,10 +9765,18 @@ class ApiService {
8322
9765
  };
8323
9766
  }
8324
9767
  /**
8325
- * Internal streaming implementation (without retry logic)
8326
- * Uses LangChain agent endpoint for improved middleware support
9768
+ * 기존 sendMessageStream 유지 (하위 호환성)
9769
+ * 내부적으로 sendAgentV2Stream 호출
9770
+ * @deprecated sendChatStream 또는 sendAgentV2Stream을 직접 사용하세요
9771
+ */
9772
+ async sendMessageStream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId) {
9773
+ return this.sendAgentV2Stream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId);
9774
+ }
9775
+ /**
9776
+ * Agent V2 스트리밍 내부 구현 - /agent/langchain/stream 사용
9777
+ * (기존 sendMessageStreamInternal에서 이름 변경)
8327
9778
  */
8328
- async sendMessageStreamInternal(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId) {
9779
+ async sendAgentV2StreamInternal(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId) {
8329
9780
  // Convert IChatRequest to LangChain AgentRequest format
8330
9781
  // Frontend's context has limited fields, map what's available
8331
9782
  const resourceContext = await this.getResourceUsageSnapshot();
@@ -8713,6 +10164,24 @@ class ApiService {
8713
10164
  }
8714
10165
  return result;
8715
10166
  }
10167
+ async readFile(path, options) {
10168
+ const response = await fetch(`${this.baseUrl}/read-file`, {
10169
+ method: 'POST',
10170
+ headers: this.getHeaders(),
10171
+ credentials: 'include',
10172
+ body: JSON.stringify({
10173
+ path,
10174
+ encoding: options?.encoding || 'utf-8',
10175
+ cwd: options?.cwd
10176
+ })
10177
+ });
10178
+ const payload = await response.json().catch(() => ({}));
10179
+ if (!response.ok) {
10180
+ const errorMessage = payload.error || 'Failed to read file';
10181
+ return { success: false, error: errorMessage };
10182
+ }
10183
+ return payload;
10184
+ }
8716
10185
  async writeFile(path, content, options) {
8717
10186
  const response = await fetch(`${this.baseUrl}/write-file`, {
8718
10187
  method: 'POST',
@@ -8799,6 +10268,14 @@ class ApiService {
8799
10268
  });
8800
10269
  const payload = await response.json().catch(() => ({}));
8801
10270
  if (!response.ok) {
10271
+ if (response.status === 404) {
10272
+ return {
10273
+ success: false,
10274
+ files: [],
10275
+ dataframes: [],
10276
+ error: 'Resource check endpoint is not available on this Jupyter server.'
10277
+ };
10278
+ }
8802
10279
  const errorMessage = payload.error || 'Failed to check resources';
8803
10280
  throw new Error(errorMessage);
8804
10281
  }
@@ -13513,9 +14990,11 @@ class SafetyChecker {
13513
14990
 
13514
14991
  __webpack_require__.r(__webpack_exports__);
13515
14992
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
14993
+ /* harmony export */ countDiffLines: () => (/* binding */ countDiffLines),
13516
14994
  /* harmony export */ escapeHtml: () => (/* binding */ escapeHtml),
13517
14995
  /* harmony export */ formatInlineMarkdown: () => (/* binding */ formatInlineMarkdown),
13518
14996
  /* harmony export */ formatMarkdownToHtml: () => (/* binding */ formatMarkdownToHtml),
14997
+ /* harmony export */ highlightDiff: () => (/* binding */ highlightDiff),
13519
14998
  /* harmony export */ highlightJavaScript: () => (/* binding */ highlightJavaScript),
13520
14999
  /* harmony export */ highlightPython: () => (/* binding */ highlightPython),
13521
15000
  /* harmony export */ normalizeIndentation: () => (/* binding */ normalizeIndentation),
@@ -13849,6 +15328,52 @@ function highlightPython(code) {
13849
15328
  });
13850
15329
  return highlighted;
13851
15330
  }
15331
+ /**
15332
+ * Highlight diff output with colors for additions/deletions
15333
+ */
15334
+ function highlightDiff(code) {
15335
+ const lines = code.split('\n');
15336
+ const highlightedLines = lines.map(line => {
15337
+ const escapedLine = escapeHtml(line);
15338
+ // File headers (--- and +++)
15339
+ if (line.startsWith('---') || line.startsWith('+++')) {
15340
+ return `<span class="diff-header">${escapedLine}</span>`;
15341
+ }
15342
+ // Hunk headers (@@ ... @@)
15343
+ if (line.startsWith('@@')) {
15344
+ return `<span class="diff-hunk">${escapedLine}</span>`;
15345
+ }
15346
+ // Added lines
15347
+ if (line.startsWith('+')) {
15348
+ return `<span class="diff-add">${escapedLine}</span>`;
15349
+ }
15350
+ // Removed lines
15351
+ if (line.startsWith('-')) {
15352
+ return `<span class="diff-del">${escapedLine}</span>`;
15353
+ }
15354
+ // Context lines (unchanged)
15355
+ return `<span class="diff-context">${escapedLine}</span>`;
15356
+ });
15357
+ return highlightedLines.join('\n');
15358
+ }
15359
+ /**
15360
+ * Count additions and deletions in diff code
15361
+ */
15362
+ function countDiffLines(code) {
15363
+ const lines = code.split('\n');
15364
+ let additions = 0;
15365
+ let deletions = 0;
15366
+ for (const line of lines) {
15367
+ // Skip file headers
15368
+ if (line.startsWith('+++') || line.startsWith('---'))
15369
+ continue;
15370
+ if (line.startsWith('+'))
15371
+ additions++;
15372
+ else if (line.startsWith('-'))
15373
+ deletions++;
15374
+ }
15375
+ return { additions, deletions };
15376
+ }
13852
15377
  /**
13853
15378
  * Highlight JavaScript code
13854
15379
  */
@@ -14155,16 +15680,29 @@ function formatMarkdownToHtml(text) {
14155
15680
  language: lang
14156
15681
  });
14157
15682
  // Create HTML for code block
14158
- const highlightedCode = lang === 'python' || lang === 'py'
14159
- ? highlightPython(trimmedCode)
14160
- : lang === 'javascript' || lang === 'js'
14161
- ? highlightJavaScript(trimmedCode)
14162
- : escapeHtml(trimmedCode);
14163
- const htmlBlock = '<div class="code-block-container" data-block-id="' + blockId + '">' +
15683
+ const isDiff = lang === 'diff';
15684
+ const highlightedCode = isDiff
15685
+ ? highlightDiff(trimmedCode)
15686
+ : lang === 'python' || lang === 'py'
15687
+ ? highlightPython(trimmedCode)
15688
+ : lang === 'javascript' || lang === 'js'
15689
+ ? highlightJavaScript(trimmedCode)
15690
+ : escapeHtml(trimmedCode);
15691
+ // For diff blocks, calculate and show line counts
15692
+ let diffLineCountHtml = '';
15693
+ if (isDiff) {
15694
+ const { additions, deletions } = countDiffLines(trimmedCode);
15695
+ diffLineCountHtml = '<span class="diff-line-counts">' +
15696
+ '<span class="diff-additions">+' + additions + '</span>' +
15697
+ '<span class="diff-deletions">-' + deletions + '</span>' +
15698
+ '</span>';
15699
+ }
15700
+ const htmlBlock = '<div class="code-block-container' + (isDiff ? ' diff-block' : '') + '" data-block-id="' + blockId + '">' +
14164
15701
  '<div class="code-block-header">' +
14165
15702
  '<span class="code-block-language">' + escapeHtml(lang) + '</span>' +
15703
+ diffLineCountHtml +
14166
15704
  '<div class="code-block-actions">' +
14167
- '<button class="code-block-apply" data-block-id="' + blockId + '" title="셀에 적용">셀에 적용</button>' +
15705
+ (isDiff ? '' : '<button class="code-block-apply" data-block-id="' + blockId + '" title="셀에 적용">셀에 적용</button>') +
14168
15706
  '<button class="code-block-copy" data-block-id="' + blockId + '" title="복사">복사</button>' +
14169
15707
  '</div>' +
14170
15708
  '</div>' +
@@ -14446,4 +15984,4 @@ __webpack_require__.r(__webpack_exports__);
14446
15984
  /***/ }
14447
15985
 
14448
15986
  }]);
14449
- //# sourceMappingURL=lib_index_js.29cf4312af19e86f82af.js.map
15987
+ //# sourceMappingURL=lib_index_js.e4ff4b5779b5e049f84c.js.map