chore: standardize string quotes and fix import ordering across multiple modules
This commit is contained in:
+79
-18
@@ -1,25 +1,86 @@
|
||||
from pr.tools.agents import (
|
||||
collaborate_agents,
|
||||
create_agent,
|
||||
execute_agent_task,
|
||||
list_agents,
|
||||
remove_agent,
|
||||
)
|
||||
from pr.tools.base import get_tools_definition
|
||||
from pr.tools.command import (
|
||||
kill_process,
|
||||
run_command,
|
||||
run_command_interactive,
|
||||
tail_process,
|
||||
)
|
||||
from pr.tools.database import db_get, db_query, db_set
|
||||
from pr.tools.editor import (
|
||||
close_editor,
|
||||
editor_insert_text,
|
||||
editor_replace_text,
|
||||
editor_search,
|
||||
open_editor,
|
||||
)
|
||||
from pr.tools.filesystem import (
|
||||
read_file, write_file, list_directory, mkdir, chdir, getpwd, index_source_directory, search_replace
|
||||
chdir,
|
||||
getpwd,
|
||||
index_source_directory,
|
||||
list_directory,
|
||||
mkdir,
|
||||
read_file,
|
||||
search_replace,
|
||||
write_file,
|
||||
)
|
||||
from pr.tools.memory import (
|
||||
add_knowledge_entry,
|
||||
delete_knowledge_entry,
|
||||
get_knowledge_by_category,
|
||||
get_knowledge_entry,
|
||||
get_knowledge_statistics,
|
||||
search_knowledge,
|
||||
update_knowledge_importance,
|
||||
)
|
||||
from pr.tools.command import run_command, run_command_interactive, tail_process, kill_process
|
||||
from pr.tools.editor import open_editor, editor_insert_text, editor_replace_text, editor_search, close_editor
|
||||
from pr.tools.database import db_set, db_get, db_query
|
||||
from pr.tools.web import http_fetch, web_search, web_search_news
|
||||
from pr.tools.python_exec import python_exec
|
||||
from pr.tools.patch import apply_patch, create_diff
|
||||
from pr.tools.agents import create_agent, list_agents, execute_agent_task, remove_agent, collaborate_agents
|
||||
from pr.tools.memory import add_knowledge_entry, get_knowledge_entry, search_knowledge, get_knowledge_by_category, update_knowledge_importance, delete_knowledge_entry, get_knowledge_statistics
|
||||
from pr.tools.python_exec import python_exec
|
||||
from pr.tools.web import http_fetch, web_search, web_search_news
|
||||
|
||||
__all__ = [
|
||||
'get_tools_definition',
|
||||
'read_file', 'write_file', 'list_directory', 'mkdir', 'chdir', 'getpwd', 'index_source_directory', 'search_replace',
|
||||
'open_editor', 'editor_insert_text', 'editor_replace_text', 'editor_search','close_editor',
|
||||
'run_command', 'run_command_interactive',
|
||||
'db_set', 'db_get', 'db_query',
|
||||
'http_fetch', 'web_search', 'web_search_news',
|
||||
'python_exec','tail_process', 'kill_process',
|
||||
'apply_patch', 'create_diff',
|
||||
'create_agent', 'list_agents', 'execute_agent_task', 'remove_agent', 'collaborate_agents',
|
||||
'add_knowledge_entry', 'get_knowledge_entry', 'search_knowledge', 'get_knowledge_by_category', 'update_knowledge_importance', 'delete_knowledge_entry', 'get_knowledge_statistics'
|
||||
"get_tools_definition",
|
||||
"read_file",
|
||||
"write_file",
|
||||
"list_directory",
|
||||
"mkdir",
|
||||
"chdir",
|
||||
"getpwd",
|
||||
"index_source_directory",
|
||||
"search_replace",
|
||||
"open_editor",
|
||||
"editor_insert_text",
|
||||
"editor_replace_text",
|
||||
"editor_search",
|
||||
"close_editor",
|
||||
"run_command",
|
||||
"run_command_interactive",
|
||||
"db_set",
|
||||
"db_get",
|
||||
"db_query",
|
||||
"http_fetch",
|
||||
"web_search",
|
||||
"web_search_news",
|
||||
"python_exec",
|
||||
"tail_process",
|
||||
"kill_process",
|
||||
"apply_patch",
|
||||
"create_diff",
|
||||
"create_agent",
|
||||
"list_agents",
|
||||
"execute_agent_task",
|
||||
"remove_agent",
|
||||
"collaborate_agents",
|
||||
"add_knowledge_entry",
|
||||
"get_knowledge_entry",
|
||||
"search_knowledge",
|
||||
"get_knowledge_by_category",
|
||||
"update_knowledge_importance",
|
||||
"delete_knowledge_entry",
|
||||
"get_knowledge_statistics",
|
||||
]
|
||||
|
||||
+26
-14
@@ -1,13 +1,15 @@
|
||||
import os
|
||||
from typing import Dict, Any, List
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pr.agents.agent_manager import AgentManager
|
||||
from pr.core.api import call_api
|
||||
|
||||
|
||||
def create_agent(role_name: str, agent_id: str = None) -> Dict[str, Any]:
|
||||
"""Create a new agent with the specified role."""
|
||||
try:
|
||||
# Get db_path from environment or default
|
||||
db_path = os.environ.get('ASSISTANT_DB_PATH', '~/.assistant_db.sqlite')
|
||||
db_path = os.environ.get("ASSISTANT_DB_PATH", "~/.assistant_db.sqlite")
|
||||
db_path = os.path.expanduser(db_path)
|
||||
|
||||
manager = AgentManager(db_path, call_api)
|
||||
@@ -16,47 +18,57 @@ def create_agent(role_name: str, agent_id: str = None) -> Dict[str, Any]:
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def list_agents() -> Dict[str, Any]:
|
||||
"""List all active agents."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
manager = AgentManager(db_path, call_api)
|
||||
agents = []
|
||||
for agent_id, agent in manager.active_agents.items():
|
||||
agents.append({
|
||||
"agent_id": agent_id,
|
||||
"role": agent.role.name,
|
||||
"task_count": agent.task_count,
|
||||
"message_count": len(agent.message_history)
|
||||
})
|
||||
agents.append(
|
||||
{
|
||||
"agent_id": agent_id,
|
||||
"role": agent.role.name,
|
||||
"task_count": agent.task_count,
|
||||
"message_count": len(agent.message_history),
|
||||
}
|
||||
)
|
||||
return {"status": "success", "agents": agents}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def execute_agent_task(agent_id: str, task: str, context: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
|
||||
def execute_agent_task(
|
||||
agent_id: str, task: str, context: Dict[str, Any] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute a task with the specified agent."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
manager = AgentManager(db_path, call_api)
|
||||
result = manager.execute_agent_task(agent_id, task, context)
|
||||
return result
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def remove_agent(agent_id: str) -> Dict[str, Any]:
|
||||
"""Remove an agent."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
manager = AgentManager(db_path, call_api)
|
||||
success = manager.remove_agent(agent_id)
|
||||
return {"status": "success" if success else "not_found", "agent_id": agent_id}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def collaborate_agents(orchestrator_id: str, task: str, agent_roles: List[str]) -> Dict[str, Any]:
|
||||
|
||||
def collaborate_agents(
|
||||
orchestrator_id: str, task: str, agent_roles: List[str]
|
||||
) -> Dict[str, Any]:
|
||||
"""Collaborate multiple agents on a task."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
manager = AgentManager(db_path, call_api)
|
||||
result = manager.collaborate_agents(orchestrator_id, task, agent_roles)
|
||||
return result
|
||||
|
||||
+267
-150
@@ -10,12 +10,12 @@ def get_tools_definition():
|
||||
"properties": {
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "The process ID returned by run_command when status is 'running'."
|
||||
"description": "The process ID returned by run_command when status is 'running'.",
|
||||
}
|
||||
},
|
||||
"required": ["pid"]
|
||||
}
|
||||
}
|
||||
"required": ["pid"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -27,17 +27,17 @@ def get_tools_definition():
|
||||
"properties": {
|
||||
"pid": {
|
||||
"type": "integer",
|
||||
"description": "The process ID returned by run_command when status is 'running'."
|
||||
"description": "The process ID returned by run_command when status is 'running'.",
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"description": "Maximum seconds to wait for process completion. Returns partial output if still running.",
|
||||
"default": 30
|
||||
}
|
||||
"default": 30,
|
||||
},
|
||||
},
|
||||
"required": ["pid"]
|
||||
}
|
||||
}
|
||||
"required": ["pid"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -48,11 +48,14 @@ def get_tools_definition():
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {"type": "string", "description": "The URL to fetch"},
|
||||
"headers": {"type": "object", "description": "Optional HTTP headers"}
|
||||
"headers": {
|
||||
"type": "object",
|
||||
"description": "Optional HTTP headers",
|
||||
},
|
||||
},
|
||||
"required": ["url"]
|
||||
}
|
||||
}
|
||||
"required": ["url"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -62,12 +65,19 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {"type": "string", "description": "The shell command to execute"},
|
||||
"timeout": {"type": "integer", "description": "Maximum seconds to wait for completion", "default": 30}
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The shell command to execute",
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"description": "Maximum seconds to wait for completion",
|
||||
"default": 30,
|
||||
},
|
||||
},
|
||||
"required": ["command"]
|
||||
}
|
||||
}
|
||||
"required": ["command"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -77,11 +87,14 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {"type": "string", "description": "The interactive command to execute (e.g., vim, nano, top)"}
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The interactive command to execute (e.g., vim, nano, top)",
|
||||
}
|
||||
},
|
||||
"required": ["command"]
|
||||
}
|
||||
}
|
||||
"required": ["command"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -91,12 +104,18 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"session_name": {"type": "string", "description": "The name of the session"},
|
||||
"input_data": {"type": "string", "description": "The input to send to the session"}
|
||||
"session_name": {
|
||||
"type": "string",
|
||||
"description": "The name of the session",
|
||||
},
|
||||
"input_data": {
|
||||
"type": "string",
|
||||
"description": "The input to send to the session",
|
||||
},
|
||||
},
|
||||
"required": ["session_name", "input_data"]
|
||||
}
|
||||
}
|
||||
"required": ["session_name", "input_data"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -106,11 +125,14 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"session_name": {"type": "string", "description": "The name of the session"}
|
||||
"session_name": {
|
||||
"type": "string",
|
||||
"description": "The name of the session",
|
||||
}
|
||||
},
|
||||
"required": ["session_name"]
|
||||
}
|
||||
}
|
||||
"required": ["session_name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -120,11 +142,14 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"session_name": {"type": "string", "description": "The name of the session"}
|
||||
"session_name": {
|
||||
"type": "string",
|
||||
"description": "The name of the session",
|
||||
}
|
||||
},
|
||||
"required": ["session_name"]
|
||||
}
|
||||
}
|
||||
"required": ["session_name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -134,11 +159,14 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to the file"}
|
||||
"filepath": {
|
||||
"type": "string",
|
||||
"description": "Path to the file",
|
||||
}
|
||||
},
|
||||
"required": ["filepath"]
|
||||
}
|
||||
}
|
||||
"required": ["filepath"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -148,12 +176,18 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to the file"},
|
||||
"content": {"type": "string", "description": "Content to write"}
|
||||
"filepath": {
|
||||
"type": "string",
|
||||
"description": "Path to the file",
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "Content to write",
|
||||
},
|
||||
},
|
||||
"required": ["filepath", "content"]
|
||||
}
|
||||
}
|
||||
"required": ["filepath", "content"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -163,11 +197,19 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Directory path", "default": "."},
|
||||
"recursive": {"type": "boolean", "description": "List recursively", "default": False}
|
||||
}
|
||||
}
|
||||
}
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Directory path",
|
||||
"default": ".",
|
||||
},
|
||||
"recursive": {
|
||||
"type": "boolean",
|
||||
"description": "List recursively",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -177,11 +219,14 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Path of the directory to create"}
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path of the directory to create",
|
||||
}
|
||||
},
|
||||
"required": ["path"]
|
||||
}
|
||||
}
|
||||
"required": ["path"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -193,17 +238,17 @@ def get_tools_definition():
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Path to change to"}
|
||||
},
|
||||
"required": ["path"]
|
||||
}
|
||||
}
|
||||
"required": ["path"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "getpwd",
|
||||
"description": "Get the current working directory",
|
||||
"parameters": {"type": "object", "properties": {}}
|
||||
}
|
||||
"parameters": {"type": "object", "properties": {}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -214,11 +259,11 @@ def get_tools_definition():
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {"type": "string", "description": "The key"},
|
||||
"value": {"type": "string", "description": "The value"}
|
||||
"value": {"type": "string", "description": "The value"},
|
||||
},
|
||||
"required": ["key", "value"]
|
||||
}
|
||||
}
|
||||
"required": ["key", "value"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -227,12 +272,10 @@ def get_tools_definition():
|
||||
"description": "Get a value from the database",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {"type": "string", "description": "The key"}
|
||||
},
|
||||
"required": ["key"]
|
||||
}
|
||||
}
|
||||
"properties": {"key": {"type": "string", "description": "The key"}},
|
||||
"required": ["key"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -244,9 +287,9 @@ def get_tools_definition():
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "SQL query"}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -258,9 +301,9 @@ def get_tools_definition():
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query"}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -270,11 +313,14 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query for news"}
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Search query for news",
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -284,11 +330,14 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {"type": "string", "description": "Python code to execute"}
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "Python code to execute",
|
||||
}
|
||||
},
|
||||
"required": ["code"]
|
||||
}
|
||||
}
|
||||
"required": ["code"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -300,9 +349,9 @@ def get_tools_definition():
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Path to index"}
|
||||
},
|
||||
"required": ["path"]
|
||||
}
|
||||
}
|
||||
"required": ["path"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -312,13 +361,22 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to the file"},
|
||||
"old_string": {"type": "string", "description": "String to replace"},
|
||||
"new_string": {"type": "string", "description": "Replacement string"}
|
||||
"filepath": {
|
||||
"type": "string",
|
||||
"description": "Path to the file",
|
||||
},
|
||||
"old_string": {
|
||||
"type": "string",
|
||||
"description": "String to replace",
|
||||
},
|
||||
"new_string": {
|
||||
"type": "string",
|
||||
"description": "Replacement string",
|
||||
},
|
||||
},
|
||||
"required": ["filepath", "old_string", "new_string"]
|
||||
}
|
||||
}
|
||||
"required": ["filepath", "old_string", "new_string"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -328,12 +386,18 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to the file to patch"},
|
||||
"patch_content": {"type": "string", "description": "The patch content as a string"}
|
||||
"filepath": {
|
||||
"type": "string",
|
||||
"description": "Path to the file to patch",
|
||||
},
|
||||
"patch_content": {
|
||||
"type": "string",
|
||||
"description": "The patch content as a string",
|
||||
},
|
||||
},
|
||||
"required": ["filepath", "patch_content"]
|
||||
}
|
||||
}
|
||||
"required": ["filepath", "patch_content"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -343,14 +407,28 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file1": {"type": "string", "description": "Path to the first file"},
|
||||
"file2": {"type": "string", "description": "Path to the second file"},
|
||||
"fromfile": {"type": "string", "description": "Label for the first file", "default": "file1"},
|
||||
"tofile": {"type": "string", "description": "Label for the second file", "default": "file2"}
|
||||
"file1": {
|
||||
"type": "string",
|
||||
"description": "Path to the first file",
|
||||
},
|
||||
"file2": {
|
||||
"type": "string",
|
||||
"description": "Path to the second file",
|
||||
},
|
||||
"fromfile": {
|
||||
"type": "string",
|
||||
"description": "Label for the first file",
|
||||
"default": "file1",
|
||||
},
|
||||
"tofile": {
|
||||
"type": "string",
|
||||
"description": "Label for the second file",
|
||||
"default": "file2",
|
||||
},
|
||||
},
|
||||
"required": ["file1", "file2"]
|
||||
}
|
||||
}
|
||||
"required": ["file1", "file2"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -360,11 +438,14 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to the file"}
|
||||
"filepath": {
|
||||
"type": "string",
|
||||
"description": "Path to the file",
|
||||
}
|
||||
},
|
||||
"required": ["filepath"]
|
||||
}
|
||||
}
|
||||
"required": ["filepath"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -374,11 +455,14 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to the file"}
|
||||
"filepath": {
|
||||
"type": "string",
|
||||
"description": "Path to the file",
|
||||
}
|
||||
},
|
||||
"required": ["filepath"]
|
||||
}
|
||||
}
|
||||
"required": ["filepath"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -388,14 +472,23 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to the file"},
|
||||
"filepath": {
|
||||
"type": "string",
|
||||
"description": "Path to the file",
|
||||
},
|
||||
"text": {"type": "string", "description": "Text to insert"},
|
||||
"line": {"type": "integer", "description": "Line number (optional)"},
|
||||
"col": {"type": "integer", "description": "Column number (optional)"}
|
||||
"line": {
|
||||
"type": "integer",
|
||||
"description": "Line number (optional)",
|
||||
},
|
||||
"col": {
|
||||
"type": "integer",
|
||||
"description": "Column number (optional)",
|
||||
},
|
||||
},
|
||||
"required": ["filepath", "text"]
|
||||
}
|
||||
}
|
||||
"required": ["filepath", "text"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -405,16 +498,26 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to the file"},
|
||||
"filepath": {
|
||||
"type": "string",
|
||||
"description": "Path to the file",
|
||||
},
|
||||
"start_line": {"type": "integer", "description": "Start line"},
|
||||
"start_col": {"type": "integer", "description": "Start column"},
|
||||
"end_line": {"type": "integer", "description": "End line"},
|
||||
"end_col": {"type": "integer", "description": "End column"},
|
||||
"new_text": {"type": "string", "description": "New text"}
|
||||
"new_text": {"type": "string", "description": "New text"},
|
||||
},
|
||||
"required": ["filepath", "start_line", "start_col", "end_line", "end_col", "new_text"]
|
||||
}
|
||||
}
|
||||
"required": [
|
||||
"filepath",
|
||||
"start_line",
|
||||
"start_col",
|
||||
"end_line",
|
||||
"end_col",
|
||||
"new_text",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -424,13 +527,20 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to the file"},
|
||||
"filepath": {
|
||||
"type": "string",
|
||||
"description": "Path to the file",
|
||||
},
|
||||
"pattern": {"type": "string", "description": "Regex pattern"},
|
||||
"start_line": {"type": "integer", "description": "Start line", "default": 0}
|
||||
"start_line": {
|
||||
"type": "integer",
|
||||
"description": "Start line",
|
||||
"default": 0,
|
||||
},
|
||||
},
|
||||
"required": ["filepath", "pattern"]
|
||||
}
|
||||
}
|
||||
"required": ["filepath", "pattern"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -440,24 +550,31 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath1": {"type": "string", "description": "Path to the original file"},
|
||||
"filepath2": {"type": "string", "description": "Path to the modified file"},
|
||||
"format_type": {"type": "string", "description": "Display format: 'unified' or 'side-by-side'", "default": "unified"}
|
||||
"filepath1": {
|
||||
"type": "string",
|
||||
"description": "Path to the original file",
|
||||
},
|
||||
"filepath2": {
|
||||
"type": "string",
|
||||
"description": "Path to the modified file",
|
||||
},
|
||||
"format_type": {
|
||||
"type": "string",
|
||||
"description": "Display format: 'unified' or 'side-by-side'",
|
||||
"default": "unified",
|
||||
},
|
||||
},
|
||||
"required": ["filepath1", "filepath2"]
|
||||
}
|
||||
}
|
||||
"required": ["filepath1", "filepath2"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "display_edit_summary",
|
||||
"description": "Display a summary of all edit operations performed during the session",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
"parameters": {"type": "object", "properties": {}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -467,21 +584,21 @@ def get_tools_definition():
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"show_content": {"type": "boolean", "description": "Show content previews", "default": False}
|
||||
}
|
||||
}
|
||||
}
|
||||
"show_content": {
|
||||
"type": "boolean",
|
||||
"description": "Show content previews",
|
||||
"default": False,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "clear_edit_tracker",
|
||||
"description": "Clear the edit tracker to start fresh",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
"parameters": {"type": "object", "properties": {}},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
+25
-13
@@ -1,21 +1,23 @@
|
||||
import os
|
||||
import select
|
||||
import subprocess
|
||||
import time
|
||||
import select
|
||||
from pr.multiplexer import create_multiplexer, close_multiplexer, get_multiplexer
|
||||
from pr.tools.interactive_control import start_interactive_session
|
||||
from pr.config import MAX_CONCURRENT_SESSIONS
|
||||
|
||||
from pr.multiplexer import close_multiplexer, create_multiplexer, get_multiplexer
|
||||
|
||||
_processes = {}
|
||||
|
||||
def _register_process(pid:int, process):
|
||||
|
||||
def _register_process(pid: int, process):
|
||||
_processes[pid] = process
|
||||
return _processes
|
||||
|
||||
def _get_process(pid:int):
|
||||
|
||||
def _get_process(pid: int):
|
||||
return _processes.get(pid)
|
||||
|
||||
def kill_process(pid:int):
|
||||
|
||||
def kill_process(pid: int):
|
||||
try:
|
||||
process = _get_process(pid)
|
||||
if process:
|
||||
@@ -67,7 +69,7 @@ def tail_process(pid: int, timeout: int = 30):
|
||||
"status": "success",
|
||||
"stdout": stdout_content,
|
||||
"stderr": stderr_content,
|
||||
"returncode": process.returncode
|
||||
"returncode": process.returncode,
|
||||
}
|
||||
|
||||
if time.time() - start_time > timeout_duration:
|
||||
@@ -76,10 +78,12 @@ def tail_process(pid: int, timeout: int = 30):
|
||||
"message": "Process is still running. Call tail_process again to continue monitoring.",
|
||||
"stdout_so_far": stdout_content,
|
||||
"stderr_so_far": stderr_content,
|
||||
"pid": pid
|
||||
"pid": pid,
|
||||
}
|
||||
|
||||
ready, _, _ = select.select([process.stdout, process.stderr], [], [], 0.1)
|
||||
ready, _, _ = select.select(
|
||||
[process.stdout, process.stderr], [], [], 0.1
|
||||
)
|
||||
for pipe in ready:
|
||||
if pipe == process.stdout:
|
||||
line = process.stdout.readline()
|
||||
@@ -100,7 +104,13 @@ def tail_process(pid: int, timeout: int = 30):
|
||||
def run_command(command, timeout=30, monitored=False):
|
||||
mux_name = None
|
||||
try:
|
||||
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
_register_process(process.pid, process)
|
||||
|
||||
mux_name, mux = create_multiplexer(f"cmd-{process.pid}", show_output=True)
|
||||
@@ -129,7 +139,7 @@ def run_command(command, timeout=30, monitored=False):
|
||||
"status": "success",
|
||||
"stdout": stdout_content,
|
||||
"stderr": stderr_content,
|
||||
"returncode": process.returncode
|
||||
"returncode": process.returncode,
|
||||
}
|
||||
|
||||
if time.time() - start_time > timeout_duration:
|
||||
@@ -139,7 +149,7 @@ def run_command(command, timeout=30, monitored=False):
|
||||
"stdout_so_far": stdout_content,
|
||||
"stderr_so_far": stderr_content,
|
||||
"pid": process.pid,
|
||||
"mux_name": mux_name
|
||||
"mux_name": mux_name,
|
||||
}
|
||||
|
||||
ready, _, _ = select.select([process.stdout, process.stderr], [], [], 0.1)
|
||||
@@ -158,6 +168,8 @@ def run_command(command, timeout=30, monitored=False):
|
||||
if mux_name:
|
||||
close_multiplexer(mux_name)
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def run_command_interactive(command):
|
||||
try:
|
||||
return_code = os.system(command)
|
||||
|
||||
+12
-4
@@ -1,18 +1,23 @@
|
||||
import time
|
||||
|
||||
|
||||
def db_set(key, value, db_conn):
|
||||
if not db_conn:
|
||||
return {"status": "error", "error": "Database not initialized"}
|
||||
|
||||
try:
|
||||
cursor = db_conn.cursor()
|
||||
cursor.execute("""INSERT OR REPLACE INTO kv_store (key, value, timestamp)
|
||||
VALUES (?, ?, ?)""", (key, value, time.time()))
|
||||
cursor.execute(
|
||||
"""INSERT OR REPLACE INTO kv_store (key, value, timestamp)
|
||||
VALUES (?, ?, ?)""",
|
||||
(key, value, time.time()),
|
||||
)
|
||||
db_conn.commit()
|
||||
return {"status": "success", "message": f"Set {key}"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def db_get(key, db_conn):
|
||||
if not db_conn:
|
||||
return {"status": "error", "error": "Database not initialized"}
|
||||
@@ -28,6 +33,7 @@ def db_get(key, db_conn):
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def db_query(query, db_conn):
|
||||
if not db_conn:
|
||||
return {"status": "error", "error": "Database not initialized"}
|
||||
@@ -36,9 +42,11 @@ def db_query(query, db_conn):
|
||||
cursor = db_conn.cursor()
|
||||
cursor.execute(query)
|
||||
|
||||
if query.strip().upper().startswith('SELECT'):
|
||||
if query.strip().upper().startswith("SELECT"):
|
||||
results = cursor.fetchall()
|
||||
columns = [desc[0] for desc in cursor.description] if cursor.description else []
|
||||
columns = (
|
||||
[desc[0] for desc in cursor.description] if cursor.description else []
|
||||
)
|
||||
return {"status": "success", "columns": columns, "rows": results}
|
||||
else:
|
||||
db_conn.commit()
|
||||
|
||||
+49
-20
@@ -1,18 +1,21 @@
|
||||
from pr.editor import RPEditor
|
||||
from pr.multiplexer import create_multiplexer, close_multiplexer, get_multiplexer
|
||||
from ..ui.diff_display import display_diff, get_diff_stats
|
||||
from ..ui.edit_feedback import track_edit, tracker
|
||||
from ..tools.patch import display_content_diff
|
||||
import os
|
||||
import os.path
|
||||
|
||||
from pr.editor import RPEditor
|
||||
from pr.multiplexer import close_multiplexer, create_multiplexer, get_multiplexer
|
||||
|
||||
from ..tools.patch import display_content_diff
|
||||
from ..ui.edit_feedback import track_edit, tracker
|
||||
|
||||
_editors = {}
|
||||
|
||||
|
||||
def get_editor(filepath):
|
||||
if filepath not in _editors:
|
||||
_editors[filepath] = RPEditor(filepath)
|
||||
return _editors[filepath]
|
||||
|
||||
|
||||
def close_editor(filepath):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
@@ -29,6 +32,7 @@ def close_editor(filepath):
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def open_editor(filepath):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
@@ -39,21 +43,28 @@ def open_editor(filepath):
|
||||
mux_name, mux = create_multiplexer(mux_name, show_output=True)
|
||||
mux.write_stdout(f"Opened editor for: {path}\n")
|
||||
|
||||
return {"status": "success", "message": f"Editor opened for {path}", "mux_name": mux_name}
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Editor opened for {path}",
|
||||
"mux_name": mux_name,
|
||||
}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def editor_insert_text(filepath, text, line=None, col=None, show_diff=True):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
|
||||
old_content = ""
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
old_content = f.read()
|
||||
|
||||
position = (line if line is not None else 0) * 1000 + (col if col is not None else 0)
|
||||
operation = track_edit('INSERT', filepath, start_pos=position, content=text)
|
||||
position = (line if line is not None else 0) * 1000 + (
|
||||
col if col is not None else 0
|
||||
)
|
||||
operation = track_edit("INSERT", filepath, start_pos=position, content=text)
|
||||
tracker.mark_in_progress(operation)
|
||||
|
||||
editor = get_editor(path)
|
||||
@@ -65,12 +76,16 @@ def editor_insert_text(filepath, text, line=None, col=None, show_diff=True):
|
||||
mux_name = f"editor-{path}"
|
||||
mux = get_multiplexer(mux_name)
|
||||
if mux:
|
||||
location = f" at line {line}, col {col}" if line is not None and col is not None else ""
|
||||
location = (
|
||||
f" at line {line}, col {col}"
|
||||
if line is not None and col is not None
|
||||
else ""
|
||||
)
|
||||
preview = text[:50] + "..." if len(text) > 50 else text
|
||||
mux.write_stdout(f"Inserted text{location}: {repr(preview)}\n")
|
||||
|
||||
if show_diff and old_content:
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
new_content = f.read()
|
||||
diff_result = display_content_diff(old_content, new_content, filepath)
|
||||
if diff_result["status"] == "success":
|
||||
@@ -81,23 +96,32 @@ def editor_insert_text(filepath, text, line=None, col=None, show_diff=True):
|
||||
close_editor(filepath)
|
||||
return result
|
||||
except Exception as e:
|
||||
if 'operation' in locals():
|
||||
if "operation" in locals():
|
||||
tracker.mark_failed(operation)
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def editor_replace_text(filepath, start_line, start_col, end_line, end_col, new_text, show_diff=True):
|
||||
|
||||
def editor_replace_text(
|
||||
filepath, start_line, start_col, end_line, end_col, new_text, show_diff=True
|
||||
):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
|
||||
old_content = ""
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
old_content = f.read()
|
||||
|
||||
start_pos = start_line * 1000 + start_col
|
||||
end_pos = end_line * 1000 + end_col
|
||||
operation = track_edit('REPLACE', filepath, start_pos=start_pos, end_pos=end_pos,
|
||||
content=new_text, old_content=old_content)
|
||||
operation = track_edit(
|
||||
"REPLACE",
|
||||
filepath,
|
||||
start_pos=start_pos,
|
||||
end_pos=end_pos,
|
||||
content=new_text,
|
||||
old_content=old_content,
|
||||
)
|
||||
tracker.mark_in_progress(operation)
|
||||
|
||||
editor = get_editor(path)
|
||||
@@ -108,10 +132,12 @@ def editor_replace_text(filepath, start_line, start_col, end_line, end_col, new_
|
||||
mux = get_multiplexer(mux_name)
|
||||
if mux:
|
||||
preview = new_text[:50] + "..." if len(new_text) > 50 else new_text
|
||||
mux.write_stdout(f"Replaced text from ({start_line},{start_col}) to ({end_line},{end_col}): {repr(preview)}\n")
|
||||
mux.write_stdout(
|
||||
f"Replaced text from ({start_line},{start_col}) to ({end_line},{end_col}): {repr(preview)}\n"
|
||||
)
|
||||
|
||||
if show_diff and old_content:
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
new_content = f.read()
|
||||
diff_result = display_content_diff(old_content, new_content, filepath)
|
||||
if diff_result["status"] == "success":
|
||||
@@ -122,10 +148,11 @@ def editor_replace_text(filepath, start_line, start_col, end_line, end_col, new_
|
||||
close_editor(filepath)
|
||||
return result
|
||||
except Exception as e:
|
||||
if 'operation' in locals():
|
||||
if "operation" in locals():
|
||||
tracker.mark_failed(operation)
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def editor_search(filepath, pattern, start_line=0):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
@@ -135,7 +162,9 @@ def editor_search(filepath, pattern, start_line=0):
|
||||
mux_name = f"editor-{path}"
|
||||
mux = get_multiplexer(mux_name)
|
||||
if mux:
|
||||
mux.write_stdout(f"Searched for pattern '{pattern}' from line {start_line}: {len(results)} matches\n")
|
||||
mux.write_stdout(
|
||||
f"Searched for pattern '{pattern}' from line {start_line}: {len(results)} matches\n"
|
||||
)
|
||||
|
||||
result = {"status": "success", "results": results}
|
||||
close_editor(filepath)
|
||||
|
||||
+150
-48
@@ -1,31 +1,36 @@
|
||||
import os
|
||||
import hashlib
|
||||
import os
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
from pr.editor import RPEditor
|
||||
from ..ui.diff_display import display_diff, get_diff_stats
|
||||
from ..ui.edit_feedback import track_edit, tracker
|
||||
|
||||
from ..tools.patch import display_content_diff
|
||||
from ..ui.diff_display import get_diff_stats
|
||||
from ..ui.edit_feedback import track_edit, tracker
|
||||
|
||||
_id = 0
|
||||
|
||||
|
||||
def get_uid():
|
||||
global _id
|
||||
_id += 3
|
||||
return _id
|
||||
|
||||
|
||||
def read_file(filepath, db_conn=None):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
if db_conn:
|
||||
from pr.tools.database import db_set
|
||||
|
||||
db_set("read:" + path, "true", db_conn)
|
||||
return {"status": "success", "content": content}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def write_file(filepath, content, db_conn=None, show_diff=True):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
@@ -34,15 +39,24 @@ def write_file(filepath, content, db_conn=None, show_diff=True):
|
||||
|
||||
if not is_new_file and db_conn:
|
||||
from pr.tools.database import db_get
|
||||
|
||||
read_status = db_get("read:" + path, db_conn)
|
||||
if read_status.get("status") != "success" or read_status.get("value") != "true":
|
||||
return {"status": "error", "error": "File must be read before writing. Please read the file first."}
|
||||
if (
|
||||
read_status.get("status") != "success"
|
||||
or read_status.get("value") != "true"
|
||||
):
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "File must be read before writing. Please read the file first.",
|
||||
}
|
||||
|
||||
if not is_new_file:
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
old_content = f.read()
|
||||
|
||||
operation = track_edit('WRITE', filepath, content=content, old_content=old_content)
|
||||
operation = track_edit(
|
||||
"WRITE", filepath, content=content, old_content=old_content
|
||||
)
|
||||
tracker.mark_in_progress(operation)
|
||||
|
||||
if show_diff and not is_new_file:
|
||||
@@ -59,13 +73,18 @@ def write_file(filepath, content, db_conn=None, show_diff=True):
|
||||
cursor = db_conn.cursor()
|
||||
file_hash = hashlib.md5(old_content.encode()).hexdigest()
|
||||
|
||||
cursor.execute("SELECT MAX(version) FROM file_versions WHERE filepath = ?", (filepath,))
|
||||
cursor.execute(
|
||||
"SELECT MAX(version) FROM file_versions WHERE filepath = ?",
|
||||
(filepath,),
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
version = (result[0] + 1) if result[0] else 1
|
||||
|
||||
cursor.execute("""INSERT INTO file_versions (filepath, content, hash, timestamp, version)
|
||||
cursor.execute(
|
||||
"""INSERT INTO file_versions (filepath, content, hash, timestamp, version)
|
||||
VALUES (?, ?, ?, ?, ?)""",
|
||||
(filepath, old_content, file_hash, time.time(), version))
|
||||
(filepath, old_content, file_hash, time.time(), version),
|
||||
)
|
||||
db_conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
@@ -79,10 +98,11 @@ def write_file(filepath, content, db_conn=None, show_diff=True):
|
||||
|
||||
return {"status": "success", "message": message}
|
||||
except Exception as e:
|
||||
if 'operation' in locals():
|
||||
if "operation" in locals():
|
||||
tracker.mark_failed(operation)
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def list_directory(path=".", recursive=False):
|
||||
try:
|
||||
path = os.path.expanduser(path)
|
||||
@@ -91,21 +111,36 @@ def list_directory(path=".", recursive=False):
|
||||
for root, dirs, files in os.walk(path):
|
||||
for name in files:
|
||||
item_path = os.path.join(root, name)
|
||||
items.append({"path": item_path, "type": "file", "size": os.path.getsize(item_path)})
|
||||
items.append(
|
||||
{
|
||||
"path": item_path,
|
||||
"type": "file",
|
||||
"size": os.path.getsize(item_path),
|
||||
}
|
||||
)
|
||||
for name in dirs:
|
||||
items.append({"path": os.path.join(root, name), "type": "directory"})
|
||||
items.append(
|
||||
{"path": os.path.join(root, name), "type": "directory"}
|
||||
)
|
||||
else:
|
||||
for item in os.listdir(path):
|
||||
item_path = os.path.join(path, item)
|
||||
items.append({
|
||||
"name": item,
|
||||
"type": "directory" if os.path.isdir(item_path) else "file",
|
||||
"size": os.path.getsize(item_path) if os.path.isfile(item_path) else None
|
||||
})
|
||||
items.append(
|
||||
{
|
||||
"name": item,
|
||||
"type": "directory" if os.path.isdir(item_path) else "file",
|
||||
"size": (
|
||||
os.path.getsize(item_path)
|
||||
if os.path.isfile(item_path)
|
||||
else None
|
||||
),
|
||||
}
|
||||
)
|
||||
return {"status": "success", "items": items}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def mkdir(path):
|
||||
try:
|
||||
os.makedirs(os.path.expanduser(path), exist_ok=True)
|
||||
@@ -113,6 +148,7 @@ def mkdir(path):
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def chdir(path):
|
||||
try:
|
||||
os.chdir(os.path.expanduser(path))
|
||||
@@ -120,16 +156,32 @@ def chdir(path):
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def getpwd():
|
||||
try:
|
||||
return {"status": "success", "path": os.getcwd()}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def index_source_directory(path):
|
||||
extensions = [
|
||||
".py", ".js", ".ts", ".java", ".cpp", ".c", ".h", ".hpp",
|
||||
".html", ".css", ".json", ".xml", ".md", ".sh", ".rb", ".go"
|
||||
".py",
|
||||
".js",
|
||||
".ts",
|
||||
".java",
|
||||
".cpp",
|
||||
".c",
|
||||
".h",
|
||||
".hpp",
|
||||
".html",
|
||||
".css",
|
||||
".json",
|
||||
".xml",
|
||||
".md",
|
||||
".sh",
|
||||
".rb",
|
||||
".go",
|
||||
]
|
||||
source_files = []
|
||||
try:
|
||||
@@ -138,18 +190,16 @@ def index_source_directory(path):
|
||||
if any(file.endswith(ext) for ext in extensions):
|
||||
filepath = os.path.join(root, file)
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
with open(filepath, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
source_files.append({
|
||||
"path": filepath,
|
||||
"content": content
|
||||
})
|
||||
source_files.append({"path": filepath, "content": content})
|
||||
except Exception:
|
||||
continue
|
||||
return {"status": "success", "indexed_files": source_files}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def search_replace(filepath, old_string, new_string, db_conn=None):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
@@ -157,25 +207,38 @@ def search_replace(filepath, old_string, new_string, db_conn=None):
|
||||
return {"status": "error", "error": "File does not exist"}
|
||||
if db_conn:
|
||||
from pr.tools.database import db_get
|
||||
|
||||
read_status = db_get("read:" + path, db_conn)
|
||||
if read_status.get("status") != "success" or read_status.get("value") != "true":
|
||||
return {"status": "error", "error": "File must be read before writing. Please read the file first."}
|
||||
with open(path, 'r') as f:
|
||||
if (
|
||||
read_status.get("status") != "success"
|
||||
or read_status.get("value") != "true"
|
||||
):
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "File must be read before writing. Please read the file first.",
|
||||
}
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
content = content.replace(old_string, new_string)
|
||||
with open(path, 'w') as f:
|
||||
with open(path, "w") as f:
|
||||
f.write(content)
|
||||
return {"status": "success", "message": f"Replaced '{old_string}' with '{new_string}' in {path}"}
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Replaced '{old_string}' with '{new_string}' in {path}",
|
||||
}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
_editors = {}
|
||||
|
||||
|
||||
def get_editor(filepath):
|
||||
if filepath not in _editors:
|
||||
_editors[filepath] = RPEditor(filepath)
|
||||
return _editors[filepath]
|
||||
|
||||
|
||||
def close_editor(filepath):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
@@ -185,6 +248,7 @@ def close_editor(filepath):
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def open_editor(filepath):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
@@ -194,22 +258,34 @@ def open_editor(filepath):
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def editor_insert_text(filepath, text, line=None, col=None, show_diff=True, db_conn=None):
|
||||
|
||||
def editor_insert_text(
|
||||
filepath, text, line=None, col=None, show_diff=True, db_conn=None
|
||||
):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
if db_conn:
|
||||
from pr.tools.database import db_get
|
||||
|
||||
read_status = db_get("read:" + path, db_conn)
|
||||
if read_status.get("status") != "success" or read_status.get("value") != "true":
|
||||
return {"status": "error", "error": "File must be read before writing. Please read the file first."}
|
||||
if (
|
||||
read_status.get("status") != "success"
|
||||
or read_status.get("value") != "true"
|
||||
):
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "File must be read before writing. Please read the file first.",
|
||||
}
|
||||
|
||||
old_content = ""
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
old_content = f.read()
|
||||
|
||||
position = (line if line is not None else 0) * 1000 + (col if col is not None else 0)
|
||||
operation = track_edit('INSERT', filepath, start_pos=position, content=text)
|
||||
position = (line if line is not None else 0) * 1000 + (
|
||||
col if col is not None else 0
|
||||
)
|
||||
operation = track_edit("INSERT", filepath, start_pos=position, content=text)
|
||||
tracker.mark_in_progress(operation)
|
||||
|
||||
editor = get_editor(path)
|
||||
@@ -219,7 +295,7 @@ def editor_insert_text(filepath, text, line=None, col=None, show_diff=True, db_c
|
||||
editor.save_file()
|
||||
|
||||
if show_diff and old_content:
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
new_content = f.read()
|
||||
diff_result = display_content_diff(old_content, new_content, filepath)
|
||||
if diff_result["status"] == "success":
|
||||
@@ -228,28 +304,51 @@ def editor_insert_text(filepath, text, line=None, col=None, show_diff=True, db_c
|
||||
tracker.mark_completed(operation)
|
||||
return {"status": "success", "message": f"Inserted text in {path}"}
|
||||
except Exception as e:
|
||||
if 'operation' in locals():
|
||||
if "operation" in locals():
|
||||
tracker.mark_failed(operation)
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def editor_replace_text(filepath, start_line, start_col, end_line, end_col, new_text, show_diff=True, db_conn=None):
|
||||
|
||||
def editor_replace_text(
|
||||
filepath,
|
||||
start_line,
|
||||
start_col,
|
||||
end_line,
|
||||
end_col,
|
||||
new_text,
|
||||
show_diff=True,
|
||||
db_conn=None,
|
||||
):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
if db_conn:
|
||||
from pr.tools.database import db_get
|
||||
|
||||
read_status = db_get("read:" + path, db_conn)
|
||||
if read_status.get("status") != "success" or read_status.get("value") != "true":
|
||||
return {"status": "error", "error": "File must be read before writing. Please read the file first."}
|
||||
if (
|
||||
read_status.get("status") != "success"
|
||||
or read_status.get("value") != "true"
|
||||
):
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "File must be read before writing. Please read the file first.",
|
||||
}
|
||||
|
||||
old_content = ""
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
old_content = f.read()
|
||||
|
||||
start_pos = start_line * 1000 + start_col
|
||||
end_pos = end_line * 1000 + end_col
|
||||
operation = track_edit('REPLACE', filepath, start_pos=start_pos, end_pos=end_pos,
|
||||
content=new_text, old_content=old_content)
|
||||
operation = track_edit(
|
||||
"REPLACE",
|
||||
filepath,
|
||||
start_pos=start_pos,
|
||||
end_pos=end_pos,
|
||||
content=new_text,
|
||||
old_content=old_content,
|
||||
)
|
||||
tracker.mark_in_progress(operation)
|
||||
|
||||
editor = get_editor(path)
|
||||
@@ -257,7 +356,7 @@ def editor_replace_text(filepath, start_line, start_col, end_line, end_col, new_
|
||||
editor.save_file()
|
||||
|
||||
if show_diff and old_content:
|
||||
with open(path, 'r') as f:
|
||||
with open(path) as f:
|
||||
new_content = f.read()
|
||||
diff_result = display_content_diff(old_content, new_content, filepath)
|
||||
if diff_result["status"] == "success":
|
||||
@@ -266,22 +365,25 @@ def editor_replace_text(filepath, start_line, start_col, end_line, end_col, new_
|
||||
tracker.mark_completed(operation)
|
||||
return {"status": "success", "message": f"Replaced text in {path}"}
|
||||
except Exception as e:
|
||||
if 'operation' in locals():
|
||||
if "operation" in locals():
|
||||
tracker.mark_failed(operation)
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def display_edit_summary():
|
||||
from ..ui.edit_feedback import display_edit_summary
|
||||
|
||||
return display_edit_summary()
|
||||
|
||||
|
||||
def display_edit_timeline(show_content=False):
|
||||
from ..ui.edit_feedback import display_edit_timeline
|
||||
|
||||
return display_edit_timeline(show_content)
|
||||
|
||||
|
||||
def clear_edit_tracker():
|
||||
from ..ui.edit_feedback import clear_tracker
|
||||
|
||||
clear_tracker()
|
||||
return {"status": "success", "message": "Edit tracker cleared"}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from pr.multiplexer import create_multiplexer, get_multiplexer, close_multiplexer, get_all_multiplexer_states
|
||||
|
||||
def start_interactive_session(command, session_name=None, process_type='generic'):
|
||||
from pr.multiplexer import (
|
||||
close_multiplexer,
|
||||
create_multiplexer,
|
||||
get_all_multiplexer_states,
|
||||
get_multiplexer,
|
||||
)
|
||||
|
||||
|
||||
def start_interactive_session(command, session_name=None, process_type="generic"):
|
||||
"""
|
||||
Start an interactive session in a dedicated multiplexer.
|
||||
|
||||
@@ -16,7 +22,7 @@ def start_interactive_session(command, session_name=None, process_type='generic'
|
||||
session_name: The name of the created session
|
||||
"""
|
||||
name, mux = create_multiplexer(session_name)
|
||||
mux.update_metadata('process_type', process_type)
|
||||
mux.update_metadata("process_type", process_type)
|
||||
|
||||
# Start the process
|
||||
if isinstance(command, str):
|
||||
@@ -29,19 +35,23 @@ def start_interactive_session(command, session_name=None, process_type='generic'
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1
|
||||
bufsize=1,
|
||||
)
|
||||
|
||||
mux.process = process
|
||||
mux.update_metadata('pid', process.pid)
|
||||
mux.update_metadata("pid", process.pid)
|
||||
|
||||
# Set process type and handler
|
||||
detected_type = detect_process_type(command)
|
||||
mux.set_process_type(detected_type)
|
||||
|
||||
# Start output readers
|
||||
stdout_thread = threading.Thread(target=_read_output, args=(process.stdout, mux.write_stdout), daemon=True)
|
||||
stderr_thread = threading.Thread(target=_read_output, args=(process.stderr, mux.write_stderr), daemon=True)
|
||||
stdout_thread = threading.Thread(
|
||||
target=_read_output, args=(process.stdout, mux.write_stdout), daemon=True
|
||||
)
|
||||
stderr_thread = threading.Thread(
|
||||
target=_read_output, args=(process.stderr, mux.write_stderr), daemon=True
|
||||
)
|
||||
|
||||
stdout_thread.start()
|
||||
stderr_thread.start()
|
||||
@@ -54,15 +64,17 @@ def start_interactive_session(command, session_name=None, process_type='generic'
|
||||
close_multiplexer(name)
|
||||
raise e
|
||||
|
||||
|
||||
def _read_output(stream, write_func):
|
||||
"""Read from a stream and write to multiplexer buffer."""
|
||||
try:
|
||||
for line in iter(stream.readline, ''):
|
||||
for line in iter(stream.readline, ""):
|
||||
if line:
|
||||
write_func(line.rstrip('\n'))
|
||||
write_func(line.rstrip("\n"))
|
||||
except Exception as e:
|
||||
print(f"Error reading output: {e}")
|
||||
|
||||
|
||||
def send_input_to_session(session_name, input_data):
|
||||
"""
|
||||
Send input to an interactive session.
|
||||
@@ -75,15 +87,16 @@ def send_input_to_session(session_name, input_data):
|
||||
if not mux:
|
||||
raise ValueError(f"Session {session_name} not found")
|
||||
|
||||
if not hasattr(mux, 'process') or mux.process.poll() is not None:
|
||||
if not hasattr(mux, "process") or mux.process.poll() is not None:
|
||||
raise ValueError(f"Session {session_name} is not active")
|
||||
|
||||
try:
|
||||
mux.process.stdin.write(input_data + '\n')
|
||||
mux.process.stdin.write(input_data + "\n")
|
||||
mux.process.stdin.flush()
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to send input to session {session_name}: {e}")
|
||||
|
||||
|
||||
def read_session_output(session_name, lines=None):
|
||||
"""
|
||||
Read output from a session.
|
||||
@@ -102,14 +115,12 @@ def read_session_output(session_name, lines=None):
|
||||
output = mux.get_all_output()
|
||||
if lines is not None:
|
||||
# Return last N lines
|
||||
stdout_lines = output['stdout'].split('\n')[-lines:] if output['stdout'] else []
|
||||
stderr_lines = output['stderr'].split('\n')[-lines:] if output['stderr'] else []
|
||||
output = {
|
||||
'stdout': '\n'.join(stdout_lines),
|
||||
'stderr': '\n'.join(stderr_lines)
|
||||
}
|
||||
stdout_lines = output["stdout"].split("\n")[-lines:] if output["stdout"] else []
|
||||
stderr_lines = output["stderr"].split("\n")[-lines:] if output["stderr"] else []
|
||||
output = {"stdout": "\n".join(stdout_lines), "stderr": "\n".join(stderr_lines)}
|
||||
return output
|
||||
|
||||
|
||||
def list_active_sessions():
|
||||
"""
|
||||
List all active interactive sessions.
|
||||
@@ -119,6 +130,7 @@ def list_active_sessions():
|
||||
"""
|
||||
return get_all_multiplexer_states()
|
||||
|
||||
|
||||
def get_session_status(session_name):
|
||||
"""
|
||||
Get detailed status of a session.
|
||||
@@ -134,15 +146,16 @@ def get_session_status(session_name):
|
||||
return None
|
||||
|
||||
status = mux.get_metadata()
|
||||
status['is_active'] = hasattr(mux, 'process') and mux.process.poll() is None
|
||||
if status['is_active']:
|
||||
status['pid'] = mux.process.pid
|
||||
status['output_summary'] = {
|
||||
'stdout_lines': len(mux.stdout_buffer),
|
||||
'stderr_lines': len(mux.stderr_buffer)
|
||||
status["is_active"] = hasattr(mux, "process") and mux.process.poll() is None
|
||||
if status["is_active"]:
|
||||
status["pid"] = mux.process.pid
|
||||
status["output_summary"] = {
|
||||
"stdout_lines": len(mux.stdout_buffer),
|
||||
"stderr_lines": len(mux.stderr_buffer),
|
||||
}
|
||||
return status
|
||||
|
||||
|
||||
def close_interactive_session(session_name):
|
||||
"""
|
||||
Close an interactive session.
|
||||
|
||||
+41
-23
@@ -1,38 +1,43 @@
|
||||
import os
|
||||
from typing import Dict, Any, List
|
||||
from pr.memory.knowledge_store import KnowledgeStore, KnowledgeEntry
|
||||
import time
|
||||
import uuid
|
||||
from typing import Any, Dict
|
||||
|
||||
def add_knowledge_entry(category: str, content: str, metadata: Dict[str, Any] = None, entry_id: str = None) -> Dict[str, Any]:
|
||||
from pr.memory.knowledge_store import KnowledgeEntry, KnowledgeStore
|
||||
|
||||
|
||||
def add_knowledge_entry(
|
||||
category: str, content: str, metadata: Dict[str, Any] = None, entry_id: str = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Add a new entry to the knowledge base."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
store = KnowledgeStore(db_path)
|
||||
|
||||
|
||||
if entry_id is None:
|
||||
entry_id = str(uuid.uuid4())[:16]
|
||||
|
||||
|
||||
entry = KnowledgeEntry(
|
||||
entry_id=entry_id,
|
||||
category=category,
|
||||
content=content,
|
||||
metadata=metadata or {},
|
||||
created_at=time.time(),
|
||||
updated_at=time.time()
|
||||
updated_at=time.time(),
|
||||
)
|
||||
|
||||
|
||||
store.add_entry(entry)
|
||||
return {"status": "success", "entry_id": entry_id}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def get_knowledge_entry(entry_id: str) -> Dict[str, Any]:
|
||||
"""Retrieve a knowledge entry by ID."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
store = KnowledgeStore(db_path)
|
||||
|
||||
|
||||
entry = store.get_entry(entry_id)
|
||||
if entry:
|
||||
return {"status": "success", "entry": entry.to_dict()}
|
||||
@@ -41,58 +46,71 @@ def get_knowledge_entry(entry_id: str) -> Dict[str, Any]:
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def search_knowledge(query: str, category: str = None, top_k: int = 5) -> Dict[str, Any]:
|
||||
|
||||
def search_knowledge(
|
||||
query: str, category: str = None, top_k: int = 5
|
||||
) -> Dict[str, Any]:
|
||||
"""Search the knowledge base semantically."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
store = KnowledgeStore(db_path)
|
||||
|
||||
|
||||
entries = store.search_entries(query, category, top_k)
|
||||
results = [entry.to_dict() for entry in entries]
|
||||
return {"status": "success", "results": results}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def get_knowledge_by_category(category: str, limit: int = 20) -> Dict[str, Any]:
|
||||
"""Get knowledge entries by category."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
store = KnowledgeStore(db_path)
|
||||
|
||||
|
||||
entries = store.get_by_category(category, limit)
|
||||
results = [entry.to_dict() for entry in entries]
|
||||
return {"status": "success", "entries": results}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def update_knowledge_importance(entry_id: str, importance_score: float) -> Dict[str, Any]:
|
||||
|
||||
def update_knowledge_importance(
|
||||
entry_id: str, importance_score: float
|
||||
) -> Dict[str, Any]:
|
||||
"""Update the importance score of a knowledge entry."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
store = KnowledgeStore(db_path)
|
||||
|
||||
|
||||
store.update_importance(entry_id, importance_score)
|
||||
return {"status": "success", "entry_id": entry_id, "importance_score": importance_score}
|
||||
return {
|
||||
"status": "success",
|
||||
"entry_id": entry_id,
|
||||
"importance_score": importance_score,
|
||||
}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def delete_knowledge_entry(entry_id: str) -> Dict[str, Any]:
|
||||
"""Delete a knowledge entry."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
store = KnowledgeStore(db_path)
|
||||
|
||||
|
||||
success = store.delete_entry(entry_id)
|
||||
return {"status": "success" if success else "not_found", "entry_id": entry_id}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def get_knowledge_statistics() -> Dict[str, Any]:
|
||||
"""Get statistics about the knowledge base."""
|
||||
try:
|
||||
db_path = os.path.expanduser('~/.assistant_db.sqlite')
|
||||
db_path = os.path.expanduser("~/.assistant_db.sqlite")
|
||||
store = KnowledgeStore(db_path)
|
||||
|
||||
|
||||
stats = store.get_statistics()
|
||||
return {"status": "success", "statistics": stats}
|
||||
except Exception as e:
|
||||
|
||||
+45
-30
@@ -1,23 +1,37 @@
|
||||
import os
|
||||
import tempfile
|
||||
import subprocess
|
||||
import difflib
|
||||
from ..ui.diff_display import display_diff, get_diff_stats, DiffDisplay
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from ..ui.diff_display import display_diff, get_diff_stats
|
||||
|
||||
|
||||
def apply_patch(filepath, patch_content, db_conn=None):
|
||||
try:
|
||||
path = os.path.expanduser(filepath)
|
||||
if db_conn:
|
||||
from pr.tools.database import db_get
|
||||
|
||||
read_status = db_get("read:" + path, db_conn)
|
||||
if read_status.get("status") != "success" or read_status.get("value") != "true":
|
||||
return {"status": "error", "error": "File must be read before writing. Please read the file first."}
|
||||
if (
|
||||
read_status.get("status") != "success"
|
||||
or read_status.get("value") != "true"
|
||||
):
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "File must be read before writing. Please read the file first.",
|
||||
}
|
||||
# Write patch to temp file
|
||||
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.patch') as f:
|
||||
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".patch") as f:
|
||||
f.write(patch_content)
|
||||
patch_file = f.name
|
||||
# Run patch command
|
||||
result = subprocess.run(['patch', path, patch_file], capture_output=True, text=True, cwd=os.path.dirname(path))
|
||||
result = subprocess.run(
|
||||
["patch", path, patch_file],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=os.path.dirname(path),
|
||||
)
|
||||
os.unlink(patch_file)
|
||||
if result.returncode == 0:
|
||||
return {"status": "success", "output": result.stdout.strip()}
|
||||
@@ -26,11 +40,14 @@ def apply_patch(filepath, patch_content, db_conn=None):
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def create_diff(file1, file2, fromfile='file1', tofile='file2', visual=False, format_type='unified'):
|
||||
|
||||
def create_diff(
|
||||
file1, file2, fromfile="file1", tofile="file2", visual=False, format_type="unified"
|
||||
):
|
||||
try:
|
||||
path1 = os.path.expanduser(file1)
|
||||
path2 = os.path.expanduser(file2)
|
||||
with open(path1, 'r') as f1, open(path2, 'r') as f2:
|
||||
with open(path1) as f1, open(path2) as f2:
|
||||
content1 = f1.read()
|
||||
content2 = f2.read()
|
||||
|
||||
@@ -39,53 +56,51 @@ def create_diff(file1, file2, fromfile='file1', tofile='file2', visual=False, fo
|
||||
stats = get_diff_stats(content1, content2)
|
||||
lines1 = content1.splitlines(keepends=True)
|
||||
lines2 = content2.splitlines(keepends=True)
|
||||
plain_diff = list(difflib.unified_diff(lines1, lines2, fromfile=fromfile, tofile=tofile))
|
||||
plain_diff = list(
|
||||
difflib.unified_diff(lines1, lines2, fromfile=fromfile, tofile=tofile)
|
||||
)
|
||||
return {
|
||||
"status": "success",
|
||||
"diff": ''.join(plain_diff),
|
||||
"diff": "".join(plain_diff),
|
||||
"visual_diff": visual_diff,
|
||||
"stats": stats
|
||||
"stats": stats,
|
||||
}
|
||||
else:
|
||||
lines1 = content1.splitlines(keepends=True)
|
||||
lines2 = content2.splitlines(keepends=True)
|
||||
diff = list(difflib.unified_diff(lines1, lines2, fromfile=fromfile, tofile=tofile))
|
||||
return {"status": "success", "diff": ''.join(diff)}
|
||||
diff = list(
|
||||
difflib.unified_diff(lines1, lines2, fromfile=fromfile, tofile=tofile)
|
||||
)
|
||||
return {"status": "success", "diff": "".join(diff)}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def display_file_diff(filepath1, filepath2, format_type='unified', context_lines=3):
|
||||
def display_file_diff(filepath1, filepath2, format_type="unified", context_lines=3):
|
||||
try:
|
||||
path1 = os.path.expanduser(filepath1)
|
||||
path2 = os.path.expanduser(filepath2)
|
||||
|
||||
with open(path1, 'r') as f1:
|
||||
with open(path1) as f1:
|
||||
old_content = f1.read()
|
||||
with open(path2, 'r') as f2:
|
||||
with open(path2) as f2:
|
||||
new_content = f2.read()
|
||||
|
||||
visual_diff = display_diff(old_content, new_content, filepath1, format_type)
|
||||
stats = get_diff_stats(old_content, new_content)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"visual_diff": visual_diff,
|
||||
"stats": stats
|
||||
}
|
||||
return {"status": "success", "visual_diff": visual_diff, "stats": stats}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def display_content_diff(old_content, new_content, filename='file', format_type='unified'):
|
||||
def display_content_diff(
|
||||
old_content, new_content, filename="file", format_type="unified"
|
||||
):
|
||||
try:
|
||||
visual_diff = display_diff(old_content, new_content, filename, format_type)
|
||||
stats = get_diff_stats(old_content, new_content)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"visual_diff": visual_diff,
|
||||
"stats": stats
|
||||
}
|
||||
return {"status": "success", "visual_diff": visual_diff, "stats": stats}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
+136
-128
@@ -1,14 +1,13 @@
|
||||
import re
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class ProcessHandler(ABC):
|
||||
"""Base class for process-specific handlers."""
|
||||
|
||||
def __init__(self, multiplexer):
|
||||
self.multiplexer = multiplexer
|
||||
self.state_machine = {}
|
||||
self.current_state = 'initial'
|
||||
self.current_state = "initial"
|
||||
self.prompt_patterns = []
|
||||
self.response_suggestions = {}
|
||||
|
||||
@@ -27,7 +26,8 @@ class ProcessHandler(ABC):
|
||||
|
||||
def is_waiting_for_input(self):
|
||||
"""Check if process appears to be waiting for input."""
|
||||
return self.current_state in ['waiting_confirmation', 'waiting_input']
|
||||
return self.current_state in ["waiting_confirmation", "waiting_input"]
|
||||
|
||||
|
||||
class AptHandler(ProcessHandler):
|
||||
"""Handler for apt package manager interactions."""
|
||||
@@ -35,230 +35,238 @@ class AptHandler(ProcessHandler):
|
||||
def __init__(self, multiplexer):
|
||||
super().__init__(multiplexer)
|
||||
self.state_machine = {
|
||||
'initial': ['running_command'],
|
||||
'running_command': ['waiting_confirmation', 'completed'],
|
||||
'waiting_confirmation': ['confirmed', 'cancelled'],
|
||||
'confirmed': ['installing', 'completed'],
|
||||
'installing': ['completed', 'error'],
|
||||
'completed': [],
|
||||
'error': [],
|
||||
'cancelled': []
|
||||
"initial": ["running_command"],
|
||||
"running_command": ["waiting_confirmation", "completed"],
|
||||
"waiting_confirmation": ["confirmed", "cancelled"],
|
||||
"confirmed": ["installing", "completed"],
|
||||
"installing": ["completed", "error"],
|
||||
"completed": [],
|
||||
"error": [],
|
||||
"cancelled": [],
|
||||
}
|
||||
self.prompt_patterns = [
|
||||
(r'Do you want to continue\?', 'confirmation'),
|
||||
(r'After this operation.*installed\.', 'size_info'),
|
||||
(r'Need to get.*B of archives\.', 'download_info'),
|
||||
(r'Unpacking.*Configuring', 'configuring'),
|
||||
(r'Setting up', 'setting_up'),
|
||||
(r'E:\s', 'error')
|
||||
(r"Do you want to continue\?", "confirmation"),
|
||||
(r"After this operation.*installed\.", "size_info"),
|
||||
(r"Need to get.*B of archives\.", "download_info"),
|
||||
(r"Unpacking.*Configuring", "configuring"),
|
||||
(r"Setting up", "setting_up"),
|
||||
(r"E:\s", "error"),
|
||||
]
|
||||
|
||||
def get_process_type(self):
|
||||
return 'apt'
|
||||
return "apt"
|
||||
|
||||
def update_state(self, output):
|
||||
"""Update state based on apt output patterns."""
|
||||
output_lower = output.lower()
|
||||
|
||||
# Check for completion
|
||||
if 'processing triggers' in output_lower or 'done' in output_lower:
|
||||
self.current_state = 'completed'
|
||||
if "processing triggers" in output_lower or "done" in output_lower:
|
||||
self.current_state = "completed"
|
||||
# Check for confirmation prompts
|
||||
elif 'do you want to continue' in output_lower:
|
||||
self.current_state = 'waiting_confirmation'
|
||||
elif "do you want to continue" in output_lower:
|
||||
self.current_state = "waiting_confirmation"
|
||||
# Check for installation progress
|
||||
elif 'setting up' in output_lower or 'unpacking' in output_lower:
|
||||
self.current_state = 'installing'
|
||||
elif "setting up" in output_lower or "unpacking" in output_lower:
|
||||
self.current_state = "installing"
|
||||
# Check for errors
|
||||
elif 'e:' in output_lower or 'error' in output_lower:
|
||||
self.current_state = 'error'
|
||||
elif "e:" in output_lower or "error" in output_lower:
|
||||
self.current_state = "error"
|
||||
|
||||
def get_prompt_suggestions(self):
|
||||
"""Return suggested responses for apt prompts."""
|
||||
suggestions = super().get_prompt_suggestions()
|
||||
if self.current_state == 'waiting_confirmation':
|
||||
suggestions.extend(['y', 'yes', 'n', 'no'])
|
||||
if self.current_state == "waiting_confirmation":
|
||||
suggestions.extend(["y", "yes", "n", "no"])
|
||||
return suggestions
|
||||
|
||||
|
||||
class VimHandler(ProcessHandler):
|
||||
"""Handler for vim editor interactions."""
|
||||
|
||||
def __init__(self, multiplexer):
|
||||
super().__init__(multiplexer)
|
||||
self.state_machine = {
|
||||
'initial': ['normal_mode', 'insert_mode'],
|
||||
'normal_mode': ['insert_mode', 'command_mode', 'visual_mode'],
|
||||
'insert_mode': ['normal_mode'],
|
||||
'command_mode': ['normal_mode'],
|
||||
'visual_mode': ['normal_mode'],
|
||||
'exiting': []
|
||||
"initial": ["normal_mode", "insert_mode"],
|
||||
"normal_mode": ["insert_mode", "command_mode", "visual_mode"],
|
||||
"insert_mode": ["normal_mode"],
|
||||
"command_mode": ["normal_mode"],
|
||||
"visual_mode": ["normal_mode"],
|
||||
"exiting": [],
|
||||
}
|
||||
self.prompt_patterns = [
|
||||
(r'-- INSERT --', 'insert_mode'),
|
||||
(r'-- VISUAL --', 'visual_mode'),
|
||||
(r':', 'command_mode'),
|
||||
(r'Press ENTER', 'waiting_enter'),
|
||||
(r'Saved', 'saved')
|
||||
(r"-- INSERT --", "insert_mode"),
|
||||
(r"-- VISUAL --", "visual_mode"),
|
||||
(r":", "command_mode"),
|
||||
(r"Press ENTER", "waiting_enter"),
|
||||
(r"Saved", "saved"),
|
||||
]
|
||||
self.mode_indicators = {
|
||||
'insert': '-- INSERT --',
|
||||
'visual': '-- VISUAL --',
|
||||
'command': ':'
|
||||
"insert": "-- INSERT --",
|
||||
"visual": "-- VISUAL --",
|
||||
"command": ":",
|
||||
}
|
||||
|
||||
def get_process_type(self):
|
||||
return 'vim'
|
||||
return "vim"
|
||||
|
||||
def update_state(self, output):
|
||||
"""Update state based on vim mode indicators."""
|
||||
if '-- INSERT --' in output:
|
||||
self.current_state = 'insert_mode'
|
||||
elif '-- VISUAL --' in output:
|
||||
self.current_state = 'visual_mode'
|
||||
elif output.strip().endswith(':'):
|
||||
self.current_state = 'command_mode'
|
||||
elif 'Press ENTER' in output:
|
||||
self.current_state = 'waiting_enter'
|
||||
if "-- INSERT --" in output:
|
||||
self.current_state = "insert_mode"
|
||||
elif "-- VISUAL --" in output:
|
||||
self.current_state = "visual_mode"
|
||||
elif output.strip().endswith(":"):
|
||||
self.current_state = "command_mode"
|
||||
elif "Press ENTER" in output:
|
||||
self.current_state = "waiting_enter"
|
||||
else:
|
||||
# Default to normal mode if no specific indicators
|
||||
self.current_state = 'normal_mode'
|
||||
self.current_state = "normal_mode"
|
||||
|
||||
def get_prompt_suggestions(self):
|
||||
"""Return suggested commands for vim modes."""
|
||||
suggestions = super().get_prompt_suggestions()
|
||||
if self.current_state == 'command_mode':
|
||||
suggestions.extend(['w', 'q', 'wq', 'q!', 'w!'])
|
||||
elif self.current_state == 'normal_mode':
|
||||
suggestions.extend(['i', 'a', 'o', 'dd', ':w', ':q'])
|
||||
elif self.current_state == 'waiting_enter':
|
||||
suggestions.extend(['\n'])
|
||||
if self.current_state == "command_mode":
|
||||
suggestions.extend(["w", "q", "wq", "q!", "w!"])
|
||||
elif self.current_state == "normal_mode":
|
||||
suggestions.extend(["i", "a", "o", "dd", ":w", ":q"])
|
||||
elif self.current_state == "waiting_enter":
|
||||
suggestions.extend(["\n"])
|
||||
return suggestions
|
||||
|
||||
|
||||
class SSHHandler(ProcessHandler):
|
||||
"""Handler for SSH connection interactions."""
|
||||
|
||||
def __init__(self, multiplexer):
|
||||
super().__init__(multiplexer)
|
||||
self.state_machine = {
|
||||
'initial': ['connecting'],
|
||||
'connecting': ['auth_prompt', 'connected', 'failed'],
|
||||
'auth_prompt': ['connected', 'failed'],
|
||||
'connected': ['shell', 'disconnected'],
|
||||
'shell': ['disconnected'],
|
||||
'failed': [],
|
||||
'disconnected': []
|
||||
"initial": ["connecting"],
|
||||
"connecting": ["auth_prompt", "connected", "failed"],
|
||||
"auth_prompt": ["connected", "failed"],
|
||||
"connected": ["shell", "disconnected"],
|
||||
"shell": ["disconnected"],
|
||||
"failed": [],
|
||||
"disconnected": [],
|
||||
}
|
||||
self.prompt_patterns = [
|
||||
(r'password:', 'password_prompt'),
|
||||
(r'yes/no', 'host_key_prompt'),
|
||||
(r'Permission denied', 'auth_failed'),
|
||||
(r'Welcome to', 'connected'),
|
||||
(r'\$', 'shell_prompt'),
|
||||
(r'\#', 'root_shell_prompt'),
|
||||
(r'Connection closed', 'disconnected')
|
||||
(r"password:", "password_prompt"),
|
||||
(r"yes/no", "host_key_prompt"),
|
||||
(r"Permission denied", "auth_failed"),
|
||||
(r"Welcome to", "connected"),
|
||||
(r"\$", "shell_prompt"),
|
||||
(r"\#", "root_shell_prompt"),
|
||||
(r"Connection closed", "disconnected"),
|
||||
]
|
||||
|
||||
def get_process_type(self):
|
||||
return 'ssh'
|
||||
return "ssh"
|
||||
|
||||
def update_state(self, output):
|
||||
"""Update state based on SSH connection output."""
|
||||
output_lower = output.lower()
|
||||
|
||||
if 'permission denied' in output_lower:
|
||||
self.current_state = 'failed'
|
||||
elif 'password:' in output_lower:
|
||||
self.current_state = 'auth_prompt'
|
||||
elif 'yes/no' in output_lower:
|
||||
self.current_state = 'auth_prompt'
|
||||
elif 'welcome to' in output_lower or 'last login' in output_lower:
|
||||
self.current_state = 'connected'
|
||||
elif output.strip().endswith('$') or output.strip().endswith('#'):
|
||||
self.current_state = 'shell'
|
||||
elif 'connection closed' in output_lower:
|
||||
self.current_state = 'disconnected'
|
||||
if "permission denied" in output_lower:
|
||||
self.current_state = "failed"
|
||||
elif "password:" in output_lower:
|
||||
self.current_state = "auth_prompt"
|
||||
elif "yes/no" in output_lower:
|
||||
self.current_state = "auth_prompt"
|
||||
elif "welcome to" in output_lower or "last login" in output_lower:
|
||||
self.current_state = "connected"
|
||||
elif output.strip().endswith("$") or output.strip().endswith("#"):
|
||||
self.current_state = "shell"
|
||||
elif "connection closed" in output_lower:
|
||||
self.current_state = "disconnected"
|
||||
|
||||
def get_prompt_suggestions(self):
|
||||
"""Return suggested responses for SSH prompts."""
|
||||
suggestions = super().get_prompt_suggestions()
|
||||
if self.current_state == 'auth_prompt':
|
||||
if 'password:' in self.multiplexer.get_all_output()['stdout']:
|
||||
suggestions.extend(['<password>']) # Placeholder for actual password
|
||||
elif 'yes/no' in self.multiplexer.get_all_output()['stdout']:
|
||||
suggestions.extend(['yes', 'no'])
|
||||
if self.current_state == "auth_prompt":
|
||||
if "password:" in self.multiplexer.get_all_output()["stdout"]:
|
||||
suggestions.extend(["<password>"]) # Placeholder for actual password
|
||||
elif "yes/no" in self.multiplexer.get_all_output()["stdout"]:
|
||||
suggestions.extend(["yes", "no"])
|
||||
return suggestions
|
||||
|
||||
|
||||
class GenericProcessHandler(ProcessHandler):
|
||||
"""Fallback handler for unknown process types."""
|
||||
|
||||
def __init__(self, multiplexer):
|
||||
super().__init__(multiplexer)
|
||||
self.state_machine = {
|
||||
'initial': ['running'],
|
||||
'running': ['waiting_input', 'completed'],
|
||||
'waiting_input': ['running'],
|
||||
'completed': []
|
||||
"initial": ["running"],
|
||||
"running": ["waiting_input", "completed"],
|
||||
"waiting_input": ["running"],
|
||||
"completed": [],
|
||||
}
|
||||
self.prompt_patterns = [
|
||||
(r'\?\s*$', 'waiting_input'), # Lines ending with ?
|
||||
(r'>\s*$', 'waiting_input'), # Lines ending with >
|
||||
(r':\s*$', 'waiting_input'), # Lines ending with :
|
||||
(r'done', 'completed'),
|
||||
(r'finished', 'completed'),
|
||||
(r'exit code', 'completed')
|
||||
(r"\?\s*$", "waiting_input"), # Lines ending with ?
|
||||
(r">\s*$", "waiting_input"), # Lines ending with >
|
||||
(r":\s*$", "waiting_input"), # Lines ending with :
|
||||
(r"done", "completed"),
|
||||
(r"finished", "completed"),
|
||||
(r"exit code", "completed"),
|
||||
]
|
||||
|
||||
def get_process_type(self):
|
||||
return 'generic'
|
||||
return "generic"
|
||||
|
||||
def update_state(self, output):
|
||||
"""Basic state detection for generic processes."""
|
||||
output_lower = output.lower()
|
||||
|
||||
if any(pattern in output_lower for pattern in ['done', 'finished', 'complete']):
|
||||
self.current_state = 'completed'
|
||||
elif any(output.strip().endswith(char) for char in ['?', '>', ':']):
|
||||
self.current_state = 'waiting_input'
|
||||
if any(pattern in output_lower for pattern in ["done", "finished", "complete"]):
|
||||
self.current_state = "completed"
|
||||
elif any(output.strip().endswith(char) for char in ["?", ">", ":"]):
|
||||
self.current_state = "waiting_input"
|
||||
else:
|
||||
self.current_state = 'running'
|
||||
self.current_state = "running"
|
||||
|
||||
|
||||
# Handler registry
|
||||
_handler_classes = {
|
||||
'apt': AptHandler,
|
||||
'vim': VimHandler,
|
||||
'ssh': SSHHandler,
|
||||
'generic': GenericProcessHandler
|
||||
"apt": AptHandler,
|
||||
"vim": VimHandler,
|
||||
"ssh": SSHHandler,
|
||||
"generic": GenericProcessHandler,
|
||||
}
|
||||
|
||||
|
||||
def get_handler_for_process(process_type, multiplexer):
|
||||
"""Get appropriate handler for a process type."""
|
||||
handler_class = _handler_classes.get(process_type, GenericProcessHandler)
|
||||
return handler_class(multiplexer)
|
||||
|
||||
|
||||
def detect_process_type(command):
|
||||
"""Detect process type from command."""
|
||||
command_str = ' '.join(command) if isinstance(command, list) else command
|
||||
command_str = " ".join(command) if isinstance(command, list) else command
|
||||
command_lower = command_str.lower()
|
||||
|
||||
if 'apt' in command_lower or 'apt-get' in command_lower:
|
||||
return 'apt'
|
||||
elif 'vim' in command_lower or 'vi ' in command_lower:
|
||||
return 'vim'
|
||||
elif 'ssh' in command_lower:
|
||||
return 'ssh'
|
||||
if "apt" in command_lower or "apt-get" in command_lower:
|
||||
return "apt"
|
||||
elif "vim" in command_lower or "vi " in command_lower:
|
||||
return "vim"
|
||||
elif "ssh" in command_lower:
|
||||
return "ssh"
|
||||
else:
|
||||
return 'generic'
|
||||
return 'ssh'
|
||||
return "generic"
|
||||
return "ssh"
|
||||
|
||||
|
||||
def detect_process_type(command):
|
||||
"""Detect process type from command."""
|
||||
command_str = ' '.join(command) if isinstance(command, list) else command
|
||||
command_str = " ".join(command) if isinstance(command, list) else command
|
||||
command_lower = command_str.lower()
|
||||
|
||||
if 'apt' in command_lower or 'apt-get' in command_lower:
|
||||
return 'apt'
|
||||
elif 'vim' in command_lower or 'vi ' in command_lower:
|
||||
return 'vim'
|
||||
elif 'ssh' in command_lower:
|
||||
return 'ssh'
|
||||
if "apt" in command_lower or "apt-get" in command_lower:
|
||||
return "apt"
|
||||
elif "vim" in command_lower or "vi " in command_lower:
|
||||
return "vim"
|
||||
elif "ssh" in command_lower:
|
||||
return "ssh"
|
||||
else:
|
||||
return 'generic'
|
||||
return "generic"
|
||||
|
||||
+172
-148
@@ -1,6 +1,6 @@
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class PromptDetector:
|
||||
"""Detects various process prompts and manages interaction state."""
|
||||
@@ -10,101 +10,119 @@ class PromptDetector:
|
||||
self.state_machines = self._load_state_machines()
|
||||
self.session_states = {}
|
||||
self.timeout_configs = {
|
||||
'default': 30, # 30 seconds default timeout
|
||||
'apt': 300, # 5 minutes for apt operations
|
||||
'ssh': 60, # 1 minute for SSH connections
|
||||
'vim': 3600 # 1 hour for vim sessions
|
||||
"default": 30, # 30 seconds default timeout
|
||||
"apt": 300, # 5 minutes for apt operations
|
||||
"ssh": 60, # 1 minute for SSH connections
|
||||
"vim": 3600, # 1 hour for vim sessions
|
||||
}
|
||||
|
||||
def _load_prompt_patterns(self):
|
||||
"""Load regex patterns for detecting various prompts."""
|
||||
return {
|
||||
'bash_prompt': [
|
||||
re.compile(r'[\w\-\.]+@[\w\-\.]+:.*[\$#]\s*$'),
|
||||
re.compile(r'\$\s*$'),
|
||||
re.compile(r'#\s*$'),
|
||||
re.compile(r'>\s*$') # Continuation prompt
|
||||
"bash_prompt": [
|
||||
re.compile(r"[\w\-\.]+@[\w\-\.]+:.*[\$#]\s*$"),
|
||||
re.compile(r"\$\s*$"),
|
||||
re.compile(r"#\s*$"),
|
||||
re.compile(r">\s*$"), # Continuation prompt
|
||||
],
|
||||
'confirmation': [
|
||||
re.compile(r'[Yy]/[Nn]', re.IGNORECASE),
|
||||
re.compile(r'[Yy]es/[Nn]o', re.IGNORECASE),
|
||||
re.compile(r'continue\?', re.IGNORECASE),
|
||||
re.compile(r'proceed\?', re.IGNORECASE)
|
||||
"confirmation": [
|
||||
re.compile(r"[Yy]/[Nn]", re.IGNORECASE),
|
||||
re.compile(r"[Yy]es/[Nn]o", re.IGNORECASE),
|
||||
re.compile(r"continue\?", re.IGNORECASE),
|
||||
re.compile(r"proceed\?", re.IGNORECASE),
|
||||
],
|
||||
'password': [
|
||||
re.compile(r'password:', re.IGNORECASE),
|
||||
re.compile(r'passphrase:', re.IGNORECASE),
|
||||
re.compile(r'enter password', re.IGNORECASE)
|
||||
"password": [
|
||||
re.compile(r"password:", re.IGNORECASE),
|
||||
re.compile(r"passphrase:", re.IGNORECASE),
|
||||
re.compile(r"enter password", re.IGNORECASE),
|
||||
],
|
||||
'sudo_password': [
|
||||
re.compile(r'\[sudo\].*password', re.IGNORECASE)
|
||||
"sudo_password": [re.compile(r"\[sudo\].*password", re.IGNORECASE)],
|
||||
"apt": [
|
||||
re.compile(r"Do you want to continue\?", re.IGNORECASE),
|
||||
re.compile(r"After this operation", re.IGNORECASE),
|
||||
re.compile(r"Need to get", re.IGNORECASE),
|
||||
],
|
||||
'apt': [
|
||||
re.compile(r'Do you want to continue\?', re.IGNORECASE),
|
||||
re.compile(r'After this operation', re.IGNORECASE),
|
||||
re.compile(r'Need to get', re.IGNORECASE)
|
||||
"vim": [
|
||||
re.compile(r"-- INSERT --"),
|
||||
re.compile(r"-- VISUAL --"),
|
||||
re.compile(r":"),
|
||||
re.compile(r"Press ENTER", re.IGNORECASE),
|
||||
],
|
||||
'vim': [
|
||||
re.compile(r'-- INSERT --'),
|
||||
re.compile(r'-- VISUAL --'),
|
||||
re.compile(r':'),
|
||||
re.compile(r'Press ENTER', re.IGNORECASE)
|
||||
"ssh": [
|
||||
re.compile(r"yes/no", re.IGNORECASE),
|
||||
re.compile(r"password:", re.IGNORECASE),
|
||||
re.compile(r"Permission denied", re.IGNORECASE),
|
||||
],
|
||||
'ssh': [
|
||||
re.compile(r'yes/no', re.IGNORECASE),
|
||||
re.compile(r'password:', re.IGNORECASE),
|
||||
re.compile(r'Permission denied', re.IGNORECASE)
|
||||
"git": [
|
||||
re.compile(r"Username:", re.IGNORECASE),
|
||||
re.compile(r"Email:", re.IGNORECASE),
|
||||
],
|
||||
'git': [
|
||||
re.compile(r'Username:', re.IGNORECASE),
|
||||
re.compile(r'Email:', re.IGNORECASE)
|
||||
"error": [
|
||||
re.compile(r"error:", re.IGNORECASE),
|
||||
re.compile(r"failed", re.IGNORECASE),
|
||||
re.compile(r"exception", re.IGNORECASE),
|
||||
],
|
||||
'error': [
|
||||
re.compile(r'error:', re.IGNORECASE),
|
||||
re.compile(r'failed', re.IGNORECASE),
|
||||
re.compile(r'exception', re.IGNORECASE)
|
||||
]
|
||||
}
|
||||
|
||||
def _load_state_machines(self):
|
||||
"""Load state machines for different process types."""
|
||||
return {
|
||||
'apt': {
|
||||
'states': ['initial', 'running', 'confirming', 'installing', 'completed', 'error'],
|
||||
'transitions': {
|
||||
'initial': ['running'],
|
||||
'running': ['confirming', 'installing', 'completed', 'error'],
|
||||
'confirming': ['installing', 'cancelled'],
|
||||
'installing': ['completed', 'error'],
|
||||
'completed': [],
|
||||
'error': [],
|
||||
'cancelled': []
|
||||
}
|
||||
"apt": {
|
||||
"states": [
|
||||
"initial",
|
||||
"running",
|
||||
"confirming",
|
||||
"installing",
|
||||
"completed",
|
||||
"error",
|
||||
],
|
||||
"transitions": {
|
||||
"initial": ["running"],
|
||||
"running": ["confirming", "installing", "completed", "error"],
|
||||
"confirming": ["installing", "cancelled"],
|
||||
"installing": ["completed", "error"],
|
||||
"completed": [],
|
||||
"error": [],
|
||||
"cancelled": [],
|
||||
},
|
||||
},
|
||||
'ssh': {
|
||||
'states': ['initial', 'connecting', 'authenticating', 'connected', 'error'],
|
||||
'transitions': {
|
||||
'initial': ['connecting'],
|
||||
'connecting': ['authenticating', 'connected', 'error'],
|
||||
'authenticating': ['connected', 'error'],
|
||||
'connected': ['error'],
|
||||
'error': []
|
||||
}
|
||||
"ssh": {
|
||||
"states": [
|
||||
"initial",
|
||||
"connecting",
|
||||
"authenticating",
|
||||
"connected",
|
||||
"error",
|
||||
],
|
||||
"transitions": {
|
||||
"initial": ["connecting"],
|
||||
"connecting": ["authenticating", "connected", "error"],
|
||||
"authenticating": ["connected", "error"],
|
||||
"connected": ["error"],
|
||||
"error": [],
|
||||
},
|
||||
},
|
||||
"vim": {
|
||||
"states": [
|
||||
"initial",
|
||||
"normal",
|
||||
"insert",
|
||||
"visual",
|
||||
"command",
|
||||
"exiting",
|
||||
],
|
||||
"transitions": {
|
||||
"initial": ["normal", "insert"],
|
||||
"normal": ["insert", "visual", "command", "exiting"],
|
||||
"insert": ["normal"],
|
||||
"visual": ["normal"],
|
||||
"command": ["normal", "exiting"],
|
||||
"exiting": [],
|
||||
},
|
||||
},
|
||||
'vim': {
|
||||
'states': ['initial', 'normal', 'insert', 'visual', 'command', 'exiting'],
|
||||
'transitions': {
|
||||
'initial': ['normal', 'insert'],
|
||||
'normal': ['insert', 'visual', 'command', 'exiting'],
|
||||
'insert': ['normal'],
|
||||
'visual': ['normal'],
|
||||
'command': ['normal', 'exiting'],
|
||||
'exiting': []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def detect_prompt(self, output, process_type='generic'):
|
||||
def detect_prompt(self, output, process_type="generic"):
|
||||
"""Detect what type of prompt is present in the output."""
|
||||
detections = {}
|
||||
|
||||
@@ -125,93 +143,97 @@ class PromptDetector:
|
||||
|
||||
return detections
|
||||
|
||||
def get_response_suggestions(self, prompt_detections, process_type='generic'):
|
||||
def get_response_suggestions(self, prompt_detections, process_type="generic"):
|
||||
"""Get suggested responses based on detected prompts."""
|
||||
suggestions = []
|
||||
|
||||
for category, patterns in prompt_detections.items():
|
||||
if category == 'confirmation':
|
||||
suggestions.extend(['y', 'yes', 'n', 'no'])
|
||||
elif category == 'password':
|
||||
suggestions.append('<password>')
|
||||
elif category == 'sudo_password':
|
||||
suggestions.append('<sudo_password>')
|
||||
elif category == 'apt':
|
||||
if any('continue' in p for p in patterns):
|
||||
suggestions.extend(['y', 'yes'])
|
||||
elif category == 'vim':
|
||||
if any(':' in p for p in patterns):
|
||||
suggestions.extend(['w', 'q', 'wq', 'q!'])
|
||||
elif any('ENTER' in p for p in patterns):
|
||||
suggestions.append('\n')
|
||||
elif category == 'ssh':
|
||||
if any('yes/no' in p for p in patterns):
|
||||
suggestions.extend(['yes', 'no'])
|
||||
elif any('password' in p for p in patterns):
|
||||
suggestions.append('<password>')
|
||||
elif category == 'bash_prompt':
|
||||
suggestions.extend(['help', 'ls', 'pwd', 'exit'])
|
||||
if category == "confirmation":
|
||||
suggestions.extend(["y", "yes", "n", "no"])
|
||||
elif category == "password":
|
||||
suggestions.append("<password>")
|
||||
elif category == "sudo_password":
|
||||
suggestions.append("<sudo_password>")
|
||||
elif category == "apt":
|
||||
if any("continue" in p for p in patterns):
|
||||
suggestions.extend(["y", "yes"])
|
||||
elif category == "vim":
|
||||
if any(":" in p for p in patterns):
|
||||
suggestions.extend(["w", "q", "wq", "q!"])
|
||||
elif any("ENTER" in p for p in patterns):
|
||||
suggestions.append("\n")
|
||||
elif category == "ssh":
|
||||
if any("yes/no" in p for p in patterns):
|
||||
suggestions.extend(["yes", "no"])
|
||||
elif any("password" in p for p in patterns):
|
||||
suggestions.append("<password>")
|
||||
elif category == "bash_prompt":
|
||||
suggestions.extend(["help", "ls", "pwd", "exit"])
|
||||
|
||||
return list(set(suggestions)) # Remove duplicates
|
||||
|
||||
def update_session_state(self, session_name, output, process_type='generic'):
|
||||
def update_session_state(self, session_name, output, process_type="generic"):
|
||||
"""Update the state machine for a session based on output."""
|
||||
if session_name not in self.session_states:
|
||||
self.session_states[session_name] = {
|
||||
'current_state': 'initial',
|
||||
'process_type': process_type,
|
||||
'last_activity': time.time(),
|
||||
'transitions': []
|
||||
"current_state": "initial",
|
||||
"process_type": process_type,
|
||||
"last_activity": time.time(),
|
||||
"transitions": [],
|
||||
}
|
||||
|
||||
session_state = self.session_states[session_name]
|
||||
old_state = session_state['current_state']
|
||||
old_state = session_state["current_state"]
|
||||
|
||||
# Detect prompts and determine new state
|
||||
detections = self.detect_prompt(output, process_type)
|
||||
new_state = self._determine_state_from_detections(detections, process_type, old_state)
|
||||
new_state = self._determine_state_from_detections(
|
||||
detections, process_type, old_state
|
||||
)
|
||||
|
||||
if new_state != old_state:
|
||||
session_state['transitions'].append({
|
||||
'from': old_state,
|
||||
'to': new_state,
|
||||
'timestamp': time.time(),
|
||||
'trigger': detections
|
||||
})
|
||||
session_state['current_state'] = new_state
|
||||
session_state["transitions"].append(
|
||||
{
|
||||
"from": old_state,
|
||||
"to": new_state,
|
||||
"timestamp": time.time(),
|
||||
"trigger": detections,
|
||||
}
|
||||
)
|
||||
session_state["current_state"] = new_state
|
||||
|
||||
session_state['last_activity'] = time.time()
|
||||
session_state["last_activity"] = time.time()
|
||||
return new_state
|
||||
|
||||
def _determine_state_from_detections(self, detections, process_type, current_state):
|
||||
"""Determine new state based on prompt detections."""
|
||||
if process_type in self.state_machines:
|
||||
state_machine = self.state_machines[process_type]
|
||||
self.state_machines[process_type]
|
||||
|
||||
# State transition logic based on detections
|
||||
if 'confirmation' in detections and current_state in ['running', 'initial']:
|
||||
return 'confirming'
|
||||
elif 'password' in detections or 'sudo_password' in detections:
|
||||
return 'authenticating'
|
||||
elif 'error' in detections:
|
||||
return 'error'
|
||||
elif 'bash_prompt' in detections and current_state != 'initial':
|
||||
return 'connected' if process_type == 'ssh' else 'completed'
|
||||
elif 'vim' in detections:
|
||||
if any('-- INSERT --' in p for p in detections.get('vim', [])):
|
||||
return 'insert'
|
||||
elif any('-- VISUAL --' in p for p in detections.get('vim', [])):
|
||||
return 'visual'
|
||||
elif any(':' in p for p in detections.get('vim', [])):
|
||||
return 'command'
|
||||
if "confirmation" in detections and current_state in ["running", "initial"]:
|
||||
return "confirming"
|
||||
elif "password" in detections or "sudo_password" in detections:
|
||||
return "authenticating"
|
||||
elif "error" in detections:
|
||||
return "error"
|
||||
elif "bash_prompt" in detections and current_state != "initial":
|
||||
return "connected" if process_type == "ssh" else "completed"
|
||||
elif "vim" in detections:
|
||||
if any("-- INSERT --" in p for p in detections.get("vim", [])):
|
||||
return "insert"
|
||||
elif any("-- VISUAL --" in p for p in detections.get("vim", [])):
|
||||
return "visual"
|
||||
elif any(":" in p for p in detections.get("vim", [])):
|
||||
return "command"
|
||||
|
||||
# Default state progression
|
||||
if current_state == 'initial':
|
||||
return 'running'
|
||||
elif current_state == 'running' and detections:
|
||||
return 'waiting_input'
|
||||
elif current_state == 'waiting_input' and not detections:
|
||||
return 'running'
|
||||
if current_state == "initial":
|
||||
return "running"
|
||||
elif current_state == "running" and detections:
|
||||
return "waiting_input"
|
||||
elif current_state == "waiting_input" and not detections:
|
||||
return "running"
|
||||
|
||||
return current_state
|
||||
|
||||
@@ -220,15 +242,15 @@ class PromptDetector:
|
||||
if session_name not in self.session_states:
|
||||
return False
|
||||
|
||||
state = self.session_states[session_name]['current_state']
|
||||
process_type = self.session_states[session_name]['process_type']
|
||||
state = self.session_states[session_name]["current_state"]
|
||||
process_type = self.session_states[session_name]["process_type"]
|
||||
|
||||
# States that typically indicate waiting for input
|
||||
waiting_states = {
|
||||
'generic': ['waiting_input'],
|
||||
'apt': ['confirming'],
|
||||
'ssh': ['authenticating'],
|
||||
'vim': ['command', 'insert', 'visual']
|
||||
"generic": ["waiting_input"],
|
||||
"apt": ["confirming"],
|
||||
"ssh": ["authenticating"],
|
||||
"vim": ["command", "insert", "visual"],
|
||||
}
|
||||
|
||||
return state in waiting_states.get(process_type, [])
|
||||
@@ -236,10 +258,10 @@ class PromptDetector:
|
||||
def get_session_timeout(self, session_name):
|
||||
"""Get the timeout for a session based on its process type."""
|
||||
if session_name not in self.session_states:
|
||||
return self.timeout_configs['default']
|
||||
return self.timeout_configs["default"]
|
||||
|
||||
process_type = self.session_states[session_name]['process_type']
|
||||
return self.timeout_configs.get(process_type, self.timeout_configs['default'])
|
||||
process_type = self.session_states[session_name]["process_type"]
|
||||
return self.timeout_configs.get(process_type, self.timeout_configs["default"])
|
||||
|
||||
def check_for_timeouts(self):
|
||||
"""Check all sessions for timeouts and return timed out sessions."""
|
||||
@@ -248,7 +270,7 @@ class PromptDetector:
|
||||
|
||||
for session_name, state in self.session_states.items():
|
||||
timeout = self.get_session_timeout(session_name)
|
||||
if current_time - state['last_activity'] > timeout:
|
||||
if current_time - state["last_activity"] > timeout:
|
||||
timed_out.append(session_name)
|
||||
|
||||
return timed_out
|
||||
@@ -260,16 +282,18 @@ class PromptDetector:
|
||||
|
||||
state = self.session_states[session_name]
|
||||
return {
|
||||
'current_state': state['current_state'],
|
||||
'process_type': state['process_type'],
|
||||
'last_activity': state['last_activity'],
|
||||
'transitions': state['transitions'][-5:], # Last 5 transitions
|
||||
'is_waiting': self.is_waiting_for_input(session_name)
|
||||
"current_state": state["current_state"],
|
||||
"process_type": state["process_type"],
|
||||
"last_activity": state["last_activity"],
|
||||
"transitions": state["transitions"][-5:], # Last 5 transitions
|
||||
"is_waiting": self.is_waiting_for_input(session_name),
|
||||
}
|
||||
|
||||
|
||||
# Global detector instance
|
||||
_detector = None
|
||||
|
||||
|
||||
def get_global_detector():
|
||||
"""Get the global prompt detector instance."""
|
||||
global _detector
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import contextlib
|
||||
import traceback
|
||||
from io import StringIO
|
||||
import contextlib
|
||||
|
||||
|
||||
def python_exec(code, python_globals):
|
||||
try:
|
||||
|
||||
+9
-6
@@ -1,7 +1,8 @@
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
def http_fetch(url, headers=None):
|
||||
try:
|
||||
@@ -11,26 +12,28 @@ def http_fetch(url, headers=None):
|
||||
req.add_header(key, value)
|
||||
|
||||
with urllib.request.urlopen(req) as response:
|
||||
content = response.read().decode('utf-8')
|
||||
content = response.read().decode("utf-8")
|
||||
return {"status": "success", "content": content[:10000]}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def _perform_search(base_url, query, params=None):
|
||||
try:
|
||||
full_url = f"https://static.molodetz.nl/search.cgi?query={query}"
|
||||
|
||||
with urllib.request.urlopen(full_url) as response:
|
||||
content = response.read().decode('utf-8')
|
||||
content = response.read().decode("utf-8")
|
||||
return {"status": "success", "content": json.loads(content)}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
def web_search(query):
|
||||
base_url = "https://search.molodetz.nl/search"
|
||||
return _perform_search(base_url, query)
|
||||
|
||||
|
||||
def web_search_news(query):
|
||||
base_url = "https://search.molodetz.nl/search"
|
||||
return _perform_search(base_url, query)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user