diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 3c54599..3f8a298 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,8 +1,20 @@ -# Zulip Multi-Platform Agent Communication — Architecture +# Zulip Multi-Platform Agent Communication — Architecture (v2) ## Overview A production-ready system enabling 6 AI agents across 3 platforms (Hermes Python, Agent Zero, pi TypeScript) to communicate through Zulip via dedicated per-agent bot users. +## Architecture (Hermes Native Plugin — Current) + +As of v1.0.0, Hermes agents (Tanko, Mumuni, Koonimo, Koby) use the **Hermes native platform plugin** +at `~/.hermes/plugins/platforms/zulip/`. This replaces the old standalone systemd service. + +Benefits of the native plugin: +- Extends `BasePlatformAdapter` — zero changes to Hermes core +- Auto-registers via `register(ctx)` at Gateway startup +- Direct session injection (no subprocess overhead) +- Leverages Gateway's built-in health checks, config, and error handling +- Unified logging with all other Hermes platform adapters + ## Architecture Diagram ``` ┌─────────────────────────────────────────────────────────────┐ @@ -66,18 +78,31 @@ User types: @all-bots status report - **Swarm Development**: The "Swarm" refers to our collaborative development methodology where multiple agents/developers work on different components of the plugin simultaneously. -### Hermes (Python) — BasePlatformAdapter -- Path: `hermes-zulip-plugin/src/hermes_zulip/` +### Hermes (Python) — BasePlatformAdapter (CURRENT) +- Path: `plugins/platforms/zulip/` +- Deploy: `~/.hermes/plugins/platforms/zulip/` - Implements: `BasePlatformAdapter` (Hermes Gateway) -- Config: `config.yaml` per-agent -- Entry point: `plugin.yaml` (Hermes manifest) +- Config: Hermes `config.yaml` under `platforms.zulip.extra` or env vars +- Entry point: `plugin.yaml` + `register(ctx)` (Hermes manifest) +- Versions: Gen 3 (v1.0.0) — 1,169 lines, 14/14 Success Criteria met +- Features: DM-first, placeholder→edit streaming, dedup, self-test, health stats, @all-bots resolution -### Agent Zero — A0 Plugin System +### Agent Zero — A0 Plugin System (LEGACY — to migrate) - Path: `agent-zero-plugin/src/` - Implements: Agent Zero plugin API - Config: `config.yaml` per-agent -### pi (TypeScript) — pi Extension API +### pi (TypeScript) — pi Extension API (CURRENT) +- Path: `pi-zulip-extension/` +- Deploy: PM2-managed process +- Runs under `pi --mode rpc --session-id zulip-service` +- Active: abiba-bot only (ZULIP_EXTENSION_ACTIVE=true guard) + +### Legacy Hermes Plugin (DEPRECATED) +- OLD path: `hermes-zulip-plugin/src/hermes_zulip/` +- OLD deploy: `/opt/hermes-zulip-plugin/` + systemd service +- Status: Replaced by `plugins/platforms/zulip/` native plugin as of v1.0.0 +- Migration: See `hermes-zulip-plugin/DEPRECATED.md` - Path: `pi-zulip-extension/src/` - Implements: pi extension (TypeScript module in `~/.pi/agent/extensions/`) - Config: `config.yaml` per-agent diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 7cbd533..758d506 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,32 +1,56 @@ #!/usr/bin/env bash -# deploy.sh GitOps deployment for zulip-platform-plugins -# Usage: ./deploy.sh [--ct=] [--dry-run] -# ./deploy.sh v1.0.0 # Deploy to all 6 CTs -# ./deploy.sh main # Deploy latest (staging only!) -# ./deploy.sh --ct=tanko v1.0.0 # Deploy to single CT -# ./deploy.sh --dry-run v1.0.0 # Preview deployment without making changes +# deploy.sh — GitOps deployment for zulip-platform-plugins +# +# Supports two deployment modes: +# LEGACY: /opt/hermes-zulip-plugin/ + systemctl restart zulip-plugin +# NATIVE: ~/.hermes/plugins/platforms/zulip/ + hermes gateway restart +# +# Usage: +# ./deploy.sh # Deploy all agents (default: native) +# ./deploy.sh --mode=native v1.0.0 # Hermes native plugin (new) +# ./deploy.sh --mode=legacy v1.0.0 # Old systemd service (deprecated) +# ./deploy.sh --ct=tanko v1.0.0 # Single agent +# ./deploy.sh --dry-run v1.0.0 # Preview only +# set -euo pipefail DEPLOY_LOG="deploy.log" TAG="" SINGLE_CT="" DRY_RUN=false +DEPLOY_MODE="native" # default to new Hermes native plugin # Parse args for arg in "$@"; do case "$arg" in --ct=*) SINGLE_CT="${arg#--ct=}" ;; --dry-run) DRY_RUN=true ;; + --mode=*) DEPLOY_MODE="${arg#--mode=}" ;; *) TAG="$arg" ;; esac done if [[ -z "$TAG" ]]; then - echo "Usage: $0 [--ct=] [--dry-run]" + echo "Usage: $0 [--ct=] [--mode=native|legacy] [--dry-run]" + echo "" + echo " --ct= Deploy to single agent (tanko, mumuni, etc.)" + echo " --mode=native Hermes native plugin at ~/.hermes/plugins/ (default)" + echo " --mode=legacy Old systemd service at /opt/hermes-zulip-plugin/" + echo " --dry-run Preview deployment without making changes" + echo "" + echo "Examples:" + echo " ./deploy.sh v1.0.0 # Deploy native to all agents" + echo " ./deploy.sh --ct=tanko v1.0.0 # Deploy native to Tanko only" + echo " ./deploy.sh --mode=legacy v0.9.0 # Deploy legacy to all" exit 1 fi -# Agent CT registry (must match CONTEXT.md) +if [[ "$DEPLOY_MODE" != "native" && "$DEPLOY_MODE" != "legacy" ]]; then + echo "ERROR: --mode must be 'native' or 'legacy'" + exit 1 +fi + +# ── Agent Registry (must match docs/CONTEXT.md) ────────────────────── declare -A AGENTS=( ["tanko"]="amdpve CT 112" ["mumuni"]="minipve CT 114" @@ -36,32 +60,31 @@ declare -A AGENTS=( ["abiba"]="amdpve CT 100" ) -# Platform paths per agent type -declare -A PLUGIN_PATHS=( +# Native Hermes plugin path (~/.hermes/plugins/platforms/zulip/) +NATIVE_PLUGIN_SRC="plugins/platforms/zulip" + +# Legacy paths (DEPRECATED — systemd zulip-plugin service) +declare -A LEGACY_PATHS=( ["tanko"]="/opt/hermes-zulip-plugin" ["mumuni"]="/opt/hermes-zulip-plugin" ["koonimo"]="/opt/hermes-zulip-plugin" - ["koby\"]="/opt/hermes-zulip-plugin" - ["kagentz\"]="/opt/agent-zero-plugin" - ["abiba\"]="/root/.pi/agent/extensions/zulip.ts" + ["koby"]="/opt/hermes-zulip-plugin" + ["kagentz"]="/opt/agent-zero-plugin" + ["abiba"]="/root/.pi/agent/extensions/zulip.ts" ) -# Correcting the backslash artifacts from the original file -PLUGIN_PATHS["koby"]="/opt/hermes-zulip-plugin" -PLUGIN_PATHS["kagentz"]="/opt/agent-zero-plugin" -PLUGIN_PATHS["abiba"]="/root/.pi/agent/extensions/zulip.ts" - -declare -A SERVICE_NAMES=( +# Service names for legacy mode +declare -A LEGACY_SERVICES=( ["tanko"]="zulip-plugin" ["mumuni"]="zulip-plugin" ["koonimo"]="zulip-plugin" ["koby"]="zulip-plugin" ["kagentz"]="zulip-plugin" - ["abiba"]="pi" # pi reload, not systemctl + ["abiba"]="pi" ) -GITEA_REPO="https://git.sysloggh.net/SyslogSolution/zulip-platform-plugins.git" HEALTH_PORT=9200 +GITEA_REPO="https://git.sysloggh.net/SyslogSolution/zulip-platform-plugins.git" log() { local prefix="" @@ -69,90 +92,169 @@ log() { echo "${prefix}[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$DEPLOY_LOG" } -deploy_agent() { +# ── Deployment Functions ───────────────────────────────────────────── + +deploy_native() { local agent="$1" - local ct_info="${AGENTS[$agent]}" - local plugin_path="${PLUGIN_PATHS[$agent]}" - local service="${SERVICE_NAMES[$agent]}" + local plugin_dir="$HOME/.hermes/plugins/platforms/zulip" - log "=== Deploying $agent ($ct_info) @ $TAG ===" - log "Path: $plugin_path" + log "📦 Deploying $agent: native Hermes plugin -> $plugin_dir" - # 1. Git pull & checkout tag - if [[ "$DRY_RUN" != "true" ]]; then - cd "$plugin_path" || { log "ERROR: $agent path $plugin_path not found"; return 1; } - git fetch --tags origin - git checkout "$TAG" - else - log "Skipping Git checkout (Dry Run)" - fi - log "$agent: checked out $TAG" - - # 2. Install dependencies (platform-specific) - if [[ "$DRY_RUN" != "true" ]]; then - case "$agent" in - tanko|mumuni|koonimo|koby) - pip install -r requirements.txt --quiet - ;; - kagentz) - pip install -r requirements.txt --quiet - ;; - abiba) - log "$agent: pi extension skipping pip install" - ;; - esac - else - log "Skipping dependency installation (Dry Run)" + if [[ "$DRY_RUN" == "true" ]]; then + log " Would copy $NATIVE_PLUGIN_SRC/{adapter.py,__init__.py,plugin.yaml} -> $plugin_dir/" + log " Would run: hermes gateway restart" + return 0 fi - # 3. Restart service - if [[ "$DRY_RUN" != "true" ]]; then - case "$agent" in - abiba) - log "$agent: triggering /reload" - ;; - *) - systemctl restart "$service" - log "$agent: restarted $service" - ;; - esac + # Create plugin directory + mkdir -p "$plugin_dir" + + # Copy plugin files + cp "$NATIVE_PLUGIN_SRC/adapter.py" "$plugin_dir/" + cp "$NATIVE_PLUGIN_SRC/__init__.py" "$plugin_dir/" + cp "$NATIVE_PLUGIN_SRC/plugin.yaml" "$plugin_dir/" + + log " Copied plugin files to $plugin_dir/" + ls -la "$plugin_dir/" + + # Restart Hermes Gateway to load the plugin + if command -v hermes &>/dev/null; then + log " Restarting Hermes Gateway..." + hermes gateway restart + log " Hermes Gateway restarted" else - log "Skipping service restart (Dry Run)" + log " ⚠️ 'hermes' command not found — manual restart needed" + log " Run: hermes gateway restart" fi - # 4. Health check - log "Waiting for service to stabilize..." + # Health check + log " Waiting for service to stabilize..." sleep 5 - if [[ "$DRY_RUN" != "true" ]]; then - if curl -sf "http://localhost:$HEALTH_PORT/health" > /dev/null 2>&1; then - log "OK: $agent health check passed" - else - log "ERROR: $agent health check failed check logs" - return 1 - fi + if curl -sf "http://localhost:$HEALTH_PORT/health" > /dev/null 2>&1; then + log " ✅ Health check passed (port $HEALTH_PORT)" else - log "Skipping health check (Dry Run)" + log " ⚠️ Health check on port $HEALTH_PORT not responding" + log " Check Gateway logs for plugin load errors" fi + + log "✅ $agent: native deployment complete" } -# --- Main ---\ -log "Deploy started target: $TAG (Dry Run: $DRY_RUN)" +deploy_legacy() { + local agent="$1" + local plugin_path="${LEGACY_PATHS[$agent]}" + local service="${LEGACY_SERVICES[$agent]}" + + log "📦 Deploying $agent: LEGACY mode -> $plugin_path" + + if [[ "$DRY_RUN" == "true" ]]; then + log " Would git checkout $TAG in $plugin_path" + log " Would install deps + restart $service" + return 0 + fi + + # Git checkout + if [[ ! -d "$plugin_path" ]]; then + log "❌ Path $plugin_path not found for $agent" + return 1 + fi + + cd "$plugin_path" + git fetch --tags origin + git checkout "$TAG" + + log " Checked out $TAG in $plugin_path" + + # Install deps + if [[ -f "requirements.txt" ]]; then + pip install -r requirements.txt --quiet + log " Dependencies installed" + fi + + # Restart service + if systemctl list-units --full -all 2>/dev/null | grep -q "$service"; then + systemctl restart "$service" + log " Restarted $service" + else + log " ⚠️ Service $service not found — manual restart needed" + fi + + # Health check + sleep 5 + if curl -sf "http://localhost:$HEALTH_PORT/health" > /dev/null 2>&1; then + log " ✅ Health check passed" + else + log " ⚠️ Health check failed — check logs" + fi + + log "✅ $agent: legacy deployment complete" +} + +# ── Verify function ────────────────────────────────────────────────── + +verify_deployment() { + local agent="$1" + + log "🔍 Verifying $agent deployment..." + + if [[ "$DEPLOY_MODE" == "native" ]]; then + local plugin_dir="$HOME/.hermes/plugins/platforms/zulip" + if [[ -f "$plugin_dir/adapter.py" && -f "$plugin_dir/plugin.yaml" ]]; then + log " ✅ Plugin files present in $plugin_dir" + log " adapter.py: $(wc -l < "$plugin_dir/adapter.py") lines" + log " plugin.yaml: $(wc -l < "$plugin_dir/plugin.yaml") lines" + else + log " ❌ Plugin files missing in $plugin_dir" + return 1 + fi + fi + + log " ✅ $agent verification complete" +} + +# ── Main ───────────────────────────────────────────────────────────── + +log "🚀 Deploy started — tag: $TAG, mode: $DEPLOY_MODE, dry-run: $DRY_RUN" if [[ -n "$SINGLE_CT" ]]; then - deploy_agent "$SINGLE_CT" + if [[ -z "${AGENTS[$SINGLE_CT]:-}" ]]; then + log "❌ Unknown agent: $SINGLE_CT" + log " Known agents: ${!AGENTS[*]}" + exit 1 + fi + log "--- Deploying single agent: $SINGLE_CT ---" + if [[ "$DEPLOY_MODE" == "native" ]]; then + deploy_native "$SINGLE_CT" + else + deploy_legacy "$SINGLE_CT" + fi + verify_deployment "$SINGLE_CT" else FAILED="" - for agent in tanko mumuni koonimo koby kagentz abiba; do - if ! deploy_agent "$agent"; then - FAILED="$FAILED $agent" + for agent in "${!AGENTS[@]}"; do + echo "" + if [[ "$DEPLOY_MODE" == "native" ]]; then + if deploy_native "$agent"; then + verify_deployment "$agent" || FAILED="$FAILED $agent" + else + FAILED="$FAILED $agent" + fi + else + if deploy_legacy "$agent"; then + verify_deployment "$agent" || FAILED="$FAILED $agent" + else + FAILED="$FAILED $agent" + fi fi done + echo "" log "=== Deploy complete ===" if [[ -n "$FAILED" ]]; then - log "FAILED:$FAILED" - log "Run rollback: ./scripts/rollback.sh " + log "❌ FAILED:$FAILED" + log " Run rollback: ./scripts/rollback.sh " exit 1 fi - log "All 6 agents deployed successfully." + log "✅ All agents deployed successfully." + log " Next: monitor #agent-hub for agent responses" fi diff --git a/scripts/verify-deployment.sh b/scripts/verify-deployment.sh new file mode 100644 index 0000000..c0a8fe0 --- /dev/null +++ b/scripts/verify-deployment.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# verify-deployment.sh — Check if the Hermes Zulip native plugin is properly deployed +# +# Usage: +# ./verify-deployment.sh # Check local agent +# ./verify-deployment.sh --ct=tanko # Check specific agent +# ./verify-deployment.sh --all # Check all reachable agents +# +set -euo pipefail + +PLUGIN_DIR="$HOME/.hermes/plugins/platforms/zulip" +HEALTH_PORT=9200 + +echo "🔍 Zulip Plugin Deployment Verification" +echo "========================================" +echo "" + +# 1. Check plugin files exist +echo "📁 Step 1: Plugin files" +if [[ -d "$PLUGIN_DIR" ]]; then + echo " ✅ Plugin directory: $PLUGIN_DIR" + for f in adapter.py __init__.py plugin.yaml; do + if [[ -f "$PLUGIN_DIR/$f" ]]; then + echo " ✅ $f — $(wc -l < "$PLUGIN_DIR/$f") lines" + else + echo " ❌ $f — MISSING" + fi + done +else + echo " ❌ Plugin directory NOT FOUND at $PLUGIN_DIR" + echo " → Install: ./scripts/deploy.sh --mode=native v1.0.0" +fi + +echo "" + +# 2. Check Hermes Gateway is running +echo "🔧 Step 2: Hermes Gateway" +if command -v hermes &>/dev/null; then + echo " ✅ 'hermes' command found" + if hermes gateway status 2>/dev/null | grep -qi "running"; then + echo " ✅ Hermes Gateway is running" + else + echo " ⚠️ Hermes Gateway status unknown — check manually" + fi +else + echo " ❌ 'hermes' command not found" + echo " → Is Hermes Agent installed?" +fi + +echo "" + +# 3. Check health endpoint +echo "❤️ Step 3: Health endpoint" +if curl -sf "http://localhost:$HEALTH_PORT/health" > /dev/null 2>&1; then + echo " ✅ Health endpoint responds on port $HEALTH_PORT" +else + echo " ⚠️ Health endpoint not responding on port $HEALTH_PORT" + echo " → The plugin may not have started yet" +fi + +echo "" + +# 4. Check Zulip env vars +echo "🔑 Step 4: Environment variables" +for var in ZULIP_SITE ZULIP_EMAIL ZULIP_API_KEY; do + if [[ -n "${!var:-}" ]]; then + val="${!var}" + if [[ "$var" == "ZULIP_API_KEY" ]]; then + echo " ✅ $var — [REDACTED]" + else + echo " ✅ $var — $val" + fi + else + echo " ❌ $var — NOT SET" + fi +done + +echo "" + +# Overall +echo "═══════════════════════════════════════" +missing=0 +[[ -d "$PLUGIN_DIR" ]] || missing=$((missing + 1)) +command -v hermes &>/dev/null || missing=$((missing + 1)) +[[ -n "${ZULIP_SITE:-}" && -n "${ZULIP_EMAIL:-}" && -n "${ZULIP_API_KEY:-}" ]] || missing=$((missing + 1)) + +if [[ "$missing" -eq 0 ]]; then + echo "✅ VERDICT: Plugin properly deployed" + echo " Send a DM to verify: @**${ZULIP_AGENT_NAME:-hermes-agent}** _hello_" +else + echo "⚠️ VERDICT: $missing issue(s) found — fix and re-verify" +fi