hdsp-jupyter-extension 2.0.8__py3-none-any.whl → 2.0.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. agent_server/core/notebook_generator.py +4 -4
  2. agent_server/core/rag_manager.py +12 -3
  3. agent_server/core/retriever.py +2 -1
  4. agent_server/core/vllm_embedding_service.py +8 -5
  5. agent_server/langchain/ARCHITECTURE.md +7 -51
  6. agent_server/langchain/agent.py +31 -20
  7. agent_server/langchain/custom_middleware.py +234 -31
  8. agent_server/langchain/hitl_config.py +5 -8
  9. agent_server/langchain/logging_utils.py +7 -7
  10. agent_server/langchain/prompts.py +106 -120
  11. agent_server/langchain/tools/__init__.py +1 -10
  12. agent_server/langchain/tools/file_tools.py +9 -61
  13. agent_server/langchain/tools/jupyter_tools.py +0 -1
  14. agent_server/langchain/tools/lsp_tools.py +8 -8
  15. agent_server/langchain/tools/resource_tools.py +12 -12
  16. agent_server/langchain/tools/search_tools.py +3 -158
  17. agent_server/prompts/file_action_prompts.py +8 -8
  18. agent_server/routers/langchain_agent.py +200 -125
  19. hdsp_agent_core/__init__.py +46 -47
  20. hdsp_agent_core/factory.py +6 -10
  21. hdsp_agent_core/interfaces.py +4 -2
  22. hdsp_agent_core/knowledge/__init__.py +5 -5
  23. hdsp_agent_core/knowledge/chunking.py +87 -61
  24. hdsp_agent_core/knowledge/loader.py +103 -101
  25. hdsp_agent_core/llm/service.py +192 -107
  26. hdsp_agent_core/managers/config_manager.py +16 -22
  27. hdsp_agent_core/managers/session_manager.py +5 -4
  28. hdsp_agent_core/models/__init__.py +12 -12
  29. hdsp_agent_core/models/agent.py +15 -8
  30. hdsp_agent_core/models/common.py +1 -2
  31. hdsp_agent_core/models/rag.py +48 -111
  32. hdsp_agent_core/prompts/__init__.py +12 -12
  33. hdsp_agent_core/prompts/cell_action_prompts.py +9 -7
  34. hdsp_agent_core/services/agent_service.py +10 -8
  35. hdsp_agent_core/services/chat_service.py +10 -6
  36. hdsp_agent_core/services/rag_service.py +3 -6
  37. hdsp_agent_core/tests/conftest.py +4 -1
  38. hdsp_agent_core/tests/test_factory.py +2 -2
  39. hdsp_agent_core/tests/test_services.py +12 -19
  40. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  41. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  42. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js +93 -4
  43. hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
  44. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js +153 -130
  45. hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
  46. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js +6 -6
  47. hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
  48. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/METADATA +1 -3
  49. hdsp_jupyter_extension-2.0.11.dist-info/RECORD +144 -0
  50. jupyter_ext/__init__.py +21 -11
  51. jupyter_ext/_version.py +1 -1
  52. jupyter_ext/handlers.py +69 -50
  53. jupyter_ext/labextension/build_log.json +1 -1
  54. jupyter_ext/labextension/package.json +2 -2
  55. jupyter_ext/labextension/static/{frontend_styles_index_js.8740a527757068814573.js → frontend_styles_index_js.2d9fb488c82498c45c2d.js} +93 -4
  56. jupyter_ext/labextension/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
  57. jupyter_ext/labextension/static/{lib_index_js.e4ff4b5779b5e049f84c.js → lib_index_js.58c1e128ba0b76f41f04.js} +153 -130
  58. jupyter_ext/labextension/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
  59. jupyter_ext/labextension/static/{remoteEntry.020cdb0b864cfaa4e41e.js → remoteEntry.9da31d1134a53b0c4af5.js} +6 -6
  60. jupyter_ext/labextension/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
  61. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
  62. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
  63. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
  64. hdsp_jupyter_extension-2.0.8.dist-info/RECORD +0 -144
  65. jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
  66. jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
  67. jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
  68. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  69. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  70. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +0 -0
  71. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +0 -0
  72. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +0 -0
  73. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +0 -0
  74. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  75. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +0 -0
  76. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +0 -0
  77. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
  78. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -0
  79. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
  80. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -0
  81. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  82. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +0 -0
  83. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  84. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  85. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
  86. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
  87. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/WHEEL +0 -0
  88. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/licenses/LICENSE +0 -0
@@ -32,17 +32,9 @@ def get_hitl_interrupt_config() -> Dict[str, Any]:
32
32
  "allowed_decisions": ["approve", "edit"],
33
33
  "description": "📄 파일 읽기 실행 중",
34
34
  },
35
- "list_files_tool": {
36
- "allowed_decisions": ["approve", "edit"],
37
- "description": "📂 파일 목록 조회 중",
38
- },
39
35
  "write_todos": False, # Todo updates don't need approval
40
36
  # Search tools need HITL for client-side execution (auto-approved by frontend)
41
37
  # Uses 'edit' decision to pass execution_result back
42
- "search_workspace_tool": {
43
- "allowed_decisions": ["approve", "edit"],
44
- "description": "🔍 Searching workspace files",
45
- },
46
38
  "search_notebook_cells_tool": {
47
39
  "allowed_decisions": ["approve", "edit"],
48
40
  "description": "🔍 Searching notebook cells",
@@ -66,6 +58,11 @@ def get_hitl_interrupt_config() -> Dict[str, Any]:
66
58
  "allowed_decisions": ["approve", "edit", "reject"],
67
59
  "description": "File edit requires approval",
68
60
  },
61
+ # Multi-edit requires approval (multiple string replacements atomically)
62
+ "multiedit_file_tool": {
63
+ "allowed_decisions": ["approve", "edit", "reject"],
64
+ "description": "Multi-edit requires approval",
65
+ },
69
66
  # Final answer doesn't need approval
70
67
  "final_answer_tool": False,
71
68
  }
@@ -116,7 +116,7 @@ def _with_middleware_logging(name: str):
116
116
 
117
117
  class LLMTraceLogger(BaseCallbackHandler):
118
118
  """Log prompts, responses, tool calls, and tool messages.
119
-
119
+
120
120
  Only logs newly added messages to avoid duplicate logging of conversation history.
121
121
  Uses content hash of first message (usually system prompt) to identify conversation threads.
122
122
  """
@@ -126,7 +126,7 @@ class LLMTraceLogger(BaseCallbackHandler):
126
126
  # Track last logged message count per conversation thread
127
127
  # Key: hash of first message content, Value: message count
128
128
  self._last_message_counts: Dict[str, int] = {}
129
-
129
+
130
130
  def _get_conversation_key(self, batch) -> str:
131
131
  """Get a stable key for the conversation based on first message content."""
132
132
  if not batch:
@@ -136,7 +136,7 @@ class LLMTraceLogger(BaseCallbackHandler):
136
136
  # Use hash of first 200 chars of first message (usually system prompt)
137
137
  content_preview = str(content)[:200] if content else ""
138
138
  return str(hash(content_preview))
139
-
139
+
140
140
  def _normalize_batches(self, messages):
141
141
  if not messages:
142
142
  return []
@@ -151,7 +151,7 @@ class LLMTraceLogger(BaseCallbackHandler):
151
151
  conv_key = self._get_conversation_key(batch)
152
152
  batch_key = f"{conv_key}_{batch_idx}"
153
153
  last_count = self._last_message_counts.get(batch_key, 0)
154
-
154
+
155
155
  # Only log new messages
156
156
  new_messages = batch[last_count:]
157
157
  if not new_messages:
@@ -161,13 +161,13 @@ class LLMTraceLogger(BaseCallbackHandler):
161
161
  last_count,
162
162
  )
163
163
  continue
164
-
164
+
165
165
  # Update count
166
166
  self._last_message_counts[batch_key] = len(batch)
167
-
167
+
168
168
  # Log with offset info
169
169
  header = f"{title} (batch={batch_idx}, new={len(new_messages)}, total={len(batch)})"
170
-
170
+
171
171
  # Format new messages with correct indices
172
172
  lines = [LOG_SEPARATOR, header, LOG_SEPARATOR]
173
173
  for idx, message in enumerate(new_messages, start=last_count):
@@ -5,129 +5,119 @@ Contains system prompts, JSON schema for fallback tool calling,
5
5
  and middleware-specific prompts.
6
6
  """
7
7
 
8
- DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter notebook assistant.
9
- Your role is to help users with data analysis, visualization, and Python coding tasks in Jupyter notebooks. You can use only Korean
10
-
11
- # Core Behavior
12
- Be concise and direct. Answer in fewer than 4 lines unless the user asks for detail.
13
- After working on a file, just stop - don't explain what you did unless asked.
14
- Avoid unnecessary introductions or conclusions.
15
-
16
- ## Task Management
17
- Use write_todos for complex multi-step tasks (3+ steps). Mark tasks in_progress before starting, completed immediately after finishing.
18
- For simple 1-2 step tasks, just do them directly without todos.
19
-
20
- You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
21
- 1. Check your todo list - are there pending or in_progress items?
22
- 2. If YES call the next appropriate tool (jupyter_cell_tool, markdown_tool, etc.)
23
- 3. When you suggest next steps for todo item '다음 단계 제시', you MUST create next steps in json format matching this schema:
24
- {
25
- "next_items": [
26
- {
27
- "subject": "<subject for next step>",
28
- "description": "<detailed description for the next step>"
29
- }, ...
30
- ]
31
- }
32
- 4. If ALL todos are completed → call final_answer_tool with a summary
33
-
34
- ## 🔴 MANDATORY: Resource Check Before Data Hanlding
35
- **ALWAYS call check_resource_tool FIRST** when the task involves:
36
- - Loading files: .csv, .parquet, .json, .xlsx, .pickle, .h5, .feather
37
- - Handling datasets(dataframe) with pandas, polars, dask, or similar libraries
38
- - Training ML models on data files
39
-
40
- ## Mandatory Workflow
41
- 1. After EVERY tool result, immediately call the next tool
42
- 2. Continue until ALL todos show status: "completed"
43
- 3. ONLY THEN call final_answer_tool to summarize
44
- 4. Only use jupyter_cell_tool for Python code or when the user explicitly asks to run in a notebook cell
45
- 5. For plots and charts, use English text only.
46
-
47
- ## FORBIDDEN (will break the workflow)
48
- - Producing an empty response (no tool call, no content)
49
- - Stopping after any tool without calling the next tool
50
- - Ending without calling final_answer_tool
51
- - Leaving todos in "in_progress" or "pending" state without continuing
52
-
53
- ## 📖 File Reading Best Practices
54
- **CRITICAL**: When exploring codebases or reading files, use pagination to prevent context overflow.
55
-
56
- **Pattern for codebase exploration:**
57
- 1. First scan: `read_file_tool(path, limit=100)` - See file structure and key sections
58
- 2. Targeted read: `read_file_tool(path, offset=100, limit=200)` - Read specific sections if needed
59
- 3. Full read: Only read without limit when necessary for immediate editing
60
-
61
- **When to paginate (use offset/limit):**
62
- - Reading any file >500 lines
63
- - Exploring unfamiliar codebases (always start with limit=100)
64
- - Reading multiple files in sequence
65
- - Any research or investigation task
66
-
67
- **When full read is OK:**
68
- - Small files (<500 lines)
69
- - Files you need to edit immediately after reading
70
- - After confirming file size with first scan
71
-
72
- ## 🔧 Code Development
73
- 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.
8
+ DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter notebook assistant. Respond in Korean only.
9
+
10
+ # Core Rules
11
+ 1. Be concise (≤4 lines unless detail requested)
12
+ 2. ALWAYS call a tool in every response - never respond with text only
13
+ 3. ALWAYS include a brief Korean explanation before tool calls
14
+
15
+ # Task Workflow
16
+
17
+ ## Simple Tasks (1-2 steps)
18
+ Execute directly without todos.
19
+
20
+ ## Complex Tasks (3+ steps)
21
+ 1. Create todos with write_todos (all items in Korean)
22
+ 2. ALWAYS include "작업 요약 다음단계 제시" as the LAST item
23
+ 3. After each tool result: check todos call next tool repeat
24
+ 4. **Final todo ("작업 요약 및 다음단계 제시")**:
25
+ - FIRST: Output summary JSON in your content (REQUIRED!)
26
+ - THEN: Call write_todos to mark all as completed
27
+ - Both must be in the SAME response
28
+
29
+ ### Summary JSON Format (MUST output before marking complete)
30
+ ```json
31
+ {"summary": "실행된 작업 요약", "next_items": [{"subject": "제목", "description": "설명"}]}
32
+ ```
33
+ Suggest 3-5 next items. **You CANNOT mark "작업 요약" as completed without outputting this JSON first.**
34
+
35
+ # Mandatory Checks
36
+
37
+ ## Resource Check (BEFORE data operations)
38
+ Call `check_resource_tool` FIRST when:
39
+ - Loading files (.csv, .parquet, .json, .xlsx, .pickle, .h5, .feather)
40
+ - Using pandas/polars/dask for dataframes
41
+ - Training ML models
42
+
43
+ # Tool Usage
44
+
45
+ ## File Search (execute_command_tool)
46
+ ```bash
47
+ find . -iname '*filename*.csv' 2>/dev/null # Find by name
48
+ grep -rn 'pattern' --include='*.py' . # Search contents
49
+ ```
50
+
51
+ ## File Reading (read_file_tool)
52
+ - Large files: `read_file_tool(path, limit=100)` first
53
+ - Use `offset` for pagination
54
+ - Small files (<500 lines): Read without limit
55
+
56
+ ## Code Output
57
+ - For plots/charts: Use English labels only
58
+ - Use LSP tools for error checking and symbol lookup
59
+ - Use multiedit_file_tool for multiple changes
60
+
61
+ # Forbidden
62
+ - Empty responses (no tool call AND no content)
63
+ - Tool calls without Korean explanation
64
+ - Stopping with pending/in_progress todos
74
65
  """
75
66
 
76
- JSON_TOOL_SCHEMA = """You MUST respond with ONLY valid JSON matching this schema:
77
- {
78
- "tool": "<tool_name>",
79
- "arguments": {"arg1": "value1", ...}
80
- }
67
+ JSON_TOOL_SCHEMA = """Respond with ONLY valid JSON:
68
+ {"tool": "<name>", "arguments": {...}}
69
+
70
+ Tools:
71
+ - jupyter_cell_tool: {"code": "<python>"}
72
+ - markdown_tool: {"content": "<markdown>"}
73
+ - write_todos: {"todos": [{"content": "한국어 내용", "status": "pending|in_progress|completed"}]}
74
+ - read_file_tool: {"path": "<path>", "offset": 0, "limit": 500}
75
+ - write_file_tool: {"path": "<path>", "content": "<content>", "overwrite": false}
76
+ - search_notebook_cells_tool: {"pattern": "<regex>"}
77
+ - execute_command_tool: {"command": "<cmd>"}
78
+ - check_resource_tool: {"files": ["<path>"], "dataframes": ["<var>"]}
81
79
 
82
- Available tools:
83
- - jupyter_cell_tool: Execute Python code. Arguments: {"code": "<python_code>"}
84
- - markdown_tool: Add markdown cell. Arguments: {"content": "<markdown>"}
85
- - final_answer_tool: Complete task. Arguments: {"answer": "<summary>"}
86
- - write_todos: Update task list. Arguments: {"todos": [{"content": "...", "status": "pending|in_progress|completed"}]}
87
- - read_file_tool: Read file with pagination. Arguments: {"path": "<file_path>", "offset": 0, "limit": 500}
88
- - write_file_tool: Write file. Arguments: {"path": "<path>", "content": "<content>", "overwrite": false}
89
- - list_files_tool: List directory. Arguments: {"path": ".", "recursive": false}
90
- - search_workspace_tool: Search files. Arguments: {"pattern": "<regex>", "file_types": ["py"], "path": "."}
91
- - search_notebook_cells_tool: Search notebook cells. Arguments: {"pattern": "<regex>"}
92
- - execute_command_tool: Execute shell command. Arguments: {"command": "<command>", "stdin": "<input_for_prompts>"}
93
- - check_resource_tool: Check resources before data processing. Arguments: {"files": ["<path>"], "dataframes": ["<var_name>"]}
94
-
95
- Output ONLY the JSON object, no markdown, no explanation."""
80
+ No markdown wrapping. JSON only."""
96
81
 
97
82
  TODO_LIST_SYSTEM_PROMPT = """
98
- ## CRITICAL WORKFLOW RULES - MUST FOLLOW:
99
- - NEVER stop after calling write_todos - ALWAYS make another tool call immediately
100
- - For simple 1-2 step tasks, just do them directly without todos.
101
-
102
- ## 🔴 NEW USER MESSAGE = FRESH START:
103
- - When user sends a NEW message, treat it as a COMPLETELY NEW TASK
104
- - IGNORE any previous todo completion history - start fresh
105
- - Do NOT assume any work was already done based on past conversations
106
- - Create a NEW todo list for the new request, even if similar items existed before
107
- - "다음 단계 제시" from a previous task is NOT completed for the new task
108
-
109
- ## Todo List Management:
110
- - Before complex tasks, use write_todos to create a task list
111
- - Update todos as you complete each step (mark 'in_progress' → 'completed')
112
- - Each todo item should be specific and descriptive
113
- - All todo items must be written in Korean
114
- - ALWAYS include "다음 단계 제시" as the LAST item
115
-
116
- ## Task Completion Flow:
117
- 1. When current task is done mark it 'completed' with write_todos
118
- 2. For "다음 단계 제시" mark completed, then call final_answer_tool with suggestions
119
-
120
- ## FORBIDDEN PATTERNS:
121
- ❌ Calling write_todos and then stopping
122
- Updating todo status without doing the actual work
123
- Ending turn without calling final_answer_tool when all tasks are done
124
- Marking a todo as 'completed' without actually executing it in THIS conversation
83
+ # Todo Rules
84
+
85
+ ## New Message = Fresh Start
86
+ - Each user message is a NEW task
87
+ - Ignore completion status from chat history
88
+ - Execute ALL current todos from scratch
89
+
90
+ ## Structure
91
+ All todo items must be in Korean. Always end with:
92
+ - 작업 요약 다음단계 제시 ← 필수 마지막 항목!
93
+
94
+ ## Workflow
95
+ 1. Find pending/in_progress todo
96
+ 2. Execute it NOW in THIS response
97
+ 3. Mark completed
98
+ 4. Repeat until all done
99
+
100
+ ## 🔴 Final Todo ("작업 요약 및 다음단계 제시") - CRITICAL
101
+ When executing this todo, you MUST:
102
+ 1. Output the summary JSON in your content FIRST:
103
+ {"summary": "작업 내용 요약", "next_items": [{"subject": "...", "description": "..."}]}
104
+ 2. THEN call write_todos to mark all as completed
105
+ 3. If you don't output the JSON, the todo will NOT be marked as completed!
106
+
107
+ ## Completion Check
108
+ - Done: Executed in THIS response
109
+ - Not done: Only visible in chat history
110
+ - ❌ "작업 요약" cannot be completed without outputting summary JSON
111
+
112
+ ## Forbidden
113
+ - Marking complete without executing in THIS response
114
+ - Marking "작업 요약" complete without outputting JSON summary
115
+ - Todos without "작업 요약 및 다음단계 제시" as final item
125
116
  """
126
117
 
127
- TODO_LIST_TOOL_DESCRIPTION = """Update the task list for tracking progress.
128
- ⚠️ CRITICAL: This tool is ONLY for tracking - it does NOT do any actual work.
129
- After calling this tool, you MUST IMMEDIATELY call another tool (jupyter_cell_tool, markdown_tool, or final_answer_tool).
130
- NEVER end your response after calling write_todos - always continue with the next action tool."""
118
+ TODO_LIST_TOOL_DESCRIPTION = """Update task list for tracking progress.
119
+ This tool ONLY tracks status - does NOT execute tasks.
120
+ After calling: immediately call next action tool (unless ALL completed)."""
131
121
 
132
122
  # Non-HITL tools that execute immediately without user approval
133
123
  NON_HITL_TOOLS = {
@@ -135,10 +125,6 @@ NON_HITL_TOOLS = {
135
125
  "markdown",
136
126
  "read_file_tool",
137
127
  "read_file",
138
- "list_files_tool",
139
- "list_files",
140
- "search_workspace_tool",
141
- "search_workspace",
142
128
  "search_notebook_cells_tool",
143
129
  "search_notebook_cells",
144
130
  "write_todos",
@@ -4,14 +4,11 @@ LangChain Tools for Jupyter Agent
4
4
  Tools available:
5
5
  - jupyter_cell: Execute Python code in notebook
6
6
  - markdown: Add markdown cell
7
- - final_answer: Complete the task
8
7
  - read_file: Read file content
9
8
  - write_file: Write file content
10
9
  - edit_file: Edit file with string replacement
11
- - list_files: List directory contents
12
- - search_workspace: Search files in workspace
13
10
  - search_notebook_cells: Search cells in notebooks
14
- - execute_command_tool: Run shell commands (client-executed)
11
+ - execute_command_tool: Run shell commands (client-executed, also for file search)
15
12
  - check_resource_tool: Check resources before data processing (client-executed)
16
13
  - diagnostics_tool: Get LSP diagnostics (errors, warnings)
17
14
  - references_tool: Find symbol references via LSP
@@ -19,13 +16,11 @@ Tools available:
19
16
 
20
17
  from agent_server.langchain.tools.file_tools import (
21
18
  edit_file_tool,
22
- list_files_tool,
23
19
  multiedit_file_tool,
24
20
  read_file_tool,
25
21
  write_file_tool,
26
22
  )
27
23
  from agent_server.langchain.tools.jupyter_tools import (
28
- final_answer_tool,
29
24
  jupyter_cell_tool,
30
25
  markdown_tool,
31
26
  )
@@ -36,20 +31,16 @@ from agent_server.langchain.tools.lsp_tools import (
36
31
  from agent_server.langchain.tools.resource_tools import check_resource_tool
37
32
  from agent_server.langchain.tools.search_tools import (
38
33
  search_notebook_cells_tool,
39
- search_workspace_tool,
40
34
  )
41
35
  from agent_server.langchain.tools.shell_tools import execute_command_tool
42
36
 
43
37
  __all__ = [
44
38
  "jupyter_cell_tool",
45
39
  "markdown_tool",
46
- "final_answer_tool",
47
40
  "read_file_tool",
48
41
  "write_file_tool",
49
42
  "edit_file_tool",
50
43
  "multiedit_file_tool",
51
- "list_files_tool",
52
- "search_workspace_tool",
53
44
  "search_notebook_cells_tool",
54
45
  "execute_command_tool",
55
46
  "check_resource_tool",
@@ -5,7 +5,6 @@ Provides tools for file system operations:
5
5
  - read_file: Read file content
6
6
  - write_file: Write content to file (requires approval)
7
7
  - edit_file: Edit file with string replacement (requires approval)
8
- - list_files: List directory contents
9
8
  """
10
9
 
11
10
  import os
@@ -70,21 +69,6 @@ class EditFileInput(BaseModel):
70
69
  )
71
70
 
72
71
 
73
- class ListFilesInput(BaseModel):
74
- """Input schema for list_files tool"""
75
-
76
- path: str = Field(default=".", description="Directory path to list")
77
- recursive: bool = Field(default=False, description="Whether to list recursively")
78
- pattern: Optional[str] = Field(
79
- default=None,
80
- description="Glob pattern to filter files (e.g., '*.py', '*.ipynb')",
81
- )
82
- execution_result: Optional[Dict[str, Any]] = Field(
83
- default=None,
84
- description="Optional execution result payload from the client",
85
- )
86
-
87
-
88
72
  def _validate_path(path: str, workspace_root: str = ".") -> str:
89
73
  """
90
74
  Validate and resolve file path.
@@ -315,50 +299,13 @@ def edit_file_tool(
315
299
  }
316
300
 
317
301
 
318
- @tool(args_schema=ListFilesInput)
319
- def list_files_tool(
320
- path: str = ".",
321
- recursive: bool = False,
322
- pattern: Optional[str] = None,
323
- execution_result: Optional[Dict[str, Any]] = None,
324
- workspace_root: str = ".",
325
- ) -> Dict[str, Any]:
326
- """
327
- List files and directories.
328
-
329
- Args:
330
- path: Directory path to list (default: current directory)
331
- recursive: Whether to list recursively
332
- pattern: Optional glob pattern to filter (e.g., '*.py')
333
-
334
- Returns:
335
- Dict with list of files and directories
336
- """
337
- response: Dict[str, Any] = {
338
- "tool": "list_files_tool",
339
- "parameters": {
340
- "path": path,
341
- "recursive": recursive,
342
- "pattern": pattern,
343
- },
344
- "status": "pending_execution",
345
- "message": "File listing queued for execution by client",
346
- }
347
- if execution_result is not None:
348
- response["execution_result"] = execution_result
349
- response["status"] = "complete"
350
- response["message"] = "File listing executed with client-reported results"
351
- return response
352
-
353
-
354
302
  class EditOperation(BaseModel):
355
303
  """Single edit operation for multiedit_file tool"""
356
304
 
357
305
  old_string: str = Field(description="The exact string to find and replace")
358
306
  new_string: str = Field(description="The replacement string")
359
307
  replace_all: bool = Field(
360
- default=False,
361
- description="Whether to replace all occurrences (default: false)"
308
+ default=False, description="Whether to replace all occurrences (default: false)"
362
309
  )
363
310
 
364
311
 
@@ -456,12 +403,14 @@ def multiedit_file_tool(
456
403
 
457
404
  old_preview = (old_str[:50] + "...") if len(old_str) > 50 else old_str
458
405
  new_preview = (new_str[:50] + "...") if len(new_str) > 50 else new_str
459
- edits_preview.append({
460
- "index": i,
461
- "old_preview": old_preview,
462
- "new_preview": new_preview,
463
- "replace_all": replace_all_val
464
- })
406
+ edits_preview.append(
407
+ {
408
+ "index": i,
409
+ "old_preview": old_preview,
410
+ "new_preview": new_preview,
411
+ "replace_all": replace_all_val,
412
+ }
413
+ )
465
414
 
466
415
  # Convert all edits to dicts for serialization
467
416
  for edit in edits:
@@ -515,5 +464,4 @@ FILE_TOOLS = [
515
464
  write_file_tool,
516
465
  edit_file_tool,
517
466
  multiedit_file_tool,
518
- list_files_tool,
519
467
  ]
@@ -139,5 +139,4 @@ def final_answer_tool(answer: str, summary: Optional[str] = None) -> Dict[str, A
139
139
  JUPYTER_TOOLS = [
140
140
  jupyter_cell_tool,
141
141
  markdown_tool,
142
- final_answer_tool,
143
142
  ]
@@ -103,7 +103,7 @@ def diagnostics_tool(
103
103
  return {
104
104
  "tool": "diagnostics_tool",
105
105
  "success": True,
106
- "output": "LSP not available. Install jupyterlab-lsp for code diagnostics.\nUse search_workspace_tool for text-based code search instead.",
106
+ "output": "LSP not available. Install jupyterlab-lsp for code diagnostics.\nUse execute_command_tool with grep for text-based code search instead.",
107
107
  "counts": {"errors": 0, "warnings": 0, "total": 0},
108
108
  }
109
109
 
@@ -122,9 +122,7 @@ def diagnostics_tool(
122
122
 
123
123
  # Filter by severity if specified
124
124
  if severity_filter:
125
- sorted_diags = [
126
- d for d in sorted_diags if d.get("severity") == severity_filter
127
- ]
125
+ sorted_diags = [d for d in sorted_diags if d.get("severity") == severity_filter]
128
126
 
129
127
  # Format output (Crush의 formatDiagnostics 패턴)
130
128
  formatted_lines = []
@@ -153,7 +151,7 @@ def diagnostics_tool(
153
151
  # Add summary
154
152
  summary = f"\n--- Summary: {errors} errors, {warnings} warnings, {total} total"
155
153
  if total > 10:
156
- summary += f" (showing first 10)"
154
+ summary += " (showing first 10)"
157
155
 
158
156
  output = (
159
157
  "\n".join(formatted_lines) + summary
@@ -185,7 +183,7 @@ def references_tool(
185
183
  - Understand how a variable is used throughout the code
186
184
  - Find all usages before refactoring
187
185
 
188
- If LSP is not available, falls back to search_workspace_tool.
186
+ If LSP is not available, falls back to execute_command_tool with grep.
189
187
 
190
188
  Args:
191
189
  symbol: Symbol name (function, class, variable)
@@ -218,7 +216,7 @@ def references_tool(
218
216
  return {
219
217
  "tool": "references_tool",
220
218
  "success": True,
221
- "output": f"LSP not available. Use search_workspace_tool with pattern='{symbol}' for text-based search.",
219
+ "output": f"LSP not available. Use execute_command_tool with grep with pattern='{symbol}' for text-based search.",
222
220
  "count": 0,
223
221
  }
224
222
  return {
@@ -238,7 +236,9 @@ def references_tool(
238
236
 
239
237
  # Format output
240
238
  method_note = " (grep-based)" if used_grep else " (LSP)"
241
- formatted_lines = [f"Found {len(locations)} references to '{symbol}'{method_note}:\n"]
239
+ formatted_lines = [
240
+ f"Found {len(locations)} references to '{symbol}'{method_note}:\n"
241
+ ]
242
242
 
243
243
  for file, locs in sorted(by_file.items()):
244
244
  formatted_lines.append(f"\n📄 {file}")
@@ -46,7 +46,7 @@ def _build_file_size_command(files: List[str]) -> str:
46
46
  """
47
47
  if not files:
48
48
  return ""
49
-
49
+
50
50
  # Use stat with format that works on both macOS and Linux
51
51
  # macOS: stat -f "%z %N"
52
52
  # Linux: stat -c "%s %n"
@@ -62,10 +62,10 @@ def _build_dataframe_check_code(dataframes: List[str]) -> str:
62
62
  """
63
63
  if not dataframes:
64
64
  return ""
65
-
65
+
66
66
  df_checks = []
67
67
  for df_name in dataframes:
68
- df_checks.append(f'''
68
+ df_checks.append(f"""
69
69
  try:
70
70
  _df = {df_name}
71
71
  _info = {{
@@ -79,14 +79,14 @@ try:
79
79
  except NameError:
80
80
  _info = {{"name": "{df_name}", "exists": False}}
81
81
  _results.append(_info)
82
- ''')
83
-
84
- code = f'''
82
+ """)
83
+
84
+ code = f"""
85
85
  import json
86
86
  _results = []
87
87
  {chr(10).join(df_checks)}
88
88
  print(json.dumps(_results))
89
- '''
89
+ """
90
90
  return code.strip()
91
91
 
92
92
 
@@ -137,20 +137,20 @@ def check_resource_tool(
137
137
  response["execution_result"] = execution_result
138
138
  response["status"] = "complete"
139
139
  response["message"] = "Resource check completed"
140
-
140
+
141
141
  # Parse the execution result
142
142
  if isinstance(execution_result, dict):
143
143
  response["success"] = execution_result.get("success", False)
144
-
144
+
145
145
  # System resources
146
146
  response["system"] = execution_result.get("system", {})
147
-
147
+
148
148
  # File sizes
149
149
  response["files"] = execution_result.get("files", [])
150
-
150
+
151
151
  # DataFrame info
152
152
  response["dataframes"] = execution_result.get("dataframes", [])
153
-
153
+
154
154
  if "error" in execution_result:
155
155
  response["error"] = execution_result["error"]
156
156