hanzo-mcp 0.8.11__py3-none-any.whl → 0.8.13__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.
Potentially problematic release.
This version of hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +1 -3
- hanzo_mcp/analytics/posthog_analytics.py +3 -9
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +6 -15
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +1 -3
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +2 -6
- hanzo_mcp/tools/__init__.py +10 -24
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +5 -15
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +14 -41
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +75 -74
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/swarm_tool.py +9 -29
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/permissions.py +3 -9
- hanzo_mcp/tools/common/personality.py +9 -34
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +6 -18
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/filesystem/__init__.py +2 -3
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/base.py +4 -12
- hanzo_mcp/tools/filesystem/batch_search.py +35 -115
- hanzo_mcp/tools/filesystem/content_replace.py +4 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
- hanzo_mcp/tools/filesystem/edit.py +6 -18
- hanzo_mcp/tools/filesystem/find.py +3 -9
- hanzo_mcp/tools/filesystem/find_files.py +2 -6
- hanzo_mcp/tools/filesystem/git_search.py +9 -24
- hanzo_mcp/tools/filesystem/grep.py +9 -27
- hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
- hanzo_mcp/tools/filesystem/read.py +8 -26
- hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
- hanzo_mcp/tools/filesystem/search_tool.py +18 -62
- hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
- hanzo_mcp/tools/filesystem/tree.py +1 -3
- hanzo_mcp/tools/filesystem/watch.py +1 -3
- hanzo_mcp/tools/filesystem/write.py +1 -3
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
- hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
- hanzo_mcp/tools/mcp/mcp_add.py +1 -3
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +10 -27
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +6 -18
- hanzo_mcp/tools/search/find_tool.py +10 -32
- hanzo_mcp/tools/search/unified_search.py +24 -78
- hanzo_mcp/tools/shell/__init__.py +2 -2
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +26 -79
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/todo/todo_read.py +3 -9
- hanzo_mcp/tools/todo/todo_write.py +6 -18
- hanzo_mcp/tools/vector/__init__.py +3 -9
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +7 -27
- hanzo_mcp/tools/vector/mock_infinity.py +1 -3
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.13.dist-info}/METADATA +2 -2
- hanzo_mcp-0.8.13.dist-info/RECORD +193 -0
- hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.13.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.13.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.13.dist-info}/top_level.txt +0 -0
|
@@ -171,9 +171,7 @@ class UnifiedSearch(BaseTool):
|
|
|
171
171
|
# Use vector search for natural language queries
|
|
172
172
|
indicators = [
|
|
173
173
|
len(query.split()) > 2, # Multi-word queries
|
|
174
|
-
not any(
|
|
175
|
-
c in query for c in ["(", ")", "{", "}", "[", "]"]
|
|
176
|
-
), # Not code syntax
|
|
174
|
+
not any(c in query for c in ["(", ")", "{", "}", "[", "]"]), # Not code syntax
|
|
177
175
|
" " in query, # Has spaces (natural language)
|
|
178
176
|
not query.startswith("^") and not query.endswith("$"), # Not regex anchors
|
|
179
177
|
]
|
|
@@ -185,10 +183,7 @@ class UnifiedSearch(BaseTool):
|
|
|
185
183
|
indicators = [
|
|
186
184
|
"class " in query or "function " in query or "def " in query,
|
|
187
185
|
"import " in query or "from " in query,
|
|
188
|
-
any(
|
|
189
|
-
kw in query.lower()
|
|
190
|
-
for kw in ["method", "function", "class", "interface", "struct"]
|
|
191
|
-
),
|
|
186
|
+
any(kw in query.lower() for kw in ["method", "function", "class", "interface", "struct"]),
|
|
192
187
|
"::" in query or "->" in query or "." in query, # Member access
|
|
193
188
|
]
|
|
194
189
|
return any(indicators)
|
|
@@ -198,9 +193,7 @@ class UnifiedSearch(BaseTool):
|
|
|
198
193
|
# Use symbol search for identifiers
|
|
199
194
|
return (
|
|
200
195
|
len(query.split()) <= 2 # Short queries
|
|
201
|
-
and query.replace("_", "")
|
|
202
|
-
.replace("-", "")
|
|
203
|
-
.isalnum() # Looks like identifier
|
|
196
|
+
and query.replace("_", "").replace("-", "").isalnum() # Looks like identifier
|
|
204
197
|
and not " " in query.strip() # Single token
|
|
205
198
|
)
|
|
206
199
|
|
|
@@ -243,19 +236,14 @@ class UnifiedSearch(BaseTool):
|
|
|
243
236
|
# Search memory for natural language queries or specific references
|
|
244
237
|
search_memory = MEMORY_AVAILABLE and (
|
|
245
238
|
self._should_use_vector_search(pattern)
|
|
246
|
-
or any(
|
|
247
|
-
word in pattern.lower()
|
|
248
|
-
for word in ["previous", "discussion", "remember", "last"]
|
|
249
|
-
)
|
|
239
|
+
or any(word in pattern.lower() for word in ["previous", "discussion", "remember", "last"])
|
|
250
240
|
)
|
|
251
241
|
|
|
252
242
|
if enable_text is None:
|
|
253
243
|
enable_text = True # Always use text search as baseline
|
|
254
244
|
|
|
255
245
|
if enable_vector is None:
|
|
256
|
-
enable_vector = (
|
|
257
|
-
self._should_use_vector_search(pattern) and VECTOR_SEARCH_AVAILABLE
|
|
258
|
-
)
|
|
246
|
+
enable_vector = self._should_use_vector_search(pattern) and VECTOR_SEARCH_AVAILABLE
|
|
259
247
|
|
|
260
248
|
if enable_ast is None:
|
|
261
249
|
enable_ast = self._should_use_ast_search(pattern) and TREESITTER_AVAILABLE
|
|
@@ -277,9 +265,7 @@ class UnifiedSearch(BaseTool):
|
|
|
277
265
|
# 1. Text search (ripgrep) - always fast, do first
|
|
278
266
|
if enable_text:
|
|
279
267
|
start = time.time()
|
|
280
|
-
text_results = await self._text_search(
|
|
281
|
-
pattern, path, include, exclude, max_results_per_type, context_lines
|
|
282
|
-
)
|
|
268
|
+
text_results = await self._text_search(pattern, path, include, exclude, max_results_per_type, context_lines)
|
|
283
269
|
search_stats["time_ms"]["text"] = int((time.time() - start) * 1000)
|
|
284
270
|
search_stats["search_types_used"].append("text")
|
|
285
271
|
all_results.extend(text_results)
|
|
@@ -287,9 +273,7 @@ class UnifiedSearch(BaseTool):
|
|
|
287
273
|
# 2. AST search - for code structure
|
|
288
274
|
if enable_ast and TREESITTER_AVAILABLE:
|
|
289
275
|
start = time.time()
|
|
290
|
-
ast_results = await self._ast_search(
|
|
291
|
-
pattern, path, include, exclude, max_results_per_type, context_lines
|
|
292
|
-
)
|
|
276
|
+
ast_results = await self._ast_search(pattern, path, include, exclude, max_results_per_type, context_lines)
|
|
293
277
|
search_stats["time_ms"]["ast"] = int((time.time() - start) * 1000)
|
|
294
278
|
search_stats["search_types_used"].append("ast")
|
|
295
279
|
all_results.extend(ast_results)
|
|
@@ -297,9 +281,7 @@ class UnifiedSearch(BaseTool):
|
|
|
297
281
|
# 3. Symbol search - for definitions
|
|
298
282
|
if enable_symbol:
|
|
299
283
|
start = time.time()
|
|
300
|
-
symbol_results = await self._symbol_search(
|
|
301
|
-
pattern, path, include, exclude, max_results_per_type
|
|
302
|
-
)
|
|
284
|
+
symbol_results = await self._symbol_search(pattern, path, include, exclude, max_results_per_type)
|
|
303
285
|
search_stats["time_ms"]["symbol"] = int((time.time() - start) * 1000)
|
|
304
286
|
search_stats["search_types_used"].append("symbol")
|
|
305
287
|
all_results.extend(symbol_results)
|
|
@@ -317,9 +299,7 @@ class UnifiedSearch(BaseTool):
|
|
|
317
299
|
# 5. File search - for finding files by name/pattern
|
|
318
300
|
if search_files:
|
|
319
301
|
start = time.time()
|
|
320
|
-
file_results = await self._file_search(
|
|
321
|
-
pattern, path, include, exclude, max_results_per_type
|
|
322
|
-
)
|
|
302
|
+
file_results = await self._file_search(pattern, path, include, exclude, max_results_per_type)
|
|
323
303
|
search_stats["time_ms"]["files"] = int((time.time() - start) * 1000)
|
|
324
304
|
search_stats["search_types_used"].append("files")
|
|
325
305
|
all_results.extend(file_results)
|
|
@@ -327,9 +307,7 @@ class UnifiedSearch(BaseTool):
|
|
|
327
307
|
# 6. Memory search - for knowledge base and previous discussions
|
|
328
308
|
if search_memory:
|
|
329
309
|
start = time.time()
|
|
330
|
-
memory_results = await self._memory_search(
|
|
331
|
-
pattern, max_results_per_type, context_lines
|
|
332
|
-
)
|
|
310
|
+
memory_results = await self._memory_search(pattern, max_results_per_type, context_lines)
|
|
333
311
|
search_stats["time_ms"]["memory"] = int((time.time() - start) * 1000)
|
|
334
312
|
search_stats["search_types_used"].append("memory")
|
|
335
313
|
all_results.extend(memory_results)
|
|
@@ -428,9 +406,7 @@ class UnifiedSearch(BaseTool):
|
|
|
428
406
|
|
|
429
407
|
if not self.ripgrep_available:
|
|
430
408
|
# Fallback to Python implementation
|
|
431
|
-
return await self._python_text_search(
|
|
432
|
-
pattern, path, include, exclude, max_results, context_lines
|
|
433
|
-
)
|
|
409
|
+
return await self._python_text_search(pattern, path, include, exclude, max_results, context_lines)
|
|
434
410
|
|
|
435
411
|
# Build ripgrep command
|
|
436
412
|
cmd = ["rg", "--json", "--max-count", str(max_results)]
|
|
@@ -547,17 +523,9 @@ class UnifiedSearch(BaseTool):
|
|
|
547
523
|
file_path=str(file_path),
|
|
548
524
|
line_number=line_num,
|
|
549
525
|
column=0,
|
|
550
|
-
match_text=(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
else ""
|
|
554
|
-
),
|
|
555
|
-
context_before=lines[
|
|
556
|
-
max(0, line_num - context_lines - 1) : line_num - 1
|
|
557
|
-
],
|
|
558
|
-
context_after=lines[
|
|
559
|
-
line_num : min(len(lines), line_num + context_lines)
|
|
560
|
-
],
|
|
526
|
+
match_text=(lines[line_num - 1] if 0 < line_num <= len(lines) else ""),
|
|
527
|
+
context_before=lines[max(0, line_num - context_lines - 1) : line_num - 1],
|
|
528
|
+
context_after=lines[line_num : min(len(lines), line_num + context_lines)],
|
|
561
529
|
match_type="ast",
|
|
562
530
|
score=0.9,
|
|
563
531
|
node_type="ast_match",
|
|
@@ -650,10 +618,7 @@ class UnifiedSearch(BaseTool):
|
|
|
650
618
|
context_before=[],
|
|
651
619
|
context_after=[],
|
|
652
620
|
match_type="vector",
|
|
653
|
-
score=1.0
|
|
654
|
-
- search_results["distances"][0][
|
|
655
|
-
i
|
|
656
|
-
], # Convert distance to similarity
|
|
621
|
+
score=1.0 - search_results["distances"][0][i], # Convert distance to similarity
|
|
657
622
|
semantic_context=metadata.get("context", ""),
|
|
658
623
|
)
|
|
659
624
|
results.append(result)
|
|
@@ -712,9 +677,7 @@ class UnifiedSearch(BaseTool):
|
|
|
712
677
|
|
|
713
678
|
return results
|
|
714
679
|
|
|
715
|
-
async def _memory_search(
|
|
716
|
-
self, query: str, max_results: int, context_lines: int
|
|
717
|
-
) -> List[SearchResult]:
|
|
680
|
+
async def _memory_search(self, query: str, max_results: int, context_lines: int) -> List[SearchResult]:
|
|
718
681
|
"""Search in memory/knowledge base."""
|
|
719
682
|
results = []
|
|
720
683
|
|
|
@@ -748,9 +711,7 @@ class UnifiedSearch(BaseTool):
|
|
|
748
711
|
file_path=virtual_path,
|
|
749
712
|
line_number=1,
|
|
750
713
|
column=0,
|
|
751
|
-
match_text=(
|
|
752
|
-
content[:200] + "..." if len(content) > 200 else content
|
|
753
|
-
),
|
|
714
|
+
match_text=(content[:200] + "..." if len(content) > 200 else content),
|
|
754
715
|
context_before=[],
|
|
755
716
|
context_after=[],
|
|
756
717
|
match_type="memory",
|
|
@@ -793,9 +754,7 @@ class UnifiedSearch(BaseTool):
|
|
|
793
754
|
|
|
794
755
|
return unique
|
|
795
756
|
|
|
796
|
-
def _rank_results(
|
|
797
|
-
self, results: List[SearchResult], query: str
|
|
798
|
-
) -> List[SearchResult]:
|
|
757
|
+
def _rank_results(self, results: List[SearchResult], query: str) -> List[SearchResult]:
|
|
799
758
|
"""Rank results by relevance."""
|
|
800
759
|
# Simple ranking based on:
|
|
801
760
|
# 1. Match type score
|
|
@@ -808,16 +767,11 @@ class UnifiedSearch(BaseTool):
|
|
|
808
767
|
result.score *= 1.2
|
|
809
768
|
|
|
810
769
|
# Path relevance (prefer non-test, non-vendor files)
|
|
811
|
-
if any(
|
|
812
|
-
skip in result.file_path for skip in ["test", "vendor", "node_modules"]
|
|
813
|
-
):
|
|
770
|
+
if any(skip in result.file_path for skip in ["test", "vendor", "node_modules"]):
|
|
814
771
|
result.score *= 0.8
|
|
815
772
|
|
|
816
773
|
# Prefer definition files
|
|
817
|
-
if any(
|
|
818
|
-
pattern in result.file_path
|
|
819
|
-
for pattern in ["index.", "main.", "api.", "types."]
|
|
820
|
-
):
|
|
774
|
+
if any(pattern in result.file_path for pattern in ["index.", "main.", "api.", "types."]):
|
|
821
775
|
result.score *= 1.1
|
|
822
776
|
|
|
823
777
|
# Sort by score descending, then by file path
|
|
@@ -955,13 +909,9 @@ class CodeIndexer:
|
|
|
955
909
|
# Batch embed and store
|
|
956
910
|
if documents:
|
|
957
911
|
embeddings = self.embedder.encode(documents).tolist()
|
|
958
|
-
self.collection.add(
|
|
959
|
-
embeddings=embeddings, documents=documents, metadatas=metadatas, ids=ids
|
|
960
|
-
)
|
|
912
|
+
self.collection.add(embeddings=embeddings, documents=documents, metadatas=metadatas, ids=ids)
|
|
961
913
|
|
|
962
|
-
def _split_code_intelligently(
|
|
963
|
-
self, content: str, file_path: Path
|
|
964
|
-
) -> List[Dict[str, Any]]:
|
|
914
|
+
def _split_code_intelligently(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
|
|
965
915
|
"""Split code into meaningful chunks."""
|
|
966
916
|
# Simple line-based splitting for now
|
|
967
917
|
# TODO: Use AST for better splitting
|
|
@@ -976,9 +926,7 @@ class CodeIndexer:
|
|
|
976
926
|
current_chunk.append(line)
|
|
977
927
|
|
|
978
928
|
# Split on function/class definitions or every 50 lines
|
|
979
|
-
if len(current_chunk) >= 50 or any(
|
|
980
|
-
kw in line for kw in ["def ", "function ", "class ", "interface "]
|
|
981
|
-
):
|
|
929
|
+
if len(current_chunk) >= 50 or any(kw in line for kw in ["def ", "function ", "class ", "interface "]):
|
|
982
930
|
if current_chunk:
|
|
983
931
|
chunks.append(
|
|
984
932
|
{
|
|
@@ -992,9 +940,7 @@ class CodeIndexer:
|
|
|
992
940
|
|
|
993
941
|
# Add remaining
|
|
994
942
|
if current_chunk:
|
|
995
|
-
chunks.append(
|
|
996
|
-
{"text": "\n".join(current_chunk), "line": current_line, "type": "code"}
|
|
997
|
-
)
|
|
943
|
+
chunks.append({"text": "\n".join(current_chunk), "line": current_line, "type": "code"})
|
|
998
944
|
|
|
999
945
|
return chunks
|
|
1000
946
|
|
|
@@ -49,8 +49,8 @@ def get_shell_tools(
|
|
|
49
49
|
# Return shell_tool first (smart default), then specific shells
|
|
50
50
|
return [
|
|
51
51
|
shell_tool, # Smart shell (prefers zsh if available)
|
|
52
|
-
zsh_tool,
|
|
53
|
-
bash_tool,
|
|
52
|
+
zsh_tool, # Explicit zsh
|
|
53
|
+
bash_tool, # Explicit bash
|
|
54
54
|
npx_tool,
|
|
55
55
|
uvx_tool,
|
|
56
56
|
process_tool,
|
|
@@ -19,9 +19,7 @@ class AutoBackgroundExecutor:
|
|
|
19
19
|
# Default timeout before auto-backgrounding (2 minutes)
|
|
20
20
|
DEFAULT_TIMEOUT = 120.0
|
|
21
21
|
|
|
22
|
-
def __init__(
|
|
23
|
-
self, process_manager: ProcessManager, timeout: float = DEFAULT_TIMEOUT
|
|
24
|
-
):
|
|
22
|
+
def __init__(self, process_manager: ProcessManager, timeout: float = DEFAULT_TIMEOUT):
|
|
25
23
|
"""Initialize the auto-background executor.
|
|
26
24
|
|
|
27
25
|
Args:
|
|
@@ -148,9 +146,7 @@ class AutoBackgroundExecutor:
|
|
|
148
146
|
task.cancel()
|
|
149
147
|
|
|
150
148
|
# Continue reading output in background
|
|
151
|
-
asyncio.create_task(
|
|
152
|
-
self._background_reader(process, process_id, log_file)
|
|
153
|
-
)
|
|
149
|
+
asyncio.create_task(self._background_reader(process, process_id, log_file))
|
|
154
150
|
|
|
155
151
|
# Return status message
|
|
156
152
|
elapsed = time.time() - start_time
|
hanzo_mcp/tools/shell/base.py
CHANGED
|
@@ -63,11 +63,7 @@ class CommandResult:
|
|
|
63
63
|
Returns:
|
|
64
64
|
True if the command succeeded, False otherwise
|
|
65
65
|
"""
|
|
66
|
-
return
|
|
67
|
-
self.return_code == 0
|
|
68
|
-
and self.status == BashCommandStatus.COMPLETED
|
|
69
|
-
and not self.error_message
|
|
70
|
-
)
|
|
66
|
+
return self.return_code == 0 and self.status == BashCommandStatus.COMPLETED and not self.error_message
|
|
71
67
|
|
|
72
68
|
@property
|
|
73
69
|
def is_running(self) -> bool:
|
|
@@ -171,13 +171,11 @@ class BaseProcessTool(BaseTool):
|
|
|
171
171
|
process_env.update(env)
|
|
172
172
|
|
|
173
173
|
# Execute with auto-backgrounding
|
|
174
|
-
output, was_backgrounded, process_id = (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
env=process_env,
|
|
180
|
-
)
|
|
174
|
+
output, was_backgrounded, process_id = await self.auto_background_executor.execute_with_auto_background(
|
|
175
|
+
cmd_args=cmd_args,
|
|
176
|
+
tool_name=self.get_tool_name(),
|
|
177
|
+
cwd=cwd,
|
|
178
|
+
env=process_env,
|
|
181
179
|
)
|
|
182
180
|
|
|
183
181
|
if was_backgrounded:
|
|
@@ -84,11 +84,7 @@ def escape_bash_special_chars(command: str) -> str:
|
|
|
84
84
|
|
|
85
85
|
def visit_node(node: Any) -> None:
|
|
86
86
|
nonlocal last_pos
|
|
87
|
-
if (
|
|
88
|
-
node.kind == "redirect"
|
|
89
|
-
and hasattr(node, "heredoc")
|
|
90
|
-
and node.heredoc is not None
|
|
91
|
-
):
|
|
87
|
+
if node.kind == "redirect" and hasattr(node, "heredoc") and node.heredoc is not None:
|
|
92
88
|
# We're entering a heredoc - preserve everything as-is until we see EOF
|
|
93
89
|
between = command[last_pos : node.pos[0]]
|
|
94
90
|
parts.append(between)
|
|
@@ -414,15 +410,11 @@ class BashSession:
|
|
|
414
410
|
"> ", # generic
|
|
415
411
|
">", # generic without space
|
|
416
412
|
]
|
|
417
|
-
output_ends_with_prompt = any(
|
|
418
|
-
cur_pane_output.rstrip().endswith(pattern)
|
|
419
|
-
for pattern in prompt_patterns
|
|
420
|
-
)
|
|
413
|
+
output_ends_with_prompt = any(cur_pane_output.rstrip().endswith(pattern) for pattern in prompt_patterns)
|
|
421
414
|
|
|
422
415
|
# Also check for username@hostname pattern (common in many shells)
|
|
423
416
|
has_user_host_pattern = "@" in cur_pane_output and any(
|
|
424
|
-
cur_pane_output.rstrip().endswith(indicator)
|
|
425
|
-
for indicator in prompt_patterns
|
|
417
|
+
cur_pane_output.rstrip().endswith(indicator) for indicator in prompt_patterns
|
|
426
418
|
)
|
|
427
419
|
|
|
428
420
|
if output_ends_with_prompt or has_user_host_pattern:
|
|
@@ -430,10 +422,7 @@ class BashSession:
|
|
|
430
422
|
|
|
431
423
|
# 2) No-change timeout (only if not blocking)
|
|
432
424
|
time_since_last_change = time.time() - last_change_time
|
|
433
|
-
if
|
|
434
|
-
not blocking
|
|
435
|
-
and time_since_last_change >= self.NO_CHANGE_TIMEOUT_SECONDS
|
|
436
|
-
):
|
|
425
|
+
if not blocking and time_since_last_change >= self.NO_CHANGE_TIMEOUT_SECONDS:
|
|
437
426
|
# Extract current output
|
|
438
427
|
lines = cur_pane_output.strip().split("\n")
|
|
439
428
|
output = "\n".join(lines)
|
|
@@ -497,9 +486,7 @@ class BashSession:
|
|
|
497
486
|
session_id=self.id,
|
|
498
487
|
)
|
|
499
488
|
|
|
500
|
-
def _handle_nochange_timeout_command(
|
|
501
|
-
self, command: str, pane_content: str
|
|
502
|
-
) -> CommandResult:
|
|
489
|
+
def _handle_nochange_timeout_command(self, command: str, pane_content: str) -> CommandResult:
|
|
503
490
|
"""Handle a command that timed out due to no output changes."""
|
|
504
491
|
self.prev_status = BashCommandStatus.NO_CHANGE_TIMEOUT
|
|
505
492
|
|
|
@@ -530,9 +517,7 @@ class BashSession:
|
|
|
530
517
|
session_id=self.id,
|
|
531
518
|
)
|
|
532
519
|
|
|
533
|
-
def _handle_hard_timeout_command(
|
|
534
|
-
self, command: str, pane_content: str, timeout: float
|
|
535
|
-
) -> CommandResult:
|
|
520
|
+
def _handle_hard_timeout_command(self, command: str, pane_content: str, timeout: float) -> CommandResult:
|
|
536
521
|
"""Handle a command that hit the hard timeout."""
|
|
537
522
|
self.prev_status = BashCommandStatus.HARD_TIMEOUT
|
|
538
523
|
|
|
@@ -563,9 +548,7 @@ class BashSession:
|
|
|
563
548
|
session_id=self.id,
|
|
564
549
|
)
|
|
565
550
|
|
|
566
|
-
def _fallback_completion_detection(
|
|
567
|
-
self, command: str, pane_content: str
|
|
568
|
-
) -> CommandResult:
|
|
551
|
+
def _fallback_completion_detection(self, command: str, pane_content: str) -> CommandResult:
|
|
569
552
|
"""Fallback completion detection when PS1 metadata is not available."""
|
|
570
553
|
# Use the old logic as fallback
|
|
571
554
|
self.pane.send_keys("echo EXIT_CODE:$?", enter=True)
|
|
@@ -44,9 +44,7 @@ class BashSessionExecutor:
|
|
|
44
44
|
self.fast_test_mode: bool = fast_test_mode
|
|
45
45
|
|
|
46
46
|
# If no session manager is provided, create a non-singleton instance to avoid shared state
|
|
47
|
-
self.session_manager: SessionManager = session_manager or SessionManager(
|
|
48
|
-
use_singleton=False
|
|
49
|
-
)
|
|
47
|
+
self.session_manager: SessionManager = session_manager or SessionManager(use_singleton=False)
|
|
50
48
|
|
|
51
49
|
# Excluded commands or patterns (for compatibility)
|
|
52
50
|
self.excluded_commands: list[str] = ["rm"]
|
|
@@ -146,9 +144,7 @@ class BashSessionExecutor:
|
|
|
146
144
|
Returns:
|
|
147
145
|
CommandResult containing execution results
|
|
148
146
|
"""
|
|
149
|
-
self._log(
|
|
150
|
-
f"Executing command: {command} (is_input={is_input}, blocking={blocking})"
|
|
151
|
-
)
|
|
147
|
+
self._log(f"Executing command: {command} (is_input={is_input}, blocking={blocking})")
|
|
152
148
|
|
|
153
149
|
# Check if the command is allowed (skip for input to running processes)
|
|
154
150
|
if not is_input and not self.is_command_allowed(command):
|
|
@@ -183,9 +179,7 @@ class BashSessionExecutor:
|
|
|
183
179
|
if session is None:
|
|
184
180
|
# Use faster timeouts and polling for tests
|
|
185
181
|
if self.fast_test_mode:
|
|
186
|
-
timeout_seconds =
|
|
187
|
-
10 # Faster timeout for tests but not too aggressive
|
|
188
|
-
)
|
|
182
|
+
timeout_seconds = 10 # Faster timeout for tests but not too aggressive
|
|
189
183
|
poll_interval = 0.2 # Faster polling for tests but still reasonable
|
|
190
184
|
else:
|
|
191
185
|
timeout_seconds = 30 # Default timeout
|
|
@@ -206,9 +200,7 @@ class BashSessionExecutor:
|
|
|
206
200
|
self._log(f"Failed to set environment variable {key}")
|
|
207
201
|
|
|
208
202
|
# Execute the command with enhanced parameters
|
|
209
|
-
result = session.execute(
|
|
210
|
-
command=command, is_input=is_input, blocking=blocking, timeout=timeout
|
|
211
|
-
)
|
|
203
|
+
result = session.execute(command=command, is_input=is_input, blocking=blocking, timeout=timeout)
|
|
212
204
|
|
|
213
205
|
# Add session_id to the result
|
|
214
206
|
result.session_id = effective_session_id
|
|
@@ -257,9 +249,7 @@ class BashSessionExecutor:
|
|
|
257
249
|
|
|
258
250
|
# Wait for completion with timeout
|
|
259
251
|
try:
|
|
260
|
-
stdout, stderr = await asyncio.wait_for(
|
|
261
|
-
process.communicate(), timeout=timeout
|
|
262
|
-
)
|
|
252
|
+
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
|
263
253
|
except asyncio.TimeoutError:
|
|
264
254
|
# Kill the process if it times out
|
|
265
255
|
process.kill()
|
|
@@ -28,9 +28,7 @@ class BashTool(BaseScriptTool):
|
|
|
28
28
|
env: Optional[dict[str, str]] = None,
|
|
29
29
|
timeout: Optional[int] = None,
|
|
30
30
|
) -> str:
|
|
31
|
-
return await tool_self.run(
|
|
32
|
-
ctx, command=command, cwd=cwd, env=env, timeout=timeout
|
|
33
|
-
)
|
|
31
|
+
return await tool_self.run(ctx, command=command, cwd=cwd, env=env, timeout=timeout)
|
|
34
32
|
|
|
35
33
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
36
34
|
"""Call the tool with arguments."""
|
|
@@ -71,7 +69,7 @@ bash "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
71
69
|
if Path(path).exists():
|
|
72
70
|
return path
|
|
73
71
|
return "cmd.exe" # Fall back to cmd if no bash found
|
|
74
|
-
|
|
72
|
+
|
|
75
73
|
# On Unix-like systems, always use bash
|
|
76
74
|
return "bash"
|
|
77
75
|
|
|
@@ -112,9 +110,7 @@ bash "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
112
110
|
work_dir = Path(cwd).resolve() if cwd else Path.cwd()
|
|
113
111
|
|
|
114
112
|
# Always use execute_sync which now has auto-backgrounding
|
|
115
|
-
output = await self.execute_sync(
|
|
116
|
-
command, cwd=work_dir, env=env, timeout=timeout
|
|
117
|
-
)
|
|
113
|
+
output = await self.execute_sync(command, cwd=work_dir, env=env, timeout=timeout)
|
|
118
114
|
return output if output else "Command completed successfully (no output)"
|
|
119
115
|
|
|
120
116
|
|