hdsp-jupyter-extension 2.0.8__py3-none-any.whl → 2.0.10__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 (63) hide show
  1. agent_server/core/rag_manager.py +12 -3
  2. agent_server/core/retriever.py +2 -1
  3. agent_server/core/vllm_embedding_service.py +8 -5
  4. agent_server/langchain/ARCHITECTURE.md +7 -51
  5. agent_server/langchain/agent.py +31 -20
  6. agent_server/langchain/custom_middleware.py +148 -31
  7. agent_server/langchain/hitl_config.py +0 -8
  8. agent_server/langchain/llm_factory.py +85 -1
  9. agent_server/langchain/logging_utils.py +7 -7
  10. agent_server/langchain/prompts.py +45 -36
  11. agent_server/langchain/tools/__init__.py +1 -10
  12. agent_server/langchain/tools/file_tools.py +9 -61
  13. agent_server/langchain/tools/jupyter_tools.py +0 -1
  14. agent_server/langchain/tools/lsp_tools.py +8 -8
  15. agent_server/langchain/tools/resource_tools.py +12 -12
  16. agent_server/langchain/tools/search_tools.py +3 -158
  17. agent_server/routers/langchain_agent.py +122 -113
  18. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  19. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  20. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js → hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js +93 -4
  21. hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
  22. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js → hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.dc6434bee96ab03a0539.js +90 -71
  23. hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.dc6434bee96ab03a0539.js.map +1 -0
  24. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js → hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.4a252df3ade74efee8d6.js +6 -6
  25. hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.4a252df3ade74efee8d6.js.map +1 -0
  26. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/METADATA +1 -3
  27. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/RECORD +57 -57
  28. jupyter_ext/_version.py +1 -1
  29. jupyter_ext/labextension/build_log.json +1 -1
  30. jupyter_ext/labextension/package.json +2 -2
  31. jupyter_ext/labextension/static/{frontend_styles_index_js.8740a527757068814573.js → frontend_styles_index_js.2d9fb488c82498c45c2d.js} +93 -4
  32. jupyter_ext/labextension/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
  33. jupyter_ext/labextension/static/{lib_index_js.e4ff4b5779b5e049f84c.js → lib_index_js.dc6434bee96ab03a0539.js} +90 -71
  34. jupyter_ext/labextension/static/lib_index_js.dc6434bee96ab03a0539.js.map +1 -0
  35. jupyter_ext/labextension/static/{remoteEntry.020cdb0b864cfaa4e41e.js → remoteEntry.4a252df3ade74efee8d6.js} +6 -6
  36. jupyter_ext/labextension/static/remoteEntry.4a252df3ade74efee8d6.js.map +1 -0
  37. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
  38. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
  39. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
  40. jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
  41. jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
  42. jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
  43. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  44. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  45. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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
  46. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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
  47. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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
  48. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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
  49. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  50. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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
  51. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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
  52. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
  53. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -0
  54. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
  55. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -0
  56. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  57. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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
  58. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  59. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  60. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
  61. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
  62. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/WHEEL +0 -0
  63. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/licenses/LICENSE +0 -0
@@ -1157,10 +1157,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1157
1157
  console.log('[AgentPanel] Captured agentThreadId from interrupt:', interrupt.threadId);
1158
1158
  }
1159
1159
  // Auto-approve search/file/resource tools - execute immediately without user interaction
1160
- if (interrupt.action === 'search_workspace_tool'
1161
- || interrupt.action === 'search_notebook_cells_tool'
1160
+ if (interrupt.action === 'search_notebook_cells_tool'
1162
1161
  || interrupt.action === 'check_resource_tool'
1163
- || interrupt.action === 'list_files_tool'
1164
1162
  || interrupt.action === 'read_file_tool') {
1165
1163
  void handleAutoToolInterrupt(interrupt);
1166
1164
  return;
@@ -2212,8 +2210,12 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2212
2210
  if (!pathCheck.valid) {
2213
2211
  return { success: false, error: pathCheck.error };
2214
2212
  }
2215
- const maxLines = typeof params.maxLines === 'number' ? params.maxLines : 1000;
2216
- const safeMaxLines = Math.max(0, maxLines);
2213
+ // Support both old (maxLines) and new (offset/limit) parameters
2214
+ const offset = typeof params.offset === 'number' ? Math.max(0, params.offset) : 0;
2215
+ const limit = typeof params.limit === 'number'
2216
+ ? params.limit
2217
+ : (typeof params.maxLines === 'number' ? params.maxLines : 500);
2218
+ const safeLimit = Math.max(0, limit);
2217
2219
  const contentsResult = await fetchContentsModel(params.path, { content: true, format: 'text' });
2218
2220
  if (!contentsResult.success) {
2219
2221
  return { success: false, error: contentsResult.error };
@@ -2233,13 +2235,18 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2233
2235
  content = JSON.stringify(content, null, 2);
2234
2236
  }
2235
2237
  const lines = content.split('\n');
2236
- const sliced = lines.slice(0, safeMaxLines);
2238
+ const totalLines = lines.length;
2239
+ // Apply offset and limit
2240
+ const sliced = lines.slice(offset, offset + safeLimit);
2237
2241
  return {
2238
2242
  success: true,
2239
2243
  output: sliced.join('\n'),
2240
2244
  metadata: {
2241
2245
  lineCount: sliced.length,
2242
- truncated: lines.length > safeMaxLines
2246
+ totalLines,
2247
+ offset,
2248
+ limit: safeLimit,
2249
+ truncated: totalLines > offset + safeLimit
2243
2250
  }
2244
2251
  };
2245
2252
  };
@@ -2249,18 +2256,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2249
2256
  console.log('[AgentPanel] Auto-approving tool:', action, args);
2250
2257
  try {
2251
2258
  let executionResult;
2252
- if (action === 'search_workspace_tool') {
2253
- setDebugStatus(`🔍 검색 실행 중: ${args?.pattern || ''}`);
2254
- executionResult = await apiService.searchWorkspace({
2255
- pattern: args?.pattern || '',
2256
- file_types: args?.file_types || ['*.py', '*.ipynb'],
2257
- path: args?.path || '.',
2258
- max_results: args?.max_results || 50,
2259
- case_sensitive: args?.case_sensitive || false
2260
- });
2261
- console.log('[AgentPanel] search_workspace result:', executionResult);
2262
- }
2263
- else if (action === 'search_notebook_cells_tool') {
2259
+ if (action === 'search_notebook_cells_tool') {
2264
2260
  setDebugStatus(`🔍 노트북 검색 실행 중: ${args?.pattern || ''}`);
2265
2261
  executionResult = await apiService.searchNotebookCells({
2266
2262
  pattern: args?.pattern || '',
@@ -2282,22 +2278,13 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2282
2278
  });
2283
2279
  console.log('[AgentPanel] check_resource result:', executionResult);
2284
2280
  }
2285
- else if (action === 'list_files_tool') {
2286
- setDebugStatus('📂 파일 목록 조회 중...');
2287
- const listParams = {
2288
- path: typeof args?.path === 'string' ? args.path : '.',
2289
- recursive: args?.recursive ?? false,
2290
- pattern: args?.pattern ?? undefined
2291
- };
2292
- executionResult = await executeListFilesTool(listParams);
2293
- console.log('[AgentPanel] list_files result:', executionResult);
2294
- }
2295
2281
  else if (action === 'read_file_tool') {
2296
2282
  setDebugStatus('📄 파일 읽는 중...');
2297
2283
  const readParams = {
2298
2284
  path: typeof args?.path === 'string' ? args.path : '',
2299
2285
  encoding: typeof args?.encoding === 'string' ? args.encoding : undefined,
2300
- maxLines: args?.max_lines ?? args?.maxLines
2286
+ offset: typeof args?.offset === 'number' ? args.offset : 0,
2287
+ limit: typeof args?.limit === 'number' ? args.limit : (args?.max_lines ?? args?.maxLines ?? 500)
2301
2288
  };
2302
2289
  executionResult = await executeReadFileTool(readParams);
2303
2290
  console.log('[AgentPanel] read_file result:', executionResult);
@@ -2341,10 +2328,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2341
2328
  approvalPendingRef.current = true;
2342
2329
  const autoApproveEnabled = getAutoApproveEnabled(llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)());
2343
2330
  // Handle next interrupt (could be another search or code execution)
2344
- if (nextInterrupt.action === 'search_workspace_tool'
2345
- || nextInterrupt.action === 'search_notebook_cells_tool'
2331
+ if (nextInterrupt.action === 'search_notebook_cells_tool'
2346
2332
  || nextInterrupt.action === 'check_resource_tool'
2347
- || nextInterrupt.action === 'list_files_tool'
2348
2333
  || nextInterrupt.action === 'read_file_tool') {
2349
2334
  void handleAutoToolInterrupt(nextInterrupt);
2350
2335
  return;
@@ -2673,7 +2658,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2673
2658
  };
2674
2659
  }
2675
2660
  else {
2676
- // LSP not available - suggest using search_workspace_tool
2661
+ // LSP not available - suggest using execute_command_tool with grep
2677
2662
  console.log('[AgentPanel] LSP not available for references, suggesting grep fallback');
2678
2663
  resumeDecision = 'approve';
2679
2664
  resumeArgs = {
@@ -2839,10 +2824,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2839
2824
  approvalPendingRef.current = true;
2840
2825
  const autoApproveEnabled = getAutoApproveEnabled(llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)());
2841
2826
  // Auto-approve search/file/resource tools
2842
- if (nextInterrupt.action === 'search_workspace_tool'
2843
- || nextInterrupt.action === 'search_notebook_cells_tool'
2827
+ if (nextInterrupt.action === 'search_notebook_cells_tool'
2844
2828
  || nextInterrupt.action === 'check_resource_tool'
2845
- || nextInterrupt.action === 'list_files_tool'
2846
2829
  || nextInterrupt.action === 'read_file_tool') {
2847
2830
  void handleAutoToolInterrupt(nextInterrupt);
2848
2831
  return;
@@ -5699,10 +5682,10 @@ class IdleMonitorService {
5699
5682
  console.log('[IdleMonitor] Idle timeout reached. Triggering shutdown...');
5700
5683
  // Hide countdown
5701
5684
  this.hideCountdown();
5702
- // Show alert popup
5685
+ // Call shutdown API FIRST (non-blocking) - shutdown starts immediately
5686
+ this.callShutdownApi().catch(e => console.error('[IdleMonitor] Shutdown API error:', e));
5687
+ // Show notification popup (for user info only - shutdown already initiated)
5703
5688
  alert(`${this.idleTimeoutMinutes}분 동안 활동이 없어 세션이 종료됩니다.`);
5704
- // Call shutdown API
5705
- await this.callShutdownApi();
5706
5689
  }
5707
5690
  /**
5708
5691
  * Call HDSP shutdown API via Jupyter server endpoint
@@ -6059,7 +6042,7 @@ class LSPBridgeService {
6059
6042
  };
6060
6043
  }
6061
6044
  // TODO: jupyterlab-lsp 연결 시 구현
6062
- // 현재는 빈 결과 반환 (Agent가 search_workspace_tool 사용하도록)
6045
+ // 현재는 빈 결과 반환 (Agent가 execute_command_tool with grep 사용하도록)
6063
6046
  return {
6064
6047
  success: false,
6065
6048
  locations: []
@@ -9062,20 +9045,15 @@ Avoid unnecessary introductions or conclusions.
9062
9045
  ## Task Management
9063
9046
  Use write_todos for complex multi-step tasks (3+ steps). Mark tasks in_progress before starting, completed immediately after finishing.
9064
9047
  For simple 1-2 step tasks, just do them directly without todos.
9048
+ When creating a todo list, ALWAYS include "작업 요약 및 다음단계 제시" as the LAST todo item.
9065
9049
 
9066
9050
  You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
9067
9051
  1. Check your todo list - are there pending or in_progress items?
9068
9052
  2. If YES → call the next appropriate tool (jupyter_cell_tool, markdown_tool, etc.)
9069
- 3. When you suggest next steps for todo item '다음 단계 제시', you MUST create next steps in json format matching this schema:
9070
- {
9071
- "next_items": [
9072
- {
9073
- "subject": "<subject for next step>",
9074
- "description": "<detailed description for the next step>"
9075
- }, ...
9076
- ]
9077
- }
9078
- 4. If ALL todos are completed → call final_answer_tool with a summary
9053
+ 3. When executing "작업 요약 다음단계 제시" (final todo):
9054
+ - Output this JSON as text content: {"summary": "실행된 작업 요약", "next_items": [{"subject": "제목", "description": "설명"}]}
9055
+ - AND call write_todos to mark all as 'completed' IN THE SAME RESPONSE
9056
+ - Both content AND tool call must be in ONE response
9079
9057
 
9080
9058
  ## 🔴 MANDATORY: Resource Check Before Data Hanlding
9081
9059
  **ALWAYS call check_resource_tool FIRST** when the task involves:
@@ -9086,16 +9064,26 @@ You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
9086
9064
  ## Mandatory Workflow
9087
9065
  1. After EVERY tool result, immediately call the next tool
9088
9066
  2. Continue until ALL todos show status: "completed"
9089
- 3. ONLY THEN call final_answer_tool to summarize
9067
+ 3. When all todos are completed, the session ends automatically
9090
9068
  4. Only use jupyter_cell_tool for Python code or when the user explicitly asks to run in a notebook cell
9091
9069
  5. For plots and charts, use English text only.
9092
9070
 
9093
9071
  ## ❌ FORBIDDEN (will break the workflow)
9094
9072
  - Producing an empty response (no tool call, no content)
9095
9073
  - Stopping after any tool without calling the next tool
9096
- - Ending without calling final_answer_tool
9097
9074
  - Leaving todos in "in_progress" or "pending" state without continuing
9098
9075
 
9076
+ ## 📂 File Search Best Practices
9077
+ **CRITICAL**: Use \`execute_command_tool\` with find/grep commands for file searching.
9078
+
9079
+ **To find files by NAME** (e.g., find titanic.csv):
9080
+ - \`execute_command_tool(command="find . -iname '*titanic*.csv' 2>/dev/null")\`
9081
+ - \`execute_command_tool(command="find . -name '*.csv' 2>/dev/null")\` - find all CSV files
9082
+
9083
+ **To search file CONTENTS** (e.g., find code containing "import pandas"):
9084
+ - \`execute_command_tool(command="grep -rn 'import pandas' --include='*.py' .")\`
9085
+ - \`execute_command_tool(command="grep -rn 'def train_model' --include='*.py' --include='*.ipynb' .")\`
9086
+
9099
9087
  ## 📖 File Reading Best Practices
9100
9088
  **CRITICAL**: When exploring codebases or reading files, use pagination to prevent context overflow.
9101
9089
 
@@ -12183,7 +12171,7 @@ class ToolExecutor {
12183
12171
  return { text, truncated: lines.length > maxLines };
12184
12172
  }
12185
12173
  /**
12186
- * read_file 도구: 파일 읽기
12174
+ * read_file 도구: 파일 읽기 (offset/limit 지원)
12187
12175
  */
12188
12176
  async executeReadFile(params) {
12189
12177
  console.log('[ToolExecutor] executeReadFile:', params);
@@ -12193,15 +12181,29 @@ class ToolExecutor {
12193
12181
  return { success: false, error: pathCheck.error };
12194
12182
  }
12195
12183
  const encoding = params.encoding || 'utf-8';
12196
- const maxLines = params.maxLines || 1000;
12197
- // Python 코드로 파일 읽기 (커널에서 실행)
12184
+ // Support both old (maxLines) and new (offset/limit) parameters
12185
+ const offset = typeof params.offset === 'number' ? Math.max(0, params.offset) : 0;
12186
+ const limit = typeof params.limit === 'number'
12187
+ ? params.limit
12188
+ : (typeof params.maxLines === 'number' ? params.maxLines : 500);
12189
+ // Python 코드로 파일 읽기 (커널에서 실행) - offset/limit 적용
12198
12190
  const pythonCode = `
12199
12191
  import json
12200
12192
  try:
12201
12193
  with open(${JSON.stringify(params.path)}, 'r', encoding=${JSON.stringify(encoding)}) as f:
12202
- lines = f.readlines()[:${maxLines}]
12203
- content = ''.join(lines)
12204
- result = {'success': True, 'content': content, 'lineCount': len(lines), 'truncated': len(lines) >= ${maxLines}}
12194
+ all_lines = f.readlines()
12195
+ total_lines = len(all_lines)
12196
+ sliced_lines = all_lines[${offset}:${offset + limit}]
12197
+ content = ''.join(sliced_lines)
12198
+ result = {
12199
+ 'success': True,
12200
+ 'content': content,
12201
+ 'lineCount': len(sliced_lines),
12202
+ 'totalLines': total_lines,
12203
+ 'offset': ${offset},
12204
+ 'limit': ${limit},
12205
+ 'truncated': total_lines > ${offset + limit}
12206
+ }
12205
12207
  except FileNotFoundError:
12206
12208
  result = {'success': False, 'error': f'File not found: ${params.path}'}
12207
12209
  except PermissionError:
@@ -15068,19 +15070,19 @@ function extractNextItemsBlock(text) {
15068
15070
  if (lang && lang !== 'json' && !content.includes('"next_items"')) {
15069
15071
  continue;
15070
15072
  }
15071
- const items = parseNextItemsPayload(content);
15072
- if (items) {
15073
+ const parsed = parseNextItemsPayload(content);
15074
+ if (parsed) {
15073
15075
  const placeholder = `__NEXT_ITEMS_${Math.random().toString(36).slice(2, 11)}__`;
15074
15076
  const updated = text.slice(0, match.index) + placeholder + text.slice(match.index + match[0].length);
15075
- return { items, placeholder, text: updated };
15077
+ return { items: parsed.items, summary: parsed.summary, placeholder, text: updated };
15076
15078
  }
15077
15079
  }
15078
15080
  const range = findNextItemsJsonRange(text);
15079
15081
  if (!range)
15080
15082
  return null;
15081
15083
  const candidate = text.slice(range.start, range.end + 1);
15082
- const items = parseNextItemsPayload(candidate);
15083
- if (!items)
15084
+ const parsed = parseNextItemsPayload(candidate);
15085
+ if (!parsed)
15084
15086
  return null;
15085
15087
  let replacementStart = range.start;
15086
15088
  const lineStart = text.lastIndexOf('\n', range.start - 1) + 1;
@@ -15091,7 +15093,7 @@ function extractNextItemsBlock(text) {
15091
15093
  }
15092
15094
  const placeholder = `__NEXT_ITEMS_${Math.random().toString(36).slice(2, 11)}__`;
15093
15095
  const updated = text.slice(0, replacementStart) + placeholder + text.slice(range.end + 1);
15094
- return { items, placeholder, text: updated };
15096
+ return { items: parsed.items, summary: parsed.summary, placeholder, text: updated };
15095
15097
  }
15096
15098
  function findNextItemsJsonRange(text) {
15097
15099
  const key = '"next_items"';
@@ -15169,17 +15171,25 @@ function parseNextItemsPayload(payload) {
15169
15171
  return { subject, description };
15170
15172
  })
15171
15173
  .filter((item) => Boolean(item));
15172
- return items.length > 0 ? items : null;
15174
+ // Extract summary field if present
15175
+ const summaryRaw = parsed.summary;
15176
+ const summary = typeof summaryRaw === 'string' && summaryRaw.trim() ? summaryRaw.trim() : null;
15177
+ return items.length > 0 ? { items, summary } : null;
15173
15178
  }
15174
15179
  catch {
15175
15180
  return null;
15176
15181
  }
15177
15182
  }
15178
- function renderNextItemsList(items) {
15179
- // Simple arrow icon
15183
+ function renderNextItemsList(items, summary) {
15184
+ // Simple arrow icon for next items
15180
15185
  const arrowSvg = `
15181
15186
  <svg class="jp-next-items-icon" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
15182
15187
  <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"/>
15188
+ </svg>`;
15189
+ // Checkmark icon for summary
15190
+ const checkSvg = `
15191
+ <svg class="jp-summary-icon" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
15192
+ <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
15183
15193
  </svg>`;
15184
15194
  const listItems = items.map((item) => {
15185
15195
  const subject = escapeHtml(item.subject);
@@ -15195,7 +15205,16 @@ function renderNextItemsList(items) {
15195
15205
  ${arrowSvg}
15196
15206
  </li>`;
15197
15207
  }).join('');
15198
- return `
15208
+ // Render summary as separate block above next items
15209
+ const summaryHtml = summary ? `
15210
+ <div class="jp-summary-block">
15211
+ <div class="jp-summary-header">작업 요약</div>
15212
+ <div class="jp-summary-body">
15213
+ ${checkSvg}
15214
+ <div class="jp-summary-content">${escapeHtml(summary)}</div>
15215
+ </div>
15216
+ </div>` : '';
15217
+ return `${summaryHtml}
15199
15218
  <div class="jp-next-items" data-next-items="true">
15200
15219
  <div class="jp-next-items-header">다음 단계 제안</div>
15201
15220
  <ul class="jp-next-items-list" role="list">
@@ -15821,7 +15840,7 @@ function formatMarkdownToHtml(text) {
15821
15840
  });
15822
15841
  // Step 8.5: Restore next items list placeholders
15823
15842
  if (nextItemsBlock) {
15824
- html = html.split(nextItemsBlock.placeholder).join(renderNextItemsList(nextItemsBlock.items));
15843
+ html = html.split(nextItemsBlock.placeholder).join(renderNextItemsList(nextItemsBlock.items, nextItemsBlock.summary));
15825
15844
  }
15826
15845
  return html;
15827
15846
  }
@@ -15984,4 +16003,4 @@ __webpack_require__.r(__webpack_exports__);
15984
16003
  /***/ }
15985
16004
 
15986
16005
  }]);
15987
- //# sourceMappingURL=lib_index_js.e4ff4b5779b5e049f84c.js.map
16006
+ //# sourceMappingURL=lib_index_js.dc6434bee96ab03a0539.js.map