Compare commits

..

No commits in common. "28bd27f79c3aa7af6bd1f76b07883b33ab0fd418" and "6725b7bf0cb2aad7d95eff255f2aa998453f8d01" have entirely different histories.

9 changed files with 85 additions and 59 deletions

View File

@ -2,7 +2,7 @@ skill : programmer
name : Hendrik name : Hendrik
age : 35 age : 35
gender : male gender : male
tone : formal tone : casual
verbosity : concise verbosity : concise
humor : none humor : none
language : id language : id

View File

@ -2,8 +2,8 @@
agent: agent:
character: hendrik # Directory name in agent/characters/<character>/ character: hendrik # Directory name in agent/characters/<character>/
max_iterations: 30 # step max_iterations: 40 # step
max_tool_output: 4000 max_tool_output: 40000
llm: llm:
timeout: 3000 # second timeout: 3000 # second

View File

@ -1,17 +1,14 @@
import os, sys, threading, time, signal import os, sys, threading, time
import signal
import config import config
from tools import coder, rag, carrack
from services.xmpp_client import XMPPClient from services.xmpp_client import XMPPClient
from services.telegram_client import TelegramClient
from services.llm_client import LLMClient from scripts.llm_client import LLMClient
from scripts.personality import build_system_prompt from tools import coder, rag, carrack
from scripts import gadget from scripts import gadget
from scripts.personality import build_system_prompt
from tui import HendrikTUI
tools_definition = [ tools_definition = [
@ -49,6 +46,7 @@ def main():
i += 2 i += 2
else: else:
i += 1 i += 1
if workspace: if workspace:
resolved = os.path.abspath(workspace) resolved = os.path.abspath(workspace)
if not os.path.isdir(resolved): if not os.path.isdir(resolved):
@ -76,6 +74,8 @@ def main():
services.append(client) services.append(client)
if config.TELEGRAM_ENABLED: if config.TELEGRAM_ENABLED:
from services.telegram_client import TelegramClient
allowed_ids = [] allowed_ids = []
if config.TELEGRAM_ALLOWED_GROUP_IDS.strip(): if config.TELEGRAM_ALLOWED_GROUP_IDS.strip():
allowed_ids = [r.strip() for r in config.TELEGRAM_ALLOWED_GROUP_IDS.split(',') if r.strip()] allowed_ids = [r.strip() for r in config.TELEGRAM_ALLOWED_GROUP_IDS.split(',') if r.strip()]
@ -108,8 +108,8 @@ def main():
svc.stop() svc.stop()
_shutdown = True _shutdown = True
signal.signal(signal.SIGTERM, _handle_sig) # Handling interupt signal.signal(signal.SIGTERM, _handle_sig)
signal.signal(signal.SIGINT, _handle_sig) # Handling terminate signal.signal(signal.SIGINT, _handle_sig)
try: try:
while not _shutdown: while not _shutdown:
@ -117,8 +117,8 @@ def main():
except KeyboardInterrupt: except KeyboardInterrupt:
_shutdown = True _shutdown = True
print("Exiting.", flush=True) print("Exiting.", flush=True)
else: else:
from tui import HendrikTUI
HendrikTUI( HendrikTUI(
llm_client = llm_client, llm_client = llm_client,
tools_definition = tools_definition, tools_definition = tools_definition,

View File

@ -1,4 +1,4 @@
import re from .personality import build_system_prompt
def tools_mapping(schema, handler, name=None): def tools_mapping(schema, handler, name=None):
tool_name = name or schema["function"]["name"] tool_name = name or schema["function"]["name"]
@ -10,25 +10,3 @@ def tool_schemas(tools_definition):
def tool_handlers(tools_definition): def tool_handlers(tools_definition):
return {t["name"]: t["handler"] for t in tools_definition} return {t["name"]: t["handler"] for t in tools_definition}
def strip_thinking(text: str) -> str:
if not text:
return text
# Strip XML-style thinking blocks (case-insensitive, DOTALL for multiline)
text = re.sub(r'<think[^>]*>.*?</think>', '', text, flags=re.DOTALL | re.IGNORECASE)
text = re.sub(r'<reasoning[^>]*>.*?</reasoning>', '', text, flags=re.DOTALL | re.IGNORECASE)
# Strip lines starting with Thinking: / Reasoning: / Let me think...
lines = text.splitlines()
cleaned = []
skip_block = False
for line in lines:
stripped = line.strip().lower()
if stripped.startswith(('thinking:', 'reasoning:', 'let me thought', 'let me think')):
skip_block = True
continue
if skip_block and not stripped:
skip_block = False
continue
if not skip_block:
cleaned.append(line)
result = '\n'.join(cleaned).strip()
return result

View File

@ -1,14 +1,49 @@
import json import json
from scripts import gadget import re
import urllib.request import urllib.request
import urllib.error import urllib.error
def _strip_thinking(text: str) -> str:
"""
Hapus semua bentuk thinking/reasoning dari response text.
Handles:
- <think>...</think> blocks (any case)
- <reasoning>...</reasoning> blocks
- "Thinking:" / "Reasoning:" inline prefixes
"""
if not text:
return text
# Strip XML-style thinking blocks (case-insensitive, DOTALL for multiline)
text = re.sub(r'<think[^>]*>.*?</think>', '', text, flags=re.DOTALL | re.IGNORECASE)
text = re.sub(r'<reasoning[^>]*>.*?</reasoning>', '', text, flags=re.DOTALL | re.IGNORECASE)
# Strip lines starting with Thinking: / Reasoning: / Let me think...
lines = text.splitlines()
cleaned = []
skip_block = False
for line in lines:
stripped = line.strip().lower()
if stripped.startswith(('thinking:', 'reasoning:', 'let me thought', 'let me think')):
skip_block = True
continue
if skip_block and not stripped:
skip_block = False
continue
if not skip_block:
cleaned.append(line)
result = '\n'.join(cleaned).strip()
return result
class LLMClient: class LLMClient:
class Message: class Message:
def __init__(self, msg): def __init__(self, msg):
raw_content = msg.get('content', '') raw_content = msg.get('content', '')
# Auto-strip thinking dari content # Auto-strip thinking dari content
self.content = gadget.strip_thinking(raw_content) if isinstance(raw_content, str) else raw_content self.content = _strip_thinking(raw_content) if isinstance(raw_content, str) else raw_content
self.tool_calls = msg.get('tool_calls', None) self.tool_calls = msg.get('tool_calls', None)
self.warning = None self.warning = None

View File

@ -1,9 +1,11 @@
import json import json
from datetime import datetime from datetime import datetime
def _ts(): def _ts():
return datetime.now().strftime('%H:%M:%S') return datetime.now().strftime('%H:%M:%S')
def execute_tool(tool_call, TOOL_HANDLERS): def execute_tool(tool_call, TOOL_HANDLERS):
tname = tool_call['function']['name'] tname = tool_call['function']['name']
targs = json.loads(tool_call['function']['arguments']) targs = json.loads(tool_call['function']['arguments'])
@ -24,6 +26,7 @@ def execute_tool(tool_call, TOOL_HANDLERS):
except Exception as e: except Exception as e:
return f'Error executing tool: {str(e)}' return f'Error executing tool: {str(e)}'
def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on_tool_calls=None): def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on_tool_calls=None):
for step in range(max_iterations): for step in range(max_iterations):
print(f'[{_ts()}] Step {step + 1} — calling LLM...', flush=True) print(f'[{_ts()}] Step {step + 1} — calling LLM...', flush=True)
@ -63,4 +66,3 @@ def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on
'content': 'Max iterations reached without final answer.', 'content': 'Max iterations reached without final answer.',
}) })
return None return None

View File

@ -7,7 +7,7 @@ from datetime import datetime
import config import config
from services.session_manager import SessionManager from services.session_manager import SessionManager
from scripts.agent_loop import run_agent_loop from services.agent_loop import run_agent_loop
from scripts.personality import PERSONALITY from scripts.personality import PERSONALITY
from tools.roleplayer import _name_mentioned from tools.roleplayer import _name_mentioned

View File

@ -6,7 +6,7 @@ from datetime import datetime
from slixmpp import ClientXMPP from slixmpp import ClientXMPP
from services.session_manager import SessionManager from services.session_manager import SessionManager
from scripts.agent_loop import run_agent_loop from services.agent_loop import run_agent_loop
import config import config
from tools.roleplayer import should_respond from tools.roleplayer import should_respond
@ -436,7 +436,7 @@ class XMPPClient(ClientXMPP):
session.start_timer(300, self._timeout_session, room, 'groupchat') session.start_timer(300, self._timeout_session, room, 'groupchat')
def _execute_tool(self, tool_call): def _execute_tool(self, tool_call):
from scripts.agent_loop import execute_tool from services.agent_loop import execute_tool
return execute_tool(tool_call, self._TOOL_HANDLERS) return execute_tool(tool_call, self._TOOL_HANDLERS)
def _schedule_send(self, to, body, mtype='chat'): def _schedule_send(self, to, body, mtype='chat'):

View File

@ -2,7 +2,7 @@ import json
import threading import threading
from datetime import datetime from datetime import datetime
import config import config
from scripts import ntro, agent_loop from scripts import ntro
def _add_msg(app, role, content, **kwargs): def _add_msg(app, role, content, **kwargs):
@ -15,14 +15,6 @@ def _add_msg(app, role, content, **kwargs):
) )
WELCOME_ART = """ WELCOME_ART = """
,--. ,--.,------.,--. ,--.,------. ,------. ,--.,--. ,--.
| '--' || .---'| ,'.| || .-. \\ | .--. '| || .' /
| .--. || `--, | |' ' || | \\ :| '--'.'| || . '
| | | || `---.| | ` || '--' /| |\\ \\ | || |\\ \\
`--' `--'`------'`--' `--'`-------' `--' '--'`--'`--' '--'
"""
"""
__ __ _______ __ _ ______ ______ ___ ___ _ __ __ _______ __ _ ______ ______ ___ ___ _
| | | || || | | || | | _ | | | | | | | | | | || || | | || | | _ | | | | | | |
| |_| || ___|| |_| || _ || | || | | | |_| | | |_| || ___|| |_| || _ || | || | | | |_| |
@ -43,6 +35,7 @@ WELCOME_ART = """
""" """
def log(app, role, text): def log(app, role, text):
app.log.append({ app.log.append({
"role": role, "role": role,
@ -151,9 +144,7 @@ def _agent_loop(app):
"arguments": targs, "arguments": targs,
})) }))
app.scroll = 999999 app.scroll = 999999
result = agent_loop.execute_tool(tc, app.TOOL_HANDLERS) execute_tool(app, tc)
_add_msg(app, "tool", str(result), tool_call_id=tc["id"])
# Log content AI setelah tools (jika ada) # Log content AI setelah tools (jika ada)
if response.content and response.content.strip(): if response.content and response.content.strip():
@ -174,6 +165,26 @@ def _agent_loop(app):
app.agent_done.set() app.agent_done.set()
app.agent_done.set() def execute_tool(app, tool_call):
ntro.end(stamp) tname = tool_call["function"]["name"]
targs = json.loads(tool_call["function"]["arguments"])
handler = app.TOOL_HANDLERS.get(tname)
if not handler:
result = f"Tool {tname} not found"
else:
try:
if tname == "search_code":
result = handler(
pattern=targs["pattern"],
search_type=targs["search_type"],
path=targs.get("path", "."),
)
elif tname == "git_operation":
result = handler(args=targs["args"])
else:
result = handler(**targs)
except Exception as e:
result = f"Error executing tool: {str(e)}"
_add_msg(app, "tool", str(result), tool_call_id=tool_call["id"])