chore: standardize string quotes and fix import ordering across multiple modules

This commit is contained in:
2025-11-04 07:09:12 +00:00
parent ea29bdc403
commit e9ced4a493
82 changed files with 4963 additions and 3094 deletions
+79 -18
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"}
+37 -24
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -1,6 +1,7 @@
import contextlib
import traceback
from io import StringIO
import contextlib
def python_exec(code, python_globals):
try:
+9 -6
View File
@@ -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)