diff --git a/dev-scripts/agent/create-full.sh b/dev-scripts/agent/create-full.sh index d98ac3c..9ad8b1b 100755 --- a/dev-scripts/agent/create-full.sh +++ b/dev-scripts/agent/create-full.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # create-full.sh — pipeline completo para crear un agente o robot funcional # -# Pipeline de 7 pasos: +# Pipeline de 7+1 pasos: # 1. SCAFFOLD → crear archivos base desde template # 2. BUILD → go build -tags goolm ./... # 3. REGISTER → crear usuario Matrix + token @@ -9,17 +9,34 @@ # 5. CONVERT (robot) → eliminar LLM/prompts si type=robot # 6. AUTO-AVATAR → generar y aplicar foto de perfil # 7. DISPLAY NAME → configurar nombre visible en Matrix +# 8. PERSONALIZE → (automatico si se pasan --description / --system-prompt) # -# Pasos posteriores (manuales o via father-bot): -# 8. PERSONALIZE → config.yaml, agent.go, system prompt +# Pasos posteriores: # 9. REBUILD → recompilar tras personalizacion # 10. START/RESTART → arrancar el launcher con el bot # 11. HEALTH CHECK → ./dev-scripts/agent/health-check.sh # 12. SELF-INTRODUCE → ./dev-scripts/agent/notify-developer.sh # -# Uso: -# ./dev-scripts/agent/create-full.sh "Display Name" # agente (default) -# ./dev-scripts/agent/create-full.sh "Display Name" --type robot # robot +# Uso básico: +# ./dev-scripts/agent/create-full.sh "Display Name" +# ./dev-scripts/agent/create-full.sh "Display Name" --type robot +# +# Uso con personalización automática (Paso 8 incluido): +# ./dev-scripts/agent/create-full.sh weather-bot "Weather Bot" \ +# --description "Consulta el tiempo actual y predicciones" \ +# --provider anthropic \ +# --system-prompt "Eres Weather Bot, especialista en meteorología." +# +# Flags de personalización (opcionales, activan el Paso 8 automático): +# --description "" descripcion del agente +# --provider proveedor LLM (default: auto-detect) +# --model modelo LLM (default: segun provider) +# --tone tono (default: friendly) +# --prefix "" emoji prefix (default: 🤖) +# --system-prompt "" system prompt inline +# --system-prompt-file system prompt desde archivo +# --tool-use habilitar tool_use en config +# --language idioma (default: es) # # Requisitos en .env: # MATRIX_ADMIN_TOKEN, MATRIX_HOMESERVER, MATRIX_SERVER_NAME @@ -36,21 +53,42 @@ TYPE="agent" NORM="$(normalize_id "$ID")" SCRIPT_DIR="$(dirname "$0")" -# Parse --type flag +# Flags de personalización (Paso 8) +PERSONALIZE_DESCRIPTION="" +PERSONALIZE_PROVIDER="" +PERSONALIZE_MODEL="" +PERSONALIZE_TONE="friendly" +PERSONALIZE_PREFIX="🤖" +PERSONALIZE_SYSTEM_PROMPT="" +PERSONALIZE_SYSTEM_PROMPT_FILE="" +PERSONALIZE_TOOL_USE=false +PERSONALIZE_LANGUAGE="es" +DO_PERSONALIZE=false + +# Parse flags shift 2 2>/dev/null || shift 1 2>/dev/null || true while [[ $# -gt 0 ]]; do case "$1" in - --type) - TYPE="${2:-agent}" - shift 2 - ;; - --type=*) - TYPE="${1#--type=}" - shift - ;; - *) - shift - ;; + --type) TYPE="${2:-agent}"; shift 2 ;; + --type=*) TYPE="${1#--type=}"; shift ;; + --description) PERSONALIZE_DESCRIPTION="${2:-}"; DO_PERSONALIZE=true; shift 2 ;; + --description=*) PERSONALIZE_DESCRIPTION="${1#--description=}"; DO_PERSONALIZE=true; shift ;; + --provider) PERSONALIZE_PROVIDER="${2:-}"; DO_PERSONALIZE=true; shift 2 ;; + --provider=*) PERSONALIZE_PROVIDER="${1#--provider=}"; DO_PERSONALIZE=true; shift ;; + --model) PERSONALIZE_MODEL="${2:-}"; DO_PERSONALIZE=true; shift 2 ;; + --model=*) PERSONALIZE_MODEL="${1#--model=}"; DO_PERSONALIZE=true; shift ;; + --tone) PERSONALIZE_TONE="${2:-friendly}"; DO_PERSONALIZE=true; shift 2 ;; + --tone=*) PERSONALIZE_TONE="${1#--tone=}"; DO_PERSONALIZE=true; shift ;; + --prefix) PERSONALIZE_PREFIX="${2:-🤖}"; DO_PERSONALIZE=true; shift 2 ;; + --prefix=*) PERSONALIZE_PREFIX="${1#--prefix=}"; DO_PERSONALIZE=true; shift ;; + --system-prompt) PERSONALIZE_SYSTEM_PROMPT="${2:-}"; DO_PERSONALIZE=true; shift 2 ;; + --system-prompt=*) PERSONALIZE_SYSTEM_PROMPT="${1#--system-prompt=}"; DO_PERSONALIZE=true; shift ;; + --system-prompt-file) PERSONALIZE_SYSTEM_PROMPT_FILE="${2:-}"; DO_PERSONALIZE=true; shift 2 ;; + --system-prompt-file=*) PERSONALIZE_SYSTEM_PROMPT_FILE="${1#--system-prompt-file=}"; DO_PERSONALIZE=true; shift ;; + --tool-use) PERSONALIZE_TOOL_USE=true; DO_PERSONALIZE=true; shift ;; + --language) PERSONALIZE_LANGUAGE="${2:-es}"; DO_PERSONALIZE=true; shift 2 ;; + --language=*) PERSONALIZE_LANGUAGE="${1#--language=}"; DO_PERSONALIZE=true; shift ;; + *) shift ;; esac done @@ -175,9 +213,46 @@ fi echo "" +# ── Paso 8 (automático, solo agents): Personalizar archivos ───────────── +PERSONALIZE_DONE=false +if $DO_PERSONALIZE && [[ "$TYPE" != "robot" ]]; then + PERSONALIZE_EXTRA_STEP=$((TOTAL_STEPS + 1)) + info "Paso ${PERSONALIZE_EXTRA_STEP} — Personalizando archivos del agente (automático)..." + echo "" + + # Construir args para personalize.sh + PERSONALIZE_ARGS=() + [[ -n "$PERSONALIZE_DESCRIPTION" ]] && PERSONALIZE_ARGS+=(--description "$PERSONALIZE_DESCRIPTION") + [[ -n "$PERSONALIZE_PROVIDER" ]] && PERSONALIZE_ARGS+=(--provider "$PERSONALIZE_PROVIDER") + [[ -n "$PERSONALIZE_MODEL" ]] && PERSONALIZE_ARGS+=(--model "$PERSONALIZE_MODEL") + [[ "$PERSONALIZE_TONE" != "friendly" ]] && PERSONALIZE_ARGS+=(--tone "$PERSONALIZE_TONE") + [[ "$PERSONALIZE_PREFIX" != "🤖" ]] && PERSONALIZE_ARGS+=(--prefix "$PERSONALIZE_PREFIX") + [[ -n "$PERSONALIZE_SYSTEM_PROMPT" ]] && PERSONALIZE_ARGS+=(--system-prompt "$PERSONALIZE_SYSTEM_PROMPT") + [[ -n "$PERSONALIZE_SYSTEM_PROMPT_FILE" ]] && PERSONALIZE_ARGS+=(--system-prompt-file "$PERSONALIZE_SYSTEM_PROMPT_FILE") + $PERSONALIZE_TOOL_USE && PERSONALIZE_ARGS+=(--tool-use) + [[ "$PERSONALIZE_LANGUAGE" != "es" ]] && PERSONALIZE_ARGS+=(--language "$PERSONALIZE_LANGUAGE") + + if "$SCRIPT_DIR/personalize.sh" "$ID" "${PERSONALIZE_ARGS[@]}"; then + ok "Personalización completada" + PERSONALIZE_DONE=true + + # Recompilar tras personalización + info "Recompilando tras personalización..." + if "$GO" build -tags goolm ./... 2>&1; then + ok "Recompilación exitosa" + else + fail "Error de compilación tras personalización — revisa agents/$ID/agent.go" + fi + else + warn "Personalización falló — revisa los flags o edita los archivos manualmente" + fi + + echo "" +fi + # ── Paso final: Notificar al developer ─────────────────────────────────── NOTIFY_STEP=$TOTAL_STEPS -info "Paso ${NOTIFY_STEP}/${TOTAL_STEPS} — Notificando a desarrolladores..." +info "Paso ${NOTIFY_STEP}+ — Notificando a desarrolladores..." echo "" "$SCRIPT_DIR/notify-developer.sh" "$ID" "$TYPE" "$DISPLAYNAME" || true @@ -205,8 +280,26 @@ echo "" echo -e " ${BLU}Launcher actualizado:${RST}" echo -e " cmd/launcher/main.go (import)" echo "" -echo -e "${YLW}Siguientes pasos (8-12 del pipeline):${RST}" -echo "" +if $PERSONALIZE_DONE; then + echo -e "${GRN}Paso 8 completado automáticamente ✓${RST}" + echo -e " ${DIM}config.yaml, agent.go y prompts/system.md personalizados${RST}" + echo "" + echo -e "${YLW}Siguientes pasos (9-12 del pipeline):${RST}" + echo "" + echo -e " ${BLU}9. REBUILD${RST}:" + echo -e " ${DIM}go build -tags goolm ./...${RST}" + echo "" + echo -e " ${BLU}10. START${RST}:" + echo -e " ${DIM}./dev-scripts/server/start.sh${RST}" + echo "" + echo -e " ${BLU}11. HEALTH CHECK${RST}:" + echo -e " ${DIM}./dev-scripts/agent/health-check.sh $ID${RST}" + echo "" + echo -e " ${BLU}12. SELF-INTRODUCE${RST} (tras health check ok):" + echo -e " ${DIM}./dev-scripts/agent/notify-developer.sh $ID $TYPE \"$DISPLAYNAME\"${RST}" +else + echo -e "${YLW}Siguientes pasos (8-12 del pipeline):${RST}" + echo "" if [[ "$TYPE" == "robot" ]]; then echo -e " ${BLU}8. PERSONALIZE${RST} — añadir comandos custom:" echo -e " ${DIM}agents/$ID/commands.go${RST}" @@ -230,3 +323,4 @@ echo "" echo -e " ${BLU}12. SELF-INTRODUCE${RST} (tras health check ok):" echo -e " ${DIM}./dev-scripts/agent/notify-developer.sh $ID $TYPE \"$DISPLAYNAME\"${RST}" echo "" +fi # end $PERSONALIZE_DONE else diff --git a/dev-scripts/agent/detect-provider.sh b/dev-scripts/agent/detect-provider.sh new file mode 100755 index 0000000..06da067 --- /dev/null +++ b/dev-scripts/agent/detect-provider.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# detect-provider.sh — detecta el proveedor LLM disponible desde .env +# +# Salida: dos palabras en stdout — " " +# openai gpt-4o +# anthropic claude-sonnet-4-20250514 +# +# Orden de detección: +# 1. OPENAI_API_KEY → openai gpt-4o +# 2. ANTHROPIC_API_KEY → anthropic claude-sonnet-4-20250514 +# Fallback: openai gpt-4o (con warning en stderr) +# +# Uso: +# read -r PROVIDER MODEL < <(./dev-scripts/agent/detect-provider.sh) +# ./dev-scripts/agent/detect-provider.sh # imprime "openai gpt-4o" + +source "$(dirname "$0")/../_common.sh" +load_env + +# Default models por provider +OPENAI_DEFAULT_MODEL="gpt-4o" +ANTHROPIC_DEFAULT_MODEL="claude-sonnet-4-20250514" + +# Detectar provider disponible +if [[ -n "${OPENAI_API_KEY:-}" ]]; then + echo "openai $OPENAI_DEFAULT_MODEL" + exit 0 +fi + +if [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then + echo "anthropic $ANTHROPIC_DEFAULT_MODEL" + exit 0 +fi + +# Fallback con warning +warn "Ninguna API key configurada (OPENAI_API_KEY, ANTHROPIC_API_KEY) — usando fallback openai/gpt-4o" >&2 +echo "openai $OPENAI_DEFAULT_MODEL" +exit 0 diff --git a/dev-scripts/agent/personalize.sh b/dev-scripts/agent/personalize.sh new file mode 100755 index 0000000..c934a39 --- /dev/null +++ b/dev-scripts/agent/personalize.sh @@ -0,0 +1,391 @@ +#!/usr/bin/env bash +# personalize.sh — personaliza los 3 archivos de un agente tras el scaffold +# +# Uso: +# ./dev-scripts/agent/personalize.sh [flags] +# +# Flags: +# --description "" descripcion del agente (obligatorio) +# --provider proveedor LLM (default: auto-detect) +# --model modelo LLM (default: segun provider) +# --tone tono (default: friendly) +# --prefix "" emoji prefix (default: 🤖) +# --system-prompt "" system prompt inline +# --system-prompt-file system prompt desde archivo +# --tool-use habilitar tool_use en config +# --language idioma (default: es) +# +# Genera/actualiza: +# agents//config.yaml — description, provider, model, tone, prefix, tool-use +# agents//agent.go — package name correcto y Register ID exacto +# agents//prompts/system.md — system prompt completo con seccion de seguridad +# +# Ejemplo (uso standalone): +# ./dev-scripts/agent/personalize.sh weather-bot \ +# --description "Consulta el tiempo actual y predicciones" \ +# --provider anthropic \ +# --system-prompt "Eres Weather Bot, especialista en meteorología." +# +# Ejemplo (uso desde create-full.sh): +# create-full.sh lo invoca automaticamente si se pasan --description y/o --system-prompt. + +source "$(dirname "$0")/../_common.sh" +load_env + +SCRIPT_DIR="$(dirname "$0")" +need_arg "${1:-}" + +ID="$1" +shift + +DIR="agents/$ID" + +[[ ! -d "$DIR" ]] && fail "No existe $DIR — ejecuta create-full.sh primero" + +# ── Defaults ───────────────────────────────────────────────────────────── +DESCRIPTION="" +PROVIDER="" +MODEL="" +TONE="friendly" +PREFIX="🤖" +SYSTEM_PROMPT="" +SYSTEM_PROMPT_FILE="" +TOOL_USE=false +LANGUAGE="es" + +# ── Parse flags ─────────────────────────────────────────────────────────── +while [[ $# -gt 0 ]]; do + case "$1" in + --description) DESCRIPTION="${2:-}"; shift 2 ;; + --description=*) DESCRIPTION="${1#--description=}"; shift ;; + --provider) PROVIDER="${2:-}"; shift 2 ;; + --provider=*) PROVIDER="${1#--provider=}"; shift ;; + --model) MODEL="${2:-}"; shift 2 ;; + --model=*) MODEL="${1#--model=}"; shift ;; + --tone) TONE="${2:-friendly}"; shift 2 ;; + --tone=*) TONE="${1#--tone=}"; shift ;; + --prefix) PREFIX="${2:-🤖}"; shift 2 ;; + --prefix=*) PREFIX="${1#--prefix=}"; shift ;; + --system-prompt) SYSTEM_PROMPT="${2:-}"; shift 2 ;; + --system-prompt=*) SYSTEM_PROMPT="${1#--system-prompt=}"; shift ;; + --system-prompt-file) SYSTEM_PROMPT_FILE="${2:-}"; shift 2 ;; + --system-prompt-file=*) SYSTEM_PROMPT_FILE="${1#--system-prompt-file=}"; shift ;; + --tool-use) TOOL_USE=true; shift ;; + --language) LANGUAGE="${2:-es}"; shift 2 ;; + --language=*) LANGUAGE="${1#--language=}"; shift ;; + *) warn "Flag desconocido: $1 (ignorado)"; shift ;; + esac +done + +# ── Resolver provider/model ─────────────────────────────────────────────── +if [[ -z "$PROVIDER" ]]; then + read -r PROVIDER MODEL_DETECTED < <("$SCRIPT_DIR/detect-provider.sh" 2>/dev/null) + if [[ -z "$MODEL" ]]; then + MODEL="$MODEL_DETECTED" + fi +else + if [[ -z "$MODEL" ]]; then + case "$PROVIDER" in + anthropic) MODEL="claude-sonnet-4-20250514" ;; + claude-code) MODEL="sonnet" ;; + *) MODEL="gpt-4o" ;; + esac + fi +fi + +# Resolver api_key_env segun provider +case "$PROVIDER" in + anthropic) API_KEY_ENV="ANTHROPIC_API_KEY" ;; + claude-code) API_KEY_ENV="" ;; + *) API_KEY_ENV="OPENAI_API_KEY" ;; +esac + +# Package name = ID sin guiones, sin sufijo -bot/_bot (ej: monitor-bot → monitor) +PACKAGE="$(echo "$ID" | tr '-' '_' | sed 's/_bot$//')" +NORM="$(normalize_id "$ID")" +DISPLAYNAME="$(python3 -c " +import yaml, sys +with open('$DIR/config.yaml') as f: + cfg = yaml.safe_load(f) +print(cfg.get('agent', {}).get('name', '$ID')) +" 2>/dev/null || echo "$ID")" + +info "Personalizando agente: $ID" +dim " provider: $PROVIDER / $MODEL" +dim " tone: $TONE | prefix: $PREFIX | tool-use: $TOOL_USE" +[[ -n "$DESCRIPTION" ]] && dim " description: $DESCRIPTION" +echo "" + +# ═══════════════════════════════════════════════════════════════════════════ +# Paso 1 — Actualizar config.yaml +# ═══════════════════════════════════════════════════════════════════════════ +info "Actualizando config.yaml..." + +TOOL_USE_BOOL="false" +$TOOL_USE && TOOL_USE_BOOL="true" + +python3 - <"$DESCRIPTION"', + r'^(\s+tone:\s*).*$': rf'\g<1>$TONE', + r'^(\s+language:\s*).*$': rf'\g<1>$LANGUAGE', + r'^(\s+prefix:\s*).*$': rf'\g<1>"$PREFIX"', + r'^(\s+provider:\s*).*$': rf'\g<1>$PROVIDER', + r'^(\s+model:\s*).*$': rf'\g<1>"$MODEL"', +} + +lines = content.splitlines(keepends=True) +new_lines = [] + +# State machine to apply updates in the right sections +in_agent = False +in_personality = False +in_llm_primary = False +in_tool_use = False +desc_done = False +tone_done = False +lang_done = False +prefix_done = False +provider_done = False +model_done = False +api_key_done = False +tool_use_done = False + +for i, line in enumerate(lines): + stripped = line.lstrip() + indent = len(line) - len(stripped) + key = stripped.split(":")[0].rstrip() if ":" in stripped else "" + + # Detect section headers + if not line.startswith(" ") and not line.startswith("\t"): + in_agent = key == "agent" + in_personality = key == "personality" + in_llm_primary = False + in_tool_use = False + elif indent == 2: + if in_llm_primary or in_tool_use: + if key not in ("provider", "model", "api_key_env", "base_url", "max_tokens", + "temperature", "claude_code", "enabled", "max_iterations", "parallel_calls"): + in_llm_primary = False + in_tool_use = False + if key == "primary": + in_llm_primary = True + in_tool_use = False + elif key == "tool_use": + in_tool_use = True + in_llm_primary = False + + # Apply substitutions + if in_agent and indent == 2 and key == "description" and not desc_done and "$DESCRIPTION": + new_lines.append(f' description: "$DESCRIPTION"\n') + desc_done = True + continue + + if in_personality and indent == 2: + if key == "tone" and not tone_done: + new_lines.append(f' tone: $TONE\n') + tone_done = True + continue + if key == "language" and not lang_done: + new_lines.append(f' language: $LANGUAGE\n') + lang_done = True + continue + if key == "prefix" and not prefix_done: + new_lines.append(f' prefix: "$PREFIX"\n') + prefix_done = True + continue + + if in_llm_primary and indent == 4: + if key == "provider" and not provider_done: + new_lines.append(f' provider: $PROVIDER\n') + provider_done = True + continue + if key == "model" and not model_done: + new_lines.append(f' model: "$MODEL"\n') + model_done = True + continue + if key == "api_key_env" and not api_key_done and "$API_KEY_ENV": + new_lines.append(f' api_key_env: $API_KEY_ENV\n') + api_key_done = True + continue + + if in_tool_use and indent == 4: + if key == "enabled" and not tool_use_done: + new_lines.append(f' enabled: {"true" if _tool_use_enabled else "false"}\n') + tool_use_done = True + continue + + new_lines.append(line) + +with open(config_path, "w") as f: + f.writelines(new_lines) + +print("OK") +PYTHON + +if [[ $? -ne 0 ]]; then + fail "Error actualizando config.yaml" +fi +ok "config.yaml actualizado" + +# ═══════════════════════════════════════════════════════════════════════════ +# Paso 2 — Regenerar agent.go +# ═══════════════════════════════════════════════════════════════════════════ +info "Regenerando agent.go..." + +cat > "$DIR/agent.go" < "$DIR/prompts/system.md" + +ok "prompts/system.md generado ($(wc -l < "$DIR/prompts/system.md") líneas)" + +# ═══════════════════════════════════════════════════════════════════════════ +# Resumen +# ═══════════════════════════════════════════════════════════════════════════ +echo "" +echo -e "${GRN}✓ Personalización completada para $ID${RST}" +dim " agents/$ID/config.yaml" +dim " agents/$ID/agent.go (package $PACKAGE)" +dim " agents/$ID/prompts/system.md" +echo "" +echo -e "${YLW}Siguiente paso:${RST} go build -tags goolm ./..." +echo ""