diff --git a/config.py b/config.py index c840122..40ea9c9 100644 --- a/config.py +++ b/config.py @@ -79,22 +79,22 @@ XMPP_PASSWORD = os.getenv("XMPP_PASSWORD", default="") AGENT_MAX_ITERATIONS = int(os.getenv("AGENT_MAX_ITERATIONS", default=_yaml_get("agent", "max_iterations", default="30"))) AGENT_MAX_TOOL_OUTPUT = int(os.getenv("AGENT_MAX_TOOL_OUTPUT", default=_yaml_get("agent", "max_tool_output", default="40000"))) -AGENT_SKILL = os.getenv("AGENT_SKILL", default="programmer").strip().lower() -PERSONALITY_NAME = os.getenv("PERSONALITY_NAME", default=_yaml_get("personality", "name", default="Hendrik")).strip() or "Hendrik" -PERSONALITY_AGE = os.getenv("PERSONALITY_AGE", default=_yaml_get("personality", "age", default="")).strip() -PERSONALITY_GENDER = os.getenv("PERSONALITY_GENDER", default=_yaml_get("personality", "gender", default="")).strip() -PERSONALITY_TONE = os.getenv("PERSONALITY_TONE", default=_yaml_get("personality", "tone", default="casual")).strip().lower() or "casual" -PERSONALITY_VERBOSITY = os.getenv("PERSONALITY_VERBOSITY", default=_yaml_get("personality", "verbosity", default="balanced")).strip().lower() or "balanced" -PERSONALITY_HUMOR = os.getenv("PERSONALITY_HUMOR", default=_yaml_get("personality", "humor", default="light")).strip().lower() or "light" -PERSONALITY_LANGUAGE = os.getenv("PERSONALITY_LANGUAGE", default=_yaml_get("personality", "language", default="id")).strip().lower() or "id" -PERSONALITY_MOOD = os.getenv("PERSONALITY_MOOD", default=_yaml_get("personality", "mood", default="cheerful")).strip().lower() or "cheerful" -PERSONALITY_CATCHPHRASES = os.getenv("PERSONALITY_CATCHPHRASES", default=_yaml_get("personality", "catchphrases", default="")).strip() +AGENT_SKILL = "programmer" +PERSONALITY_NAME = "Hendrik" +PERSONALITY_AGE = "" +PERSONALITY_GENDER = "" +PERSONALITY_TONE = "casual" +PERSONALITY_VERBOSITY = "balanced" +PERSONALITY_HUMOR = "light" +PERSONALITY_LANGUAGE = "id" +PERSONALITY_MOOD = "cheerful" +PERSONALITY_CATCHPHRASES = "" -# ─── Character & Skills (YAML, bisa di-override dari .env) ───────────────────── +# ─── Character & Skills ──────────────────────────────────────────────────────── -AGENT_CHARACTER = os.getenv("AGENT_CHARACTER", default=_yaml_get("agent", "character", default="")).strip().lower() -AGENT_SKILLS = os.getenv("AGENT_SKILLS", default="").strip().lower() +AGENT_CHARACTER = _yaml_get("agent", "character", default="").strip().lower() +AGENT_SKILLS = os.getenv("AGENT_SKILLS", default="").strip().lower() # ─── XMPP (non-credential dari YAML, credential dari .env) ───────────────────── @@ -127,41 +127,65 @@ TYPING_SPEED = float( _yaml_get("delay", "typing_speed", default="15.0" ) ) TYPING_MAX = float( _yaml_get("delay", "typing_max", default="10.0" ) ) -# ─── Character Preset Override ────────────────────────────────────────────────── -# Jika AGENT_CHARACTER di-set, baca character.md dari agent/characters// -# dan override nilai personality yang relevan. +# ─── Character Loader ────────────────────────────────────────────────────────── -ENV_CHARACTERS_DIR = Path(__file__).resolve().parent / "agent" / "characters" -ENV_CHARACTER_CONFIG_PATH = ENV_CHARACTERS_DIR / AGENT_CHARACTER / "character.md" if AGENT_CHARACTER else None +CHARACTERS_DIR = Path(__file__).resolve().parent / "agent" / "characters" -# Coba personality.yaml dulu (prioritas utama), kalau tidak ada fallback ke character.md -_personality_yaml_path = ENV_CHARACTERS_DIR / AGENT_CHARACTER / "personality.yaml" if AGENT_CHARACTER else None +_yaml_to_config_key = { + "AGENT_SKILL": "skill", + "PERSONALITY_NAME": "name", + "PERSONALITY_AGE": "age", + "PERSONALITY_GENDER": "gender", + "PERSONALITY_TONE": "tone", + "PERSONALITY_VERBOSITY": "verbosity", + "PERSONALITY_HUMOR": "humor", + "PERSONALITY_LANGUAGE": "language", + "PERSONALITY_MOOD": "mood", + "PERSONALITY_CATCHPHRASES": "catchphrases", +} -if _personality_yaml_path and _personality_yaml_path.is_file(): - # Prioritas utama: baca personality.yaml dari character directory - try: - _character_env = yaml.safe_load(_personality_yaml_path.read_text(encoding="utf-8")) or {} - if not isinstance(_character_env, dict): +_lowercase_keys = {"AGENT_SKILL", "PERSONALITY_TONE", "PERSONALITY_VERBOSITY", "PERSONALITY_HUMOR", "PERSONALITY_LANGUAGE", "PERSONALITY_MOOD"} + + +def load_character(char_override=None): + """Load personality dari character directory. Bisa di-call ulang untuk override.""" + global AGENT_CHARACTER, AGENT_SKILL, PERSONALITY_NAME, PERSONALITY_AGE, PERSONALITY_GENDER + global PERSONALITY_TONE, PERSONALITY_VERBOSITY, PERSONALITY_HUMOR, PERSONALITY_LANGUAGE + global PERSONALITY_MOOD, PERSONALITY_CATCHPHRASES + + if char_override: + AGENT_CHARACTER = char_override.strip().lower() + + if not AGENT_CHARACTER: + return + + personality_yaml_path = CHARACTERS_DIR / AGENT_CHARACTER / "personality.yaml" + character_config_path = CHARACTERS_DIR / AGENT_CHARACTER / "character.md" + + if personality_yaml_path.is_file(): + try: + _character_env = yaml.safe_load(personality_yaml_path.read_text(encoding="utf-8")) or {} + if not isinstance(_character_env, dict): + _character_env = {} + except Exception as _e: + print(f"[config] Warning: gagal load personality.yaml untuk '{AGENT_CHARACTER}': {_e}", flush=True) _character_env = {} - except Exception as _e: - print(f"[config] Warning: gagal load personality.yaml untuk '{AGENT_CHARACTER}': {_e}", flush=True) + elif character_config_path.is_file(): _character_env = {} -elif ENV_CHARACTER_CONFIG_PATH and ENV_CHARACTER_CONFIG_PATH.is_file(): - # Fallback: baca character.md (format lama) - _character_env = {} - for line in ENV_CHARACTER_CONFIG_PATH.read_text(encoding="utf-8").splitlines(): - line = line.strip() - if not line or line.startswith("#"): - continue - if "=" in line: - key, value = line.split("=", 1) - _character_env[key.strip()] = value.strip() -else: - _character_env = None + for line in character_config_path.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + if "=" in line: + key, value = line.split("=", 1) + _character_env[key.strip()] = value.strip() + else: + return -if _character_env: + if not _character_env: + return - _character_overrides = { + _defaults = { "AGENT_SKILL": AGENT_SKILL, "PERSONALITY_NAME": PERSONALITY_NAME, "PERSONALITY_AGE": PERSONALITY_AGE, @@ -173,29 +197,20 @@ if _character_env: "PERSONALITY_MOOD": PERSONALITY_MOOD, "PERSONALITY_CATCHPHRASES": PERSONALITY_CATCHPHRASES, } - # Mapping dari key personality.yaml ke key config - _yaml_to_config_key = { - "AGENT_SKILL": "skill", - "PERSONALITY_NAME": "name", - "PERSONALITY_AGE": "age", - "PERSONALITY_GENDER": "gender", - "PERSONALITY_TONE": "tone", - "PERSONALITY_VERBOSITY": "verbosity", - "PERSONALITY_HUMOR": "humor", - "PERSONALITY_LANGUAGE": "language", - "PERSONALITY_MOOD": "mood", - "PERSONALITY_CATCHPHRASES": "catchphrases", - } - for key, fallback in _character_overrides.items(): + + for key, fallback in _defaults.items(): yaml_key = _yaml_to_config_key.get(key, key.lower()) raw = _character_env.get(yaml_key, _character_env.get(key, fallback)) value = str(raw).strip() if raw is not None else "" - if key in {"AGENT_SKILL", "PERSONALITY_TONE", "PERSONALITY_VERBOSITY", "PERSONALITY_HUMOR", "PERSONALITY_LANGUAGE", "PERSONALITY_MOOD"}: + if key in _lowercase_keys: value = value.lower() or fallback if key == "PERSONALITY_NAME" and not value: value = fallback - _character_overrides[key] = value + _defaults[key] = value - for key, value in _character_overrides.items(): + for key, value in _defaults.items(): globals()[key] = value - os.environ[key] = value + + +# Load character dari config.yaml saat import time +load_character() diff --git a/hendrik.py b/hendrik.py index 496ab20..326e9c2 100644 --- a/hendrik.py +++ b/hendrik.py @@ -1,13 +1,6 @@ import os, sys import argparse -# Pre-parse --char BEFORE config import (config reads os.environ at import time) -_pre = argparse.ArgumentParser(add_help=False) -_pre.add_argument('--char', type=str, default=None) -_pre_args, _ = _pre.parse_known_args() -if _pre_args.char: - os.environ['AGENT_CHARACTER'] = _pre_args.char - import threading, time, signal import config @@ -73,6 +66,9 @@ def main(): parser.add_argument('-w', '--workspace', type=str, default=None, help='Working directory') args = parser.parse_args() + if args.char: + config.load_character(char_override=args.char) + if args.workspace: resolved = os.path.abspath(args.workspace) if not os.path.isdir(resolved): diff --git a/lib/personality.py b/lib/personality.py index a57078d..693206e 100644 --- a/lib/personality.py +++ b/lib/personality.py @@ -273,7 +273,7 @@ def build_system_prompt( cfg = personality or PERSONALITY # Resolve character name - character_name = (character or os.getenv("AGENT_CHARACTER", default="")).strip().lower() + character_name = (character or "").strip().lower() # ── Load personality.yaml dari character directory ───────────────────────────────── if character_name: