diff --git a/.gitignore b/.gitignore index 40e9ed5..bfaecbb 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,7 @@ celerybeat-schedule .venv env/ venv/ +venv*/ run/ ENV/ env.bak/ diff --git a/README.md b/README.md index ceeb322..a9fad9b 100644 --- a/README.md +++ b/README.md @@ -98,13 +98,22 @@

πŸŽ‰ News

-

We have published RunAgent Pulse

- +

Introducing OpenClaw, PicoClaw, and ZeroClaw

+ +

+ OpenClaw +     + PicoClaw +     + ZeroClaw +

+- **[2026.03] πŸš€ [Architecture]** Introducing **OpenClaw**, **PicoClaw** (Go), and **ZeroClaw** (Rust): one unified engine that runs agents both **serverless** and as **24/7 always-on services**. +- **[2026.03] 🌍 [Milestone]** The first architecture to support **persistent + serverless agent execution in the same engine**, with a single codebase powering both modes. + - **[2025.12] 🎯 [New Product]** Published **RunAgent Pulse – Scheduling & Orchestration**, a self-hosted β€œGoogle Calendar for your AI agents”. -- **[2025.12] 🎯 [Integration]** Integrated the **PaperFlow** arXiv research agent with **RunAgent Serverless** and **RunAgent Pulse** for end-to-end scheduled arXiv monitoring and email notifications. ## What is RunAgent-Pulse? diff --git a/docs/logo/Openclaw-logo-text-dark.png b/docs/logo/Openclaw-logo-text-dark.png new file mode 100644 index 0000000..b14e423 Binary files /dev/null and b/docs/logo/Openclaw-logo-text-dark.png differ diff --git a/docs/logo/openclaw logo.png b/docs/logo/openclaw logo.png new file mode 100644 index 0000000..c27e8b4 Binary files /dev/null and b/docs/logo/openclaw logo.png differ diff --git a/docs/logo/openclaw-white.jpg b/docs/logo/openclaw-white.jpg new file mode 100644 index 0000000..cb5a86b Binary files /dev/null and b/docs/logo/openclaw-white.jpg differ diff --git a/docs/logo/picoclaw.png b/docs/logo/picoclaw.png new file mode 100644 index 0000000..a93732d Binary files /dev/null and b/docs/logo/picoclaw.png differ diff --git a/docs/logo/zeroclaw.png b/docs/logo/zeroclaw.png new file mode 100644 index 0000000..958144c Binary files /dev/null and b/docs/logo/zeroclaw.png differ diff --git a/examples/trip_planner/agent/runagent.config.json b/examples/trip_planner/agent/runagent.config.json index 34012c5..02e0340 100644 --- a/examples/trip_planner/agent/runagent.config.json +++ b/examples/trip_planner/agent/runagent.config.json @@ -25,7 +25,7 @@ ] }, "env_vars": {}, - "agent_id": "7ca76f5d-9524-4614-b283-d9a50db147b8", + "agent_id": "7cc76f5d-9524-4614-b283-d9a50db137b8", "auth_settings": { "type": "api_key" }, diff --git a/examples/trip_planner/sdk/python/test.py b/examples/trip_planner/sdk/python/test.py index 48a2bf9..569411a 100644 --- a/examples/trip_planner/sdk/python/test.py +++ b/examples/trip_planner/sdk/python/test.py @@ -2,12 +2,16 @@ Minimal RunAgent SDK Test for TripGenius Update AGENT_ID and run! + +Run from repo root with venv activated: + pip install -e . + python examples/trip_planner/sdk/python/test.py """ from runagent import RunAgentClient # UPDATE THIS! -AGENT_ID = "7ca76f5d-9524-4614-b283-d9a50db147b8" +AGENT_ID = "9cc76f5d-9524-4614-b283-d9a50db137b8" # ============================================ diff --git a/runagent/cli/commands/config.py b/runagent/cli/commands/config.py index 8b3fad7..dd746fe 100644 --- a/runagent/cli/commands/config.py +++ b/runagent/cli/commands/config.py @@ -32,7 +32,7 @@ def format_error_message(error_info): @click.group(invoke_without_command=True) @click.option("--set-api-key", help="Set API key directly (e.g., runagent config --set-api-key YOUR_KEY)") @click.option("--set-base-url", help="Set base URL directly (e.g., runagent config --set-base-url https://api.example.com)") -@click.option("--register-agent", type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path), help="Register a modified agent in the database") +@click.option("--register-agent", type=str, help="Register an agent: use a path (e.g., '.') or template shortcut (e.g., 'openclaw/gateway')") @click.option("--delete-agent", help="Delete an agent by ID") @click.pass_context def config(ctx, set_api_key, set_base_url, register_agent, delete_agent): @@ -65,8 +65,10 @@ def config(ctx, set_api_key, set_base_url, register_agent, delete_agent): return if register_agent: - from .register import _register_agent_core - return _register_agent_core(register_agent) + from .register import _register_agent_core, _resolve_template_path_for_registration + # Resolve path or template shortcut (e.g. "agno/default") to a Path + resolved_path, _ = _resolve_template_path_for_registration(register_agent) + return _register_agent_core(resolved_path) if delete_agent: from .delete import delete as delete_func diff --git a/runagent/cli/commands/deploy.py b/runagent/cli/commands/deploy.py index 12bc4f3..843b61f 100644 --- a/runagent/cli/commands/deploy.py +++ b/runagent/cli/commands/deploy.py @@ -2,6 +2,8 @@ CLI commands that use the restructured SDK internally. """ import os +import tempfile +from typing import Optional, Tuple from pathlib import Path @@ -11,7 +13,9 @@ from runagent import RunAgentSDK from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError AuthenticationError, + TemplateError, ) +from runagent.constants import TEMPLATE_PREPATH console = Console() @@ -31,22 +35,126 @@ def format_error_message(error_info): # ============================================================================ +def _resolve_template_path(template_path: str) -> Tuple[Path, Optional[Path]]: + """ + Resolve template path - supports both: + - Direct paths: "templates/openclaw/gateway" or "./my-agent" + - Template shortcuts: "openclaw/gateway" (resolves to templates/openclaw/gateway) + + Returns: + Tuple of (resolved_path, temp_dir_for_cleanup) + temp_dir_for_cleanup is None if path is local (not downloaded) + """ + # Support single-token aliases before path resolution + # e.g. "picoclaw" -> "picoclaw/gateway" + if template_path == "picoclaw": + template_path = "picoclaw/gateway" + if template_path == "zeroclaw": + template_path = "zeroclaw/gateway" + + path = Path(template_path) + + # If path exists, use it directly + if path.exists(): + return path.resolve(), None + + # Check if it looks like a template shortcut (framework/template format) + parts = template_path.split("/") + if len(parts) == 2 and "/" in template_path: + framework, template = parts + + # First, check if local template exists + # Try multiple locations: + # 1. Current working directory (if in runagent source repo) + # 2. Parent of runagent package (if templates are alongside package) + # 3. Relative to runagent package location + possible_paths = [ + Path.cwd() / TEMPLATE_PREPATH / framework / template, # Current dir + Path.cwd() / "templates" / framework / template, # Current dir with "templates" + ] + + # Also check relative to runagent package if it's installed + try: + import runagent + runagent_package_dir = Path(runagent.__file__).parent.parent + possible_paths.extend([ + runagent_package_dir / TEMPLATE_PREPATH / framework / template, + runagent_package_dir / "templates" / framework / template, + ]) + except (ImportError, AttributeError): + pass + + for local_template_path in possible_paths: + if local_template_path.exists(): + console.print(f"[dim]Using local template: [cyan]{framework}/{template}[/cyan][/dim]") + return local_template_path.resolve(), None + + # If local doesn't exist, try to download from remote + try: + sdk = RunAgentSDK() + temp_dir = tempfile.mkdtemp(prefix="runagent-deploy-") + temp_path = Path(temp_dir) / template + + console.print(f"[dim]Downloading template: [cyan]{framework}/{template}[/cyan][/dim]") + sdk.templates.init_template( + folder_path=temp_path, + framework=framework, + template=template, + overwrite=True + ) + + if temp_path.exists(): + console.print(f"[green]βœ“[/green] Template downloaded to temporary directory") + return temp_path.resolve(), Path(temp_dir) + else: + raise TemplateError(f"Template download failed: {framework}/{template}") + except Exception as e: + raise click.ClickException( + f"Template '{template_path}' not found locally or remotely. " + f"Available formats: 'framework/template' (e.g., 'openclaw/gateway') or a local path.\n" + f"Error: {e}" + ) + + # Check if it's templates/framework/template format + if template_path.startswith("templates/") or template_path.startswith(f"{TEMPLATE_PREPATH}/"): + # Remove templates/ prefix and try as shortcut + shortcut = template_path.replace(f"{TEMPLATE_PREPATH}/", "").replace("templates/", "") + return _resolve_template_path(shortcut) + + # Path doesn't exist and doesn't look like a template + raise click.ClickException( + f"Path not found: {path}\n" + f"Use a local path or template shortcut like 'openclaw/gateway' or 'picoclaw/gateway'" + ) + + @click.command() @click.option("--overwrite", is_flag=True, help="Overwrite existing agent if it already exists") +@click.option( + "--new-id", + is_flag=True, + help="Do NOT reuse existing agent ID for template shortcuts (e.g. always create a new agent for 'openclaw/gateway')", +) @click.argument( "path", - type=click.Path( - exists=True, - file_okay=False, - dir_okay=True, - readable=True, - resolve_path=True, - path_type=Path, - ), + type=str, default=".", ) -def deploy(path: Path, overwrite: bool): - """Deploy agent (upload + start) to remote server""" +def deploy(path: str, overwrite: bool, new_id: bool): + """ + Deploy agent (upload + start) to remote server. + + PATH can be: + - A local folder: "./my-agent" or "/path/to/agent" + - A template shortcut: "openclaw/gateway" or "openclaw/mcp" + - A template path: "templates/openclaw/gateway" + + Examples: + runagent deploy . # Deploy current directory + runagent deploy openclaw/gateway # Deploy OpenClaw gateway template + runagent deploy openclaw/mcp # Deploy OpenClaw MCP template + runagent deploy templates/openclaw/gateway # Same as above + """ try: from runagent.cli.branding import print_header @@ -61,15 +169,166 @@ def deploy(path: Path, overwrite: bool): ) raise click.ClickException("Authentication required") - # Validate folder - if not Path(path).exists(): - raise click.ClickException(f"Folder not found: {path}") + # Resolve template path (downloads template if needed) + try: + resolved_path, temp_dir_to_cleanup = _resolve_template_path(path) + except click.ClickException: + raise + except Exception as e: + raise click.ClickException(f"Failed to resolve path '{path}': {e}") console.print(f"[bold]Deploying agent (upload + start)...[/bold]") - console.print(f"Source: [cyan]{path}[/cyan]") + console.print(f"Source: [cyan]{resolved_path}[/cyan]") + + # Check if agent needs registration (has null UUID or not registered) + from runagent.utils.agent import get_agent_config + from runagent.utils.agent_id import generate_agent_id + from runagent.sdk.db import DBService + from runagent.constants import LOCAL_CACHE_DIRECTORY + import json + import shutil + + NULL_UUID = "00000000-0000-0000-0000-000000000000" + config_path = resolved_path / "runagent.config.json" + needs_registration = False + working_path = resolved_path + working_temp_dir = None # Track if we created a working copy + + # Check if this is a template shortcut (e.g., "openclaw/gateway") + is_template_shortcut = "/" in path and not Path(path).exists() + reuse_agent_id = None + + # Only try to reuse existing agent ID when *not* forcing a new one + if is_template_shortcut and not new_id: + # Check if we've deployed this template before by looking in database + try: + agent_config = get_agent_config(resolved_path) + template_name = agent_config.get('template') if isinstance(agent_config, dict) else getattr(agent_config, 'template', None) + agent_name = agent_config.get('agent_name') if isinstance(agent_config, dict) else getattr(agent_config, 'agent_name', None) + + if template_name or agent_name: + db_service = DBService() + from runagent.sdk.db import Agent + from sqlalchemy import or_ + with db_service.db_manager.get_session() as session: + query = session.query(Agent) + conditions = [] + if template_name: + conditions.append(Agent.template == template_name) + if agent_name: + conditions.append(Agent.agent_name == agent_name) + if conditions: + query = query.filter(or_(*conditions)) + existing_agents = query.order_by(Agent.created_at.desc()).all() + + if existing_agents: + # Use the most recently created agent with this template + reuse_agent_id = existing_agents[0].agent_id + console.print(f"[green]βœ“[/green] Reusing existing agent ID: [cyan]{reuse_agent_id}[/cyan]") + console.print(f"[dim]Template '{path}' was previously deployed[/dim]") + except Exception as e: + # Silently continue - will generate new UUID + pass + + # If deploying from a template, we may need to create a working copy + if config_path.exists(): + try: + agent_config = get_agent_config(resolved_path) + agent_id = agent_config.get('agent_id') if isinstance(agent_config, dict) else getattr(agent_config, 'agent_id', None) + + # Check if agent_id is null UUID or missing + if not agent_id or agent_id == NULL_UUID: + # If we found an existing agent_id for this template shortcut, reuse it + if reuse_agent_id: + new_agent_id = reuse_agent_id + needs_registration = False # Already registered + else: + needs_registration = True + new_agent_id = generate_agent_id() + console.print(f"[dim]Agent has null UUID, generating new ID[/dim]") + + # If this is a local template (not a temp download), create a working copy + # to avoid modifying the original template + if temp_dir_to_cleanup is None: + # Create a temporary working copy to avoid modifying the template + import tempfile + working_temp_dir = Path(tempfile.mkdtemp(prefix="runagent-deploy-working-")) + working_path = working_temp_dir / resolved_path.name + shutil.copytree(resolved_path, working_path) + console.print(f"[dim]Created working copy: [cyan]{working_path}[/cyan][/dim]") + config_path = working_path / "runagent.config.json" + # else: temp_dir_to_cleanup exists, so resolved_path is already a temp copy + # and we can safely modify it + + # Update config with agent ID (either reused or new) + with config_path.open('r') as f: + config_data = json.load(f) + config_data['agent_id'] = new_agent_id + with config_path.open('w') as f: + json.dump(config_data, f, indent=2) + + if reuse_agent_id: + console.print(f"[green]βœ“[/green] Using existing agent ID: [cyan]{new_agent_id}[/cyan]") + else: + console.print(f"[green]βœ“[/green] Generated agent ID: [cyan]{new_agent_id}[/cyan]") + + else: + # Check if agent is registered in database + db_service = DBService() + existing_agent = db_service.get_agent(agent_id) + if not existing_agent: + needs_registration = True + console.print(f"[dim]Agent {agent_id} not registered, will register now[/dim]") + except Exception as e: + console.print(f"[yellow]⚠[/yellow] Could not check agent registration: {e}") + # Continue with deployment anyway + + # Register agent if needed + if needs_registration: + try: + from .register import _register_agent_core + console.print(f"[dim]Registering agent...[/dim]") + _register_agent_core(working_path) + except Exception as e: + console.print(f"[yellow]⚠[/yellow] Registration warning: {e}") + # Continue with deployment anyway - remote might handle it + + # If we're reusing an agent ID, update the agent path in database + # (since each deployment creates a new temporary working copy) + if reuse_agent_id and not needs_registration: + try: + db_service = DBService() + # Directly update agent_path in database + from runagent.sdk.db import Agent + with db_service.db_manager.get_session() as session: + agent = session.query(Agent).filter(Agent.agent_id == reuse_agent_id).first() + if agent: + agent.agent_path = str(working_path) + session.commit() + console.print(f"[dim]Updated agent path in database[/dim]") + except Exception as e: + # Silently continue - path update is not critical + pass - # Deploy agent (framework auto-detected) - result = sdk.deploy_remote(folder=str(path), overwrite=overwrite) + try: + # Deploy agent (framework auto-detected) + result = sdk.deploy_remote(folder=str(working_path), overwrite=overwrite) + finally: + # Cleanup temp directories + if temp_dir_to_cleanup and temp_dir_to_cleanup.exists(): + try: + shutil.rmtree(temp_dir_to_cleanup) + console.print(f"[dim]Cleaned up temporary template directory[/dim]") + except Exception: + pass # Ignore cleanup errors + + # Cleanup working copy if we created one + if working_temp_dir is not None and working_temp_dir.exists(): + try: + shutil.rmtree(working_temp_dir) + console.print(f"[dim]Cleaned up working copy[/dim]") + except Exception: + pass # Ignore cleanup errors if result.get("success"): agent_id = result.get('agent_id') @@ -78,6 +337,142 @@ def deploy(path: Path, overwrite: bool): console.print(f"\nβœ… [green]Deployment successful![/green]") console.print(f"Agent ID: [bold magenta]{agent_id}[/bold magenta]") console.print(f"Agent URL: [link]{dashboard_url}[/link]") + + # Check if this looks like an OpenClaw Gateway deployment (by path/shortcut), + # then display gateway URL + token + pairing info + VM IP for MCP setup. + try: + is_openclaw_gateway = ( + "openclaw/gateway" in path + or path.endswith("openclaw/gateway") + or path.rstrip("/").endswith("gateway") and "openclaw" in str(path) + ) + is_picoclaw_gateway = ( + path == "picoclaw" + or "picoclaw/gateway" in path + or path.endswith("picoclaw/gateway") + ) + is_zeroclaw_gateway = ( + path == "zeroclaw" + or "zeroclaw/gateway" in path + or path.endswith("zeroclaw/gateway") + ) + + if is_openclaw_gateway: + # Fetch agent metadata and NetworkInfo to get all credentials. + # Poll a few times since serverless gateway setup runs in background. + import time + gateway_url = None + gateway_token = None + pairing_status = None + vm_ip = None + + # Check if result already has NetworkInfo (from start response) + if result.get("network_info"): + network_info = result.get("network_info", {}) + vm_ip = network_info.get("ip_address") or network_info.get("IpAddress") + elif result.get("data") and result.get("data", {}).get("network_info"): + network_info = result.get("data", {}).get("network_info", {}) + vm_ip = network_info.get("ip_address") or network_info.get("IpAddress") + + for attempt in range(5): # Try up to 5 times + time.sleep(1 + attempt) # Increasing wait: 1s, 2s, 3s, 4s, 5s + agent_info = sdk.remote.client.get_agent_status(agent_id) + if agent_info.get("success"): + data = agent_info.get("data", {}) or {} + + # Extract NetworkInfo + network_info = data.get("network_info") or {} + if not vm_ip: + vm_ip = network_info.get("ip_address") or network_info.get("IpAddress") + + # Extract metadata + metadata = data.get("metadata", {}) or {} + gateway_url = metadata.get("gateway_url") or gateway_url + gateway_token = metadata.get("gateway_token") or gateway_token + pairing_status = metadata.get("pairing_status") or pairing_status + + # If we have URL or IP, we consider setup "good enough" to show. + if gateway_url or vm_ip: + break + + # Prefer token from environment if present (user-controlled secret) + env_gateway_token = os.environ.get("OPENCLAW_GATEWAY_TOKEN") or None + if env_gateway_token: + gateway_token = env_gateway_token + + console.print(f"\n[bold cyan]OpenClaw Gateway Credentials:[/bold cyan]") + console.print(f" Agent ID: [bold magenta]{agent_id}[/bold magenta]") + + if vm_ip: + console.print(f" VM IP Address: [green]{vm_ip}[/green]") + else: + console.print(f" VM IP Address: [yellow]Not yet available (VM still starting)[/yellow]") + + if gateway_url: + console.print(f" Gateway URL: [green]{gateway_url}[/green]") + elif vm_ip: + # Construct gateway URL from IP if we have it + gateway_url = f"ws://{vm_ip}:18789" + console.print(f" Gateway URL: [green]{gateway_url}[/green]") + else: + console.print(f" Gateway URL: [yellow]Not yet available (gateway still starting)[/yellow]") + + if gateway_token: + console.print(f" Gateway Token: [green]{gateway_token}[/green]") + elif gateway_url or vm_ip: + console.print(f" Gateway Token: [yellow]Not set (token auth disabled or not provided)[/yellow]") + + if pairing_status: + console.print(f" Pairing Status: [green]{pairing_status}[/green]") + else: + console.print(f" Pairing Status: [yellow]Pending (will auto‑approve when devices connect)[/yellow]") + + if gateway_url or vm_ip: + console.print(f"\n[dim]Use these credentials to deploy MCP:[/dim]") + if gateway_url: + console.print(f" [cyan]OPENCLAW_GATEWAY_URL={gateway_url}[/cyan]") + elif vm_ip: + console.print(f" [cyan]OPENCLAW_GATEWAY_URL=ws://{vm_ip}:18789[/cyan]") + if gateway_token: + console.print(f" [cyan]OPENCLAW_GATEWAY_TOKEN={gateway_token}[/cyan]") + else: + console.print(f"\n[dim]You can fetch gateway info later with:[/dim]") + console.print(f" [cyan]runagent status {agent_id}[/cyan]") + + if is_picoclaw_gateway: + console.print(f"\n[bold cyan]Picoclaw Gateway Deployment:[/bold cyan]") + console.print(f" Agent ID: [bold magenta]{agent_id}[/bold magenta]") + console.print(" Framework: [green]picoclaw[/green]") + console.print(" Runtime: [green]picoclaw-gateway[/green]") + console.print("\n[dim]Inside the microVM, picoclaw uses:[/dim]") + console.print(" Config: [cyan]/root/.picoclaw/config.json[/cyan]") + console.print(" Workspace: [cyan]/root/.picoclaw/workspace[/cyan]") + console.print(" (backed by /persistent/.picoclaw on the VM data disk)") + console.print("\n[dim]Next steps:[/dim]") + console.print(" 1. Configure models and channels in ~/.picoclaw/config.json") + console.print(" 2. Use 'picoclaw gateway' inside the VM for channel bots") + console.print(" 3. Rely on heartbeat/cron for periodic tasks from the workspace") + + if is_zeroclaw_gateway: + console.print(f"\n[bold cyan]ZeroClaw Gateway Deployment:[/bold cyan]") + console.print(f" Agent ID: [bold magenta]{agent_id}[/bold magenta]") + console.print(" Framework: [green]zeroclaw[/green]") + console.print(" Runtime: [green]zeroclaw-gateway[/green]") + console.print("\n[dim]Inside the microVM, ZeroClaw uses:[/dim]") + console.print(" Config: [cyan]/root/.zeroclaw/config.toml[/cyan]") + console.print(" Workspace: [cyan]/root/.zeroclaw/workspace[/cyan]") + console.print(" (backed by /persistent/.zeroclaw on the VM data disk)") + console.print("\n[dim]Next steps:[/dim]") + console.print(" 1. Configure providers/models/channels in ~/.zeroclaw/config.toml") + console.print(" 2. Run `zeroclaw service install` if you need system-managed services") + console.print(" 3. Bind channel identities (Telegram/Discord) as required") + except Exception as e: + # Log error but don't fail deployment + import traceback + console.print(f"[yellow]⚠[/yellow] Could not fetch gateway credentials: {e}") + if os.getenv('DEBUG'): + console.print(f"[dim]{traceback.format_exc()}[/dim]") + pass else: error_info = result.get("error") console.print(f"❌ [red]Deployment failed:[/red] {format_error_message(error_info)}") diff --git a/runagent/cli/commands/register.py b/runagent/cli/commands/register.py index 0950c41..04cc5e4 100644 --- a/runagent/cli/commands/register.py +++ b/runagent/cli/commands/register.py @@ -2,7 +2,11 @@ CLI commands for agent registration and management. """ import os +import json +import tempfile +import shutil from pathlib import Path +from typing import Optional, Tuple import click from rich.console import Console @@ -10,12 +14,13 @@ from runagent.cli.branding import print_header from runagent.utils.agent import get_agent_config, get_agent_config_with_defaults -from runagent.utils.agent_id import generate_config_fingerprint +from runagent.utils.agent_id import generate_config_fingerprint, generate_agent_id +from runagent.constants import TEMPLATE_PREPATH, LOCAL_CACHE_DIRECTORY console = Console() -def _register_agent_core(agent_path: Path): +def _register_agent_core(agent_path: Path | str): """ Core logic for registering an agent (can be called directly or via Click command). @@ -27,8 +32,8 @@ def _register_agent_core(agent_path: Path): try: print_header("Register Agent") - # Resolve path - agent_path = agent_path.resolve() + # Resolve path (accept str from e.g. config --register-agent) + agent_path = Path(agent_path).resolve() # Check if runagent.config.json exists config_path = agent_path / "runagent.config.json" @@ -150,6 +155,191 @@ def _register_agent_core(agent_path: Path): raise click.ClickException("Agent registration failed") +def _resolve_template_path_for_registration(template_path: str) -> Tuple[Path, Optional[Path]]: + """ + Resolve template path for registration - supports template shortcuts. + Returns (resolved_path, temp_dir_for_cleanup). + """ + path = Path(template_path) + + # If path exists, use it directly + if path.exists(): + return path.resolve(), None + + # Check if it looks like a template shortcut (framework/template format) + parts = template_path.split("/") + if len(parts) == 2 and "/" in template_path: + framework, template = parts + + # Check local templates first + possible_paths = [ + Path.cwd() / TEMPLATE_PREPATH / framework / template, + Path.cwd() / "templates" / framework / template, + ] + + # Also check relative to runagent package if it's installed + try: + import runagent + runagent_package_dir = Path(runagent.__file__).parent.parent + possible_paths.extend([ + runagent_package_dir / TEMPLATE_PREPATH / framework / template, + runagent_package_dir / "templates" / framework / template, + ]) + except (ImportError, AttributeError): + pass + + for local_template_path in possible_paths: + if local_template_path.exists(): + return local_template_path.resolve(), None + + # If not found locally, try to download (but for registration, we prefer local) + raise click.ClickException( + f"Template '{template_path}' not found locally. " + f"Templates must exist locally for registration." + ) + + # Path doesn't exist and doesn't look like a template + raise click.ClickException( + f"Path not found: {path}\n" + f"Use a local path or template shortcut like 'openclaw/gateway'" + ) + + +def _register_template_agent(template_shortcut: str): + """ + Register a template agent from a shortcut (e.g., 'openclaw/gateway'). + This is a new feature that: + 1. Resolves the template shortcut to a local template + 2. Checks if template is already registered + 3. If not, generates UUID, saves it to a persistent registered templates location + 4. Registers the agent with that UUID + """ + try: + print_header("Register Template Agent") + + # Resolve template path + try: + resolved_path, _ = _resolve_template_path_for_registration(template_shortcut) + except click.ClickException: + raise + except Exception as e: + raise click.ClickException(f"Failed to resolve template '{template_shortcut}': {e}") + + console.print(f"[dim]Template: [cyan]{template_shortcut}[/cyan][/dim]") + console.print(f"[dim]Path: [cyan]{resolved_path}[/cyan][/dim]") + + # Load template config + config_path = resolved_path / "runagent.config.json" + if not config_path.exists(): + raise click.ClickException(f"No runagent.config.json found in template: {resolved_path}") + + agent_config = get_agent_config(resolved_path) + if not agent_config: + raise click.ClickException("Failed to load template configuration") + + template_name = agent_config.get('template') if isinstance(agent_config, dict) else getattr(agent_config, 'template', None) + agent_name = agent_config.get('agent_name') if isinstance(agent_config, dict) else getattr(agent_config, 'agent_name', None) + + # Check if this template is already registered + from runagent.sdk.db import DBService, Agent + from sqlalchemy import or_ + db_service = DBService() + + with db_service.db_manager.get_session() as session: + query = session.query(Agent) + conditions = [] + if template_name: + conditions.append(Agent.template == template_name) + if agent_name: + conditions.append(Agent.agent_name == agent_name) + + existing_agents = [] + if conditions: + query = query.filter(or_(*conditions)) + existing_agents = query.all() + + # If already registered, show existing agent info + if existing_agents: + console.print(f"\n[yellow]⚠ Template '{template_shortcut}' is already registered[/yellow]") + for existing in existing_agents: + console.print(f" β€’ Agent ID: [cyan]{existing.agent_id}[/cyan]") + console.print(f" Status: [cyan]{existing.status or 'unknown'}[/cyan]") + console.print(f" Name: [cyan]{existing.agent_name or 'N/A'}[/cyan]") + + from rich.prompt import Confirm + if not Confirm.ask("\n[bold]Register a new instance anyway?[/bold]", default=False): + console.print("[dim]Registration cancelled. Use existing agent ID for deployment.[/dim]") + return + + # Create a persistent registered templates directory + registered_templates_dir = Path(LOCAL_CACHE_DIRECTORY) / "registered_templates" + registered_templates_dir.mkdir(parents=True, exist_ok=True) + + # Create a working copy for this registered template + template_working_dir = registered_templates_dir / template_shortcut.replace("/", "_") + if template_working_dir.exists(): + shutil.rmtree(template_working_dir) + shutil.copytree(resolved_path, template_working_dir) + + # Generate UUID and update config + new_agent_id = generate_agent_id() + working_config_path = template_working_dir / "runagent.config.json" + with working_config_path.open('r') as f: + config_data = json.load(f) + config_data['agent_id'] = new_agent_id + with working_config_path.open('w') as f: + json.dump(config_data, f, indent=2) + + console.print(f"\n[green]βœ“[/green] Generated agent ID: [cyan]{new_agent_id}[/cyan]") + console.print(f"[dim]Registered template stored at: [cyan]{template_working_dir}[/cyan][/dim]") + + # Register the agent + config_with_defaults = get_agent_config_with_defaults(template_working_dir) + config_fingerprint = generate_config_fingerprint(template_working_dir) + active_project_id = db_service.get_active_project_id() + + result = db_service.add_agent( + agent_id=new_agent_id, + agent_path=str(template_working_dir), + host="localhost", + port=8000, + framework=agent_config.framework.value if hasattr(agent_config.framework, 'value') else str(agent_config.framework), + status="initialized", + agent_name=config_with_defaults.get('agent_name'), + description=config_with_defaults.get('description'), + template=config_with_defaults.get('template'), + version=config_with_defaults.get('version'), + initialized_at=config_with_defaults.get('created_at'), + config_fingerprint=config_fingerprint, + project_id=active_project_id, + ) + + if not result.get('success'): + raise click.ClickException(f"Failed to register agent: {result.get('error')}") + + # Show success message + console.print(Panel( + f"[bold green]βœ… Template Registered Successfully![/bold green]\n\n" + f"[dim]Template:[/dim] [cyan]{template_shortcut}[/cyan]\n" + f"[dim]Agent ID:[/dim] [cyan]{new_agent_id}[/cyan]\n" + f"[dim]Name:[/dim] [white]{config_with_defaults.get('agent_name', 'Unknown')}[/white]\n" + f"[dim]Framework:[/dim] [blue]{agent_config.framework}[/blue]\n" + f"[dim]Status:[/dim] [green]initialized[/green]", + title="[bold green]Registration Success[/bold green]", + border_style="green" + )) + + console.print("\n[bold]Next Step:[/bold]") + console.print(f" Deploy: [cyan]runagent deploy {template_shortcut}[/cyan]") + console.print(f" (This will use agent ID: [cyan]{new_agent_id}[/cyan])") + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Registration error:[/red] {e}") + raise click.ClickException("Template registration failed") + + @click.command() @click.argument( "path", diff --git a/runagent/sdk/rest_client.py b/runagent/sdk/rest_client.py index 16f2fd0..0a26eec 100644 --- a/runagent/sdk/rest_client.py +++ b/runagent/sdk/rest_client.py @@ -1084,6 +1084,9 @@ def _process_start_result(self, result: Dict, agent_id: str) -> Dict: result_data = result["data"] endpoint = result_data.get("endpoint") + # Extract NetworkInfo if available + network_info = result_data.get("network_info") + # Generate dashboard URL instead of API endpoint dashboard_url = f"https://app.run-agent.ai/dashboard/agents/{agent_id}" @@ -1096,19 +1099,25 @@ def _process_start_result(self, result: Dict, agent_id: str) -> Dict: )) # Update local deployment info - self._update_deployment_info(agent_id, { + deployment_info = { "status": "deployed", "endpoint": f"{self.base_url}{endpoint}", "started_at": time.strftime("%Y-%m-%d %H:%M:%S"), - }) + } + if network_info: + deployment_info["network_info"] = network_info + self._update_deployment_info(agent_id, deployment_info) - return { + return_result = { "success": True, "agent_id": agent_id, "endpoint": f"{self.base_url}{endpoint}", "dashboard_url": dashboard_url, "status": "deployed", } + if network_info: + return_result["network_info"] = network_info + return return_result return result def deploy_agent(self, folder_path: str, metadata: Dict = None, overwrite: bool = False) -> Dict: @@ -1285,7 +1294,11 @@ def deploy_agent(self, folder_path: str, metadata: Dict = None, overwrite: bool # Phase 2: Start agent (60-100%) retry_progress.update(retry_task, completed=70, description="Starting agent...") - start_result = self.start_agent(agent_id, {}) + # Extract metadata from agent config for OpenClaw gateway, etc. + start_metadata = {} + if isinstance(agent_config, dict) and agent_config.get('metadata'): + start_metadata = agent_config.get('metadata', {}).copy() + start_result = self.start_agent(agent_id, start_metadata) if not start_result.get("success"): return {"success": False, "error": f"Agent start failed: {start_result.get('error')}"} @@ -1328,6 +1341,13 @@ def deploy_agent(self, folder_path: str, metadata: Dict = None, overwrite: bool # Phase 3: Start agent (70-100%) progress.update(deploy_task, completed=75, description="Starting agent deployment...") + # Extract metadata from agent config if not provided + if metadata is None: + metadata = {} + # Merge with metadata from agent config (e.g., runtime for OpenClaw gateway) + # agent_config is a dict returned by get_agent_config + if isinstance(agent_config, dict) and agent_config.get('metadata'): + metadata = {**metadata, **agent_config.get('metadata', {})} start_result = self._start_agent_core(agent_id, metadata) if start_result.get("success"): diff --git a/runagent/sdk/server/framework/__init__.py b/runagent/sdk/server/framework/__init__.py index eb078a4..f71fb29 100644 --- a/runagent/sdk/server/framework/__init__.py +++ b/runagent/sdk/server/framework/__init__.py @@ -31,7 +31,8 @@ def get_executor( Framework.LETTA: GenericExecutor, Framework.LLAMAINDEX: LlamaIndexExecutor, Framework.N8N: N8NExecutor, - Framework.PARLANT: ParlantExecutor + Framework.PARLANT: ParlantExecutor, + Framework.OPENCLAW: GenericExecutor, # MCP uses Python; gateway uses separate runtime } framework_executor = executor_dict.get(framework) if framework_executor is None: diff --git a/runagent/utils/agent.py b/runagent/utils/agent.py index 3e3de85..9b5e3cf 100644 --- a/runagent/utils/agent.py +++ b/runagent/utils/agent.py @@ -166,8 +166,12 @@ def validate_agent( if config.framework.is_pythonic(): is_valid, details = validate_pythonic_agent(config, dynamic_loading, folder_path) - else: + elif config.framework.is_webhook(): is_valid, details = validate_webhook_agent(config, dynamic_loading, folder_path) + elif config.framework.is_service(): + is_valid, details = validate_service_agent(config, dynamic_loading, folder_path) + else: + is_valid, details = validate_pythonic_agent(config, dynamic_loading, folder_path) return is_valid, details @@ -183,6 +187,23 @@ def validate_webhook_agent(config, dynamic_loading, folder_path): warning_msgs=[], ) + +def validate_service_agent(config, dynamic_loading, folder_path): + """Validate OpenClaw and other service-type agents.""" + # If entrypoints exist (e.g. OpenClaw MCP), validate them like pythonic + if config.agent_architecture and config.agent_architecture.entrypoints: + return validate_pythonic_agent(config, dynamic_loading, folder_path) + # No entrypoints (e.g. OpenClaw Gateway) - minimal validation + return True, dict( + valid=True, + folder_exists=True, + files_found=[], + missing_files=[], + success_msgs=["Service framework detected (no entrypoints)"], + error_msgs=[], + warning_msgs=[], + ) + def validate_pythonic_agent(config, dynamic_loading, folder_path): validation_details = { diff --git a/runagent/utils/enums/framework.py b/runagent/utils/enums/framework.py index 6008615..2f2a4f9 100644 --- a/runagent/utils/enums/framework.py +++ b/runagent/utils/enums/framework.py @@ -16,6 +16,9 @@ class Framework(Enum): OPENAI = "openai" N8N = "n8n" PARLANT = "parlant" + OPENCLAW = "openclaw" + PICOCLAW = "picoclaw" + ZEROCLAW = "zeroclaw" @classmethod def _pythonic_frameworks_cache(cls) -> t.FrozenSet['Framework']: @@ -28,7 +31,11 @@ def _pythonic_frameworks_cache(cls) -> t.FrozenSet['Framework']: @classmethod def _webhook_frameworks_cache(cls) -> t.FrozenSet['Framework']: return frozenset({cls.N8N}) - + + @classmethod + def _service_frameworks_cache(cls) -> t.FrozenSet['Framework']: + return frozenset({cls.OPENCLAW, cls.PICOCLAW, cls.ZEROCLAW}) + @classmethod def get_pythonic_frameworks(cls) -> t.FrozenSet['Framework']: """Get all pythonic frameworks (cached)""" @@ -38,6 +45,11 @@ def get_pythonic_frameworks(cls) -> t.FrozenSet['Framework']: def get_webhook_frameworks(cls) -> t.FrozenSet['Framework']: """Get all webhook frameworks (cached)""" return cls._webhook_frameworks_cache() + + @classmethod + def get_service_frameworks(cls) -> t.FrozenSet['Framework']: + """Get all service frameworks (cached)""" + return cls._service_frameworks_cache() @classmethod def get_selectable_frameworks(cls) -> t.List['Framework']: @@ -70,6 +82,10 @@ def is_webhook(self) -> bool: """Check if this framework is webhook""" return self in self.get_webhook_frameworks() + def is_service(self) -> bool: + """Check if this framework is service (e.g. OpenClaw).""" + return self in self.get_service_frameworks() + def is_default(self) -> bool: """Check if this framework is the default""" return self == self.DEFAULT @@ -83,6 +99,8 @@ def category(self) -> str: return "pythonic" elif self.is_webhook(): return "webhook" + elif self.is_service(): + return "service" else: return "unknown" # Class methods for string validation and conversion diff --git a/templates/agno/default/runagent.config.json b/templates/agno/default/runagent.config.json index e61687c..5d910db 100644 --- a/templates/agno/default/runagent.config.json +++ b/templates/agno/default/runagent.config.json @@ -25,7 +25,7 @@ ] }, "env_vars": {}, - "agent_id": "ae29bd73-b3d3-99c8-a98f-5d7aec7cf911", + "agent_id": "ae29bd73-b3d3-29c8-b98f-5d7aec7cf911", "auth_settings": { "type": "api_key" } diff --git a/templates/openclaw/DEPLOYMENT_GUIDE.md b/templates/openclaw/DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..a926fe0 --- /dev/null +++ b/templates/openclaw/DEPLOYMENT_GUIDE.md @@ -0,0 +1,277 @@ +# OpenClaw Deployment Guide + +## Overview: Two Templates, One System + +OpenClaw has **two components** that work together: + +1. **Gateway** (`gateway/`) - The core OpenClaw server (Node.js) +2. **MCP** (`mcp/`) - MCP interface server (Python) that connects to Gateway + +You need **BOTH** for a complete OpenClaw deployment. + +--- + +## Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Your Application β”‚ +β”‚ (Cursor, SDK, REST API, etc.) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ OpenClaw MCP Agent (VM) β”‚ +β”‚ β€’ Runs: openclaw-mcp server β”‚ +β”‚ β€’ Exposes: MCP tools (send_message, agent_run, etc.) β”‚ +β”‚ β€’ Connects to: Gateway via WebSocket β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ WebSocket (ws://gateway_ip:18789) + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ OpenClaw Gateway Agent (VM) β”‚ +β”‚ β€’ Runs: openclaw gateway run β”‚ +β”‚ β€’ Manages: WhatsApp, Telegram, Skills, Agents β”‚ +β”‚ β€’ Stores: All data in .openclaw (persisted) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## Deployment Order + +### Step 1: Deploy Gateway (Required First) + +**Why first?** The MCP needs the Gateway's WebSocket URL to connect. + +```bash +# Deploy gateway template +runagent deploy openclaw/gateway + +# Or with SDK +from runagent import RunAgentSDK +sdk = RunAgentSDK() +gateway_result = sdk.deploy_remote( + folder="templates/openclaw/gateway", + persistent_memory=True, # CRITICAL for OpenClaw + user_id="your-user-id" +) + +gateway_deployment_id = gateway_result["deployment_id"] +``` + +**What happens:** +- Creates Gateway VM (Node.js runtime) +- Starts `openclaw gateway run` on port 18789 +- Gets VM IP (e.g., `192.168.100.5`) +- Gateway URL: `ws://192.168.100.5:18789` + +**Important:** +- βœ… **MUST** use `persistent_memory=True` (WhatsApp sessions need persistence) +- βœ… Gateway stores everything in `.openclaw` folder (persisted) + +--- + +### Step 2: Deploy MCP (After Gateway) + +**Why second?** MCP needs the Gateway URL to connect. + +```bash +# Deploy MCP template +runagent deploy openclaw/mcp + +# Or with SDK (you need to set gateway URL manually) +from runagent import RunAgentSDK +sdk = RunAgentSDK() + +# First, get gateway VM IP from gateway deployment +gateway_vm_ip = "192.168.100.5" # Get from gateway deployment + +# Deploy MCP with gateway URL +mcp_result = sdk.deploy_remote( + folder="templates/openclaw/mcp", + config={ + "env_vars": { + "OPENCLAW_GATEWAY_URL": f"ws://{gateway_vm_ip}:18789", + "OPENCLAW_GATEWAY_TOKEN": "" # If gateway uses token auth + } + } +) +``` + +**What happens:** +- Creates MCP VM (Python runtime) +- Installs `openclaw-mcp` package +- Starts `openclaw-mcp` server +- Connects to Gateway via `OPENCLAW_GATEWAY_URL` + +**Note:** Currently, you need to manually set `OPENCLAW_GATEWAY_URL`. The planned `POST /openclaw/deploy` endpoint would automate this. + +--- + +## Quick Start: Deploy Both + +### Option A: Manual Deployment (Current) + +```bash +# 1. Deploy Gateway +runagent deploy openclaw/gateway --persistent-memory + +# 2. Get Gateway VM IP from deployment result +# 3. Deploy MCP with gateway URL +runagent deploy openclaw/mcp +# Then update MCP agent env vars with gateway URL via middleware API +``` + +### Option B: SDK Deployment (Recommended) + +```python +from runagent import RunAgentSDK + +sdk = RunAgentSDK() + +# 1. Deploy Gateway +gateway_result = sdk.deploy_remote( + folder="templates/openclaw/gateway", + persistent_memory=True, + user_id="your-user-id" +) +gateway_deployment_id = gateway_result["deployment_id"] + +# 2. Get Gateway VM IP (from deployment metadata or middleware API) +# TODO: Add helper: gateway_ip = sdk.get_gateway_vm_ip(gateway_deployment_id) +gateway_ip = "192.168.100.5" # Get from deployment + +# 3. Deploy MCP with Gateway URL +mcp_result = sdk.deploy_remote( + folder="templates/openclaw/mcp", + config={ + "env_vars": { + "OPENCLAW_GATEWAY_URL": f"ws://{gateway_ip}:18789" + } + } +) +mcp_deployment_id = mcp_result["deployment_id"] +``` + +### Option C: Future - Single Deploy (Not Yet Implemented) + +```python +# This will be available when POST /openclaw/deploy is implemented +result = sdk.deploy_openclaw(persistent_memory=True) +# Automatically deploys gateway + MCP and wires them together +``` + +--- + +## Which Template Does What? + +### `gateway/` Template + +**Purpose:** Core OpenClaw server + +**Runs:** +- `openclaw gateway run` (Node.js process) +- Listens on port 18789 +- Manages WhatsApp, Telegram, Discord, etc. +- Runs AI agents and skills + +**Stores:** +- WhatsApp session data +- Channel credentials +- Agent workspaces +- Configuration + +**Deployment:** +- βœ… **MUST** use persistent storage (`persistent_memory=True`) +- βœ… Runtime: `openclaw-gateway` (Node.js) +- βœ… Needs `.openclaw` folder persisted + +**When to deploy:** +- First, before MCP +- Only one gateway per deployment (can have multiple MCPs connecting to same gateway) + +--- + +### `mcp/` Template + +**Purpose:** MCP protocol interface to Gateway + +**Runs:** +- `openclaw-mcp` (Python package) +- Exposes MCP tools (send_message, agent_run, channels_list, etc.) +- Connects to Gateway via WebSocket + +**Stores:** +- Nothing (stateless, connects remotely) + +**Deployment:** +- βœ… Runtime: `python` (standard) +- βœ… Needs `OPENCLAW_GATEWAY_URL` environment variable +- ❌ Does NOT need persistent storage + +**When to deploy:** +- After Gateway is deployed +- Can deploy multiple MCPs (for different clients/users) + +--- + +## Usage After Deployment + +### Via MCP (Cursor/Claude Desktop) + +1. Configure `.cursor/mcp.json`: +```json +{ + "mcpServers": { + "openclaw": { + "url": "wss://api.runagent.xyz/mcp/{mcp_deployment_id}" + } + } +} +``` + +2. Use in Cursor chat: + - "Send a WhatsApp message to +1234567890 saying Hello" + - "List my OpenClaw channels" + - "Run the agent: summarize my PRs" + +### Via REST API + +```python +# Use middleware REST API +import requests + +# Send message +requests.post( + f"https://api.runagent.xyz/api/v1/openclaw/{gateway_deployment_id}/send-message", + json={"channel_id": "whatsapp", "message": "Hello"}, + headers={"Authorization": f"Bearer {api_key}"} +) + +# List channels +requests.get( + f"https://api.runagent.xyz/api/v1/openclaw/{gateway_deployment_id}/channels/list", + headers={"Authorization": f"Bearer {api_key}"} +) +``` + +### Via SDK (Future) + +```python +# When sdk.openclaw() is implemented +openclaw = sdk.openclaw(gateway_deployment_id) +openclaw.send_message(target="+1234567890", message="Hello", channel="whatsapp") +openclaw.agent_run(message="What's the weather?") +``` + +--- + +## Summary + +| Template | Deploy Order | Purpose | Runtime | Persistent Storage | +|----------|--------------|---------|---------|-------------------| +| **gateway/** | 1st | Core OpenClaw server | Node.js | βœ… Required | +| **mcp/** | 2nd | MCP interface | Python | ❌ Not needed | + +**Deploy both** for a complete OpenClaw setup. Gateway first, then MCP. diff --git a/templates/openclaw/PERSISTENT_STORAGE.md b/templates/openclaw/PERSISTENT_STORAGE.md new file mode 100644 index 0000000..9353688 --- /dev/null +++ b/templates/openclaw/PERSISTENT_STORAGE.md @@ -0,0 +1,108 @@ +# OpenClaw Persistent Storage in RunAgent + +OpenClaw stores all its data (config, credentials, sessions, workspace) in the `.openclaw` folder. In RunAgent cloud, this folder must be persisted across VM restarts. + +## How It Works + +### 1. Configuration + +The gateway template includes `persistent_folders` in `runagent.config.json`: + +```json +{ + "persistent_folders": [".openclaw"] +} +``` + +### 2. Runtime Behavior + +When the VM starts with persistent storage enabled: + +1. **Persistent disk** (`/dev/vdb`) is mounted to `/persistent` +2. **Symlink created**: `/root/.openclaw` β†’ `/persistent/.openclaw` +3. **Data persists** across VM restarts, snapshots, and deployments + +### 3. What Gets Persisted + +The `.openclaw` folder contains: + +- `openclaw.json` - Gateway configuration +- `credentials/` - WhatsApp, Telegram, Discord, etc. authentication +- `workspace/` - Agent workspaces and conversation state +- `skills/` - Custom skills and their state +- `cron/` - Cron job definitions +- Other runtime data + +## Deployment Requirements + +**Gateway VM**: **MUST** use persistent storage (`persistent_memory=true`) + +Without persistent storage: +- ❌ WhatsApp sessions lost on restart +- ❌ Channel credentials lost +- ❌ Agent workspace state lost +- ❌ Configuration changes lost + +**MCP VM**: **NOT required** (connects remotely to Gateway) + +The MCP agent connects to Gateway via `OPENCLAW_GATEWAY_URL` and doesn't store local state. + +## Example: SDK Deployment with Persistent Storage + +```python +from runagent import RunAgentSDK + +sdk = RunAgentSDK() + +# Deploy gateway with persistent storage +deployment = sdk.deploy_remote( + folder="templates/openclaw/gateway", + persistent_memory=True, # CRITICAL for OpenClaw + user_id="user-123" # Required for persistent storage +) + +gateway_deployment_id = deployment["deployment_id"] +``` + +## Verification + +After deployment, verify persistent storage is working: + +```bash +# Execute command in gateway VM +sdk.execute_command( + vm_id=gateway_vm_id, + command="ls -la /root/.openclaw && test -L /root/.openclaw && echo 'Symlink OK' || echo 'No symlink'" +) +``` + +Expected output: +``` +Symlink OK +``` + +## Troubleshooting + +### Persistent storage not mounted + +**Symptom**: `/root/.openclaw` is a regular directory, not a symlink + +**Fix**: Ensure `persistent_memory=true` and `user_id` are set during deployment + +### Data lost after restart + +**Symptom**: WhatsApp sessions, credentials, or config disappear + +**Fix**: +1. Check VM has persistent disk: `lsblk` should show `/dev/vdb` +2. Check mount: `mount | grep persistent` should show `/dev/vdb on /persistent` +3. Check symlink: `ls -la /root/.openclaw` should show `-> /persistent/.openclaw` + +### Config file not found + +**Symptom**: Gateway can't find `openclaw.json` + +**Fix**: The config is auto-created on first run. If missing, check: +- Persistent storage is mounted +- Symlink exists: `/root/.openclaw` β†’ `/persistent/.openclaw` +- Permissions: `/persistent/.openclaw` should be writable diff --git a/templates/openclaw/gateway/.agent_files/.gitkeep b/templates/openclaw/gateway/.agent_files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/openclaw/gateway/README.md b/templates/openclaw/gateway/README.md new file mode 100644 index 0000000..a6c8749 --- /dev/null +++ b/templates/openclaw/gateway/README.md @@ -0,0 +1,87 @@ +# OpenClaw Gateway Template + +This template deploys the **OpenClaw Gateway** - the core OpenClaw server that runs WhatsApp, skills, and AI agents. + +> **πŸ“– See [DEPLOYMENT_GUIDE.md](../DEPLOYMENT_GUIDE.md) for complete deployment instructions and how Gateway + MCP work together.** + +## Structure + +``` +gateway/ + .agent_files/ + .gitkeep # Keeps directory in git (openclaw.json auto-created at runtime) + runagent.config.json # RunAgent config + README.md # This file +``` + +## Deployment + +### 1. Prepare Configuration + +The `.agent_files/` directory will contain: +- `openclaw.json` - OpenClaw configuration (auto-created on first run or copied from example) +- `credentials/` - WhatsApp and other channel credentials (populated during setup) + +These are **not** included in the template by default. They will be: +- Created automatically when the gateway starts +- Or uploaded as part of the agent deployment zip + +### 2. Deploy with RunAgent + +```bash +# From the gateway template directory +runagent deploy . + +# Or programmatically +from runagent import RunAgentSDK +sdk = RunAgentSDK() +result = sdk.deploy_remote(folder="templates/openclaw/gateway") +``` + +### 3. Runtime Configuration + +- **Runtime**: `openclaw-gateway` (automatically set via metadata) +- **Port**: Gateway listens on port 18789 inside the VM +- **Network**: VM gets a TAP device with an IP (e.g., 192.168.100.5) +- **Persistent storage**: `.openclaw` directory is persisted to `/persistent/.openclaw` + +#### Persistent Storage Details + +The template includes `"persistent_folders": [".openclaw"]` in `runagent.config.json`. This tells RunAgent to: + +1. **Mount persistent disk** (`/dev/vdb`) to `/persistent` when the VM starts +2. **Create symlink** `/root/.openclaw` β†’ `/persistent/.openclaw` +3. **Persist data** across VM restarts (WhatsApp sessions, config, credentials, etc.) + +**Important**: When deploying, ensure `persistent_memory=true` is set (or use the SDK's persistent storage option). Without persistent storage, `.openclaw` data will be lost on VM restart. + +The `.openclaw` folder contains: +- `openclaw.json` - Gateway configuration +- `credentials/` - WhatsApp and channel authentication data +- `workspace/` - Agent workspaces and state +- `skills/` - Custom skills +- Other runtime data + +### 4. Gateway URL + +After deployment, the gateway URL will be: +- Internal: `ws://{vm_ip}:18789` +- Public (via middleware proxy): `wss://api.runagent.xyz/openclaw/{deployment_id}/gateway` + +## Usage + +The gateway runs automatically when the VM starts. You can interact with it via: + +1. **MCP Server** - Connect the MCP agent to this gateway +2. **SDK** - Use `sdk.openclaw(deployment_id).send_message(...)` +3. **Direct CLI** - Execute commands in the VM: `openclaw channels list` + +## Notes + +- The gateway needs persistent storage for WhatsApp sessions and configuration +- Credentials must be provided during deployment or will be created on first run +- Auto-pairing will be handled by the middleware for MCP connections + +## Persistent Storage + +For detailed information about how persistent storage works for OpenClaw, see [PERSISTENT_STORAGE.md](../PERSISTENT_STORAGE.md). diff --git a/templates/openclaw/gateway/runagent.config.json b/templates/openclaw/gateway/runagent.config.json new file mode 100644 index 0000000..e4f408f --- /dev/null +++ b/templates/openclaw/gateway/runagent.config.json @@ -0,0 +1,21 @@ +{ + "agent_name": "openclaw_gateway", + "description": "OpenClaw Gateway - runs WhatsApp, skills, and agents", + "framework": "openclaw", + "template": "openclaw/gateway", + "version": "1.0.0", + "created_at": "2026-02-09T00:00:00Z", + "template_source": { + "repo_url": "https://github.com/runagent-dev/runagent.git", + "path": "templates/openclaw/gateway", + "author": "runagent" + }, + "agent_architecture": { + "entrypoints": [] + }, + "persistent_folders": ["rad/openclaw"], + "metadata": { + "runtime": "openclaw-gateway" + }, + "agent_id": "00000000-0000-0000-0000-000000000000" +} diff --git a/templates/openclaw/mcp/README.md b/templates/openclaw/mcp/README.md new file mode 100644 index 0000000..0a8ab5c --- /dev/null +++ b/templates/openclaw/mcp/README.md @@ -0,0 +1,99 @@ +# OpenClaw MCP Template + +This template deploys the **OpenClaw MCP server** - provides an MCP (Model Context Protocol) interface to the OpenClaw Gateway. + +> **πŸ“– See [DEPLOYMENT_GUIDE.md](../DEPLOYMENT_GUIDE.md) for complete deployment instructions and how Gateway + MCP work together.** + +> **⚠️ IMPORTANT: Deploy the Gateway template FIRST, then deploy this MCP template.** + +## Structure + +``` +mcp/ + agent.py # MCP runner entrypoint + requirements.txt # openclaw-mcp package + runagent.config.json # RunAgent config + README.md # This file +``` + +## Deployment + +### 1. Prerequisites + +Ensure the OpenClaw Gateway is deployed first and get its deployment ID. + +### 2. Deploy MCP with Gateway URL + +The MCP agent needs to connect to the gateway. Set `OPENCLAW_GATEWAY_URL`: + +```bash +# Deploy MCP template +runagent deploy openclaw/mcp + +# Then update env vars via middleware API with gateway URL +``` + +**Or programmatically:** + +```python +from runagent import RunAgentSDK + +sdk = RunAgentSDK() + +# Get gateway VM IP from gateway deployment (e.g., 192.168.100.5) +gateway_ip = "192.168.100.5" # Get from gateway deployment result + +# Deploy MCP with gateway URL +result = sdk.deploy_remote( + folder="templates/openclaw/mcp", + config={ + "env_vars": { + "OPENCLAW_GATEWAY_URL": f"ws://{gateway_ip}:18789", + "OPENCLAW_GATEWAY_TOKEN": "" # If gateway uses token auth + } + } +) +``` + +**Environment Variables:** +- `OPENCLAW_GATEWAY_URL` - **Required** - WebSocket URL (e.g., `ws://192.168.100.5:18789`) +- `OPENCLAW_GATEWAY_TOKEN` - Optional - Gateway auth token + +### 4. Runtime Configuration + +- **Runtime**: `python` (standard Python agent) +- **Package**: Uses `openclaw-mcp` package (private, not on PyPI) +- **Transport**: stdio (default) or streamable (HTTP) +- **Long-lived**: VM stays running to keep the MCP server active + +## Usage + +Once deployed, the MCP server is accessible via: + +1. **Cursor** - Configure `.cursor/mcp.json` to connect to the MCP endpoint +2. **Claude Desktop** - Add to MCP server configuration +3. **Other clients** - Use the MCP protocol to interact + +## MCP Operations + +The MCP server exposes OpenClaw operations as tools: + +- `openclaw_send_message` - Send WhatsApp/Telegram/etc. messages +- `openclaw_agent_run` - Run AI agent turn +- `openclaw_channels_list` - List available channels +- `openclaw_gateway_status` - Check gateway health +- And 40+ more tools... + +See the [mcp-openclaw README](https://github.com/your-org/mcp-openclaw) for full tool list. + +## Connection Flow + +``` +Client (Cursor) β†’ MCP Server (this agent) β†’ OpenClaw Gateway β†’ WhatsApp/Skills +``` + +## Notes + +- The MCP agent must be able to reach the gateway VM (same network or via proxy) +- Device pairing is automated during deployment +- MCP runs indefinitely to keep the VM alive diff --git a/templates/openclaw/mcp/agent.py b/templates/openclaw/mcp/agent.py new file mode 100644 index 0000000..844ef5e --- /dev/null +++ b/templates/openclaw/mcp/agent.py @@ -0,0 +1,76 @@ +""" +OpenClaw MCP Agent - Runs openclaw-mcp server. + +This agent keeps the VM running and exposes the MCP server. +""" + +import os +import subprocess +import sys +from pathlib import Path + + +def run_mcp(): + """ + Start openclaw-mcp server in background. + + Spawns the MCP server process and returns immediately. The process + runs in background; VM stays alive. The MCP server connects to + OpenClaw Gateway via OPENCLAW_GATEWAY_URL. + """ + gateway_url = os.environ.get("OPENCLAW_GATEWAY_URL") + gateway_token = os.environ.get("OPENCLAW_GATEWAY_TOKEN") + + if not gateway_url: + return { + "success": False, + "error": "OPENCLAW_GATEWAY_URL environment variable not set", + "message": "Set OPENCLAW_GATEWAY_URL to the gateway WebSocket URL (e.g., ws://192.168.100.5:18789)", + } + + env = os.environ.copy() + env["OPENCLAW_GATEWAY_URL"] = gateway_url + if gateway_token: + env["OPENCLAW_GATEWAY_TOKEN"] = gateway_token + + transport = os.environ.get("OPENCLAW_MCP_TRANSPORT", "streamable") + port = int(os.environ.get("OPENCLAW_MCP_PORT", "8000")) + + try: + if transport == "streamable": + cmd = ["openclaw-mcp", "--transport", "streamable", "--port", str(port)] + else: + cmd = ["openclaw-mcp"] + + print(f"Starting openclaw-mcp with gateway: {gateway_url}", file=sys.stderr) + print(f"Transport: {transport}, port: {port}", file=sys.stderr) + + process = subprocess.Popen( + cmd, + env=env, + stdout=sys.stdout, + stderr=sys.stderr, + text=True, + start_new_session=True, + ) + + return { + "success": True, + "message": "MCP server started in background", + "pid": process.pid, + "port": port if transport == "streamable" else None, + "gateway_url": gateway_url, + } + + except FileNotFoundError: + return { + "success": False, + "error": "openclaw-mcp not found", + "message": "Install openclaw-mcp: pip install openclaw-mcp", + } + except Exception as e: + return { + "success": False, + "error": str(e), + "message": f"Failed to start openclaw-mcp: {e}", + } diff --git a/templates/openclaw/mcp/requirements.txt b/templates/openclaw/mcp/requirements.txt new file mode 100644 index 0000000..6c50e45 --- /dev/null +++ b/templates/openclaw/mcp/requirements.txt @@ -0,0 +1 @@ +openclaw-mcp diff --git a/templates/openclaw/mcp/runagent.config.json b/templates/openclaw/mcp/runagent.config.json new file mode 100644 index 0000000..1070dca --- /dev/null +++ b/templates/openclaw/mcp/runagent.config.json @@ -0,0 +1,27 @@ +{ + "agent_name": "openclaw_mcp", + "description": "OpenClaw MCP Server - provides MCP interface to OpenClaw Gateway", + "framework": "openclaw", + "template": "openclaw/mcp", + "version": "1.0.0", + "created_at": "2026-02-09T00:00:00Z", + "template_source": { + "repo_url": "https://github.com/runagent-dev/runagent.git", + "path": "templates/openclaw/mcp", + "author": "runagent" + }, + "agent_architecture": { + "entrypoints": [ + { + "file": "agent.py", + "module": "run_mcp", + "tag": "mcp" + } + ] + }, + "env_vars": { + "OPENCLAW_GATEWAY_URL": "", + "OPENCLAW_GATEWAY_TOKEN": "" + }, + "agent_id": "00000000-0000-0000-0000-000000000000" +} diff --git a/templates/picoclaw/gateway/README.md b/templates/picoclaw/gateway/README.md new file mode 100644 index 0000000..3d41be4 --- /dev/null +++ b/templates/picoclaw/gateway/README.md @@ -0,0 +1,63 @@ +## Picoclaw Gateway Template + +This template deploys a **Picoclaw** gateway as a long‑running service on RunAgent Serverless. + +- **Framework**: `picoclaw` +- **Template shortcut**: `picoclaw/gateway` +- **CLI shortcut**: `runagent deploy picoclaw` + +### What this deployment does + +- Starts a microVM based on a picoclaw‑specific image (see `Dockerfile.picoclaw-gateway` in `runagent-serverless`). +- Runs the `picoclaw gateway` process inside the VM. +- Persists all Picoclaw data under: + - `/root/.picoclaw` inside the VM + - Backed by the Firecracker data disk via `/persistent/.picoclaw` + +### Persistent data + +Picoclaw stores configuration and workspace under `~/.picoclaw` by default: + +- `~/.picoclaw/config.json` – main configuration file (models, channels, tools, etc.). +- `~/.picoclaw/workspace/` – sessions, memory, cron jobs, skills, and heartbeat docs. + +This template declares: + +```json +{ + "persistent_folders": [".picoclaw"] +} +``` + +The serverless image mounts the VM data disk at `/persistent` and symlinks: + +```text +/root/.picoclaw -> /persistent/.picoclaw +``` + +so that all configuration and workspace data survive VM restarts as long as the disk is preserved. + +### How to deploy + +From a configured `runagent` CLI: + +```bash +runagent deploy picoclaw +# or explicitly: +runagent deploy picoclaw/gateway +``` + +The CLI will: + +1. Resolve the `picoclaw` shortcut to this template folder. +2. Register the agent if needed and upload a minimal package containing `runagent.config.json`. +3. Ask the serverless engine to create and start a microVM using the picoclaw gateway image. + +### After deployment + +Once deployment succeeds you can: + +- Configure Picoclaw inside the VM by editing `~/.picoclaw/config.json` (mounted from `/persistent/.picoclaw/config.json`). +- Use Picoclaw channels (Telegram, Discord, WhatsApp, etc.) as documented in the upstream Picoclaw README. +- Rely on Picoclaw’s heartbeat and cron features to run periodic tasks from the workspace. + diff --git a/templates/picoclaw/gateway/runagent.config.json b/templates/picoclaw/gateway/runagent.config.json new file mode 100644 index 0000000..76736a6 --- /dev/null +++ b/templates/picoclaw/gateway/runagent.config.json @@ -0,0 +1,24 @@ +{ + "agent_name": "picoclaw_gateway", + "description": "Picoclaw Gateway - ultra-light persistent AI assistant", + "framework": "picoclaw", + "template": "picoclaw/gateway", + "version": "1.0.0", + "created_at": "2026-02-16T00:00:00Z", + "template_source": { + "repo_url": "https://github.com/runagent-dev/runagent.git", + "path": "templates/picoclaw/gateway", + "author": "runagent" + }, + "agent_architecture": { + "entrypoints": [] + }, + "persistent_folders": [ + ".picoclaw" + ], + "metadata": { + "runtime": "picoclaw-gateway" + }, + "agent_id": "00000000-0000-0000-0000-000000000000" +} + diff --git a/templates/zeroclaw/gateway/README.md b/templates/zeroclaw/gateway/README.md new file mode 100644 index 0000000..bce47da --- /dev/null +++ b/templates/zeroclaw/gateway/README.md @@ -0,0 +1,30 @@ +# zeroclaw/gateway + +This template deploys a 24/7 ZeroClaw runtime inside a RunAgent serverless microVM. + +## What gets deployed +- A long-lived ZeroClaw service (the microVM keeps running) +- A persistent workspace under: + - config: `/root/.zeroclaw/config.toml` + - workspace: `/root/.zeroclaw/workspace` +- Persistent storage is backed by the VM data disk via `/persistent/.zeroclaw`. + +## Deploy +From your host: + +```bash +runagent deploy zeroclaw/gateway +# or (shortcut) +runagent deploy zeroclaw +``` + +## After deployment +1. Get the `Agent ID` from the deploy output. +2. Connect/configure using ZeroClaw configuration tools (or via your UI/Nanobot). +3. Ensure channels are bound in ZeroClaw `config.toml` / runtime as required. + +## Internal runtime +The serverless engine starts the microVM with a ZeroClaw-specific image that runs: +- `zeroclaw daemon` +- `zeroclaw gateway` (webhook gateway/server) + diff --git a/templates/zeroclaw/gateway/runagent.config.json b/templates/zeroclaw/gateway/runagent.config.json new file mode 100644 index 0000000..0f676eb --- /dev/null +++ b/templates/zeroclaw/gateway/runagent.config.json @@ -0,0 +1,23 @@ +{ + "agent_name": "zeroclaw_gateway", + "description": "ZeroClaw Gateway - ultra-light persistent agent runtime", + "framework": "zeroclaw", + "template": "zeroclaw/gateway", + "version": "1.0.0", + "created_at": "2026-03-16T00:00:00Z", + "template_source": { + "repo_url": "https://github.com/runagent-dev/runagent.git", + "path": "templates/zeroclaw/gateway", + "author": "runagent" + }, + "agent_architecture": { + "entrypoints": [] + }, + "persistent_folders": [ + ".zeroclaw" + ], + "metadata": { + "runtime": "zeroclaw-gateway" + }, + "agent_id": "00000000-0000-0000-0000-000000000000" +} diff --git a/test_scripts/dart/test_agno/.dart_tool/package_config.json b/test_scripts/dart/test_agno/.dart_tool/package_config.json index a6401df..3373d26 100644 --- a/test_scripts/dart/test_agno/.dart_tool/package_config.json +++ b/test_scripts/dart/test_agno/.dart_tool/package_config.json @@ -3,91 +3,91 @@ "packages": [ { "name": "async", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/async-2.13.0", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/async-2.13.0", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "collection", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/collection-1.19.1", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/collection-1.19.1", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "crypto", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/crypto-3.0.7", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/crypto-3.0.7", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "http", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http-1.6.0", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/http-1.6.0", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "http_parser", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http_parser-4.1.2", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/http_parser-4.1.2", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "meta", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/meta-1.17.0", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/meta-1.18.2", "packageUri": "lib/", "languageVersion": "3.5" }, { "name": "path", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/path-1.9.1", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/path-1.9.1", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "runagent", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/runagent-0.1.41", + "rootUri": "file:///root/runagent/runagent-dart", "packageUri": "lib/", "languageVersion": "3.0" }, { "name": "source_span", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/source_span-1.10.1", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/source_span-1.10.2", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "stream_channel", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", "packageUri": "lib/", "languageVersion": "3.3" }, { "name": "string_scanner", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "term_glyph", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "typed_data", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/typed_data-1.4.0", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/typed_data-1.4.0", "packageUri": "lib/", "languageVersion": "3.5" }, { "name": "web", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web-0.5.1", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/web-0.5.1", "packageUri": "lib/", "languageVersion": "3.3" }, { "name": "web_socket_channel", - "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.5", + "rootUri": "file:///root/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.5", "packageUri": "lib/", "languageVersion": "3.3" }, @@ -99,8 +99,6 @@ } ], "generator": "pub", - "generatorVersion": "3.10.0", - "flutterRoot": "file:///home/azureuser/snap/flutter/common/flutter", - "flutterVersion": "3.38.2", - "pubCache": "file:///home/azureuser/.pub-cache" + "generatorVersion": "3.11.3", + "pubCache": "file:///root/.pub-cache" } diff --git a/test_scripts/dart/test_agno/.dart_tool/package_graph.json b/test_scripts/dart/test_agno/.dart_tool/package_graph.json index 3b9e91c..4b00fb1 100644 --- a/test_scripts/dart/test_agno/.dart_tool/package_graph.json +++ b/test_scripts/dart/test_agno/.dart_tool/package_graph.json @@ -13,7 +13,7 @@ }, { "name": "runagent", - "version": "0.1.41", + "version": "0.1.43", "dependencies": [ "http", "web_socket_channel" @@ -68,7 +68,7 @@ }, { "name": "meta", - "version": "1.17.0", + "version": "1.18.2", "dependencies": [] }, { @@ -102,7 +102,7 @@ }, { "name": "source_span", - "version": "1.10.1", + "version": "1.10.2", "dependencies": [ "collection", "path", diff --git a/test_scripts/dart/test_agno/.dart_tool/pub/bin/test_agno/test_agno.dart-3.11.3.snapshot b/test_scripts/dart/test_agno/.dart_tool/pub/bin/test_agno/test_agno.dart-3.11.3.snapshot new file mode 100644 index 0000000..1a4a749 Binary files /dev/null and b/test_scripts/dart/test_agno/.dart_tool/pub/bin/test_agno/test_agno.dart-3.11.3.snapshot differ diff --git a/test_scripts/dart/test_agno/lib/main.dart b/test_scripts/dart/test_agno/bin/test_agno.dart similarity index 95% rename from test_scripts/dart/test_agno/lib/main.dart rename to test_scripts/dart/test_agno/bin/test_agno.dart index 2c1e7cb..2d023c7 100644 --- a/test_scripts/dart/test_agno/lib/main.dart +++ b/test_scripts/dart/test_agno/bin/test_agno.dart @@ -40,8 +40,10 @@ Future main() async { try { final client = await RunAgentClient.create( RunAgentClientConfig.create( - agentId: 'ae29bd73-b3d3-42c8-a98f-5d7aec7ee919', + agentId: 'ae29bd73-b3d3-29c8-b98f-5d7aec7cf911', entrypointTag: 'agno_print_response_stream', + local: false + ), ); diff --git a/test_scripts/python/client_test_agno.py b/test_scripts/python/client_test_agno.py index 7fa43f0..e2fc0f3 100644 --- a/test_scripts/python/client_test_agno.py +++ b/test_scripts/python/client_test_agno.py @@ -1,7 +1,7 @@ from runagent import RunAgentClient ra = RunAgentClient( - agent_id="ae29bd73-b3d3-99c8-a98f-5d7aec7cf911", + agent_id="ae29bd73-b3d3-29c8-b98f-5d7aec7cf911", entrypoint_tag="agno_print_response", local=False )