Merge remote-tracking branch 'origin/main' into feature/tui-dashboard

# Conflicts:
#	.github/workflows/ci.yml
This commit is contained in:
bryan
2026-02-03 12:26:51 -08:00
14 changed files with 219 additions and 137 deletions
+5 -2
View File
@@ -25,15 +25,18 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Install dependencies
run: uv sync --project core --group dev
- name: Ruff lint
run: |
uv run --project core ruff check core/
uv run --project tools ruff check tools/
uv run --project core ruff check tools/
- name: Ruff format
run: |
uv run --project core ruff format --check core/
uv run --project tools ruff format --check tools/
uv run --project core ruff format --check tools/
test:
name: Test Python Framework
+5 -4
View File
@@ -21,18 +21,19 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Install dependencies
run: |
cd core
pip install -e .
pip install -r requirements-dev.txt
uv sync
- name: Run tests
run: |
cd core
pytest tests/ -v
uv run pytest tests/ -v
- name: Generate changelog
id: changelog
+12
View File
@@ -21,6 +21,7 @@ import logging
from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass, field
from datetime import UTC
from typing import Any
from pydantic import BaseModel, Field
@@ -1533,6 +1534,8 @@ Do NOT fabricate data or return empty objects."""
def _build_system_prompt(self, ctx: NodeContext) -> str:
"""Build the system prompt."""
from datetime import datetime
parts = []
if ctx.node_spec.system_prompt:
@@ -1555,6 +1558,15 @@ Do NOT fabricate data or return empty objects."""
parts.append(prompt)
# Inject current datetime so LLM knows "now"
utc_dt = datetime.now(UTC)
local_dt = datetime.now().astimezone()
local_tz_name = local_dt.tzname() or "Unknown"
parts.append("\n## Runtime Context")
parts.append(f"- Current Date/Time (UTC): {utc_dt.isoformat()}")
parts.append(f"- Local Timezone: {local_tz_name}")
parts.append(f"- Current Date/Time (Local): {local_dt.isoformat()}")
if ctx.goal_context:
parts.append("\n# Goal Context")
parts.append(ctx.goal_context)
+55 -17
View File
@@ -516,6 +516,36 @@ def _validate_tool_credentials(tools_list: list[str]) -> dict | None:
return None
def _validate_agent_path(agent_path: str) -> tuple[Path | None, str | None]:
"""
Validate and normalize agent_path.
Returns:
(Path, None) if valid
(None, error_json) if invalid
"""
if not agent_path:
return None, json.dumps(
{
"success": False,
"error": "agent_path is required (e.g., 'exports/my_agent')",
}
)
path = Path(agent_path)
if not path.exists():
return None, json.dumps(
{
"success": False,
"error": f"Agent path not found: {path}",
"hint": "Run export_graph to create an agent in exports/ first",
}
)
return path, None
@mcp.tool()
def add_node(
node_id: Annotated[str, "Unique identifier for the node"],
@@ -2597,10 +2627,11 @@ def generate_constraint_tests(
if not agent_path and _session:
agent_path = f"exports/{_session.name}"
if not agent_path:
return json.dumps({"error": "agent_path required (e.g., 'exports/my_agent')"})
path, err = _validate_agent_path(agent_path)
if err:
return err
agent_module = _get_agent_module_from_path(agent_path)
agent_module = _get_agent_module_from_path(path)
# Format constraints for display
constraints_formatted = (
@@ -2619,9 +2650,9 @@ def generate_constraint_tests(
return json.dumps(
{
"goal_id": goal_id,
"agent_path": agent_path,
"agent_path": str(path),
"agent_module": agent_module,
"output_file": f"{agent_path}/tests/test_constraints.py",
"output_file": f"{str(path)}/tests/test_constraints.py",
"constraints": [c.model_dump() for c in goal.constraints] if goal.constraints else [],
"constraints_formatted": constraints_formatted,
"test_guidelines": {
@@ -2677,10 +2708,11 @@ def generate_success_tests(
if not agent_path and _session:
agent_path = f"exports/{_session.name}"
if not agent_path:
return json.dumps({"error": "agent_path required (e.g., 'exports/my_agent')"})
path, err = _validate_agent_path(agent_path)
if err:
return err
agent_module = _get_agent_module_from_path(agent_path)
agent_module = _get_agent_module_from_path(path)
# Parse node/tool names for context
nodes = [n.strip() for n in node_names.split(",") if n.strip()]
@@ -2705,9 +2737,9 @@ def generate_success_tests(
return json.dumps(
{
"goal_id": goal_id,
"agent_path": agent_path,
"agent_path": str(path),
"agent_module": agent_module,
"output_file": f"{agent_path}/tests/test_success_criteria.py",
"output_file": f"{str(path)}/tests/test_success_criteria.py",
"success_criteria": [c.model_dump() for c in goal.success_criteria]
if goal.success_criteria
else [],
@@ -2766,7 +2798,11 @@ def run_tests(
import re
import subprocess
tests_dir = Path(agent_path) / "tests"
path, err = _validate_agent_path(agent_path)
if err:
return err
tests_dir = path / "tests"
if not tests_dir.exists():
return json.dumps(
@@ -2957,10 +2993,11 @@ def debug_test(
if not agent_path and _session:
agent_path = f"exports/{_session.name}"
if not agent_path:
return json.dumps({"error": "agent_path required (e.g., 'exports/my_agent')"})
path, err = _validate_agent_path(agent_path)
if err:
return err
tests_dir = Path(agent_path) / "tests"
tests_dir = path / "tests"
if not tests_dir.exists():
return json.dumps(
@@ -3101,10 +3138,11 @@ def list_tests(
if not agent_path and _session:
agent_path = f"exports/{_session.name}"
if not agent_path:
return json.dumps({"error": "agent_path required (e.g., 'exports/my_agent')"})
path, err = _validate_agent_path(agent_path)
if err:
return err
tests_dir = Path(agent_path) / "tests"
tests_dir = path / "tests"
if not tests_dir.exists():
return json.dumps(
-10
View File
@@ -1,10 +0,0 @@
# Development dependencies
-r requirements.txt
# Testing
pytest>=8.0
pytest-asyncio>=0.23
# Linting & type checking
ruff>=0.1.0
mypy>=1.0
-14
View File
@@ -1,14 +0,0 @@
# Core dependencies
pydantic>=2.0
anthropic>=0.40.0
httpx>=0.27.0
litellm>=1.81.0
# MCP server dependencies
mcp
fastmcp
# Testing (required for test framework)
pytest>=8.0
pytest-asyncio>=0.23
pytest-xdist>=3.0
+1 -1
View File
@@ -41,7 +41,7 @@ Visita [adenhq.com](https://adenhq.com) para documentación completa, ejemplos y
## ¿Qué es Aden?
<p align="center">
<img width="100%" alt="Aden Architecture" src="docs/assets/aden-architecture-diagram.jpg" />
<img width="100%" alt="Aden Architecture" src="../assets/aden-architecture-diagram.jpg" />
</p>
Aden es una plataforma para construir, desplegar, operar y adaptar agentes de IA:
+1 -1
View File
@@ -44,7 +44,7 @@
# Aden क्या है?
<p align="center">
<img width="100%" alt="Aden Architecture" src="docs/assets/aden-architecture-diagram.jpg" />
<img width="100%" alt="Aden Architecture" src="../assets/aden-architecture-diagram.jpg" />
</p>
Aden एक ऐसा प्लेटफ़ॉर्म है जो AI एजेंट्स को बनाने, डिप्लॉय करने, ऑपरेट करने और अनुकूलित करने के लिए उपयोग होता है:
+1 -1
View File
@@ -42,7 +42,7 @@
## Adenとは
<p align="center">
<img width="100%" alt="Aden Architecture" src="docs/assets/aden-architecture-diagram.jpg" />
<img width="100%" alt="Aden Architecture" src="../assets/aden-architecture-diagram.jpg" />
</p>
Adenは、AIエージェントの構築、デプロイ、運用、適応のためのプラットフォームです:
+1 -1
View File
@@ -42,7 +42,7 @@
## Aden이란 무엇인가
<p align="center">
<img width="100%" alt="Aden Architecture" src="docs/assets/aden-architecture-diagram.jpg" />
<img width="100%" alt="Aden Architecture" src="../assets/aden-architecture-diagram.jpg" />
</p>
Aden은 AI 에이전트를 구축, 배포, 운영, 적응시키기 위한 플랫폼입니다:
+1 -1
View File
@@ -42,7 +42,7 @@ Visite [adenhq.com](https://adenhq.com) para documentação completa, exemplos e
## O que é Aden
<p align="center">
<img width="100%" alt="Aden Architecture" src="docs/assets/aden-architecture-diagram.jpg" />
<img width="100%" alt="Aden Architecture" src="../assets/aden-architecture-diagram.jpg" />
</p>
Aden é uma plataforma para construir, implantar, operar e adaptar agentes de IA:
+1 -1
View File
@@ -42,7 +42,7 @@
## Что такое Aden
<p align="center">
<img width="100%" alt="Aden Architecture" src="docs/assets/aden-architecture-diagram.jpg" />
<img width="100%" alt="Aden Architecture" src="../assets/aden-architecture-diagram.jpg" />
</p>
Aden — это платформа для создания, развёртывания, эксплуатации и адаптации ИИ-агентов:
+1 -1
View File
@@ -42,7 +42,7 @@
## 什么是 Aden
<p align="center">
<img width="100%" alt="Aden Architecture" src="docs/assets/aden-architecture-diagram.jpg" />
<img width="100%" alt="Aden Architecture" src="../assets/aden-architecture-diagram.jpg" />
</p>
Aden 是一个用于构建、部署、运营和适应 AI 智能体的平台:
+135 -83
View File
@@ -11,6 +11,14 @@
set -e
# Detect Bash version for compatibility
BASH_MAJOR_VERSION="${BASH_VERSINFO[0]}"
USE_ASSOC_ARRAYS=false
if [ "$BASH_MAJOR_VERSION" -ge 4 ]; then
USE_ASSOC_ARRAYS=true
fi
echo "[debug] Bash version: ${BASH_VERSION}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
@@ -52,7 +60,7 @@ prompt_choice() {
echo -e "${BOLD}$prompt${NC}"
for opt in "${options[@]}"; do
echo -e " ${CYAN}$i)${NC} $opt"
((i++))
i=$((i + 1))
done
echo ""
@@ -60,7 +68,8 @@ prompt_choice() {
while true; do
read -r -p "Enter choice (1-${#options[@]}): " choice
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#options[@]}" ]; then
return $((choice - 1))
PROMPT_CHOICE=$((choice - 1))
return 0
fi
echo -e "${RED}Invalid choice. Please enter 1-${#options[@]}${NC}"
done
@@ -174,18 +183,12 @@ echo ""
echo -e "${DIM}This may take a minute...${NC}"
echo ""
# Upgrade pip, setuptools, and wheel
echo -n " Upgrading pip... "
$PYTHON_CMD -m pip install --upgrade pip setuptools wheel > /dev/null 2>&1
echo -e "${GREEN}ok${NC}"
# Install framework package from core/
echo -n " Installing framework... "
cd "$SCRIPT_DIR/core"
if [ -f "pyproject.toml" ]; then
uv sync > /dev/null 2>&1
if [ $? -eq 0 ]; then
if uv sync > /dev/null 2>&1; then
echo -e "${GREEN} ✓ framework package installed${NC}"
else
echo -e "${YELLOW} ⚠ framework installation had issues (may be OK)${NC}"
@@ -200,8 +203,7 @@ echo -n " Installing tools... "
cd "$SCRIPT_DIR/tools"
if [ -f "pyproject.toml" ]; then
uv sync > /dev/null 2>&1
if [ $? -eq 0 ]; then
if uv sync > /dev/null 2>&1; then
echo -e "${GREEN} ✓ aden_tools package installed${NC}"
else
echo -e "${RED} ✗ aden_tools installation failed${NC}"
@@ -212,21 +214,6 @@ else
exit 1
fi
# Install MCP dependencies
echo -n " Installing MCP... "
$PYTHON_CMD -m pip install mcp fastmcp > /dev/null 2>&1
echo -e "${GREEN}ok${NC}"
# Fix openai version compatibility
echo -n " Checking openai... "
$PYTHON_CMD -m pip install "openai>=1.0.0" > /dev/null 2>&1
echo -e "${GREEN}ok${NC}"
# Install click for CLI
echo -n " Installing CLI tools... "
$PYTHON_CMD -m pip install click > /dev/null 2>&1
echo -e "${GREEN}ok${NC}"
# Install Playwright browser
echo -n " Installing Playwright browser... "
if $PYTHON_CMD -c "import playwright" > /dev/null 2>&1; then
@@ -344,53 +331,105 @@ echo ""
echo -e "${BLUE}Step 4: Verifying Claude Code skills...${NC}"
echo ""
# Provider data as parallel indexed arrays (Bash 3.2 compatible — no declare -A)
PROVIDER_ENV_VARS=(ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY GOOGLE_API_KEY GROQ_API_KEY CEREBRAS_API_KEY MISTRAL_API_KEY TOGETHER_API_KEY DEEPSEEK_API_KEY)
PROVIDER_DISPLAY_NAMES=("Anthropic (Claude)" "OpenAI (GPT)" "Google Gemini" "Google AI" "Groq" "Cerebras" "Mistral" "Together AI" "DeepSeek")
PROVIDER_ID_LIST=(anthropic openai gemini google groq cerebras mistral together deepseek)
# Provider configuration - use associative arrays (Bash 4+) or indexed arrays (Bash 3.2)
if [ "$USE_ASSOC_ARRAYS" = true ]; then
# Bash 4+ - use associative arrays (cleaner and more efficient)
declare -A PROVIDER_NAMES=(
["ANTHROPIC_API_KEY"]="Anthropic (Claude)"
["OPENAI_API_KEY"]="OpenAI (GPT)"
["GEMINI_API_KEY"]="Google Gemini"
["GOOGLE_API_KEY"]="Google AI"
["GROQ_API_KEY"]="Groq"
["CEREBRAS_API_KEY"]="Cerebras"
["MISTRAL_API_KEY"]="Mistral"
["TOGETHER_API_KEY"]="Together AI"
["DEEPSEEK_API_KEY"]="DeepSeek"
)
# Default models by provider id (parallel arrays)
MODEL_PROVIDER_IDS=(anthropic openai gemini groq cerebras mistral together_ai deepseek)
MODEL_DEFAULTS=("claude-sonnet-4-5-20250929" "gpt-4o" "gemini-3.0-flash-preview" "moonshotai/kimi-k2-instruct-0905" "zai-glm-4.7" "mistral-large-latest" "meta-llama/Llama-3.3-70B-Instruct-Turbo" "deepseek-chat")
declare -A PROVIDER_IDS=(
["ANTHROPIC_API_KEY"]="anthropic"
["OPENAI_API_KEY"]="openai"
["GEMINI_API_KEY"]="gemini"
["GOOGLE_API_KEY"]="google"
["GROQ_API_KEY"]="groq"
["CEREBRAS_API_KEY"]="cerebras"
["MISTRAL_API_KEY"]="mistral"
["TOGETHER_API_KEY"]="together"
["DEEPSEEK_API_KEY"]="deepseek"
)
# Helper: get provider display name for an env var
get_provider_name() {
local env_var="$1"
local i=0
while [ $i -lt ${#PROVIDER_ENV_VARS[@]} ]; do
if [ "${PROVIDER_ENV_VARS[$i]}" = "$env_var" ]; then
echo "${PROVIDER_DISPLAY_NAMES[$i]}"
return
fi
i=$((i + 1))
done
}
declare -A DEFAULT_MODELS=(
["anthropic"]="claude-sonnet-4-5-20250929"
["openai"]="gpt-4o"
["gemini"]="gemini-3.0-flash-preview"
["groq"]="moonshotai/kimi-k2-instruct-0905"
["cerebras"]="zai-glm-4.7"
["mistral"]="mistral-large-latest"
["together_ai"]="meta-llama/Llama-3.3-70B-Instruct-Turbo"
["deepseek"]="deepseek-chat"
)
# Helper: get provider id for an env var
get_provider_id() {
local env_var="$1"
local i=0
while [ $i -lt ${#PROVIDER_ENV_VARS[@]} ]; do
if [ "${PROVIDER_ENV_VARS[$i]}" = "$env_var" ]; then
echo "${PROVIDER_ID_LIST[$i]}"
return
fi
i=$((i + 1))
done
}
# Helper functions for Bash 4+
get_provider_name() {
echo "${PROVIDER_NAMES[$1]}"
}
# Helper: get default model for a provider id
get_default_model() {
local provider_id="$1"
local i=0
while [ $i -lt ${#MODEL_PROVIDER_IDS[@]} ]; do
if [ "${MODEL_PROVIDER_IDS[$i]}" = "$provider_id" ]; then
echo "${MODEL_DEFAULTS[$i]}"
return
fi
i=$((i + 1))
done
}
get_provider_id() {
echo "${PROVIDER_IDS[$1]}"
}
get_default_model() {
echo "${DEFAULT_MODELS[$1]}"
}
else
# Bash 3.2 - use parallel indexed arrays
PROVIDER_ENV_VARS=(ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY GOOGLE_API_KEY GROQ_API_KEY CEREBRAS_API_KEY MISTRAL_API_KEY TOGETHER_API_KEY DEEPSEEK_API_KEY)
PROVIDER_DISPLAY_NAMES=("Anthropic (Claude)" "OpenAI (GPT)" "Google Gemini" "Google AI" "Groq" "Cerebras" "Mistral" "Together AI" "DeepSeek")
PROVIDER_ID_LIST=(anthropic openai gemini google groq cerebras mistral together deepseek)
# Default models by provider id (parallel arrays)
MODEL_PROVIDER_IDS=(anthropic openai gemini groq cerebras mistral together_ai deepseek)
MODEL_DEFAULTS=("claude-sonnet-4-5-20250929" "gpt-4o" "gemini-3.0-flash-preview" "moonshotai/kimi-k2-instruct-0905" "zai-glm-4.7" "mistral-large-latest" "meta-llama/Llama-3.3-70B-Instruct-Turbo" "deepseek-chat")
# Helper: get provider display name for an env var
get_provider_name() {
local env_var="$1"
local i=0
while [ $i -lt ${#PROVIDER_ENV_VARS[@]} ]; do
if [ "${PROVIDER_ENV_VARS[$i]}" = "$env_var" ]; then
echo "${PROVIDER_DISPLAY_NAMES[$i]}"
return
fi
i=$((i + 1))
done
}
# Helper: get provider id for an env var
get_provider_id() {
local env_var="$1"
local i=0
while [ $i -lt ${#PROVIDER_ENV_VARS[@]} ]; do
if [ "${PROVIDER_ENV_VARS[$i]}" = "$env_var" ]; then
echo "${PROVIDER_ID_LIST[$i]}"
return
fi
i=$((i + 1))
done
}
# Helper: get default model for a provider id
get_default_model() {
local provider_id="$1"
local i=0
while [ $i -lt ${#MODEL_PROVIDER_IDS[@]} ]; do
if [ "${MODEL_PROVIDER_IDS[$i]}" = "$provider_id" ]; then
echo "${MODEL_DEFAULTS[$i]}"
return
fi
i=$((i + 1))
done
}
fi
# Configuration directory
HIVE_CONFIG_DIR="$HOME/.hive"
@@ -413,7 +452,7 @@ config = {
'model': '$model',
'api_key_env_var': '$env_var'
},
'created_at': '$(date -Iseconds)'
'created_at': '$(date -u +"%Y-%m-%dT%H:%M:%S+00:00")'
}
with open('$HIVE_CONFIG_FILE', 'w') as f:
json.dump(config, f, indent=2)
@@ -442,13 +481,25 @@ FOUND_ENV_VARS=() # Corresponding env var names
SELECTED_PROVIDER_ID="" # Will hold the chosen provider ID
SELECTED_ENV_VAR="" # Will hold the chosen env var
for env_var in "${PROVIDER_ENV_VARS[@]}"; do
value="${!env_var}"
if [ -n "$value" ]; then
FOUND_PROVIDERS+=("$(get_provider_name "$env_var")")
FOUND_ENV_VARS+=("$env_var")
fi
done
if [ "$USE_ASSOC_ARRAYS" = true ]; then
# Bash 4+ - iterate over associative array keys
for env_var in "${!PROVIDER_NAMES[@]}"; do
value="${!env_var}"
if [ -n "$value" ]; then
FOUND_PROVIDERS+=("$(get_provider_name "$env_var")")
FOUND_ENV_VARS+=("$env_var")
fi
done
else
# Bash 3.2 - iterate over indexed array
for env_var in "${PROVIDER_ENV_VARS[@]}"; do
value="${!env_var}"
if [ -n "$value" ]; then
FOUND_PROVIDERS+=("$(get_provider_name "$env_var")")
FOUND_ENV_VARS+=("$env_var")
fi
done
fi
if [ ${#FOUND_PROVIDERS[@]} -gt 0 ]; then
echo "Found API keys:"
@@ -476,7 +527,7 @@ if [ ${#FOUND_PROVIDERS[@]} -gt 0 ]; then
i=1
for provider in "${FOUND_PROVIDERS[@]}"; do
echo -e " ${CYAN}$i)${NC} $provider"
((i++))
i=$((i + 1))
done
echo ""
@@ -507,7 +558,7 @@ if [ -z "$SELECTED_PROVIDER_ID" ]; then
"Groq - Fast, free tier" \
"Cerebras - Fast, free tier" \
"Skip for now"
choice=$?
choice=$PROMPT_CHOICE
case $choice in
0)
@@ -542,7 +593,8 @@ if [ -z "$SELECTED_PROVIDER_ID" ]; then
;;
5)
echo ""
echo -e "${YELLOW}Skipped.${NC} Add your API key later:"
echo -e "${YELLOW}Skipped.${NC} An LLM API key is required to test and use worker agents."
echo -e "Add your API key later by running:"
echo ""
echo -e " ${CYAN}echo 'ANTHROPIC_API_KEY=your-key' >> .env${NC}"
echo ""
@@ -595,7 +647,7 @@ ERRORS=0
# Test imports
echo -n " ⬡ framework... "
if $PYTHON_CMD -c "import framework" > /dev/null 2>&1; then
if $CORE_PYTHON -c "import framework" > /dev/null 2>&1; then
echo -e "${GREEN}ok${NC}"
else
echo -e "${RED}failed${NC}"
@@ -603,7 +655,7 @@ else
fi
echo -n " ⬡ aden_tools... "
if $PYTHON_CMD -c "import aden_tools" > /dev/null 2>&1; then
if $TOOLS_PYTHON -c "import aden_tools" > /dev/null 2>&1; then
echo -e "${GREEN}ok${NC}"
else
echo -e "${RED}failed${NC}"
@@ -611,7 +663,7 @@ else
fi
echo -n " ⬡ litellm... "
if $PYTHON_CMD -c "import litellm" > /dev/null 2>&1; then
if $CORE_PYTHON -c "import litellm" > /dev/null 2>&1 || $TOOLS_PYTHON -c "import litellm" > /dev/null 2>&1; then
echo -e "${GREEN}ok${NC}"
else
echo -e "${YELLOW}--${NC}"