mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-10 11:41:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d3ba7cdf8 | ||
|
|
96616498d2 | ||
|
|
1cad345415 | ||
|
|
802e7e52f9 | ||
|
|
53704f234c | ||
|
|
b575994d91 | ||
|
|
7ed8596261 |
No files matched your search
@@ -252,20 +252,20 @@ npm install praisonai
|
||||
| 🪝 Hooks | [Example](#9-hooks) | [📖](https://docs.praison.ai/features/hooks) |
|
||||
| 📈 Telemetry | [Example](examples/python/telemetry/production-telemetry-example.py) | [📖](https://docs.praison.ai/features/telemetry) |
|
||||
| 📹 Camera Integration | [Example](examples/python/camera/) | [📖](https://docs.praison.ai/features/camera-integration) |
|
||||
| 📄 Project Docs (.praison/docs/) | [Example](#docs-cli) | [📖](https://docs.praison.ai/cli/docs) |
|
||||
| 🔌 MCP Config Management | [Example](#mcp-config-cli) | [📖](https://docs.praison.ai/cli/mcp) |
|
||||
| 💬 AI Commit Messages | [Example](#ai-commit-cli) | [📖](https://docs.praison.ai/cli/commit) |
|
||||
| @ @Mentions in Prompts | [Example](#mentions-in-prompts) | [📖](https://docs.praison.ai/cli/mentions) |
|
||||
| 💾 Auto-Save Sessions | [Example](#session-management-python) | [📖](https://docs.praison.ai/cli/session) |
|
||||
| 📜 History in Context | [Example](#session-management-python) | [📖](https://docs.praison.ai/cli/session) |
|
||||
| 📄 Project Docs (.praison/docs/) | [Example](#docs-cli) | [📖](https://docs.praison.ai/docs/cli/docs) |
|
||||
| 🔌 MCP Config Management | [Example](#mcp-config-cli) | [📖](https://docs.praison.ai/docs/cli/mcp) |
|
||||
| 💬 AI Commit Messages | [Example](#ai-commit-cli) | [📖](https://docs.praison.ai/docs/cli/commit) |
|
||||
| @ @Mentions in Prompts | [Example](#mentions-in-prompts) | [📖](https://docs.praison.ai/docs/cli/mentions) |
|
||||
| 💾 Auto-Save Sessions | [Example](#session-management-python) | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| 📜 History in Context | [Example](#session-management-python) | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| **🖥️ CLI Features** | | |
|
||||
| ↳ Slash Commands | [Example](examples/python/cli/slash_commands_example.py) | [📖](https://docs.praison.ai/cli/slash-commands) |
|
||||
| ↳ Autonomy Modes | [Example](examples/python/cli/autonomy_modes_example.py) | [📖](https://docs.praison.ai/cli/autonomy-modes) |
|
||||
| ↳ Cost Tracking | [Example](examples/python/cli/cost_tracking_example.py) | [📖](https://docs.praison.ai/cli/cost-tracking) |
|
||||
| ↳ Repository Map | [Example](examples/python/cli/repo_map_example.py) | [📖](https://docs.praison.ai/cli/repo-map) |
|
||||
| ↳ Interactive TUI | [Example](examples/python/cli/interactive_tui_example.py) | [📖](https://docs.praison.ai/cli/interactive-tui) |
|
||||
| ↳ Git Integration | [Example](examples/python/cli/git_integration_example.py) | [📖](https://docs.praison.ai/cli/git-integration) |
|
||||
| ↳ Sandbox Execution | [Example](examples/python/cli/sandbox_execution_example.py) | [📖](https://docs.praison.ai/cli/sandbox-execution) |
|
||||
| ↳ Slash Commands | [Example](examples/python/cli/slash_commands_example.py) | [📖](https://docs.praison.ai/docs/cli/slash-commands) |
|
||||
| ↳ Autonomy Modes | [Example](examples/python/cli/autonomy_modes_example.py) | [📖](https://docs.praison.ai/docs/cli/autonomy-modes) |
|
||||
| ↳ Cost Tracking | [Example](examples/python/cli/cost_tracking_example.py) | [📖](https://docs.praison.ai/docs/cli/cost-tracking) |
|
||||
| ↳ Repository Map | [Example](examples/python/cli/repo_map_example.py) | [📖](https://docs.praison.ai/docs/cli/repo-map) |
|
||||
| ↳ Interactive TUI | [Example](examples/python/cli/interactive_tui_example.py) | [📖](https://docs.praison.ai/docs/cli/interactive-tui) |
|
||||
| ↳ Git Integration | [Example](examples/python/cli/git_integration_example.py) | [📖](https://docs.praison.ai/docs/cli/git-integration) |
|
||||
| ↳ Sandbox Execution | [Example](examples/python/cli/sandbox_execution_example.py) | [📖](https://docs.praison.ai/docs/cli/sandbox-execution) |
|
||||
|
||||
---
|
||||
|
||||
@@ -972,6 +972,32 @@ export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
|
||||
praisonai --auto create a movie script about Robots in Mars
|
||||
```
|
||||
|
||||
### Interactive Mode CLI:
|
||||
```bash
|
||||
# Start interactive terminal mode (inspired by Gemini CLI, Codex CLI, Claude Code)
|
||||
praisonai --interactive
|
||||
praisonai -i
|
||||
|
||||
# Features:
|
||||
# - Streaming responses (no boxes)
|
||||
# - Built-in tools: read_file, write_file, list_files, execute_command, internet_search
|
||||
# - Slash commands: /help, /exit, /tools, /clear
|
||||
|
||||
# Chat mode - single prompt with interactive style (for testing/scripting)
|
||||
# Use --chat (or --chat-mode for backward compatibility)
|
||||
praisonai "list files in current folder" --chat
|
||||
praisonai "search the web for AI news" --chat
|
||||
praisonai "read README.md" --chat
|
||||
```
|
||||
|
||||
### Chat UI (Web Interface):
|
||||
```bash
|
||||
# Start web-based Chainlit chat interface (requires praisonai[chat])
|
||||
pip install "praisonai[chat]"
|
||||
praisonai chat
|
||||
# Opens browser at http://localhost:8084
|
||||
```
|
||||
|
||||
### Query Rewriting (works with any command):
|
||||
```bash
|
||||
# Rewrite query for better results (uses QueryRewriterAgent)
|
||||
@@ -1446,13 +1472,31 @@ praisonai mcp help
|
||||
|
||||
### AI Commit CLI:
|
||||
```bash
|
||||
# Generate AI commit message for staged changes
|
||||
# Full auto mode: stage all, security check, commit, and push
|
||||
praisonai commit -a
|
||||
|
||||
# Interactive mode (requires git add first)
|
||||
praisonai commit
|
||||
|
||||
# Generate, commit, and push
|
||||
# Interactive with auto-push
|
||||
praisonai commit --push
|
||||
|
||||
# Skip security check (not recommended)
|
||||
praisonai commit -a --no-verify
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- 🤖 AI-generated conventional commit messages
|
||||
- 🔒 Built-in security scanning (API keys, passwords, secrets, sensitive files)
|
||||
- 📦 Auto-staging with `-a` flag
|
||||
- 🚀 Auto-push in full auto mode
|
||||
- ✏️ Edit message before commit in interactive mode
|
||||
|
||||
**Security Detection:**
|
||||
- API keys, secrets, tokens (AWS, GitHub, GitLab, Slack)
|
||||
- Passwords and private keys
|
||||
- Sensitive files (`.env`, `id_rsa`, `.pem`, `.key`, etc.)
|
||||
|
||||
### Serve CLI (API Server):
|
||||
```bash
|
||||
# Start API server for agents defined in YAML
|
||||
@@ -1757,32 +1801,32 @@ app.include_router(a2a.get_router())
|
||||
|
||||
| Feature | Docs |
|
||||
|---------|:----:|
|
||||
| 🔄 Query Rewrite - RAG optimization | [📖](https://docs.praison.ai/cli/query-rewrite) |
|
||||
| 🔬 Deep Research - Automated research | [📖](https://docs.praison.ai/cli/deep-research) |
|
||||
| 📋 Planning - Step-by-step execution | [📖](https://docs.praison.ai/cli/planning) |
|
||||
| 💾 Memory - Persistent agent memory | [📖](https://docs.praison.ai/cli/memory) |
|
||||
| 📜 Rules - Auto-discovered instructions | [📖](https://docs.praison.ai/cli/rules) |
|
||||
| 🔄 Workflow - Multi-step workflows | [📖](https://docs.praison.ai/cli/workflow) |
|
||||
| 🪝 Hooks - Event-driven actions | [📖](https://docs.praison.ai/cli/hooks) |
|
||||
| 🧠 Claude Memory - Anthropic memory tool | [📖](https://docs.praison.ai/cli/claude-memory) |
|
||||
| 🛡️ Guardrail - Output validation | [📖](https://docs.praison.ai/cli/guardrail) |
|
||||
| 📊 Metrics - Token usage tracking | [📖](https://docs.praison.ai/cli/metrics) |
|
||||
| 🖼️ Image - Vision processing | [📖](https://docs.praison.ai/cli/image) |
|
||||
| 📡 Telemetry - Usage monitoring | [📖](https://docs.praison.ai/cli/telemetry) |
|
||||
| 🔌 MCP - Model Context Protocol | [📖](https://docs.praison.ai/cli/mcp) |
|
||||
| ⚡ Fast Context - Codebase search | [📖](https://docs.praison.ai/cli/fast-context) |
|
||||
| 📚 Knowledge - RAG management | [📖](https://docs.praison.ai/cli/knowledge) |
|
||||
| 💬 Session - Conversation management | [📖](https://docs.praison.ai/cli/session) |
|
||||
| 🔧 Tools - Tool discovery | [📖](https://docs.praison.ai/cli/tools) |
|
||||
| 🤝 Handoff - Agent delegation | [📖](https://docs.praison.ai/cli/handoff) |
|
||||
| 🧠 Auto Memory - Memory extraction | [📖](https://docs.praison.ai/cli/auto-memory) |
|
||||
| 📋 Todo - Task management | [📖](https://docs.praison.ai/cli/todo) |
|
||||
| 🎯 Router - Smart model selection | [📖](https://docs.praison.ai/cli/router) |
|
||||
| 📈 Flow Display - Visual workflow | [📖](https://docs.praison.ai/cli/flow-display) |
|
||||
| ✨ Prompt Expansion - Detailed prompts | [📖](https://docs.praison.ai/cli/prompt-expansion) |
|
||||
| 🌐 Web Search - Real-time search | [📖](https://docs.praison.ai/cli/web-search) |
|
||||
| 📥 Web Fetch - URL content retrieval | [📖](https://docs.praison.ai/cli/web-fetch) |
|
||||
| 💾 Prompt Caching - Cost reduction | [📖](https://docs.praison.ai/cli/prompt-caching) |
|
||||
| 🔄 Query Rewrite - RAG optimization | [📖](https://docs.praison.ai/docs/cli/query-rewrite) |
|
||||
| 🔬 Deep Research - Automated research | [📖](https://docs.praison.ai/docs/cli/deep-research) |
|
||||
| 📋 Planning - Step-by-step execution | [📖](https://docs.praison.ai/docs/cli/planning) |
|
||||
| 💾 Memory - Persistent agent memory | [📖](https://docs.praison.ai/docs/cli/memory) |
|
||||
| 📜 Rules - Auto-discovered instructions | [📖](https://docs.praison.ai/docs/cli/rules) |
|
||||
| 🔄 Workflow - Multi-step workflows | [📖](https://docs.praison.ai/docs/cli/workflow) |
|
||||
| 🪝 Hooks - Event-driven actions | [📖](https://docs.praison.ai/docs/cli/hooks) |
|
||||
| 🧠 Claude Memory - Anthropic memory tool | [📖](https://docs.praison.ai/docs/cli/claude-memory) |
|
||||
| 🛡️ Guardrail - Output validation | [📖](https://docs.praison.ai/docs/cli/guardrail) |
|
||||
| 📊 Metrics - Token usage tracking | [📖](https://docs.praison.ai/docs/cli/metrics) |
|
||||
| 🖼️ Image - Vision processing | [📖](https://docs.praison.ai/docs/cli/image) |
|
||||
| 📡 Telemetry - Usage monitoring | [📖](https://docs.praison.ai/docs/cli/telemetry) |
|
||||
| 🔌 MCP - Model Context Protocol | [📖](https://docs.praison.ai/docs/cli/mcp) |
|
||||
| ⚡ Fast Context - Codebase search | [📖](https://docs.praison.ai/docs/cli/fast-context) |
|
||||
| 📚 Knowledge - RAG management | [📖](https://docs.praison.ai/docs/cli/knowledge) |
|
||||
| 💬 Session - Conversation management | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| 🔧 Tools - Tool discovery | [📖](https://docs.praison.ai/docs/cli/tools) |
|
||||
| 🤝 Handoff - Agent delegation | [📖](https://docs.praison.ai/docs/cli/handoff) |
|
||||
| 🧠 Auto Memory - Memory extraction | [📖](https://docs.praison.ai/docs/cli/auto-memory) |
|
||||
| 📋 Todo - Task management | [📖](https://docs.praison.ai/docs/cli/todo) |
|
||||
| 🎯 Router - Smart model selection | [📖](https://docs.praison.ai/docs/cli/router) |
|
||||
| 📈 Flow Display - Visual workflow | [📖](https://docs.praison.ai/docs/cli/flow-display) |
|
||||
| ✨ Prompt Expansion - Detailed prompts | [📖](https://docs.praison.ai/docs/cli/prompt-expansion) |
|
||||
| 🌐 Web Search - Real-time search | [📖](https://docs.praison.ai/docs/cli/web-search) |
|
||||
| 📥 Web Fetch - URL content retrieval | [📖](https://docs.praison.ai/docs/cli/web-fetch) |
|
||||
| 💾 Prompt Caching - Cost reduction | [📖](https://docs.praison.ai/docs/cli/prompt-caching) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.3.47" \
|
||||
"praisonai>=2.3.52" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.3.47" \
|
||||
"praisonai>=2.3.52" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.3.47" \
|
||||
"praisonai>=2.3.52" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
+85
-41
@@ -252,20 +252,20 @@ npm install praisonai
|
||||
| 🪝 Hooks | [Example](#9-hooks) | [📖](https://docs.praison.ai/features/hooks) |
|
||||
| 📈 Telemetry | [Example](examples/python/telemetry/production-telemetry-example.py) | [📖](https://docs.praison.ai/features/telemetry) |
|
||||
| 📹 Camera Integration | [Example](examples/python/camera/) | [📖](https://docs.praison.ai/features/camera-integration) |
|
||||
| 📄 Project Docs (.praison/docs/) | [Example](#docs-cli) | [📖](https://docs.praison.ai/cli/docs) |
|
||||
| 🔌 MCP Config Management | [Example](#mcp-config-cli) | [📖](https://docs.praison.ai/cli/mcp) |
|
||||
| 💬 AI Commit Messages | [Example](#ai-commit-cli) | [📖](https://docs.praison.ai/cli/commit) |
|
||||
| @ @Mentions in Prompts | [Example](#mentions-in-prompts) | [📖](https://docs.praison.ai/cli/mentions) |
|
||||
| 💾 Auto-Save Sessions | [Example](#session-management-python) | [📖](https://docs.praison.ai/cli/session) |
|
||||
| 📜 History in Context | [Example](#session-management-python) | [📖](https://docs.praison.ai/cli/session) |
|
||||
| 📄 Project Docs (.praison/docs/) | [Example](#docs-cli) | [📖](https://docs.praison.ai/docs/cli/docs) |
|
||||
| 🔌 MCP Config Management | [Example](#mcp-config-cli) | [📖](https://docs.praison.ai/docs/cli/mcp) |
|
||||
| 💬 AI Commit Messages | [Example](#ai-commit-cli) | [📖](https://docs.praison.ai/docs/cli/commit) |
|
||||
| @ @Mentions in Prompts | [Example](#mentions-in-prompts) | [📖](https://docs.praison.ai/docs/cli/mentions) |
|
||||
| 💾 Auto-Save Sessions | [Example](#session-management-python) | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| 📜 History in Context | [Example](#session-management-python) | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| **🖥️ CLI Features** | | |
|
||||
| ↳ Slash Commands | [Example](examples/python/cli/slash_commands_example.py) | [📖](https://docs.praison.ai/cli/slash-commands) |
|
||||
| ↳ Autonomy Modes | [Example](examples/python/cli/autonomy_modes_example.py) | [📖](https://docs.praison.ai/cli/autonomy-modes) |
|
||||
| ↳ Cost Tracking | [Example](examples/python/cli/cost_tracking_example.py) | [📖](https://docs.praison.ai/cli/cost-tracking) |
|
||||
| ↳ Repository Map | [Example](examples/python/cli/repo_map_example.py) | [📖](https://docs.praison.ai/cli/repo-map) |
|
||||
| ↳ Interactive TUI | [Example](examples/python/cli/interactive_tui_example.py) | [📖](https://docs.praison.ai/cli/interactive-tui) |
|
||||
| ↳ Git Integration | [Example](examples/python/cli/git_integration_example.py) | [📖](https://docs.praison.ai/cli/git-integration) |
|
||||
| ↳ Sandbox Execution | [Example](examples/python/cli/sandbox_execution_example.py) | [📖](https://docs.praison.ai/cli/sandbox-execution) |
|
||||
| ↳ Slash Commands | [Example](examples/python/cli/slash_commands_example.py) | [📖](https://docs.praison.ai/docs/cli/slash-commands) |
|
||||
| ↳ Autonomy Modes | [Example](examples/python/cli/autonomy_modes_example.py) | [📖](https://docs.praison.ai/docs/cli/autonomy-modes) |
|
||||
| ↳ Cost Tracking | [Example](examples/python/cli/cost_tracking_example.py) | [📖](https://docs.praison.ai/docs/cli/cost-tracking) |
|
||||
| ↳ Repository Map | [Example](examples/python/cli/repo_map_example.py) | [📖](https://docs.praison.ai/docs/cli/repo-map) |
|
||||
| ↳ Interactive TUI | [Example](examples/python/cli/interactive_tui_example.py) | [📖](https://docs.praison.ai/docs/cli/interactive-tui) |
|
||||
| ↳ Git Integration | [Example](examples/python/cli/git_integration_example.py) | [📖](https://docs.praison.ai/docs/cli/git-integration) |
|
||||
| ↳ Sandbox Execution | [Example](examples/python/cli/sandbox_execution_example.py) | [📖](https://docs.praison.ai/docs/cli/sandbox-execution) |
|
||||
|
||||
---
|
||||
|
||||
@@ -972,6 +972,32 @@ export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
|
||||
praisonai --auto create a movie script about Robots in Mars
|
||||
```
|
||||
|
||||
### Interactive Mode CLI:
|
||||
```bash
|
||||
# Start interactive terminal mode (inspired by Gemini CLI, Codex CLI, Claude Code)
|
||||
praisonai --interactive
|
||||
praisonai -i
|
||||
|
||||
# Features:
|
||||
# - Streaming responses (no boxes)
|
||||
# - Built-in tools: read_file, write_file, list_files, execute_command, internet_search
|
||||
# - Slash commands: /help, /exit, /tools, /clear
|
||||
|
||||
# Chat mode - single prompt with interactive style (for testing/scripting)
|
||||
# Use --chat (or --chat-mode for backward compatibility)
|
||||
praisonai "list files in current folder" --chat
|
||||
praisonai "search the web for AI news" --chat
|
||||
praisonai "read README.md" --chat
|
||||
```
|
||||
|
||||
### Chat UI (Web Interface):
|
||||
```bash
|
||||
# Start web-based Chainlit chat interface (requires praisonai[chat])
|
||||
pip install "praisonai[chat]"
|
||||
praisonai chat
|
||||
# Opens browser at http://localhost:8084
|
||||
```
|
||||
|
||||
### Query Rewriting (works with any command):
|
||||
```bash
|
||||
# Rewrite query for better results (uses QueryRewriterAgent)
|
||||
@@ -1446,13 +1472,31 @@ praisonai mcp help
|
||||
|
||||
### AI Commit CLI:
|
||||
```bash
|
||||
# Generate AI commit message for staged changes
|
||||
# Full auto mode: stage all, security check, commit, and push
|
||||
praisonai commit -a
|
||||
|
||||
# Interactive mode (requires git add first)
|
||||
praisonai commit
|
||||
|
||||
# Generate, commit, and push
|
||||
# Interactive with auto-push
|
||||
praisonai commit --push
|
||||
|
||||
# Skip security check (not recommended)
|
||||
praisonai commit -a --no-verify
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- 🤖 AI-generated conventional commit messages
|
||||
- 🔒 Built-in security scanning (API keys, passwords, secrets, sensitive files)
|
||||
- 📦 Auto-staging with `-a` flag
|
||||
- 🚀 Auto-push in full auto mode
|
||||
- ✏️ Edit message before commit in interactive mode
|
||||
|
||||
**Security Detection:**
|
||||
- API keys, secrets, tokens (AWS, GitHub, GitLab, Slack)
|
||||
- Passwords and private keys
|
||||
- Sensitive files (`.env`, `id_rsa`, `.pem`, `.key`, etc.)
|
||||
|
||||
### Serve CLI (API Server):
|
||||
```bash
|
||||
# Start API server for agents defined in YAML
|
||||
@@ -1757,32 +1801,32 @@ app.include_router(a2a.get_router())
|
||||
|
||||
| Feature | Docs |
|
||||
|---------|:----:|
|
||||
| 🔄 Query Rewrite - RAG optimization | [📖](https://docs.praison.ai/cli/query-rewrite) |
|
||||
| 🔬 Deep Research - Automated research | [📖](https://docs.praison.ai/cli/deep-research) |
|
||||
| 📋 Planning - Step-by-step execution | [📖](https://docs.praison.ai/cli/planning) |
|
||||
| 💾 Memory - Persistent agent memory | [📖](https://docs.praison.ai/cli/memory) |
|
||||
| 📜 Rules - Auto-discovered instructions | [📖](https://docs.praison.ai/cli/rules) |
|
||||
| 🔄 Workflow - Multi-step workflows | [📖](https://docs.praison.ai/cli/workflow) |
|
||||
| 🪝 Hooks - Event-driven actions | [📖](https://docs.praison.ai/cli/hooks) |
|
||||
| 🧠 Claude Memory - Anthropic memory tool | [📖](https://docs.praison.ai/cli/claude-memory) |
|
||||
| 🛡️ Guardrail - Output validation | [📖](https://docs.praison.ai/cli/guardrail) |
|
||||
| 📊 Metrics - Token usage tracking | [📖](https://docs.praison.ai/cli/metrics) |
|
||||
| 🖼️ Image - Vision processing | [📖](https://docs.praison.ai/cli/image) |
|
||||
| 📡 Telemetry - Usage monitoring | [📖](https://docs.praison.ai/cli/telemetry) |
|
||||
| 🔌 MCP - Model Context Protocol | [📖](https://docs.praison.ai/cli/mcp) |
|
||||
| ⚡ Fast Context - Codebase search | [📖](https://docs.praison.ai/cli/fast-context) |
|
||||
| 📚 Knowledge - RAG management | [📖](https://docs.praison.ai/cli/knowledge) |
|
||||
| 💬 Session - Conversation management | [📖](https://docs.praison.ai/cli/session) |
|
||||
| 🔧 Tools - Tool discovery | [📖](https://docs.praison.ai/cli/tools) |
|
||||
| 🤝 Handoff - Agent delegation | [📖](https://docs.praison.ai/cli/handoff) |
|
||||
| 🧠 Auto Memory - Memory extraction | [📖](https://docs.praison.ai/cli/auto-memory) |
|
||||
| 📋 Todo - Task management | [📖](https://docs.praison.ai/cli/todo) |
|
||||
| 🎯 Router - Smart model selection | [📖](https://docs.praison.ai/cli/router) |
|
||||
| 📈 Flow Display - Visual workflow | [📖](https://docs.praison.ai/cli/flow-display) |
|
||||
| ✨ Prompt Expansion - Detailed prompts | [📖](https://docs.praison.ai/cli/prompt-expansion) |
|
||||
| 🌐 Web Search - Real-time search | [📖](https://docs.praison.ai/cli/web-search) |
|
||||
| 📥 Web Fetch - URL content retrieval | [📖](https://docs.praison.ai/cli/web-fetch) |
|
||||
| 💾 Prompt Caching - Cost reduction | [📖](https://docs.praison.ai/cli/prompt-caching) |
|
||||
| 🔄 Query Rewrite - RAG optimization | [📖](https://docs.praison.ai/docs/cli/query-rewrite) |
|
||||
| 🔬 Deep Research - Automated research | [📖](https://docs.praison.ai/docs/cli/deep-research) |
|
||||
| 📋 Planning - Step-by-step execution | [📖](https://docs.praison.ai/docs/cli/planning) |
|
||||
| 💾 Memory - Persistent agent memory | [📖](https://docs.praison.ai/docs/cli/memory) |
|
||||
| 📜 Rules - Auto-discovered instructions | [📖](https://docs.praison.ai/docs/cli/rules) |
|
||||
| 🔄 Workflow - Multi-step workflows | [📖](https://docs.praison.ai/docs/cli/workflow) |
|
||||
| 🪝 Hooks - Event-driven actions | [📖](https://docs.praison.ai/docs/cli/hooks) |
|
||||
| 🧠 Claude Memory - Anthropic memory tool | [📖](https://docs.praison.ai/docs/cli/claude-memory) |
|
||||
| 🛡️ Guardrail - Output validation | [📖](https://docs.praison.ai/docs/cli/guardrail) |
|
||||
| 📊 Metrics - Token usage tracking | [📖](https://docs.praison.ai/docs/cli/metrics) |
|
||||
| 🖼️ Image - Vision processing | [📖](https://docs.praison.ai/docs/cli/image) |
|
||||
| 📡 Telemetry - Usage monitoring | [📖](https://docs.praison.ai/docs/cli/telemetry) |
|
||||
| 🔌 MCP - Model Context Protocol | [📖](https://docs.praison.ai/docs/cli/mcp) |
|
||||
| ⚡ Fast Context - Codebase search | [📖](https://docs.praison.ai/docs/cli/fast-context) |
|
||||
| 📚 Knowledge - RAG management | [📖](https://docs.praison.ai/docs/cli/knowledge) |
|
||||
| 💬 Session - Conversation management | [📖](https://docs.praison.ai/docs/cli/session) |
|
||||
| 🔧 Tools - Tool discovery | [📖](https://docs.praison.ai/docs/cli/tools) |
|
||||
| 🤝 Handoff - Agent delegation | [📖](https://docs.praison.ai/docs/cli/handoff) |
|
||||
| 🧠 Auto Memory - Memory extraction | [📖](https://docs.praison.ai/docs/cli/auto-memory) |
|
||||
| 📋 Todo - Task management | [📖](https://docs.praison.ai/docs/cli/todo) |
|
||||
| 🎯 Router - Smart model selection | [📖](https://docs.praison.ai/docs/cli/router) |
|
||||
| 📈 Flow Display - Visual workflow | [📖](https://docs.praison.ai/docs/cli/flow-display) |
|
||||
| ✨ Prompt Expansion - Detailed prompts | [📖](https://docs.praison.ai/docs/cli/prompt-expansion) |
|
||||
| 🌐 Web Search - Real-time search | [📖](https://docs.praison.ai/docs/cli/web-search) |
|
||||
| 📥 Web Fetch - URL content retrieval | [📖](https://docs.praison.ai/docs/cli/web-fetch) |
|
||||
| 💾 Prompt Caching - Cost reduction | [📖](https://docs.praison.ai/docs/cli/prompt-caching) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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/v2.3.47.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.47.tar.gz | shasum -a 256`.split.first
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.52.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.52.tar.gz | shasum -a 256`.split.first
|
||||
license "MIT"
|
||||
|
||||
depends_on "[email protected]"
|
||||
|
||||
@@ -25,6 +25,7 @@ Features:
|
||||
- slash_commands: Interactive slash commands (/help, /cost, /plan, etc.)
|
||||
- autonomy_mode: Autonomy levels (suggest, auto-edit, full-auto)
|
||||
- cost_tracker: Real-time cost and token tracking
|
||||
- message_queue: Message queuing while agent is processing
|
||||
"""
|
||||
|
||||
# Type hints available for IDE support
|
||||
@@ -56,6 +57,7 @@ __all__ = [
|
||||
'InteractiveTUIHandler',
|
||||
'GitIntegrationHandler',
|
||||
'SandboxExecutorHandler',
|
||||
'MessageQueueHandler',
|
||||
]
|
||||
|
||||
def __getattr__(name):
|
||||
@@ -133,4 +135,7 @@ def __getattr__(name):
|
||||
elif name == 'SandboxExecutorHandler':
|
||||
from .sandbox_executor import SandboxExecutorHandler
|
||||
return SandboxExecutorHandler
|
||||
elif name == 'MessageQueueHandler':
|
||||
from .message_queue import MessageQueueHandler
|
||||
return MessageQueueHandler
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
@@ -0,0 +1,587 @@
|
||||
"""
|
||||
Message Queue for Interactive Mode.
|
||||
|
||||
Provides FIFO message queuing while the AI agent is processing,
|
||||
inspired by Claude Code, Windsurf Cascade, Cursor, and Gemini CLI.
|
||||
|
||||
Features:
|
||||
- Queue messages while agent is processing
|
||||
- Auto-process queue when agent becomes idle
|
||||
- Visual indicators for queue status
|
||||
- Thread-safe operations
|
||||
"""
|
||||
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Enums
|
||||
# ============================================================================
|
||||
|
||||
class ProcessingState(Enum):
|
||||
"""State of the agent processing."""
|
||||
IDLE = "idle"
|
||||
PROCESSING = "processing"
|
||||
WAITING_APPROVAL = "waiting_approval"
|
||||
|
||||
|
||||
class MessagePriority(Enum):
|
||||
"""Priority levels for queued messages."""
|
||||
LOW = "low"
|
||||
NORMAL = "normal"
|
||||
HIGH = "high"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Data Classes
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class QueuedMessage:
|
||||
"""A message in the queue with metadata."""
|
||||
content: str
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
priority: MessagePriority = MessagePriority.NORMAL
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MessageQueue - Thread-safe FIFO queue
|
||||
# ============================================================================
|
||||
|
||||
class MessageQueue:
|
||||
"""
|
||||
Thread-safe FIFO message queue.
|
||||
|
||||
Provides basic queue operations with thread safety for concurrent access.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._messages: List[str] = []
|
||||
self._lock = threading.Lock()
|
||||
|
||||
@property
|
||||
def is_empty(self) -> bool:
|
||||
"""Check if queue is empty."""
|
||||
with self._lock:
|
||||
return len(self._messages) == 0
|
||||
|
||||
@property
|
||||
def count(self) -> int:
|
||||
"""Get number of messages in queue."""
|
||||
with self._lock:
|
||||
return len(self._messages)
|
||||
|
||||
def add(self, message: str) -> bool:
|
||||
"""
|
||||
Add a message to the end of the queue.
|
||||
|
||||
Args:
|
||||
message: The message to add
|
||||
|
||||
Returns:
|
||||
True if message was added, False if empty/whitespace
|
||||
"""
|
||||
trimmed = message.strip() if message else ""
|
||||
if not trimmed:
|
||||
return False
|
||||
|
||||
with self._lock:
|
||||
self._messages.append(trimmed)
|
||||
return True
|
||||
|
||||
def pop(self) -> Optional[str]:
|
||||
"""
|
||||
Remove and return the first message (FIFO).
|
||||
|
||||
Returns:
|
||||
The first message, or None if queue is empty
|
||||
"""
|
||||
with self._lock:
|
||||
if self._messages:
|
||||
return self._messages.pop(0)
|
||||
return None
|
||||
|
||||
def peek(self) -> Optional[str]:
|
||||
"""
|
||||
View the first message without removing it.
|
||||
|
||||
Returns:
|
||||
The first message, or None if queue is empty
|
||||
"""
|
||||
with self._lock:
|
||||
if self._messages:
|
||||
return self._messages[0]
|
||||
return None
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove all messages from the queue."""
|
||||
with self._lock:
|
||||
self._messages.clear()
|
||||
|
||||
def get_all(self) -> List[str]:
|
||||
"""
|
||||
Get all messages as a list (does not remove them).
|
||||
|
||||
Returns:
|
||||
Copy of all messages in queue order
|
||||
"""
|
||||
with self._lock:
|
||||
return self._messages.copy()
|
||||
|
||||
def remove_at(self, index: int) -> Optional[str]:
|
||||
"""
|
||||
Remove message at specific index.
|
||||
|
||||
Args:
|
||||
index: The index to remove (0-based)
|
||||
|
||||
Returns:
|
||||
The removed message, or None if index is invalid
|
||||
"""
|
||||
with self._lock:
|
||||
if 0 <= index < len(self._messages):
|
||||
return self._messages.pop(index)
|
||||
return None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# StateManager - Processing state management
|
||||
# ============================================================================
|
||||
|
||||
class StateManager:
|
||||
"""
|
||||
Manages the processing state with optional callbacks.
|
||||
|
||||
Tracks whether the agent is idle, processing, or waiting for approval.
|
||||
"""
|
||||
|
||||
def __init__(self, on_state_change: Optional[Callable[[ProcessingState, ProcessingState], None]] = None):
|
||||
self._state = ProcessingState.IDLE
|
||||
self._lock = threading.Lock()
|
||||
self._on_state_change = on_state_change
|
||||
|
||||
@property
|
||||
def current_state(self) -> ProcessingState:
|
||||
"""Get current processing state."""
|
||||
with self._lock:
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def is_idle(self) -> bool:
|
||||
"""Check if currently idle."""
|
||||
return self.current_state == ProcessingState.IDLE
|
||||
|
||||
@property
|
||||
def is_processing(self) -> bool:
|
||||
"""Check if currently processing."""
|
||||
return self.current_state == ProcessingState.PROCESSING
|
||||
|
||||
def set_state(self, new_state: ProcessingState) -> None:
|
||||
"""
|
||||
Set the processing state.
|
||||
|
||||
Args:
|
||||
new_state: The new state to set
|
||||
"""
|
||||
with self._lock:
|
||||
old_state = self._state
|
||||
self._state = new_state
|
||||
|
||||
# Call callback outside lock to prevent deadlock
|
||||
if self._on_state_change and old_state != new_state:
|
||||
self._on_state_change(old_state, new_state)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# QueueDisplay - Visual formatting
|
||||
# ============================================================================
|
||||
|
||||
class QueueDisplay:
|
||||
"""
|
||||
Formats queue and status for display.
|
||||
|
||||
Provides visual indicators for queue status, inspired by Codex CLI.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
queue: MessageQueue,
|
||||
state_manager: Optional[StateManager] = None,
|
||||
max_message_length: int = 50
|
||||
):
|
||||
self._queue = queue
|
||||
self._state_manager = state_manager
|
||||
self._max_length = max_message_length
|
||||
|
||||
def format_queue(self) -> str:
|
||||
"""
|
||||
Format queued messages for display.
|
||||
|
||||
Returns:
|
||||
Formatted string with queued messages
|
||||
"""
|
||||
messages = self._queue.get_all()
|
||||
if not messages:
|
||||
return ""
|
||||
|
||||
lines = []
|
||||
for msg in messages:
|
||||
# Truncate long messages
|
||||
display_msg = msg
|
||||
if len(msg) > self._max_length:
|
||||
display_msg = msg[:self._max_length - 3] + "..."
|
||||
lines.append(f" ↳ {display_msg}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def format_status(self) -> str:
|
||||
"""
|
||||
Format current processing status.
|
||||
|
||||
Returns:
|
||||
Status string (e.g., "⏳ Processing..." or "")
|
||||
"""
|
||||
if not self._state_manager:
|
||||
return ""
|
||||
|
||||
state = self._state_manager.current_state
|
||||
if state == ProcessingState.PROCESSING:
|
||||
return "⏳ Processing..."
|
||||
elif state == ProcessingState.WAITING_APPROVAL:
|
||||
return "🔒 Waiting for approval..."
|
||||
return ""
|
||||
|
||||
def format_queue_count(self) -> str:
|
||||
"""
|
||||
Format queue count indicator.
|
||||
|
||||
Returns:
|
||||
Count string (e.g., "📋 Queued (2)")
|
||||
"""
|
||||
count = self._queue.count
|
||||
if count == 0:
|
||||
return ""
|
||||
return f"📋 Queued ({count})"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MessageQueueHandler - Main handler class
|
||||
# ============================================================================
|
||||
|
||||
class MessageQueueHandler:
|
||||
"""
|
||||
Main handler for message queue functionality.
|
||||
|
||||
Coordinates queue, state, and processing.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
processor: Optional[Callable[[str], str]] = None,
|
||||
on_response: Optional[Callable[[str], None]] = None
|
||||
):
|
||||
self._processor = processor
|
||||
self._on_response = on_response
|
||||
self._queue = MessageQueue()
|
||||
self._state_manager = StateManager(on_state_change=self._on_state_change)
|
||||
self._processing_lock = threading.Lock()
|
||||
|
||||
@property
|
||||
def queue(self) -> MessageQueue:
|
||||
"""Get the message queue."""
|
||||
return self._queue
|
||||
|
||||
@property
|
||||
def state_manager(self) -> StateManager:
|
||||
"""Get the state manager."""
|
||||
return self._state_manager
|
||||
|
||||
def submit(self, message: str) -> bool:
|
||||
"""
|
||||
Submit a message for processing.
|
||||
|
||||
If idle, processes immediately. If processing, queues the message.
|
||||
|
||||
Args:
|
||||
message: The message to submit
|
||||
|
||||
Returns:
|
||||
True if processed/queued, False if empty message
|
||||
"""
|
||||
trimmed = message.strip() if message else ""
|
||||
if not trimmed:
|
||||
return False
|
||||
|
||||
if self._state_manager.is_idle:
|
||||
# Process immediately
|
||||
self._process_message(trimmed)
|
||||
else:
|
||||
# Queue for later
|
||||
self._queue.add(trimmed)
|
||||
|
||||
return True
|
||||
|
||||
def on_processing_complete(self) -> None:
|
||||
"""
|
||||
Called when current processing completes.
|
||||
|
||||
Sets state to idle and processes next queued message if any.
|
||||
"""
|
||||
self._state_manager.set_state(ProcessingState.IDLE)
|
||||
self._process_next_in_queue()
|
||||
|
||||
def get_status(self) -> dict:
|
||||
"""
|
||||
Get current status.
|
||||
|
||||
Returns:
|
||||
Dict with queue_count and state
|
||||
"""
|
||||
return {
|
||||
'queue_count': self._queue.count,
|
||||
'state': self._state_manager.current_state.value,
|
||||
'messages': self._queue.get_all()
|
||||
}
|
||||
|
||||
def clear_queue(self) -> None:
|
||||
"""Clear all queued messages."""
|
||||
self._queue.clear()
|
||||
|
||||
def _process_message(self, message: str) -> None:
|
||||
"""Process a single message."""
|
||||
if not self._processor:
|
||||
return
|
||||
|
||||
with self._processing_lock:
|
||||
self._state_manager.set_state(ProcessingState.PROCESSING)
|
||||
try:
|
||||
response = self._processor(message)
|
||||
if self._on_response and response:
|
||||
self._on_response(response)
|
||||
finally:
|
||||
self._state_manager.set_state(ProcessingState.IDLE)
|
||||
|
||||
def _process_next_in_queue(self) -> None:
|
||||
"""Process the next message in queue if any."""
|
||||
next_msg = self._queue.pop()
|
||||
if next_msg:
|
||||
self._process_message(next_msg)
|
||||
|
||||
def _on_state_change(self, old_state: ProcessingState, new_state: ProcessingState) -> None:
|
||||
"""Handle state changes."""
|
||||
# Auto-process queue when becoming idle
|
||||
if new_state == ProcessingState.IDLE and old_state == ProcessingState.PROCESSING:
|
||||
# Don't auto-process here to avoid recursion
|
||||
# The on_processing_complete method handles this
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# AsyncProcessor - Background processing
|
||||
# ============================================================================
|
||||
|
||||
class AsyncProcessor:
|
||||
"""
|
||||
Runs work functions in background threads.
|
||||
|
||||
Allows agent processing to happen asynchronously while
|
||||
the main thread remains responsive for user input.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
on_complete: Optional[Callable[[str], None]] = None,
|
||||
on_status: Optional[Callable[[str], None]] = None,
|
||||
on_error: Optional[Callable[[Exception], None]] = None
|
||||
):
|
||||
self._on_complete = on_complete
|
||||
self._on_status = on_status
|
||||
self._on_error = on_error
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
self._running = False
|
||||
self._lock = threading.Lock()
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
"""Check if currently processing."""
|
||||
with self._lock:
|
||||
return self._running
|
||||
|
||||
def start(self, work_fn: Callable[[], str]) -> None:
|
||||
"""
|
||||
Start processing work in background thread.
|
||||
|
||||
Args:
|
||||
work_fn: Function to execute (should return result string)
|
||||
"""
|
||||
def _worker():
|
||||
try:
|
||||
with self._lock:
|
||||
self._running = True
|
||||
|
||||
result = work_fn()
|
||||
|
||||
with self._lock:
|
||||
self._running = False
|
||||
|
||||
if self._on_complete:
|
||||
self._on_complete(result)
|
||||
except Exception as e:
|
||||
with self._lock:
|
||||
self._running = False
|
||||
if self._on_error:
|
||||
self._on_error(e)
|
||||
|
||||
self._thread = threading.Thread(target=_worker, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
def update_status(self, status: str) -> None:
|
||||
"""Update status (can be called from work function)."""
|
||||
if self._on_status:
|
||||
self._on_status(status)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# LiveStatusDisplay - Real-time status display
|
||||
# ============================================================================
|
||||
|
||||
class LiveStatusDisplay:
|
||||
"""
|
||||
Tracks and displays real-time status during processing.
|
||||
|
||||
Shows tool calls, command executions, and current status.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._status = ""
|
||||
self._tool_calls: List[dict] = []
|
||||
self._commands: List[str] = []
|
||||
self._lock = threading.Lock()
|
||||
|
||||
@property
|
||||
def current_status(self) -> str:
|
||||
"""Get current status message."""
|
||||
with self._lock:
|
||||
return self._status
|
||||
|
||||
@property
|
||||
def tool_calls(self) -> List[dict]:
|
||||
"""Get list of tool calls."""
|
||||
with self._lock:
|
||||
return self._tool_calls.copy()
|
||||
|
||||
@property
|
||||
def commands(self) -> List[str]:
|
||||
"""Get list of commands executed."""
|
||||
with self._lock:
|
||||
return self._commands.copy()
|
||||
|
||||
def update_status(self, status: str) -> None:
|
||||
"""Update current status message."""
|
||||
with self._lock:
|
||||
self._status = status
|
||||
|
||||
def add_tool_call(self, name: str, args: dict) -> None:
|
||||
"""Add a tool call to the display."""
|
||||
with self._lock:
|
||||
self._tool_calls.append({
|
||||
'name': name,
|
||||
'args': args,
|
||||
'timestamp': time.time()
|
||||
})
|
||||
|
||||
def add_command(self, command: str) -> None:
|
||||
"""Add a command execution to the display."""
|
||||
with self._lock:
|
||||
self._commands.append(command)
|
||||
|
||||
def format(self) -> str:
|
||||
"""Format current status for display."""
|
||||
lines = []
|
||||
|
||||
with self._lock:
|
||||
if self._status:
|
||||
lines.append(f"⏳ {self._status}")
|
||||
|
||||
for tool in self._tool_calls[-3:]: # Show last 3 tools
|
||||
lines.append(f" 🔧 {tool['name']}")
|
||||
|
||||
for cmd in self._commands[-2:]: # Show last 2 commands
|
||||
display_cmd = cmd[:40] + "..." if len(cmd) > 40 else cmd
|
||||
lines.append(f" 💻 {display_cmd}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all status."""
|
||||
with self._lock:
|
||||
self._status = ""
|
||||
self._tool_calls.clear()
|
||||
self._commands.clear()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# NonBlockingInput - Async input handling
|
||||
# ============================================================================
|
||||
|
||||
class NonBlockingInput:
|
||||
"""
|
||||
Handles user input asynchronously.
|
||||
|
||||
Allows users to type new messages while processing is ongoing.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._queue = MessageQueue()
|
||||
|
||||
@property
|
||||
def has_pending(self) -> bool:
|
||||
"""Check if there are pending inputs."""
|
||||
return not self._queue.is_empty
|
||||
|
||||
@property
|
||||
def pending_count(self) -> int:
|
||||
"""Get number of pending inputs."""
|
||||
return self._queue.count
|
||||
|
||||
def submit(self, message: str) -> bool:
|
||||
"""Submit a new input message."""
|
||||
return self._queue.add(message)
|
||||
|
||||
def pop(self) -> Optional[str]:
|
||||
"""Get next pending input."""
|
||||
return self._queue.pop()
|
||||
|
||||
def peek(self) -> Optional[str]:
|
||||
"""View next pending input without removing."""
|
||||
return self._queue.peek()
|
||||
|
||||
def get_all(self) -> List[str]:
|
||||
"""Get all pending inputs."""
|
||||
return self._queue.get_all()
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all pending inputs."""
|
||||
self._queue.clear()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Exports
|
||||
# ============================================================================
|
||||
|
||||
__all__ = [
|
||||
'ProcessingState',
|
||||
'MessagePriority',
|
||||
'QueuedMessage',
|
||||
'MessageQueue',
|
||||
'StateManager',
|
||||
'QueueDisplay',
|
||||
'MessageQueueHandler',
|
||||
'AsyncProcessor',
|
||||
'LiveStatusDisplay',
|
||||
'NonBlockingInput',
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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==2.3.47 gunicorn markdown\n")
|
||||
file.write("RUN pip install flask praisonai==2.3.52 gunicorn markdown\n")
|
||||
file.write("EXPOSE 8080\n")
|
||||
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "2.3.47"
|
||||
__version__ = "2.3.52"
|
||||
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
Unit tests for --chat flag (alias for --chat-mode).
|
||||
|
||||
Tests verify that:
|
||||
1. --chat flag works as an alias for --chat-mode
|
||||
2. --chat flag does not conflict with 'chat' subcommand
|
||||
3. Both flags set chat_mode=True correctly
|
||||
4. Backward compatibility with --chat-mode is maintained
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
|
||||
|
||||
|
||||
class TestChatFlagParsing:
|
||||
"""Test argument parsing for --chat and --chat-mode flags."""
|
||||
|
||||
def test_chat_mode_flag_sets_chat_mode_true(self):
|
||||
"""--chat-mode should set chat_mode=True."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat-mode", "--chat", action="store_true", dest="chat_mode")
|
||||
parser.add_argument("command", nargs="?")
|
||||
|
||||
args, _ = parser.parse_known_args(["--chat-mode", "test prompt"])
|
||||
assert args.chat_mode is True
|
||||
assert args.command == "test prompt"
|
||||
|
||||
def test_chat_flag_sets_chat_mode_true(self):
|
||||
"""--chat should set chat_mode=True (alias for --chat-mode)."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat-mode", "--chat", action="store_true", dest="chat_mode")
|
||||
parser.add_argument("command", nargs="?")
|
||||
|
||||
args, _ = parser.parse_known_args(["--chat", "test prompt"])
|
||||
assert args.chat_mode is True
|
||||
assert args.command == "test prompt"
|
||||
|
||||
def test_chat_subcommand_does_not_set_chat_mode(self):
|
||||
"""'chat' as positional command should NOT set chat_mode."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat-mode", "--chat", action="store_true", dest="chat_mode")
|
||||
parser.add_argument("command", nargs="?")
|
||||
|
||||
args, _ = parser.parse_known_args(["chat"])
|
||||
assert args.chat_mode is False
|
||||
assert args.command == "chat"
|
||||
|
||||
def test_chat_flag_after_prompt(self):
|
||||
"""--chat after prompt should work."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat-mode", "--chat", action="store_true", dest="chat_mode")
|
||||
parser.add_argument("command", nargs="?")
|
||||
|
||||
args, _ = parser.parse_known_args(["What is 2+2?", "--chat"])
|
||||
assert args.chat_mode is True
|
||||
assert args.command == "What is 2+2?"
|
||||
|
||||
def test_chat_mode_flag_after_prompt(self):
|
||||
"""--chat-mode after prompt should work (backward compat)."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat-mode", "--chat", action="store_true", dest="chat_mode")
|
||||
parser.add_argument("command", nargs="?")
|
||||
|
||||
args, _ = parser.parse_known_args(["What is 2+2?", "--chat-mode"])
|
||||
assert args.chat_mode is True
|
||||
assert args.command == "What is 2+2?"
|
||||
|
||||
def test_no_flag_defaults_to_false(self):
|
||||
"""Without --chat or --chat-mode, chat_mode should be False."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat-mode", "--chat", action="store_true", dest="chat_mode")
|
||||
parser.add_argument("command", nargs="?")
|
||||
|
||||
args, _ = parser.parse_known_args(["some prompt"])
|
||||
assert args.chat_mode is False
|
||||
assert args.command == "some prompt"
|
||||
|
||||
|
||||
class TestChatFlagNoConflict:
|
||||
"""Test that --chat flag does not conflict with other features."""
|
||||
|
||||
def test_chat_flag_with_other_flags(self):
|
||||
"""--chat should work alongside other flags."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat-mode", "--chat", action="store_true", dest="chat_mode")
|
||||
parser.add_argument("--verbose", "-v", action="store_true")
|
||||
parser.add_argument("--model", type=str)
|
||||
parser.add_argument("command", nargs="?")
|
||||
|
||||
args, _ = parser.parse_known_args(["--chat", "--verbose", "--model", "gpt-4o", "test"])
|
||||
assert args.chat_mode is True
|
||||
assert args.verbose is True
|
||||
assert args.model == "gpt-4o"
|
||||
assert args.command == "test"
|
||||
|
||||
def test_chat_subcommand_with_other_flags(self):
|
||||
"""'chat' subcommand should work with other flags."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat-mode", "--chat", action="store_true", dest="chat_mode")
|
||||
parser.add_argument("--verbose", "-v", action="store_true")
|
||||
parser.add_argument("command", nargs="?")
|
||||
|
||||
args, _ = parser.parse_known_args(["chat", "--verbose"])
|
||||
assert args.chat_mode is False # subcommand, not flag
|
||||
assert args.command == "chat"
|
||||
assert args.verbose is True
|
||||
|
||||
|
||||
class TestChatFlagIntegration:
|
||||
"""Integration tests with actual PraisonAI CLI parser."""
|
||||
|
||||
def test_praisonai_parser_has_chat_flag(self):
|
||||
"""PraisonAI CLI should have --chat as alias for --chat-mode."""
|
||||
# This test will fail until we implement the change
|
||||
from praisonai.cli.main import PraisonAI
|
||||
|
||||
# Create instance and get parser
|
||||
pai = PraisonAI()
|
||||
|
||||
# Mock sys.argv to avoid test environment issues
|
||||
import sys
|
||||
original_argv = sys.argv
|
||||
try:
|
||||
sys.argv = ["praisonai", "--chat", "test prompt"]
|
||||
args = pai.parse_args()
|
||||
assert hasattr(args, 'chat_mode')
|
||||
# After implementation, this should be True
|
||||
# For now, it may work due to argparse prefix matching
|
||||
finally:
|
||||
sys.argv = original_argv
|
||||
|
||||
def test_praisonai_chat_subcommand_still_works(self):
|
||||
"""'praisonai chat' subcommand should still launch Chainlit.
|
||||
|
||||
Note: In test environment, parse_args returns default args to avoid
|
||||
pytest interference. This test verifies the parser structure instead.
|
||||
"""
|
||||
# Test the parser structure directly without going through PraisonAI
|
||||
# which has test environment detection
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat-mode", "--chat", action="store_true", dest="chat_mode")
|
||||
parser.add_argument("command", nargs="?")
|
||||
|
||||
# Simulate 'praisonai chat' - should set command='chat', not chat_mode
|
||||
args, _ = parser.parse_known_args(["chat"])
|
||||
assert args.command == "chat"
|
||||
assert args.chat_mode is False # subcommand, not flag
|
||||
@@ -0,0 +1,661 @@
|
||||
"""
|
||||
Unit tests for Message Queue feature.
|
||||
|
||||
Tests for:
|
||||
1. MessageQueue - FIFO queue for messages
|
||||
2. ProcessingState - State management
|
||||
3. QueuedMessage - Message with metadata
|
||||
4. MessageQueueHandler - Main handler class
|
||||
"""
|
||||
|
||||
import time
|
||||
import threading
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 1. MessageQueue Tests - FIFO operations
|
||||
# ============================================================================
|
||||
|
||||
class TestMessageQueue:
|
||||
"""Test MessageQueue FIFO operations."""
|
||||
|
||||
def test_create_empty_queue(self):
|
||||
"""Queue should start empty."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
assert queue.is_empty
|
||||
assert queue.count == 0
|
||||
|
||||
def test_add_message(self):
|
||||
"""Should add message to queue."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("Hello")
|
||||
assert not queue.is_empty
|
||||
assert queue.count == 1
|
||||
|
||||
def test_add_multiple_messages(self):
|
||||
"""Should add multiple messages in order."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("First")
|
||||
queue.add("Second")
|
||||
queue.add("Third")
|
||||
assert queue.count == 3
|
||||
|
||||
def test_pop_message_fifo(self):
|
||||
"""Should pop messages in FIFO order."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("First")
|
||||
queue.add("Second")
|
||||
msg = queue.pop()
|
||||
assert msg == "First"
|
||||
assert queue.count == 1
|
||||
|
||||
def test_pop_empty_queue(self):
|
||||
"""Should return None when popping empty queue."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
msg = queue.pop()
|
||||
assert msg is None
|
||||
|
||||
def test_peek_message(self):
|
||||
"""Should peek without removing."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("First")
|
||||
queue.add("Second")
|
||||
msg = queue.peek()
|
||||
assert msg == "First"
|
||||
assert queue.count == 2 # Not removed
|
||||
|
||||
def test_peek_empty_queue(self):
|
||||
"""Should return None when peeking empty queue."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
msg = queue.peek()
|
||||
assert msg is None
|
||||
|
||||
def test_clear_queue(self):
|
||||
"""Should clear all messages."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("First")
|
||||
queue.add("Second")
|
||||
queue.clear()
|
||||
assert queue.is_empty
|
||||
assert queue.count == 0
|
||||
|
||||
def test_get_all_messages(self):
|
||||
"""Should get all messages as list."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("First")
|
||||
queue.add("Second")
|
||||
messages = queue.get_all()
|
||||
assert messages == ["First", "Second"]
|
||||
assert queue.count == 2 # Not removed
|
||||
|
||||
def test_remove_at_index(self):
|
||||
"""Should remove message at specific index."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("First")
|
||||
queue.add("Second")
|
||||
queue.add("Third")
|
||||
removed = queue.remove_at(1)
|
||||
assert removed == "Second"
|
||||
assert queue.count == 2
|
||||
assert queue.get_all() == ["First", "Third"]
|
||||
|
||||
def test_remove_at_invalid_index(self):
|
||||
"""Should return None for invalid index."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("First")
|
||||
removed = queue.remove_at(5)
|
||||
assert removed is None
|
||||
assert queue.count == 1
|
||||
|
||||
def test_add_empty_message_ignored(self):
|
||||
"""Should ignore empty messages."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("")
|
||||
queue.add(" ")
|
||||
assert queue.is_empty
|
||||
|
||||
def test_thread_safety(self):
|
||||
"""Queue should be thread-safe."""
|
||||
from praisonai.cli.features.message_queue import MessageQueue
|
||||
queue = MessageQueue()
|
||||
results = []
|
||||
|
||||
def add_messages():
|
||||
for i in range(100):
|
||||
queue.add(f"msg-{i}")
|
||||
|
||||
def pop_messages():
|
||||
for _ in range(100):
|
||||
msg = queue.pop()
|
||||
if msg:
|
||||
results.append(msg)
|
||||
|
||||
t1 = threading.Thread(target=add_messages)
|
||||
t2 = threading.Thread(target=pop_messages)
|
||||
t1.start()
|
||||
t2.start()
|
||||
t1.join()
|
||||
t2.join()
|
||||
|
||||
# All messages should be accounted for
|
||||
total = len(results) + queue.count
|
||||
assert total == 100
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 2. ProcessingState Tests
|
||||
# ============================================================================
|
||||
|
||||
class TestProcessingState:
|
||||
"""Test ProcessingState enum and state management."""
|
||||
|
||||
def test_state_enum_values(self):
|
||||
"""Should have correct state values."""
|
||||
from praisonai.cli.features.message_queue import ProcessingState
|
||||
assert ProcessingState.IDLE.value == "idle"
|
||||
assert ProcessingState.PROCESSING.value == "processing"
|
||||
assert ProcessingState.WAITING_APPROVAL.value == "waiting_approval"
|
||||
|
||||
def test_state_manager_initial_state(self):
|
||||
"""Should start in IDLE state."""
|
||||
from praisonai.cli.features.message_queue import StateManager, ProcessingState
|
||||
manager = StateManager()
|
||||
assert manager.current_state == ProcessingState.IDLE
|
||||
|
||||
def test_state_transition(self):
|
||||
"""Should transition between states."""
|
||||
from praisonai.cli.features.message_queue import StateManager, ProcessingState
|
||||
manager = StateManager()
|
||||
manager.set_state(ProcessingState.PROCESSING)
|
||||
assert manager.current_state == ProcessingState.PROCESSING
|
||||
|
||||
def test_is_idle(self):
|
||||
"""Should correctly report idle state."""
|
||||
from praisonai.cli.features.message_queue import StateManager, ProcessingState
|
||||
manager = StateManager()
|
||||
assert manager.is_idle
|
||||
manager.set_state(ProcessingState.PROCESSING)
|
||||
assert not manager.is_idle
|
||||
|
||||
def test_is_processing(self):
|
||||
"""Should correctly report processing state."""
|
||||
from praisonai.cli.features.message_queue import StateManager, ProcessingState
|
||||
manager = StateManager()
|
||||
assert not manager.is_processing
|
||||
manager.set_state(ProcessingState.PROCESSING)
|
||||
assert manager.is_processing
|
||||
|
||||
def test_state_callback(self):
|
||||
"""Should call callback on state change."""
|
||||
from praisonai.cli.features.message_queue import StateManager, ProcessingState
|
||||
callback_called = []
|
||||
|
||||
def on_state_change(old_state, new_state):
|
||||
callback_called.append((old_state, new_state))
|
||||
|
||||
manager = StateManager(on_state_change=on_state_change)
|
||||
manager.set_state(ProcessingState.PROCESSING)
|
||||
|
||||
assert len(callback_called) == 1
|
||||
assert callback_called[0] == (ProcessingState.IDLE, ProcessingState.PROCESSING)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 3. QueuedMessage Tests
|
||||
# ============================================================================
|
||||
|
||||
class TestQueuedMessage:
|
||||
"""Test QueuedMessage dataclass."""
|
||||
|
||||
def test_create_queued_message(self):
|
||||
"""Should create message with content."""
|
||||
from praisonai.cli.features.message_queue import QueuedMessage
|
||||
msg = QueuedMessage(content="Hello")
|
||||
assert msg.content == "Hello"
|
||||
assert msg.timestamp is not None
|
||||
|
||||
def test_message_has_timestamp(self):
|
||||
"""Should have creation timestamp."""
|
||||
from praisonai.cli.features.message_queue import QueuedMessage
|
||||
before = time.time()
|
||||
msg = QueuedMessage(content="Hello")
|
||||
after = time.time()
|
||||
assert before <= msg.timestamp <= after
|
||||
|
||||
def test_message_priority(self):
|
||||
"""Should support priority levels."""
|
||||
from praisonai.cli.features.message_queue import QueuedMessage, MessagePriority
|
||||
msg = QueuedMessage(content="Hello", priority=MessagePriority.HIGH)
|
||||
assert msg.priority == MessagePriority.HIGH
|
||||
|
||||
def test_default_priority_normal(self):
|
||||
"""Should default to NORMAL priority."""
|
||||
from praisonai.cli.features.message_queue import QueuedMessage, MessagePriority
|
||||
msg = QueuedMessage(content="Hello")
|
||||
assert msg.priority == MessagePriority.NORMAL
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 4. MessageQueueHandler Tests
|
||||
# ============================================================================
|
||||
|
||||
class TestMessageQueueHandler:
|
||||
"""Test MessageQueueHandler main class."""
|
||||
|
||||
def test_handler_creation(self):
|
||||
"""Should create handler with default settings."""
|
||||
from praisonai.cli.features.message_queue import MessageQueueHandler
|
||||
handler = MessageQueueHandler()
|
||||
assert handler.queue.is_empty
|
||||
assert handler.state_manager.is_idle
|
||||
|
||||
def test_submit_when_idle(self):
|
||||
"""Should process immediately when idle."""
|
||||
from praisonai.cli.features.message_queue import MessageQueueHandler
|
||||
processed = []
|
||||
|
||||
def processor(msg):
|
||||
processed.append(msg)
|
||||
return f"Response to: {msg}"
|
||||
|
||||
handler = MessageQueueHandler(processor=processor)
|
||||
handler.submit("Hello")
|
||||
|
||||
# Should process immediately
|
||||
assert "Hello" in processed
|
||||
|
||||
def test_queue_when_processing(self):
|
||||
"""Should queue message when already processing."""
|
||||
from praisonai.cli.features.message_queue import MessageQueueHandler, ProcessingState
|
||||
|
||||
handler = MessageQueueHandler()
|
||||
handler.state_manager.set_state(ProcessingState.PROCESSING)
|
||||
handler.submit("Hello")
|
||||
|
||||
# Should be queued, not processed
|
||||
assert handler.queue.count == 1
|
||||
|
||||
def test_auto_process_queue_on_idle(self):
|
||||
"""Should auto-process queue when becoming idle."""
|
||||
from praisonai.cli.features.message_queue import MessageQueueHandler, ProcessingState
|
||||
processed = []
|
||||
|
||||
def processor(msg):
|
||||
processed.append(msg)
|
||||
return f"Response: {msg}"
|
||||
|
||||
handler = MessageQueueHandler(processor=processor)
|
||||
|
||||
# Simulate processing state
|
||||
handler.state_manager.set_state(ProcessingState.PROCESSING)
|
||||
handler.submit("Queued1")
|
||||
handler.submit("Queued2")
|
||||
|
||||
assert handler.queue.count == 2
|
||||
|
||||
# Simulate becoming idle - should trigger queue processing
|
||||
handler.on_processing_complete()
|
||||
|
||||
# Queue should be processed
|
||||
assert "Queued1" in processed or handler.queue.count < 2
|
||||
|
||||
def test_get_queue_status(self):
|
||||
"""Should return queue status."""
|
||||
from praisonai.cli.features.message_queue import MessageQueueHandler, ProcessingState
|
||||
handler = MessageQueueHandler()
|
||||
handler.state_manager.set_state(ProcessingState.PROCESSING)
|
||||
handler.submit("Hello")
|
||||
handler.submit("World")
|
||||
|
||||
status = handler.get_status()
|
||||
assert status['queue_count'] == 2
|
||||
assert status['state'] == 'processing'
|
||||
|
||||
def test_clear_queue(self):
|
||||
"""Should clear queue via handler."""
|
||||
from praisonai.cli.features.message_queue import MessageQueueHandler, ProcessingState
|
||||
handler = MessageQueueHandler()
|
||||
handler.state_manager.set_state(ProcessingState.PROCESSING)
|
||||
handler.submit("Hello")
|
||||
handler.clear_queue()
|
||||
assert handler.queue.is_empty
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 5. QueueDisplay Tests
|
||||
# ============================================================================
|
||||
|
||||
class TestQueueDisplay:
|
||||
"""Test QueueDisplay for visual indicators."""
|
||||
|
||||
def test_format_empty_queue(self):
|
||||
"""Should return empty string for empty queue."""
|
||||
from praisonai.cli.features.message_queue import QueueDisplay, MessageQueue
|
||||
queue = MessageQueue()
|
||||
display = QueueDisplay(queue)
|
||||
assert display.format_queue() == ""
|
||||
|
||||
def test_format_single_message(self):
|
||||
"""Should format single message."""
|
||||
from praisonai.cli.features.message_queue import QueueDisplay, MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("Hello world")
|
||||
display = QueueDisplay(queue)
|
||||
formatted = display.format_queue()
|
||||
assert "↳" in formatted
|
||||
assert "Hello world" in formatted
|
||||
|
||||
def test_format_multiple_messages(self):
|
||||
"""Should format multiple messages."""
|
||||
from praisonai.cli.features.message_queue import QueueDisplay, MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("First")
|
||||
queue.add("Second")
|
||||
display = QueueDisplay(queue)
|
||||
formatted = display.format_queue()
|
||||
assert "First" in formatted
|
||||
assert "Second" in formatted
|
||||
|
||||
def test_truncate_long_message(self):
|
||||
"""Should truncate long messages."""
|
||||
from praisonai.cli.features.message_queue import QueueDisplay, MessageQueue
|
||||
queue = MessageQueue()
|
||||
queue.add("A" * 100)
|
||||
display = QueueDisplay(queue, max_message_length=20)
|
||||
formatted = display.format_queue()
|
||||
assert "..." in formatted
|
||||
|
||||
def test_format_status_idle(self):
|
||||
"""Should show idle status."""
|
||||
from praisonai.cli.features.message_queue import QueueDisplay, MessageQueue, StateManager
|
||||
queue = MessageQueue()
|
||||
state = StateManager()
|
||||
display = QueueDisplay(queue, state_manager=state)
|
||||
status = display.format_status()
|
||||
assert "idle" in status.lower() or status == ""
|
||||
|
||||
def test_format_status_processing(self):
|
||||
"""Should show processing status."""
|
||||
from praisonai.cli.features.message_queue import (
|
||||
QueueDisplay, MessageQueue, StateManager, ProcessingState
|
||||
)
|
||||
queue = MessageQueue()
|
||||
state = StateManager()
|
||||
state.set_state(ProcessingState.PROCESSING)
|
||||
display = QueueDisplay(queue, state_manager=state)
|
||||
status = display.format_status()
|
||||
assert "processing" in status.lower() or "⏳" in status
|
||||
|
||||
def test_format_queue_count(self):
|
||||
"""Should show queue count."""
|
||||
from praisonai.cli.features.message_queue import QueueDisplay, MessageQueue, ProcessingState, StateManager
|
||||
queue = MessageQueue()
|
||||
queue.add("First")
|
||||
queue.add("Second")
|
||||
state = StateManager()
|
||||
state.set_state(ProcessingState.PROCESSING)
|
||||
display = QueueDisplay(queue, state_manager=state)
|
||||
count_str = display.format_queue_count()
|
||||
assert "2" in count_str
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 6. Integration Tests
|
||||
# ============================================================================
|
||||
|
||||
class TestMessageQueueIntegration:
|
||||
"""Integration tests for message queue system."""
|
||||
|
||||
def test_full_workflow(self):
|
||||
"""Test complete queue workflow."""
|
||||
from praisonai.cli.features.message_queue import (
|
||||
MessageQueueHandler, ProcessingState
|
||||
)
|
||||
|
||||
results = []
|
||||
|
||||
def processor(msg):
|
||||
results.append(msg)
|
||||
return f"Processed: {msg}"
|
||||
|
||||
handler = MessageQueueHandler(processor=processor)
|
||||
|
||||
# 1. Submit when idle - processes immediately
|
||||
handler.submit("First")
|
||||
assert "First" in results
|
||||
|
||||
# 2. Simulate processing
|
||||
handler.state_manager.set_state(ProcessingState.PROCESSING)
|
||||
|
||||
# 3. Queue messages while processing
|
||||
handler.submit("Second")
|
||||
handler.submit("Third")
|
||||
assert handler.queue.count == 2
|
||||
|
||||
# 4. Complete processing - should auto-process queue
|
||||
handler.on_processing_complete()
|
||||
|
||||
# Queue should be processed
|
||||
assert handler.queue.count < 2 or "Second" in results
|
||||
|
||||
def test_queue_with_display(self):
|
||||
"""Test queue with display formatting."""
|
||||
from praisonai.cli.features.message_queue import (
|
||||
MessageQueueHandler, QueueDisplay, ProcessingState
|
||||
)
|
||||
|
||||
handler = MessageQueueHandler()
|
||||
handler.state_manager.set_state(ProcessingState.PROCESSING)
|
||||
handler.submit("Task 1")
|
||||
handler.submit("Task 2")
|
||||
|
||||
display = QueueDisplay(handler.queue, state_manager=handler.state_manager)
|
||||
|
||||
# Should show processing status
|
||||
status = display.format_status()
|
||||
assert "processing" in status.lower() or "⏳" in status
|
||||
|
||||
# Should show queued messages
|
||||
queue_str = display.format_queue()
|
||||
assert "Task 1" in queue_str
|
||||
assert "Task 2" in queue_str
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 9. AsyncProcessor Tests - Background processing
|
||||
# ============================================================================
|
||||
|
||||
class TestAsyncProcessor:
|
||||
"""Test AsyncProcessor for background agent execution."""
|
||||
|
||||
def test_processor_creation(self):
|
||||
"""Should create processor with callback."""
|
||||
from praisonai.cli.features.message_queue import AsyncProcessor
|
||||
|
||||
results = []
|
||||
def on_complete(result):
|
||||
results.append(result)
|
||||
|
||||
processor = AsyncProcessor(on_complete=on_complete)
|
||||
assert processor is not None
|
||||
|
||||
def test_start_processing(self):
|
||||
"""Should start processing in background thread."""
|
||||
from praisonai.cli.features.message_queue import AsyncProcessor
|
||||
import time
|
||||
|
||||
results = []
|
||||
def work_fn():
|
||||
time.sleep(0.05)
|
||||
return "done"
|
||||
|
||||
def on_complete(result):
|
||||
results.append(result)
|
||||
|
||||
processor = AsyncProcessor(on_complete=on_complete)
|
||||
processor.start(work_fn)
|
||||
|
||||
# Should be processing
|
||||
assert processor.is_running
|
||||
|
||||
# Wait for completion
|
||||
time.sleep(0.15)
|
||||
assert not processor.is_running
|
||||
assert "done" in results
|
||||
|
||||
def test_is_running_property(self):
|
||||
"""Should track running state."""
|
||||
from praisonai.cli.features.message_queue import AsyncProcessor
|
||||
import time
|
||||
|
||||
def slow_work():
|
||||
time.sleep(0.1)
|
||||
return "result"
|
||||
|
||||
processor = AsyncProcessor()
|
||||
assert not processor.is_running
|
||||
|
||||
processor.start(slow_work)
|
||||
assert processor.is_running
|
||||
|
||||
time.sleep(0.15)
|
||||
assert not processor.is_running
|
||||
|
||||
def test_on_status_callback(self):
|
||||
"""Should call status callback during processing."""
|
||||
from praisonai.cli.features.message_queue import AsyncProcessor
|
||||
import time
|
||||
|
||||
statuses = []
|
||||
def on_status(status):
|
||||
statuses.append(status)
|
||||
|
||||
def work_with_status(status_callback):
|
||||
status_callback("Starting...")
|
||||
time.sleep(0.02)
|
||||
status_callback("Working...")
|
||||
return "done"
|
||||
|
||||
processor = AsyncProcessor(on_status=on_status)
|
||||
processor.start(lambda: work_with_status(on_status))
|
||||
|
||||
time.sleep(0.1)
|
||||
assert len(statuses) >= 1
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 10. LiveStatusDisplay Tests - Real-time status
|
||||
# ============================================================================
|
||||
|
||||
class TestLiveStatusDisplay:
|
||||
"""Test LiveStatusDisplay for real-time tool/command status."""
|
||||
|
||||
def test_display_creation(self):
|
||||
"""Should create display."""
|
||||
from praisonai.cli.features.message_queue import LiveStatusDisplay
|
||||
|
||||
display = LiveStatusDisplay()
|
||||
assert display is not None
|
||||
|
||||
def test_update_status(self):
|
||||
"""Should update current status."""
|
||||
from praisonai.cli.features.message_queue import LiveStatusDisplay
|
||||
|
||||
display = LiveStatusDisplay()
|
||||
display.update_status("Processing...")
|
||||
assert display.current_status == "Processing..."
|
||||
|
||||
def test_add_tool_call(self):
|
||||
"""Should track tool calls."""
|
||||
from praisonai.cli.features.message_queue import LiveStatusDisplay
|
||||
|
||||
display = LiveStatusDisplay()
|
||||
display.add_tool_call("read_file", {"path": "test.py"})
|
||||
|
||||
assert len(display.tool_calls) == 1
|
||||
assert display.tool_calls[0]['name'] == "read_file"
|
||||
|
||||
def test_add_command_execution(self):
|
||||
"""Should track command executions."""
|
||||
from praisonai.cli.features.message_queue import LiveStatusDisplay
|
||||
|
||||
display = LiveStatusDisplay()
|
||||
display.add_command("ls -la")
|
||||
|
||||
assert len(display.commands) == 1
|
||||
assert display.commands[0] == "ls -la"
|
||||
|
||||
def test_format_live_status(self):
|
||||
"""Should format live status for display."""
|
||||
from praisonai.cli.features.message_queue import LiveStatusDisplay
|
||||
|
||||
display = LiveStatusDisplay()
|
||||
display.update_status("Thinking...")
|
||||
display.add_tool_call("read_file", {"path": "main.py"})
|
||||
|
||||
formatted = display.format()
|
||||
assert "Thinking" in formatted or "read_file" in formatted
|
||||
|
||||
def test_clear_status(self):
|
||||
"""Should clear all status."""
|
||||
from praisonai.cli.features.message_queue import LiveStatusDisplay
|
||||
|
||||
display = LiveStatusDisplay()
|
||||
display.update_status("Working")
|
||||
display.add_tool_call("test", {})
|
||||
display.add_command("echo hi")
|
||||
|
||||
display.clear()
|
||||
|
||||
assert display.current_status == ""
|
||||
assert len(display.tool_calls) == 0
|
||||
assert len(display.commands) == 0
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 11. NonBlockingInput Tests - Async input handling
|
||||
# ============================================================================
|
||||
|
||||
class TestNonBlockingInput:
|
||||
"""Test NonBlockingInput for async user input."""
|
||||
|
||||
def test_input_handler_creation(self):
|
||||
"""Should create input handler."""
|
||||
from praisonai.cli.features.message_queue import NonBlockingInput
|
||||
|
||||
handler = NonBlockingInput()
|
||||
assert handler is not None
|
||||
|
||||
def test_submit_input(self):
|
||||
"""Should accept input while processing."""
|
||||
from praisonai.cli.features.message_queue import NonBlockingInput
|
||||
|
||||
handler = NonBlockingInput()
|
||||
handler.submit("new message")
|
||||
|
||||
assert handler.has_pending
|
||||
assert handler.pop() == "new message"
|
||||
|
||||
def test_multiple_inputs(self):
|
||||
"""Should queue multiple inputs."""
|
||||
from praisonai.cli.features.message_queue import NonBlockingInput
|
||||
|
||||
handler = NonBlockingInput()
|
||||
handler.submit("first")
|
||||
handler.submit("second")
|
||||
|
||||
assert handler.pending_count == 2
|
||||
assert handler.pop() == "first"
|
||||
assert handler.pop() == "second"
|
||||
Reference in new issue
Block a user