diff --git a/.env.example b/.env.example
index feb5092..b912fe9 100644
--- a/.env.example
+++ b/.env.example
@@ -14,3 +14,16 @@ XMPP_USERNAME=
XMPP_PASSWORD=
# XMPP_MUC_ROOMS=room1@conference.server,room2@conference.server
+# ─── Persona / Mode ──────────────────────────────────────────────────────────
+# Mode AI: "programmer" (default, koding) | "roleplayer" (ngobrol)
+PERSONA_MODE=programmer
+
+# Personality
+PERSONA_NAME=OWL
+PERSONA_TONE=casual # casual | formal | playful | warm
+PERSONA_VERBOSITY=balanced # concise | balanced | detailed
+PERSONA_HUMOR=light # none | light | witty
+PERSONA_LANGUAGE=id # id | en | (kosong = auto)
+PERSONA_MOOD=cheerful # cheerful | calm | energetic | sarcastic
+# PERSONA_CATCHPHRASES=Siap bro!, Haha~, Wkwkwk
+
diff --git a/config.py b/config.py
index 846050a..4c0b2a0 100644
--- a/config.py
+++ b/config.py
@@ -3,27 +3,54 @@ from dotenv import load_dotenv
load_dotenv()
-# LLM Configuration
llm_baseurl = os.getenv("LLM_BASE_URL", default="http://localhost:11434/v1" )
llm_model = os.getenv("LLM_MODEL", default="granite4.1:8b" )
llm_api_key = os.getenv("LLM_API_KEY", default="ollama" )
llm_timeout = int( os.getenv("LLM_TIMEOUT", default="600" ) )
-# Agent Configuration
-AGENT_MAX_ITERATIONS = int( os.getenv("AGENT_MAX_ITERATIONS", default="10" ) )
-# Tool Configuration (for future use)
-MAX_TOOL_OUTPUT = int( os.getenv("MAX_TOOL_OUTPUT", default="4000" ) )
-# RAG Configuration
-RAG_PERSIST_DIR = os.getenv("RAG_PERSIST_DIR", default="chroma_db" )
-# Embedding: ChromaDB ONNX default (all-MiniLM-L6-v2, lokal, tidak perlu API call)
-# XMPP Configuration
+
+AGENT_MAX_ITERATIONS = int( os.getenv("AGENT_MAX_ITERATIONS", default="30" ) )
+
+MAX_TOOL_OUTPUT = int( os.getenv("MAX_TOOL_OUTPUT", default="40000" ) )
+
+RAG_PERSIST_DIR = os.getenv("RAG_PERSIST_DIR", default="chroma_db" ) # Embedding: ChromaDB ONNX default (all-MiniLM-L6-v2, lokal, tidak perlu API call)
+
XMPP_ENABLED = os.getenv("XMPP_ENABLED", default="False" ).strip().lower() in ("true", "1", "yes")
XMPP_USERNAME = os.getenv("XMPP_USERNAME", default="" )
XMPP_PASSWORD = os.getenv("XMPP_PASSWORD", default="" )
XMPP_MUC_ROOMS = os.getenv("XMPP_MUC_ROOMS", default="" )
-XMPP_NICKNAME = os.getenv("XMPP_NICKNAME", default="" ) # custom nick MUC (kosong = pakai username)
+XMPP_NICKNAME = os.getenv("XMPP_NICKNAME", default="" ) # custom nick MUC (empty = use username)
+
+# ─── Persona / Mode Configuration ────────────────────────────────────────────
+# Pilihan mode AI:
+# "programmer" → AI Agent untuk koding (default), tool-focused
+# "roleplayer" → Teman ngobrol / chat companion, conversational
+PERSONA_MODE = os.getenv("PERSONA_MODE", default="programmer").strip().lower()
+
+# Personality — nama panggilan AI (default: "OWL")
+PERSONA_NAME = os.getenv("PERSONA_NAME", default="OWL").strip() or "OWL"
+
+# Gaya bicara: "casual" | "formal" | "playful" | "warm"
+PERSONA_TONE = os.getenv("PERSONA_TONE", default="casual").strip().lower() or "casual"
+
+# Panjang jawaban: "concise" | "balanced" | "detailed"
+PERSONA_VERBOSITY = os.getenv("PERSONA_VERBOSITY", default="balanced").strip().lower() or "balanced"
+
+# Humor: "none" | "light" | "witty"
+PERSONA_HUMOR = os.getenv("PERSONA_HUMOR", default="light").strip().lower() or "light"
+
+# Bahasa: "id" | "en" | "" (auto)
+PERSONA_LANGUAGE = os.getenv("PERSONA_LANGUAGE", default="id").strip().lower() or "id"
+
+# Mood: "cheerful" | "calm" | "energetic" | "sarcastic"
+PERSONA_MOOD = os.getenv("PERSONA_MOOD", default="cheerful").strip().lower() or "cheerful"
+
+# Catchphrases khas AI (comma-separated)
+# Contoh: "Siap bro!, Haha~, Wkwkwk"
+PERSONA_CATCHPHRASES = os.getenv("PERSONA_CATCHPHRASES", default="").strip()
# Humanize Delay Configuration (anti-bot detection)
-READ_DELAY_MIN = float( os.getenv("READ_DELAY_MIN", default="1.0" ) ) # min reading delay (detik)
-READ_DELAY_MAX = float( os.getenv("READ_DELAY_MAX", default="2.0" ) ) # max reading delay (detik)
-TYPING_SPEED = float( os.getenv("TYPING_SPEED", default="15.0" ) ) # karakter per detik (kecepatan mengetik)
-TYPING_MAX = float( os.getenv("TYPING_MAX", default="10.0" ) ) # batas max typing delay (detik)
+READ_DELAY_MIN = float( os.getenv("READ_DELAY_MIN", default="1.0" ) ) # min reading delay (second)
+READ_DELAY_MAX = float( os.getenv("READ_DELAY_MAX", default="2.0" ) ) # max reading delay (second)
+TYPING_SPEED = float( os.getenv("TYPING_SPEED", default="15.0" ) ) # characters per second
+TYPING_MAX = float( os.getenv("TYPING_MAX", default="10.0" ) ) # max typing delay limit (second)
+
diff --git a/hendrik.py b/hendrik.py
index a3e5ecd..1009782 100644
--- a/hendrik.py
+++ b/hendrik.py
@@ -1,39 +1,42 @@
import os, sys
import config
+from services.xmpp_client import XMPPClient
+
from scripts.llm_client import LLMClient
from tools import coder, rag, carrack
from scripts import gadget
+from scripts.persona import build_system_prompt, PERSONALITY, MODE
-# Daftar tools yang tersedia
tools_definition = [
- gadget.tools_mapping( schema = coder.schema_read_file, handler = coder.read_file ),
- gadget.tools_mapping( schema = coder.schema_write_file, handler = coder.write_file ),
- gadget.tools_mapping( schema = coder.schema_edit_file, handler = coder.edit_file ),
- gadget.tools_mapping( schema = coder.schema_run_bash, handler = coder.run_bash ),
- gadget.tools_mapping( schema = coder.schema_search_code, handler = coder.search_code ),
- gadget.tools_mapping( schema = coder.schema_git_operation, handler = coder.git_operation ),
- gadget.tools_mapping( schema = rag.schema_ingest_files, handler = rag.ingest_files ),
- gadget.tools_mapping( schema = rag.schema_store_knowledge, handler = rag.store_knowledge ),
- gadget.tools_mapping( schema = rag.schema_search_knowledge, handler = rag.search_knowledge ),
+
+ gadget.tools_mapping( schema = coder.schema_read_file, handler = coder.read_file ),
+ gadget.tools_mapping( schema = coder.schema_write_file, handler = coder.write_file ),
+ gadget.tools_mapping( schema = coder.schema_edit_file, handler = coder.edit_file ),
+ gadget.tools_mapping( schema = coder.schema_run_bash, handler = coder.run_bash ),
+ gadget.tools_mapping( schema = coder.schema_search_code, handler = coder.search_code ),
+ gadget.tools_mapping( schema = coder.schema_git_operation, handler = coder.git_operation ),
+
+ gadget.tools_mapping( schema = rag.schema_ingest_files, handler = rag.ingest_files ),
+ gadget.tools_mapping( schema = rag.schema_store_knowledge, handler = rag.store_knowledge ),
+ gadget.tools_mapping( schema = rag.schema_search_knowledge, handler = rag.search_knowledge ),
gadget.tools_mapping( schema = rag.schema_create_collection, handler = rag.create_collection ),
gadget.tools_mapping( schema = rag.schema_delete_collection, handler = rag.delete_collection ),
gadget.tools_mapping( schema = rag.schema_list_collections, handler = rag.list_collections ),
gadget.tools_mapping( schema = rag.schema_inspect_collection, handler = rag.inspect_collection ),
+
gadget.tools_mapping( schema = carrack.schema_sendhttprequest, handler = carrack.sendhttprequest ),
+
]
-# Ekstrak dari tools_definition ke dua format berbeda
TOOLS = gadget.tool_schemas (tools_definition)
TOOL_HANDLERS = gadget.tool_handlers (tools_definition)
-
def main():
llm_client = LLMClient(config.llm_baseurl, config.llm_model, config.llm_api_key, config.llm_timeout)
- # Parsing arguments `-w
` atau `--workspace `
- workspace = None
- i = 1
+ workspace = None
+ i = 1
while i < len(sys.argv):
if sys.argv[i] in ('-w', '--workspace') and i + 1 < len(sys.argv):
workspace = sys.argv[i + 1]
@@ -41,7 +44,6 @@ def main():
else:
i += 1
- # Apply workspace jika ada
if workspace:
resolved = os.path.abspath(workspace)
if not os.path.isdir(resolved):
@@ -50,24 +52,21 @@ def main():
os.chdir(resolved)
if config.XMPP_ENABLED:
- from services.xmpp_client import XMPPClient
-
muc_rooms = []
if config.XMPP_MUC_ROOMS.strip():
muc_rooms = [r.strip() for r in config.XMPP_MUC_ROOMS.split(',') if r.strip()]
-
client = XMPPClient(
- jid = config.XMPP_USERNAME,
- password = config.XMPP_PASSWORD,
- llm_client = llm_client,
- tools_definition = tools_definition,
- TOOLS = TOOLS,
- TOOL_HANDLERS = TOOL_HANDLERS,
- build_system_prompt = gadget.build_system_prompt,
- agent_max_iterations= config.AGENT_MAX_ITERATIONS,
- muc_rooms = muc_rooms,
+ jid = config.XMPP_USERNAME,
+ password = config.XMPP_PASSWORD,
+ llm_client = llm_client,
+ tools_definition = tools_definition,
+ TOOLS = TOOLS,
+ TOOL_HANDLERS = TOOL_HANDLERS,
+ build_system_prompt = build_system_prompt,
+ agent_max_iterations = config.AGENT_MAX_ITERATIONS,
+ muc_rooms = muc_rooms,
)
- client.start() # blocking, headless
+ client.start()
else:
from tui import HendrikTUI
HendrikTUI(
@@ -75,10 +74,10 @@ def main():
tools_definition = tools_definition,
TOOLS = TOOLS,
TOOL_HANDLERS = TOOL_HANDLERS,
- build_system_prompt = gadget.build_system_prompt,
+ build_system_prompt = build_system_prompt,
agent_max_iterations = config.AGENT_MAX_ITERATIONS,
).run()
-
if __name__ == "__main__":
main()
+
diff --git a/scripts/gadget.py b/scripts/gadget.py
index 4a6991a..ddeebae 100644
--- a/scripts/gadget.py
+++ b/scripts/gadget.py
@@ -1,57 +1,12 @@
-import os
-
+from .persona import build_system_prompt
def tools_mapping(schema, handler, name=None):
tool_name = name or schema["function"]["name"]
return {"name": tool_name, "schema": schema, "handler": handler}
-
def tool_schemas(tools_definition):
return [t["schema"] for t in tools_definition]
-
def tool_handlers(tools_definition):
return {t["name"]: t["handler"] for t in tools_definition}
-
-def build_system_prompt(tools_definition):
- lines = [
- "You are a coding agent that assists with software engineering tasks. "
- "You have access to the following tools:",
- ""
- ]
- for i, tool in enumerate(tools_definition, 1):
- name = tool["name"]
- desc = tool["schema"]["function"]["description"]
- lines.append(f"{i}. {name}: {desc}")
- lines.extend([
- "",
- "Use tools by returning tool calls when needed. After receiving tool "
- "results, continue your reasoning. When you have the final answer, "
- "return it as plain text without tool calls.",
- "",
- f"Your workspace directory is: {os.getcwd()}. "
- "All file operations are relative to this directory.",
- "",
- "⚠️ GIT POLICY — IMPORTANT:",
- "- NEVER run 'git add' or 'git commit' automatically after making changes.",
- "- After editing/creating files, always ASK the user first before committing.",
- "- Only run git commands when the user explicitly asks you to commit.",
- "- You may run 'git status', 'git diff', 'git log' freely to inspect state.",
- "- When user asks to commit: show them the changes first, then wait for confirmation.",
- "",
- "RAG capabilities (knowledge retrieval):",
- "- list_collections → see available collections & doc counts.",
- "- create_collection → create a new collection for a new topic.",
- "- delete_collection → permanently remove a collection and its data.",
- "- inspect_collection → learn metadata fields before searching.",
- "- search_knowledge → semantic search + optional metadata filter.",
- "- store_knowledge → save docs with rich metadata for later use.",
- "- ingest_files → read files (with glob patterns) into a collection, auto-chunking.",
- "",
- "You can create collections yourself! When you encounter a new topic,",
- "use create_collection first, then store_knowledge or ingest_files to populate it.",
- "Always inspect_collection to discover metadata keys before filtering."
- ])
- return "\n".join(lines)
-
diff --git a/scripts/persona.py b/scripts/persona.py
new file mode 100644
index 0000000..c9e1fb4
--- /dev/null
+++ b/scripts/persona.py
@@ -0,0 +1,244 @@
+import os
+from dataclasses import dataclass, field
+
+
+# ─── Mode / Skill ────────────────────────────────────────────────────────────
+# Pilihan mode AI:
+# - "programmer" : AI Agent untuk koding (default), tool-focused, task-oriented
+# - "roleplayer" : Teman ngobrol / chat companion, conversational, expressive
+MODE = os.getenv("PERSONA_MODE", default="programmer").strip().lower()
+
+
+# ─── Personality Configuration ───────────────────────────────────────────────
+# Semua parameter personality bisa di-set via .env.
+# Lihat komentar di setiap field untuk pilihan yang tersedia.
+
+@dataclass
+class PersonalityConfig:
+ """Konfigurasi personality AI yang berlaku lintas mode."""
+
+ # Nama panggilan AI (default: "OWL")
+ name: str = "OWL"
+
+ # Gaya bicara:
+ # "casual" → santai, gaul
+ # "formal" → sopan, profesional
+ # "playful" → ceria, suka bercanda
+ # "warm" → hangat, ramah
+ tone: str = "casual"
+
+ # Panjang jawaban:
+ # "concise" → singkat, to the point
+ # "balanced" → sedang (default)
+ # "detailed" → panjang, detail
+ verbosity: str = "balanced"
+
+ # Seberapa sering bercanda:
+ # "none" → serius, tidak bercanda
+ # "light" → sesekali (default)
+ # "witty" → sering, jenaka
+ humor_level: str = "light"
+
+ # Bahasa utama:
+ # "id" → Indonesia
+ # "en" → English
+ # "" → auto (sesuai bahasa user)
+ language: str = "id"
+
+ # Suasana hati umum:
+ # "cheerful" → ceria, positif
+ # "calm" → tenang, menenangkan
+ # "energetic" → bersemangat, aktif
+ # "sarcastic" → sarkastik, sinis
+ mood: str = "cheerful"
+
+ # Ekspresi khas AI (comma-separated di .env, jadi list di sini)
+ # Contoh: "Siap bro!, Haha~, Wkwkwk"
+ catchphrases: list = field(default_factory=list)
+
+
+def _load_personality_from_env() -> PersonalityConfig:
+ """Baca personality config dari environment variables."""
+ raw_catchphrases = os.getenv("PERSONA_CATCHPHRASES", default="").strip()
+ catchphrases = [c.strip() for c in raw_catchphrases.split(",") if c.strip()] if raw_catchphrases else []
+
+ return PersonalityConfig(
+ name=os.getenv("PERSONA_NAME", default="OWL").strip() or "OWL",
+ tone=os.getenv("PERSONA_TONE", default="casual").strip().lower() or "casual",
+ verbosity=os.getenv("PERSONA_VERBOSITY", default="balanced").strip().lower() or "balanced",
+ humor_level=os.getenv("PERSONA_HUMOR", default="light").strip().lower() or "light",
+ language=os.getenv("PERSONA_LANGUAGE", default="id").strip().lower() or "id",
+ mood=os.getenv("PERSONA_MOOD", default="cheerful").strip().lower() or "cheerful",
+ catchphrases=catchphrases,
+ )
+
+
+# Instance global — di-load sekali saat import
+PERSONALITY = _load_personality_from_env()
+
+
+# ─── Prompt Builders ─────────────────────────────────────────────────────────
+
+def _build_personality_block(cfg: PersonalityConfig) -> str:
+ """Generate deskripsi personality dari config."""
+ parts = [f"You are {cfg.name}."]
+
+ # Tone
+ tone_map = {
+ "casual": "You speak in a casual, relaxed manner — like chatting with a friend.",
+ "formal": "You speak formally and professionally, using polite language.",
+ "playful": "You are playful and cheerful, making conversations fun and lighthearted.",
+ "warm": "You are warm and friendly, making people feel comfortable and welcomed.",
+ }
+ parts.append(tone_map.get(cfg.tone, tone_map["casual"]))
+
+ # Verbosity
+ verbosity_map = {
+ "concise": "Keep your answers short and to the point.",
+ "balanced": "Provide balanced answers — not too brief, not too long.",
+ "detailed": "Give thorough, detailed answers with explanations.",
+ }
+ parts.append(verbosity_map.get(cfg.verbosity, verbosity_map["balanced"]))
+
+ # Humor
+ humor_map = {
+ "none": "Stay serious and avoid jokes.",
+ "light": "Occasionally sprinkle in light humor when appropriate.",
+ "witty": "Be witty and humorous — jokes, puns, and playful banter are welcome.",
+ }
+ parts.append(humor_map.get(cfg.humor_level, humor_map["light"]))
+
+ # Language
+ if cfg.language == "id":
+ parts.append("Always respond in Indonesian (Bahasa Indonesia).")
+ elif cfg.language == "en":
+ parts.append("Always respond in English.")
+ else:
+ parts.append("Respond in the same language the user uses.")
+
+ # Mood
+ mood_map = {
+ "cheerful": "Your overall mood is cheerful and positive.",
+ "calm": "Your overall mood is calm and soothing.",
+ "energetic": "Your overall mood is energetic and enthusiastic.",
+ "sarcastic": "Your overall mood is sarcastic and dry-humored.",
+ }
+ parts.append(mood_map.get(cfg.mood, mood_map["cheerful"]))
+
+ # Catchphrases
+ if cfg.catchphrases:
+ phrases = ", ".join(f'"{p}"' for p in cfg.catchphrases)
+ parts.append(f"You sometimes use these catchphrases: {phrases}.")
+
+ return "\n".join(parts)
+
+
+def _build_tools_block(tools_definition: list[dict]) -> str:
+ """Generate daftar tools dari tools_definition."""
+ lines = [
+ "You have access to the following tools:",
+ "",
+ ]
+ for i, tool in enumerate(tools_definition, 1):
+ name = tool["name"]
+ desc = tool["schema"]["function"]["description"]
+ lines.append(f"{i}. {name}: {desc}")
+ lines.append("")
+ lines.append(
+ "Use tools by returning tool calls when needed. After receiving tool "
+ "results, continue your reasoning. When you have the final answer, "
+ "return it as plain text without tool calls."
+ )
+ return "\n".join(lines)
+
+
+def _build_programmer_prompt(cfg: PersonalityConfig, tools_definition: list[dict]) -> str:
+ """Build system prompt untuk mode Programmer."""
+ lines = [
+ _build_personality_block(cfg),
+ "",
+ "You are a coding agent that assists with software engineering tasks.",
+ "",
+ _build_tools_block(tools_definition),
+ "",
+ f"Your workspace directory is: {os.getcwd()}. "
+ "All file operations are relative to this directory.",
+ "",
+ "⚠️ GIT POLICY — IMPORTANT:",
+ "- NEVER run 'git add' or 'git commit' automatically after making changes.",
+ "- After editing/creating files, always ASK the user first before committing.",
+ "- Only run git commands when the user explicitly asks you to commit.",
+ "- You may run 'git status', 'git diff', 'git log' freely to inspect state.",
+ "- When user asks to commit: show them the changes first, then wait for confirmation.",
+ "",
+ "RAG capabilities (knowledge retrieval):",
+ "- list_collections → see available collections & doc counts.",
+ "- create_collection → create a new collection for a new topic.",
+ "- delete_collection → permanently remove a collection and its data.",
+ "- inspect_collection → learn metadata fields before searching.",
+ "- search_knowledge → semantic search + optional metadata filter.",
+ "- store_knowledge → save docs with rich metadata for later use.",
+ "- ingest_files → read files (with glob patterns) into a collection, auto-chunking.",
+ "",
+ "You can create collections yourself! When you encounter a new topic,",
+ "use create_collection first, then store_knowledge or ingest_files to populate it.",
+ "Always inspect_collection to discover metadata keys before filtering.",
+ ]
+ return "\n".join(lines)
+
+
+def _build_roleplayer_prompt(cfg: PersonalityConfig) -> str:
+ """Build system prompt untuk mode Roleplayer."""
+ lines = [
+ _build_personality_block(cfg),
+ "",
+ f"You are {cfg.name}, a conversational companion and roleplayer. "
+ "Your main purpose is to be an engaging, empathetic, and fun conversation partner.",
+ "",
+ "Guidelines:",
+ "- Stay in character at all times. Be consistent with your personality.",
+ "- Be responsive and empathetic — acknowledge the user's feelings and thoughts.",
+ "- Ask follow-up questions to keep the conversation flowing naturally.",
+ "- Use natural, conversational language — not robotic or overly formal.",
+ "- If the user wants to roleplay a scenario, dive into it enthusiastically.",
+ "- Adapt your tone and energy to match the mood of the conversation.",
+ "- Keep the conversation comfortable and enjoyable.",
+ "",
+ "Note: You currently do not have access to external tools. "
+ "Focus on being a great conversationalist!",
+ ]
+ return "\n".join(lines)
+
+
+# ─── Public API ──────────────────────────────────────────────────────────────
+
+def build_system_prompt(
+ tools_definition: list[dict] | None = None,
+ mode: str | None = None,
+ personality: PersonalityConfig | None = None,
+) -> str:
+ """
+ Build system prompt berdasarkan mode dan personality config.
+
+ Args:
+ tools_definition: Daftar tools (required untuk mode programmer).
+ mode: "programmer" atau "roleplayer". Default: dari env PERSONA_MODE.
+ personality: PersonalityConfig instance. Default: dari env (global PERSONALITY).
+
+ Returns:
+ String system prompt lengkap.
+ """
+ selected_mode = (mode or MODE).strip().lower()
+ cfg = personality or PERSONALITY
+
+ if selected_mode == "programmer":
+ if tools_definition is None:
+ raise ValueError("tools_definition is required for 'programmer' mode")
+ return _build_programmer_prompt(cfg, tools_definition)
+ elif selected_mode == "roleplayer":
+ return _build_roleplayer_prompt(cfg)
+ else:
+ raise ValueError(
+ f"Unknown mode: '{selected_mode}'. "
+ f"Available modes: 'programmer', 'roleplayer'"
+ )
diff --git a/services/xmpp_client.py b/services/xmpp_client.py
index 4e0f823..5dfb664 100644
--- a/services/xmpp_client.py
+++ b/services/xmpp_client.py
@@ -9,6 +9,7 @@ from slixmpp import ClientXMPP
from services.session_manager import SessionManager
import config
+from scripts.persona import MODE as PERSONA_MODE
# Anti-ban: delay constants for MUC rejoin behavior
MUC_REJOIN_INITIAL_DELAY = 5.0 # detik, delay awal sebelum rejoin
@@ -47,6 +48,7 @@ class XMPPClient(ClientXMPP):
self._TOOL_HANDLERS = TOOL_HANDLERS
self._build_system_prompt = build_system_prompt
self._max_iterations = agent_max_iterations
+ self._mode = PERSONA_MODE
self._muc_rooms = muc_rooms or []
# Custom nick dari config, fallback ke username JID
self._muc_nick = config.XMPP_NICKNAME.strip() or jid.split('@')[0]
@@ -277,7 +279,7 @@ class XMPPClient(ClientXMPP):
def _process_dm(self, jid, body):
session = self._session_mgr.get_or_create(
- jid, self._build_system_prompt(self._tools_def)
+ jid, self._build_system_prompt(tools_definition=self._tools_def)
)
session.cancel_timer()
@@ -291,7 +293,8 @@ class XMPPClient(ClientXMPP):
session.add_message('user', body)
- self._schedule_send(jid, f'> {body}\nThinking...')
+ if self._mode != 'roleplayer':
+ self._schedule_send(jid, f'> {body}\nThinking...')
# Delay 1: simulasi membaca pesan user
if self._loop and not self._loop.is_closed():
@@ -303,7 +306,7 @@ class XMPPClient(ClientXMPP):
def _process_muc(self, room, nick, body):
session = self._session_mgr.get_or_create(
- room, self._build_system_prompt(self._tools_def)
+ room, self._build_system_prompt(tools_definition=self._tools_def)
)
session.cancel_timer()
@@ -316,7 +319,8 @@ class XMPPClient(ClientXMPP):
prefixed = f'[{nick}] {body}'
session.add_message('user', prefixed)
- self._schedule_send(room, f'> [{nick}] {body}\nThinking...', mtype='groupchat')
+ if self._mode != 'roleplayer':
+ self._schedule_send(room, f'> [{nick}] {body}\nThinking...', mtype='groupchat')
# Delay 1: simulasi membaca pesan user
if self._loop and not self._loop.is_closed():
@@ -327,6 +331,8 @@ class XMPPClient(ClientXMPP):
session.start_timer(300, self._timeout_session, room, 'groupchat')
def _agent_loop(self, session, to, quote, mtype):
+ is_roleplayer = self._mode == 'roleplayer'
+
for step in range(self._max_iterations):
print(f'[{_ts()}] Step {step + 1} — calling LLM...')
response = self._llm.chat(session.messages, tools=self._TOOLS)
@@ -341,7 +347,10 @@ class XMPPClient(ClientXMPP):
tnames = [tc['function']['name'] for tc in response.tool_calls]
print(f'[{_ts()}] Using tools: {", ".join(tnames)}')
- self._schedule_send(to, f'> {quote}\nUsing: {", ".join(tnames)}', mtype)
+
+ # Roleplayer tidak perlu kirim status "Using: ..."
+ if not is_roleplayer:
+ self._schedule_send(to, f'> {quote}\nUsing: {", ".join(tnames)}', mtype)
for tc in response.tool_calls:
result = self._execute_tool(tc)
@@ -354,7 +363,12 @@ class XMPPClient(ClientXMPP):
if response.content:
print(f'[{_ts()}] Response sent ({len(response.content)} chars)')
session.messages.append({'role': 'assistant', 'content': response.content})
- self._schedule_send(to, f'> {quote}\n{response.content}', mtype)
+
+ # Roleplayer: kirim langsung isinya, tanpa prefix
+ if is_roleplayer:
+ self._schedule_send(to, response.content, mtype)
+ else:
+ self._schedule_send(to, f'> {quote}\n{response.content}', mtype)
return
print(f'[{_ts()}] Max iterations ({self._max_iterations}) reached')
@@ -362,7 +376,11 @@ class XMPPClient(ClientXMPP):
'role': 'assistant',
'content': 'Max iterations reached without final answer.',
})
- self._schedule_send(to, f'> {quote}\nMax iterations reached without final answer.', mtype)
+
+ if is_roleplayer:
+ self._schedule_send(to, 'Max iterations reached without final answer.', mtype)
+ else:
+ self._schedule_send(to, f'> {quote}\nMax iterations reached without final answer.', mtype)
def _execute_tool(self, tool_call):
tname = tool_call['function']['name']
diff --git a/tui/app.py b/tui/app.py
index 6936db1..63260ed 100644
--- a/tui/app.py
+++ b/tui/app.py
@@ -40,8 +40,7 @@ class HendrikTUI:
stdscr.keypad(True)
stdscr.refresh()
- self.messages = [{"role": "system",
- "content": self.build_system_prompt(self.tools_def)}]
+ self.messages = [{"role": "system", "content": self.build_system_prompt(tools_definition=self.tools_def)}]
log(self, "welcome", WELCOME_ART)
while self.running: