Compare commits

...
2 Commits
Author SHA1 Message Date
Mervin Praison f5d4db4262 Release v3.1.2 2026-01-02 08:17:39 +00:00
Mervin Praison 87d691c616 Release v3.1.1 2026-01-02 07:54:02 +00:00
7 changed files with 411 additions and 257 deletions

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=3.1.0" \
"praisonai>=3.1.2" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=3.1.0" \
"praisonai>=3.1.2" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=3.1.0" \
"praisonai>=3.1.2" \
"praisonai[ui]" \
"praisonai[crewai]"
+2 -2
View File
@@ -3,8 +3,8 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.1.0.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.1.0.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.1.2.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.1.2.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+404 -250
View File
@@ -4857,9 +4857,10 @@ Now, {final_instruction.lower()}:"""
# Import message queue components
from praisonai.cli.features.message_queue import (
MessageQueue, StateManager, QueueDisplay, ProcessingState,
AsyncProcessor, LiveStatusDisplay
LiveStatusDisplay
)
import threading
import queue as queue_module
# Create message queue and state manager
message_queue = MessageQueue()
@@ -4868,6 +4869,30 @@ Now, {final_instruction.lower()}:"""
live_status = LiveStatusDisplay()
processing_lock = threading.Lock()
# Create execution queue for worker thread (TRUE ASYNC)
execution_queue = queue_module.Queue()
approval_request_queue = queue_module.Queue()
approval_response_queue = queue_module.Queue()
# Worker state for cross-thread communication
# Enhanced with task-bound context for proper Q/A mapping
worker_state = {
'running': True,
'current_task': None, # Full task context object (FIFO head)
'approval_pending': False,
'waiting_for_approval_input': False,
'completed_tasks': [], # Queue of completed tasks to display (FIFO order)
'error_tasks': [], # Queue of error tasks to display
'tool_activity': None, # Current tool being used
'last_status_line': None, # For transient status updates
}
# Task counter for unique IDs (FIFO position tracking)
task_counter = {'value': 0}
# Check for verbose mode
verbose_mode = getattr(args, 'verbose', False) if hasattr(args, 'verbose') else False
# Initialize persistent session
from praisonai.cli.session import get_session_store
session_store = get_session_store()
@@ -4902,11 +4927,17 @@ Now, {final_instruction.lower()}:"""
'queue_display': queue_display,
'live_status': live_status,
'processing_lock': processing_lock,
'async_processor': None, # Will be created per-request
'execution_queue': execution_queue, # Queue for worker thread
'approval_request_queue': approval_request_queue,
'approval_response_queue': approval_response_queue,
'worker_state': worker_state,
'unified_session': unified_session, # Reference to persistent session
'session_store': session_store, # Reference to store for saving
}
# Start the execution worker thread (TRUE ASYNC - runs in background)
worker_thread = self._start_execution_worker(tools_list, console, session_state)
# Try to use prompt_toolkit for autocomplete
prompt_session = None
try:
@@ -4940,10 +4971,155 @@ Now, {final_instruction.lower()}:"""
console.print(f"[dim]Resumed session with {unified_session.message_count} messages[/dim]")
console.print("[dim]Use @file.txt to include file content | Queue messages while processing[/dim]\n")
# Start display thread for status updates (non-blocking)
import time
from rich.panel import Panel
display_running = {'value': True}
def display_loop():
"""Background thread for status display - NEVER blocks input.
FIFO-aligned display:
- Default mode: Minimal, calm output. Just show response.
- Verbose mode: Full task lifecycle with IDs, times, word counts.
"""
while display_running['value']:
try:
# Check for completed tasks to display (FIFO order guaranteed)
if worker_state['completed_tasks']:
task = worker_state['completed_tasks'].pop(0)
response = task.get('response', '')
# Clear any transient status line
if worker_state.get('last_status_line'):
console.print("\r" + " " * 80 + "\r", end="")
worker_state['last_status_line'] = None
console.print() # New line before response
if verbose_mode:
# VERBOSE: Full task lifecycle with metadata
question = task.get('question', '')
task_id = task.get('task_id', 0)
elapsed = task.get('elapsed', 0)
word_count = len(response.split()) if response else 0
q_display = question[:80] + "..." if len(question) > 80 else question
console.print(f"[bold cyan]─── Task #{task_id} completed ({elapsed:.1f}s, {word_count} words) ───[/bold cyan]")
console.print(f"[bold green]Q:[/bold green] {q_display}")
console.print(f"[bold blue]A:[/bold blue] ", end="")
# Stream response (both modes)
if response:
words = response.split()
for i, word in enumerate(words):
console.print(word + " ", end="")
if i % 20 == 19:
time.sleep(0.003)
console.print()
if verbose_mode:
task_id = task.get('task_id', 0)
console.print(f"[dim]─── End Task #{task_id} ───[/dim]")
console.print()
# Check for error tasks to display
if worker_state['error_tasks']:
task = worker_state['error_tasks'].pop(0)
error = task.get('error', '')
# Clear transient status
if worker_state.get('last_status_line'):
console.print("\r" + " " * 80 + "\r", end="")
worker_state['last_status_line'] = None
console.print()
if verbose_mode:
question = task.get('question', '')
task_id = task.get('task_id', 0)
q_display = question[:80] + "..." if len(question) > 80 else question
console.print(f"[bold red]─── Task #{task_id} failed ───[/bold red]")
console.print(f"[bold green]Q:[/bold green] {q_display}")
console.print(f"[red]Error: {error}[/red]")
console.print()
# Check for approval requests
try:
approval_info = approval_request_queue.get_nowait()
function_name = approval_info['function_name']
arguments = approval_info['arguments']
risk_level = approval_info['risk_level']
risk_colors = {"critical": "bold red", "high": "red", "medium": "yellow", "low": "blue"}
risk_color = risk_colors.get(risk_level, "white")
# Clear transient status
if worker_state.get('last_status_line'):
console.print("\r" + " " * 80 + "\r", end="")
worker_state['last_status_line'] = None
console.print()
# Show approval panel (both modes)
tool_info = f"[bold]Function:[/] {function_name}\n"
tool_info += f"[bold]Risk Level:[/] [{risk_color}]{risk_level.upper()}[/{risk_color}]\n"
tool_info += "[bold]Arguments:[/]\n"
for key, value in arguments.items():
str_value = str(value)[:100] + "..." if len(str(value)) > 100 else str(value)
tool_info += f" {key}: {str_value}\n"
console.print(Panel(tool_info.strip(), title="🔒 Tool Approval Required", border_style=risk_color))
console.print(f"[{risk_color}]Type 'y' to approve, 'n' to reject:[/{risk_color}] ", end="")
worker_state['waiting_for_approval_input'] = True
except queue_module.Empty:
pass
# Show FIFO head status (transient, both modes)
current_task = worker_state.get('current_task')
if current_task and not worker_state.get('waiting_for_approval_input'):
queue_size = execution_queue.qsize()
prompt_preview = current_task.get('question', '')[:50]
if len(current_task.get('question', '')) > 50:
prompt_preview += "..."
# Build status line
tool_activity = worker_state.get('tool_activity')
if tool_activity and tool_activity.get('status') == 'started':
tool_name = tool_activity.get('name', '')
status_line = f"⚙️ {tool_name}"
else:
status_line = f"⏳ Processing: {prompt_preview}"
if queue_size > 0:
status_line += f" | 📋 {queue_size} waiting"
# Only update if changed (avoid flicker)
if status_line != worker_state.get('last_status_line'):
console.print(f"\r[dim cyan]{status_line}[/dim cyan]" + " " * 20, end="\r")
worker_state['last_status_line'] = status_line
elif not current_task and worker_state.get('last_status_line'):
# Clear status when idle
console.print("\r" + " " * 80 + "\r", end="")
worker_state['last_status_line'] = None
time.sleep(0.1)
except Exception:
pass
# Start display thread
display_thread = threading.Thread(target=display_loop, daemon=True, name="DisplayLoop")
display_thread.start()
running = True
while running:
try:
# Get user input (with autocomplete if available)
# This is NON-BLOCKING relative to LLM execution
# Status is shown by the display thread, not here
if prompt_session:
user_input = prompt_session.prompt().strip()
else:
@@ -4952,6 +5128,21 @@ Now, {final_instruction.lower()}:"""
if not user_input:
continue
# Check if this is an approval response
if worker_state.get('waiting_for_approval_input'):
try:
from praisonaiagents.approval import ApprovalDecision
if user_input.lower() in ['y', 'yes', 'approve']:
console.print("[green]✅ Approved[/green]")
approval_response_queue.put(ApprovalDecision(approved=True, reason="User approved"))
else:
console.print("[red]❌ Denied[/red]")
approval_response_queue.put(ApprovalDecision(approved=False, reason="User denied"))
worker_state['waiting_for_approval_input'] = False
continue
except ImportError:
pass
# Handle slash commands
if user_input.startswith("/"):
cmd_parts = user_input[1:].split(maxsplit=1)
@@ -4961,6 +5152,8 @@ Now, {final_instruction.lower()}:"""
if cmd in ["exit", "quit", "q"]:
console.print("[dim]Goodbye![/dim]")
running = False
worker_state['running'] = False
display_running['value'] = False
continue
elif cmd == "help":
self._print_interactive_help(console)
@@ -5031,6 +5224,17 @@ Now, {final_instruction.lower()}:"""
session_state['request_count'] = 0
console.print(f"[cyan]Started new session: {new_session.session_id}[/cyan]")
continue
elif cmd == "status":
# Show current processing status
current = worker_state.get('current_prompt')
queue_size = execution_queue.qsize()
if current:
console.print(f"[cyan]Processing:[/cyan] {current}")
if queue_size > 0:
console.print(f"[cyan]Queued:[/cyan] {queue_size} messages")
if not current and queue_size == 0:
console.print("[dim]Idle - no messages processing[/dim]")
continue
else:
console.print(f"[yellow]Unknown command: /{cmd}. Type /help for available commands.[/yellow]")
continue
@@ -5038,20 +5242,35 @@ Now, {final_instruction.lower()}:"""
# Process @file mentions before sending to LLM
processed_input = self._process_at_mentions(user_input, console)
# Check if already processing - queue if so
if state_manager.is_processing:
message_queue.add(processed_input)
queue_count = message_queue.count
console.print(f"[dim]📋 Queued ({queue_count}) - processing will continue in order[/dim]")
continue
# Create task with unique ID and full context
task_counter['value'] += 1
task_id = task_counter['value']
# Start async processing
self._process_interactive_prompt_async(
processed_input,
tools_list,
console,
session_state=session_state
)
task = {
'task_id': task_id,
'prompt': processed_input,
'question': processed_input, # Full question for Q/A mapping
'status': 'queued',
'queued_at': time.time(),
}
# Submit to execution queue - THIS IS NON-BLOCKING (FIFO)
queue_size = execution_queue.qsize()
execution_queue.put(task)
# Show queue status (minimal by default, verbose shows task IDs)
if verbose_mode:
q_preview = processed_input[:60] + "..." if len(processed_input) > 60 else processed_input
if queue_size > 0:
console.print(f"[dim cyan]📋 Task #{task_id} queued (FIFO position {queue_size + 1})[/dim cyan]")
console.print(f"[dim] └─ {q_preview}[/dim]")
else:
console.print(f"[dim cyan]▶ Task #{task_id} started (FIFO head)[/dim cyan]")
console.print(f"[dim] └─ {q_preview}[/dim]")
else:
# DEFAULT: Minimal, calm output
if queue_size > 0:
console.print(f"[dim]📋 Queued ({queue_size + 1} in queue)[/dim]")
except KeyboardInterrupt:
console.print("\n[dim]Use /exit to quit[/dim]")
@@ -5494,266 +5713,201 @@ Provide a concise summary (max 200 words):"""
return None
def _process_interactive_prompt_async(self, prompt, tools_list, console, session_state=None):
def _start_execution_worker(self, tools_list, console, session_state):
"""
Process a prompt asynchronously with non-blocking status display.
Start the background execution worker thread.
Shows status updates without blocking user input.
Allows user to queue more messages while processing.
Supports interactive approval for tool calls.
This worker continuously processes messages from the queue,
allowing the main input loop to remain non-blocking.
"""
import threading
import time
import sys
import queue
from rich.live import Live
from rich.spinner import Spinner
import queue as queue_module
from rich.panel import Panel
from praisonai.cli.features.message_queue import ProcessingState
state_manager = session_state['state_manager']
message_queue = session_state['message_queue']
live_status = session_state['live_status']
# Set processing state
state_manager.set_state(ProcessingState.PROCESSING)
live_status.clear()
live_status.update_status("Thinking...")
# Result container for thread communication
result_container = {
'response': None,
'error': None,
'done': False,
'approval_pending': False,
'approval_info': None,
'approval_response': None
}
# Approval queue for cross-thread communication
approval_request_queue = queue.Queue()
approval_response_queue = queue.Queue()
# Get conversation history for context
conversation_history = session_state.get('conversation_history', [])
execution_queue = session_state['execution_queue']
approval_request_queue = session_state['approval_request_queue']
approval_response_queue = session_state['approval_response_queue']
worker_state = session_state['worker_state']
# Check if trust mode is enabled
trust_mode = getattr(self.args, 'trust', False) if hasattr(self, 'args') else False
def process_in_background():
"""Run agent processing in background thread."""
try:
from praisonaiagents import Agent
import logging
import warnings
# Suppress noisy loggers
for logger_name in ["httpx", "httpcore", "duckduckgo_search", "crawl4ai"]:
logging.getLogger(logger_name).setLevel(logging.WARNING)
# Set up approval callback
def worker_loop():
"""Main worker loop - processes execution queue."""
while worker_state['running']:
try:
from praisonaiagents.approval import set_approval_callback, ApprovalDecision
if trust_mode:
# Auto-approve all in trust mode
def auto_approve_all(function_name, arguments, risk_level):
return ApprovalDecision(approved=True, reason="Auto-approved via --trust flag")
set_approval_callback(auto_approve_all)
else:
# Interactive approval via queue
def interactive_approval_callback(function_name, arguments, risk_level):
"""Request approval from main thread via queue."""
# Send approval request to main thread
approval_request_queue.put({
'function_name': function_name,
'arguments': arguments,
'risk_level': risk_level
})
result_container['approval_pending'] = True
# Wait for response from main thread (with timeout)
try:
response = approval_response_queue.get(timeout=120) # 2 minute timeout
result_container['approval_pending'] = False
return response
except queue.Empty:
result_container['approval_pending'] = False
return ApprovalDecision(approved=False, reason="Approval timeout")
set_approval_callback(interactive_approval_callback)
except ImportError:
pass # Approval module not available
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
model = session_state.get('current_model')
live_status.update_status("Creating agent...")
# Build system prompt with conversation context
backstory = "You are a helpful AI assistant with access to tools for file operations, code intelligence, and shell commands."
if conversation_history:
# Add recent conversation context to backstory
recent = conversation_history[-10:] # Last 10 messages
context_lines = []
for msg in recent:
role = msg.get('role', 'unknown')
content = msg.get('content', '')[:200] # Truncate long messages
context_lines.append(f"{role}: {content}")
if context_lines:
backstory += "\n\nRecent conversation context:\n" + "\n".join(context_lines)
# Create agent with tools
agent = Agent(
name="Assistant",
role="Helpful AI Assistant",
goal="Help the user with their tasks",
backstory=backstory,
tools=tools_list if tools_list else None,
verbose=False,
llm=model
)
live_status.update_status("Calling LLM...")
# Get response
response = agent.chat(prompt, stream=False)
result_container['response'] = str(response) if response else ""
except Exception as e:
result_container['error'] = e
finally:
result_container['done'] = True
# Start background thread
bg_thread = threading.Thread(target=process_in_background, daemon=True)
bg_thread.start()
# Main loop: handle spinner display and approval requests
start_time = time.time()
live_instance = None
try:
while not result_container['done']:
# Check for pending approval requests
try:
approval_info = approval_request_queue.get_nowait()
# Stop spinner to show approval prompt
if live_instance:
live_instance.stop()
live_instance = None
# Display approval prompt
state_manager.set_state(ProcessingState.WAITING_APPROVAL)
function_name = approval_info['function_name']
arguments = approval_info['arguments']
risk_level = approval_info['risk_level']
risk_colors = {"critical": "bold red", "high": "red", "medium": "yellow", "low": "blue"}
risk_color = risk_colors.get(risk_level, "white")
tool_info = f"[bold]Function:[/] {function_name}\n"
tool_info += f"[bold]Risk Level:[/] [{risk_color}]{risk_level.upper()}[/{risk_color}]\n"
tool_info += "[bold]Arguments:[/]\n"
for key, value in arguments.items():
str_value = str(value)[:100] + "..." if len(str(value)) > 100 else str(value)
tool_info += f" {key}: {str_value}\n"
console.print(Panel(tool_info.strip(), title="🔒 Tool Approval Required", border_style=risk_color))
console.print(f"[{risk_color}]Type 'y' or 'yes' to approve, 'n' or 'no' to reject:[/{risk_color}] ", end="")
# Get user input for approval
# Wait for a message with timeout (allows checking running flag)
try:
from praisonaiagents.approval import ApprovalDecision
user_response = input().strip().lower()
if user_response in ['y', 'yes', 'approve']:
console.print("[green]✅ Approved[/green]")
approval_response_queue.put(ApprovalDecision(approved=True, reason="User approved"))
else:
console.print("[red]❌ Denied[/red]")
approval_response_queue.put(ApprovalDecision(approved=False, reason="User denied"))
except (EOFError, KeyboardInterrupt):
console.print("\n[red]❌ Cancelled[/red]")
from praisonaiagents.approval import ApprovalDecision
approval_response_queue.put(ApprovalDecision(approved=False, reason="User cancelled"))
task = execution_queue.get(timeout=0.5)
except queue_module.Empty:
continue
# Extract task context
prompt = task.get('prompt', '')
task_id = task.get('task_id', 0)
question = task.get('question', prompt)
start_time = time.time()
# Set processing state with full task context
state_manager.set_state(ProcessingState.PROCESSING)
task['status'] = 'running'
task['start_time'] = start_time
worker_state['current_task'] = task # Full task context
live_status.clear()
live_status.update_status(f"Task #{task_id}: Thinking...")
except queue.Empty:
pass # No approval pending
# Update spinner display if not waiting for approval
if not result_container['approval_pending'] and not result_container['done']:
elapsed = time.time() - start_time
current_status = live_status.current_status
queue_count = message_queue.count
queue_info = f" | Queued: {queue_count}" if queue_count > 0 else ""
spinner_text = f"{current_status} ({elapsed:.1f}s){queue_info}"
try:
from praisonaiagents import Agent
import logging
import warnings
# Suppress noisy loggers
for logger_name in ["httpx", "httpcore", "duckduckgo_search", "crawl4ai"]:
logging.getLogger(logger_name).setLevel(logging.WARNING)
# Set up approval callback
try:
from praisonaiagents.approval import set_approval_callback, ApprovalDecision
if trust_mode:
def auto_approve_all(function_name, arguments, risk_level):
return ApprovalDecision(approved=True, reason="Auto-approved via --trust flag")
set_approval_callback(auto_approve_all)
else:
def interactive_approval_callback(function_name, arguments, risk_level):
"""Request approval from main thread via queue."""
approval_request_queue.put({
'function_name': function_name,
'arguments': arguments,
'risk_level': risk_level
})
worker_state['approval_pending'] = True
try:
response = approval_response_queue.get(timeout=120)
worker_state['approval_pending'] = False
return response
except queue_module.Empty:
worker_state['approval_pending'] = False
return ApprovalDecision(approved=False, reason="Approval timeout")
set_approval_callback(interactive_approval_callback)
except ImportError:
pass
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
model = session_state.get('current_model')
conversation_history = session_state.get('conversation_history', [])
live_status.update_status("Creating agent...")
# Build backstory with context
backstory = "You are a helpful AI assistant with access to tools for file operations, code intelligence, and shell commands."
if conversation_history:
recent = conversation_history[-10:]
context_lines = []
for msg in recent:
role = msg.get('role', 'unknown')
content = msg.get('content', '')[:200]
context_lines.append(f"{role}: {content}")
if context_lines:
backstory += "\n\nRecent conversation context:\n" + "\n".join(context_lines)
agent = Agent(
name="Assistant",
role="Helpful AI Assistant",
goal="Help the user with their tasks",
backstory=backstory,
tools=tools_list if tools_list else None,
verbose=False,
llm=model
)
live_status.update_status(f"Task #{task_id}: Calling LLM...")
response = agent.chat(prompt, stream=False)
response_str = str(response) if response else ""
# Calculate elapsed time
elapsed = time.time() - start_time
# Store completed task for display with Q/A mapping
completed_task = {
'task_id': task_id,
'question': task.get('question', prompt),
'response': response_str,
'elapsed': elapsed,
'status': 'completed',
}
worker_state['completed_tasks'].append(completed_task)
# Update session state
input_tokens = len(prompt) // 4
output_tokens = len(response_str) // 4
session_state['total_input_tokens'] += input_tokens
session_state['total_output_tokens'] += output_tokens
session_state['request_count'] += 1
session_state['conversation_history'].append({'role': 'user', 'content': prompt})
session_state['conversation_history'].append({'role': 'assistant', 'content': response_str})
# Persist to UnifiedSession
if 'unified_session' in session_state and 'session_store' in session_state:
unified_session = session_state['unified_session']
unified_session.add_user_message(prompt)
unified_session.add_assistant_message(response_str)
unified_session.update_stats(input_tokens, output_tokens)
session_state['session_store'].save(unified_session)
if live_instance is None:
# Start new Live context
live_instance = Live(Spinner("dots", text=spinner_text, style="cyan"),
console=console, refresh_per_second=10, transient=True)
live_instance.start()
else:
live_instance.update(Spinner("dots", text=spinner_text, style="cyan"))
except Exception as e:
# Store error task for display with Q/A mapping
error_task = {
'task_id': task_id,
'question': task.get('question', prompt),
'error': str(e),
'status': 'failed',
}
worker_state['error_tasks'].append(error_task)
finally:
worker_state['current_task'] = None
worker_state['tool_activity'] = None
state_manager.set_state(ProcessingState.IDLE)
live_status.clear()
execution_queue.task_done()
time.sleep(0.1)
except Exception as e:
console.print(f"[red]Display error: {e}[/red]")
finally:
if live_instance:
live_instance.stop()
except Exception as e:
import traceback
traceback.print_exc()
# Wait for thread to finish
bg_thread.join(timeout=2.0)
# Start worker thread
worker_thread = threading.Thread(target=worker_loop, daemon=True, name="ExecutionWorker")
worker_thread.start()
return worker_thread
def _submit_prompt_to_worker(self, prompt, session_state):
"""
Submit a prompt to the execution worker queue.
# Handle result
if result_container['error']:
console.print(f"[red]Error: {result_container['error']}[/red]")
elif result_container['response']:
# Print response with streaming effect
response_str = result_container['response']
words = response_str.split()
for i, word in enumerate(words):
console.print(word + " ", end="")
sys.stdout.flush()
if i % 15 == 14:
time.sleep(0.005)
console.print()
# Update session state
input_tokens = len(prompt) // 4
output_tokens = len(response_str) // 4
session_state['total_input_tokens'] += input_tokens
session_state['total_output_tokens'] += output_tokens
session_state['request_count'] += 1
session_state['conversation_history'].append({'role': 'user', 'content': prompt})
session_state['conversation_history'].append({'role': 'assistant', 'content': response_str})
# Persist to UnifiedSession
if 'unified_session' in session_state and 'session_store' in session_state:
unified_session = session_state['unified_session']
unified_session.add_user_message(prompt)
unified_session.add_assistant_message(response_str)
unified_session.update_stats(input_tokens, output_tokens)
session_state['session_store'].save(unified_session)
# Set state back to idle
state_manager.set_state(ProcessingState.IDLE)
live_status.clear()
# Process next queued message if any
next_msg = message_queue.pop()
if next_msg:
console.print("\n[dim cyan]Processing queued message...[/dim cyan]")
self._process_interactive_prompt_async(next_msg, tools_list, console, session_state)
This is NON-BLOCKING - returns immediately after queuing.
"""
execution_queue = session_state['execution_queue']
execution_queue.put({'prompt': prompt})
def _process_interactive_prompt_async(self, prompt, tools_list, console, session_state=None):
"""
DEPRECATED: This method is kept for backward compatibility.
Use _submit_prompt_to_worker instead for true non-blocking behavior.
"""
# Just submit to worker queue - non-blocking
self._submit_prompt_to_worker(prompt, session_state)
def _process_interactive_prompt(self, prompt, tools_list, console, show_profiling=False, session_state=None):
"""Process a prompt in interactive mode with streaming."""
+1 -1
View File
@@ -57,7 +57,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==3.1.0 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==3.1.2 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+1 -1
View File
@@ -1 +1 @@
__version__ = "3.1.0"
__version__ = "3.1.2"