hanzo-mcp 0.8.11__py3-none-any.whl → 0.9.0__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 +26 -27
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +22 -15
- 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/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/path_utils.py +34 -0
- hanzo_mcp/tools/common/permissions.py +14 -13
- hanzo_mcp/tools/common/personality.py +983 -701
- 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/compiler/__init__.py +8 -0
- hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
- 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/environment/__init__.py +8 -0
- hanzo_mcp/tools/environment/environment_detector.py +594 -0
- hanzo_mcp/tools/filesystem/__init__.py +28 -26
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
- hanzo_mcp/tools/filesystem/base.py +20 -12
- hanzo_mcp/tools/filesystem/content_replace.py +7 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
- hanzo_mcp/tools/filesystem/edit.py +10 -18
- hanzo_mcp/tools/filesystem/find.py +312 -179
- hanzo_mcp/tools/filesystem/git_search.py +12 -24
- hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
- hanzo_mcp/tools/filesystem/read.py +14 -30
- hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
- hanzo_mcp/tools/filesystem/search.py +1160 -0
- hanzo_mcp/tools/filesystem/watch.py +2 -4
- hanzo_mcp/tools/filesystem/write.py +7 -10
- hanzo_mcp/tools/framework/__init__.py +8 -0
- hanzo_mcp/tools/framework/framework_modes.py +714 -0
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- 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 +621 -481
- 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/conversation_memory.py +636 -0
- 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 +12 -34
- hanzo_mcp/tools/search/unified_search.py +24 -78
- hanzo_mcp/tools/shell/__init__.py +16 -4
- 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/run_tool.py +56 -0
- 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/vector/__init__.py +97 -50
- 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/node_tool.py +538 -0
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/unified_vector.py +384 -0
- 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.9.0.dist-info}/METADATA +2 -2
- hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
- hanzo_mcp/tools/agent/swarm_tool.py +0 -718
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
- hanzo_mcp/tools/filesystem/batch_search.py +0 -900
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
- hanzo_mcp/tools/filesystem/find_files.py +0 -369
- hanzo_mcp/tools/filesystem/grep.py +0 -467
- hanzo_mcp/tools/filesystem/search_tool.py +0 -767
- hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
- hanzo_mcp/tools/filesystem/tree.py +0 -270
- hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
- hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
- hanzo_mcp/tools/todo/todo_read.py +0 -143
- hanzo_mcp/tools/todo/todo_write.py +0 -374
- hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
|
@@ -140,15 +140,9 @@ recall_facts(queries=["company policies"], scope="global", limit=5)
|
|
|
140
140
|
if fact.metadata and fact.metadata.get("kb_name"):
|
|
141
141
|
kb_info = f" (KB: {fact.metadata['kb_name']})"
|
|
142
142
|
formatted.append(f"{i}. {fact.content}{kb_info}")
|
|
143
|
-
if (
|
|
144
|
-
fact.metadata and len(fact.metadata) > 2
|
|
145
|
-
): # More than just type and kb_name
|
|
143
|
+
if fact.metadata and len(fact.metadata) > 2: # More than just type and kb_name
|
|
146
144
|
# Show other metadata
|
|
147
|
-
other_meta = {
|
|
148
|
-
k: v
|
|
149
|
-
for k, v in fact.metadata.items()
|
|
150
|
-
if k not in ["type", "kb_name"]
|
|
151
|
-
}
|
|
145
|
+
other_meta = {k: v for k, v in fact.metadata.items() if k not in ["type", "kb_name"]}
|
|
152
146
|
if other_meta:
|
|
153
147
|
formatted.append(f" Metadata: {other_meta}")
|
|
154
148
|
|
|
@@ -167,9 +161,7 @@ recall_facts(queries=["company policies"], scope="global", limit=5)
|
|
|
167
161
|
scope: str = "project",
|
|
168
162
|
limit: int = 10,
|
|
169
163
|
) -> str:
|
|
170
|
-
return await tool_self.call(
|
|
171
|
-
ctx, queries=queries, kb_name=kb_name, scope=scope, limit=limit
|
|
172
|
-
)
|
|
164
|
+
return await tool_self.call(ctx, queries=queries, kb_name=kb_name, scope=scope, limit=limit)
|
|
173
165
|
|
|
174
166
|
|
|
175
167
|
@final
|
|
@@ -265,9 +257,7 @@ store_facts(facts=["Company founded in 2020"], scope="global", kb_name="company_
|
|
|
265
257
|
scope: str = "project",
|
|
266
258
|
metadata: Optional[Dict[str, Any]] = None,
|
|
267
259
|
) -> str:
|
|
268
|
-
return await tool_self.call(
|
|
269
|
-
ctx, facts=facts, kb_name=kb_name, scope=scope, metadata=metadata
|
|
270
|
-
)
|
|
260
|
+
return await tool_self.call(ctx, facts=facts, kb_name=kb_name, scope=scope, metadata=metadata)
|
|
271
261
|
|
|
272
262
|
|
|
273
263
|
@final
|
|
@@ -323,11 +313,7 @@ summarize_to_memory(content="Company guidelines...", topic="Guidelines", scope="
|
|
|
323
313
|
|
|
324
314
|
# Use the memory service to create a summary
|
|
325
315
|
# This would typically use an LLM to summarize, but for now we'll store as-is
|
|
326
|
-
summary = (
|
|
327
|
-
f"Summary of {topic}:\n{content[:500]}..."
|
|
328
|
-
if len(content) > 500
|
|
329
|
-
else content
|
|
330
|
-
)
|
|
316
|
+
summary = f"Summary of {topic}:\n{content[:500]}..." if len(content) > 500 else content
|
|
331
317
|
|
|
332
318
|
# Store the summary as a memory
|
|
333
319
|
from hanzo_memory.services.memory import get_memory_service
|
|
@@ -358,9 +344,7 @@ summarize_to_memory(content="Company guidelines...", topic="Guidelines", scope="
|
|
|
358
344
|
if auto_facts:
|
|
359
345
|
# In a real implementation, this would use LLM to extract key facts
|
|
360
346
|
# For now, we'll just note it
|
|
361
|
-
result += (
|
|
362
|
-
"\n(Auto-fact extraction would extract key facts from the summary)"
|
|
363
|
-
)
|
|
347
|
+
result += "\n(Auto-fact extraction would extract key facts from the summary)"
|
|
364
348
|
|
|
365
349
|
return result
|
|
366
350
|
|
|
@@ -377,9 +361,7 @@ summarize_to_memory(content="Company guidelines...", topic="Guidelines", scope="
|
|
|
377
361
|
scope: str = "project",
|
|
378
362
|
auto_facts: bool = True,
|
|
379
363
|
) -> str:
|
|
380
|
-
return await tool_self.call(
|
|
381
|
-
ctx, content=content, topic=topic, scope=scope, auto_facts=auto_facts
|
|
382
|
-
)
|
|
364
|
+
return await tool_self.call(ctx, content=content, topic=topic, scope=scope, auto_facts=auto_facts)
|
|
383
365
|
|
|
384
366
|
|
|
385
367
|
@final
|
|
@@ -127,9 +127,7 @@ recall_memories(queries=["coding standards"], scope="global")
|
|
|
127
127
|
tool_self = self
|
|
128
128
|
|
|
129
129
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
130
|
-
async def recall_memories(
|
|
131
|
-
ctx: MCPContext, queries: List[str], limit: int = 10, scope: str = "project"
|
|
132
|
-
) -> str:
|
|
130
|
+
async def recall_memories(ctx: MCPContext, queries: List[str], limit: int = 10, scope: str = "project") -> str:
|
|
133
131
|
return await tool_self.call(ctx, queries=queries, limit=limit, scope=scope)
|
|
134
132
|
|
|
135
133
|
|
|
@@ -247,9 +245,7 @@ update_memories(updates=[
|
|
|
247
245
|
# The hanzo-memory service doesn't have update implemented yet
|
|
248
246
|
# When it's implemented, we would call:
|
|
249
247
|
# success = self.service.update_memory(self.user_id, memory_id, content=statement)
|
|
250
|
-
await tool_ctx.warning(
|
|
251
|
-
f"Memory update not fully implemented in hanzo-memory yet: {memory_id}"
|
|
252
|
-
)
|
|
248
|
+
await tool_ctx.warning(f"Memory update not fully implemented in hanzo-memory yet: {memory_id}")
|
|
253
249
|
success_count += 1
|
|
254
250
|
|
|
255
251
|
return f"Would update {success_count} of {len(updates)} memories (update not fully implemented in hanzo-memory yet)."
|
|
@@ -260,9 +256,7 @@ update_memories(updates=[
|
|
|
260
256
|
tool_self = self
|
|
261
257
|
|
|
262
258
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
263
|
-
async def update_memories(
|
|
264
|
-
ctx: MCPContext, updates: List[Dict[str, str]]
|
|
265
|
-
) -> str:
|
|
259
|
+
async def update_memories(ctx: MCPContext, updates: List[Dict[str, str]]) -> str:
|
|
266
260
|
return await tool_self.call(ctx, updates=updates)
|
|
267
261
|
|
|
268
262
|
|
|
@@ -398,13 +392,9 @@ manage_memories(
|
|
|
398
392
|
|
|
399
393
|
if memory_id and statement:
|
|
400
394
|
# Update not fully implemented in hanzo-memory yet
|
|
401
|
-
await tool_ctx.warning(
|
|
402
|
-
f"Memory update not fully implemented: {memory_id}"
|
|
403
|
-
)
|
|
395
|
+
await tool_ctx.warning(f"Memory update not fully implemented: {memory_id}")
|
|
404
396
|
success_count += 1
|
|
405
|
-
results.append(
|
|
406
|
-
f"Would update {success_count} memories (update pending implementation)"
|
|
407
|
-
)
|
|
397
|
+
results.append(f"Would update {success_count} memories (update pending implementation)")
|
|
408
398
|
|
|
409
399
|
# Delete memories
|
|
410
400
|
if deletions:
|
|
@@ -433,6 +423,4 @@ manage_memories(
|
|
|
433
423
|
updates: Optional[List[Dict[str, str]]] = None,
|
|
434
424
|
deletions: Optional[List[str]] = None,
|
|
435
425
|
) -> str:
|
|
436
|
-
return await tool_self.call(
|
|
437
|
-
ctx, creations=creations, updates=updates, deletions=deletions
|
|
438
|
-
)
|
|
426
|
+
return await tool_self.call(ctx, creations=creations, updates=updates, deletions=deletions)
|
|
@@ -75,8 +75,8 @@ class FindTool(BaseTool):
|
|
|
75
75
|
reading or searching within files.
|
|
76
76
|
"""
|
|
77
77
|
|
|
78
|
-
def __init__(self):
|
|
79
|
-
super().__init__()
|
|
78
|
+
def __init__(self, permission_manager=None):
|
|
79
|
+
super().__init__(permission_manager=permission_manager)
|
|
80
80
|
self._cache = {}
|
|
81
81
|
self._gitignore_cache = {}
|
|
82
82
|
|
|
@@ -257,9 +257,7 @@ class FindTool(BaseTool):
|
|
|
257
257
|
# Resolve path
|
|
258
258
|
root_path = Path(path).resolve()
|
|
259
259
|
if not root_path.exists():
|
|
260
|
-
return MCPResourceDocument(
|
|
261
|
-
data={"error": f"Path does not exist: {path}", "results": []}
|
|
262
|
-
)
|
|
260
|
+
return MCPResourceDocument(data={"error": f"Path does not exist: {path}", "results": []})
|
|
263
261
|
|
|
264
262
|
# Get ignore patterns
|
|
265
263
|
ignore_patterns = set()
|
|
@@ -270,9 +268,7 @@ class FindTool(BaseTool):
|
|
|
270
268
|
min_size_bytes = self._parse_size(min_size) if min_size else None
|
|
271
269
|
max_size_bytes = self._parse_size(max_size) if max_size else None
|
|
272
270
|
modified_after_ts = self._parse_time(modified_after) if modified_after else None
|
|
273
|
-
modified_before_ts = (
|
|
274
|
-
self._parse_time(modified_before) if modified_before else None
|
|
275
|
-
)
|
|
271
|
+
modified_before_ts = self._parse_time(modified_before) if modified_before else None
|
|
276
272
|
|
|
277
273
|
# Collect matches
|
|
278
274
|
matches = []
|
|
@@ -350,20 +346,14 @@ class FindTool(BaseTool):
|
|
|
350
346
|
stats = {
|
|
351
347
|
"total_found": total_results,
|
|
352
348
|
"search_time_ms": int((time.time() - start_time) * 1000),
|
|
353
|
-
"search_method": (
|
|
354
|
-
"ffind" if FFIND_AVAILABLE and not in_content else "python"
|
|
355
|
-
),
|
|
349
|
+
"search_method": ("ffind" if FFIND_AVAILABLE and not in_content else "python"),
|
|
356
350
|
"root_path": str(root_path),
|
|
357
351
|
"filters_applied": {
|
|
358
352
|
"pattern": pattern,
|
|
359
353
|
"type": type,
|
|
360
|
-
"size": (
|
|
361
|
-
{"min": min_size, "max": max_size} if min_size or max_size else None
|
|
362
|
-
),
|
|
354
|
+
"size": ({"min": min_size, "max": max_size} if min_size or max_size else None),
|
|
363
355
|
"modified": (
|
|
364
|
-
{"after": modified_after, "before": modified_before}
|
|
365
|
-
if modified_after or modified_before
|
|
366
|
-
else None
|
|
356
|
+
{"after": modified_after, "before": modified_before} if modified_after or modified_before else None
|
|
367
357
|
),
|
|
368
358
|
"max_depth": max_depth,
|
|
369
359
|
"gitignore": respect_gitignore,
|
|
@@ -594,9 +584,7 @@ class FindTool(BaseTool):
|
|
|
594
584
|
elif fuzzy:
|
|
595
585
|
pattern_lower = pattern.lower() if not case_sensitive else pattern
|
|
596
586
|
matcher = (
|
|
597
|
-
lambda name: SequenceMatcher(
|
|
598
|
-
None, pattern_lower, name.lower() if not case_sensitive else name
|
|
599
|
-
).ratio()
|
|
587
|
+
lambda name: SequenceMatcher(None, pattern_lower, name.lower() if not case_sensitive else name).ratio()
|
|
600
588
|
> 0.6
|
|
601
589
|
)
|
|
602
590
|
else:
|
|
@@ -608,9 +596,7 @@ class FindTool(BaseTool):
|
|
|
608
596
|
matcher = lambda name: fnmatch.fnmatch(name, pattern)
|
|
609
597
|
|
|
610
598
|
# Walk directory tree
|
|
611
|
-
for dirpath, dirnames, filenames in os.walk(
|
|
612
|
-
str(root), followlinks=follow_symlinks
|
|
613
|
-
):
|
|
599
|
+
for dirpath, dirnames, filenames in os.walk(str(root), followlinks=follow_symlinks):
|
|
614
600
|
# Check depth
|
|
615
601
|
if max_depth is not None:
|
|
616
602
|
depth = len(Path(dirpath).relative_to(root).parts)
|
|
@@ -621,11 +607,7 @@ class FindTool(BaseTool):
|
|
|
621
607
|
# Filter directories to skip
|
|
622
608
|
if respect_gitignore:
|
|
623
609
|
dirnames[:] = [
|
|
624
|
-
d
|
|
625
|
-
for d in dirnames
|
|
626
|
-
if not self._should_ignore(
|
|
627
|
-
os.path.join(dirpath, d), ignore_patterns
|
|
628
|
-
)
|
|
610
|
+
d for d in dirnames if not self._should_ignore(os.path.join(dirpath, d), ignore_patterns)
|
|
629
611
|
]
|
|
630
612
|
|
|
631
613
|
# Check directories
|
|
@@ -662,9 +644,7 @@ class FindTool(BaseTool):
|
|
|
662
644
|
match_found = True
|
|
663
645
|
elif in_content:
|
|
664
646
|
# Search in file content
|
|
665
|
-
match_found = await self._search_in_file(
|
|
666
|
-
full_path, pattern, case_sensitive
|
|
667
|
-
)
|
|
647
|
+
match_found = await self._search_in_file(full_path, pattern, case_sensitive)
|
|
668
648
|
else:
|
|
669
649
|
match_found = False
|
|
670
650
|
|
|
@@ -686,9 +666,7 @@ class FindTool(BaseTool):
|
|
|
686
666
|
|
|
687
667
|
return matches
|
|
688
668
|
|
|
689
|
-
async def _search_in_file(
|
|
690
|
-
self, file_path: str, pattern: str, case_sensitive: bool
|
|
691
|
-
) -> bool:
|
|
669
|
+
async def _search_in_file(self, file_path: str, pattern: str, case_sensitive: bool) -> bool:
|
|
692
670
|
"""Search for pattern in file content."""
|
|
693
671
|
try:
|
|
694
672
|
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
@@ -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
|
|
|
@@ -13,6 +13,7 @@ from hanzo_mcp.tools.shell.zsh_tool import zsh_tool, shell_tool
|
|
|
13
13
|
|
|
14
14
|
# Import tools
|
|
15
15
|
from hanzo_mcp.tools.shell.bash_tool import bash_tool
|
|
16
|
+
from hanzo_mcp.tools.shell.run_tool import run_tool
|
|
16
17
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
17
18
|
from hanzo_mcp.tools.shell.process_tool import process_tool
|
|
18
19
|
|
|
@@ -40,17 +41,19 @@ def get_shell_tools(
|
|
|
40
41
|
bash_tool.permission_manager = permission_manager
|
|
41
42
|
zsh_tool.permission_manager = permission_manager
|
|
42
43
|
shell_tool.permission_manager = permission_manager
|
|
44
|
+
run_tool.permission_manager = permission_manager
|
|
43
45
|
npx_tool.permission_manager = permission_manager
|
|
44
46
|
uvx_tool.permission_manager = permission_manager
|
|
45
47
|
|
|
46
48
|
# Note: StreamingCommandTool is abstract and shouldn't be instantiated directly
|
|
47
49
|
# It's used as a base class for other streaming tools
|
|
48
50
|
|
|
49
|
-
# Return
|
|
51
|
+
# Return run_tool first (simplified command execution), then shell_tool (smart default), then specific shells
|
|
50
52
|
return [
|
|
53
|
+
run_tool, # Simplified run command with auto-backgrounding
|
|
51
54
|
shell_tool, # Smart shell (prefers zsh if available)
|
|
52
|
-
zsh_tool,
|
|
53
|
-
bash_tool,
|
|
55
|
+
zsh_tool, # Explicit zsh
|
|
56
|
+
bash_tool, # Explicit bash
|
|
54
57
|
npx_tool,
|
|
55
58
|
uvx_tool,
|
|
56
59
|
process_tool,
|
|
@@ -62,16 +65,25 @@ def get_shell_tools(
|
|
|
62
65
|
def register_shell_tools(
|
|
63
66
|
mcp_server: FastMCP,
|
|
64
67
|
permission_manager: PermissionManager,
|
|
68
|
+
enabled_tools: dict[str, bool] | None = None,
|
|
65
69
|
) -> list[BaseTool]:
|
|
66
70
|
"""Register all shell tools with the MCP server.
|
|
67
71
|
|
|
68
72
|
Args:
|
|
69
73
|
mcp_server: The FastMCP server instance
|
|
70
74
|
permission_manager: Permission manager for access control
|
|
75
|
+
enabled_tools: Optional dict of tool names to enable/disable
|
|
71
76
|
|
|
72
77
|
Returns:
|
|
73
78
|
List of registered tools
|
|
74
79
|
"""
|
|
75
|
-
|
|
80
|
+
all_tools = get_shell_tools(permission_manager)
|
|
81
|
+
|
|
82
|
+
# Filter tools based on enabled_tools if provided
|
|
83
|
+
if enabled_tools is not None:
|
|
84
|
+
tools = [tool for tool in all_tools if enabled_tools.get(tool.name, True)]
|
|
85
|
+
else:
|
|
86
|
+
tools = all_tools
|
|
87
|
+
|
|
76
88
|
ToolRegistry.register_tools(mcp_server, tools)
|
|
77
89
|
return tools
|
|
@@ -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)
|