Compare commits

..

No commits in common. "47b07b18f6b34ce8dee501e151eb2d1df8f83e8e" and "7eb95f3bd022bf339e21288bde6207ba3f7075e2" have entirely different histories.

8 changed files with 60 additions and 97 deletions

View File

@ -7,16 +7,9 @@ Kamu dapat bertindak sebagai coding agent yang membantu software engineering tas
## Approach ## Approach
- Analisis problem sebelum mulai coding - Analisis problem sebelum mulai coding
- Pahami tujuan akhir tugas.
- Cari semua file, fungsi, tipe, API, route, validasi, dan test yang terdampak.
- Ikuti pola dan struktur yang sudah digunakan di project.
- Tulis code yang clean, readable, dan maintainable - Tulis code yang clean, readable, dan maintainable
- Jangan hanya mengubah file yang paling terlihat. Tangani juga edge case dan error yang relevan. - Selalu pertimbangkan error handling dan edge cases
- Cari referensi lama atau bagian lain yang mungkin ikut rusak.
- Jika ada error akibat perubahanmu, perbaiki lalu jalankan ulang validasinya.
- Berikan penjelasan singkat tentang perubahan yang dibuat - Berikan penjelasan singkat tentang perubahan yang dibuat
- Kerjakan tugas secara menyeluruh, bukan sekadar membuat perubahan minimal.
- Pada jawaban akhir, tuliskan Apa yang diubah, File atau bagian yang terdampak, Validasi yang dijalankan, Bagian yang belum bisa diverifikasi.
- Suggest improvements jika ada - Suggest improvements jika ada
## Code Review Style ## Code Review Style
@ -35,3 +28,35 @@ Kamu dapat bertindak sebagai coding agent yang membantu software engineering tas
- Kamu boleh menjalankan `git status`, `git diff`, `git log` secara bebas untuk inspeksi. - Kamu boleh menjalankan `git status`, `git diff`, `git log` secara bebas untuk inspeksi.
- Konfirmasi dahulu sebelum menjalankan `git add` atau `git commit` setelah membuat perubahan. - Konfirmasi dahulu sebelum menjalankan `git add` atau `git commit` setelah membuat perubahan.
Kamu adalah AI coding agent. Kerjakan tugas secara menyeluruh, bukan sekadar membuat perubahan minimal.
Sebelum mengedit:
Pahami tujuan akhir tugas.
Cari semua file, fungsi, tipe, API, route, validasi, dan test yang terdampak.
Ikuti pola dan struktur yang sudah digunakan di project.
Saat mengerjakan:
Jangan hanya mengubah file yang paling terlihat.
Tangani juga edge case dan error yang relevan.
Jangan meninggalkan TODO, placeholder, pseudocode, atau bagian setengah jadi.
Jangan mengurangi scope hanya karena bagian tertentu sulit.
Sebelum menyatakan selesai:
Cek ulang semua requirement.
Cari referensi lama atau bagian lain yang mungkin ikut rusak.
Jalankan lint, type-check, test, atau build yang tersedia.
Jika ada error akibat perubahanmu, perbaiki lalu jalankan ulang validasinya.
Tugas dianggap selesai hanya jika perubahan sudah lengkap, konsisten, dan sudah divalidasi.
Pada jawaban akhir, tuliskan:
Apa yang diubah.
File atau bagian yang terdampak.
Validasi yang dijalankan.
Bagian yang belum bisa diverifikasi.
Jangan membuat perubahan sekecil mungkin. Buat perubahan sekecil mungkin yang tetap lengkap.

View File

@ -29,8 +29,6 @@ tools_definition = [
gadget.tools_mapping( schema = coder.schema_git_operation, handler = coder.git_operation ), gadget.tools_mapping( schema = coder.schema_git_operation, handler = coder.git_operation ),
gadget.tools_mapping( schema = coder.schema_workspace_change, handler = coder.workspace_change ),
# Carrack Tools # Carrack Tools
gadget.tools_mapping( schema = carrack.schema_sendhttprequest, handler = carrack.sendhttprequest ), gadget.tools_mapping( schema = carrack.schema_sendhttprequest, handler = carrack.sendhttprequest ),
@ -89,7 +87,7 @@ def main():
if not os.path.isdir(resolved): if not os.path.isdir(resolved):
print(f"Error: '{resolved}' is not a valid directory", flush=True) print(f"Error: '{resolved}' is not a valid directory", flush=True)
sys.exit(1) sys.exit(1)
coder.set_current_workspace(resolved) os.chdir(resolved)
llm_client = LLMClient(config.llm_baseurl, config.llm_model, config.llm_api_key, config.llm_timeout) llm_client = LLMClient(config.llm_baseurl, config.llm_model, config.llm_api_key, config.llm_timeout)

View File

@ -6,7 +6,6 @@ import curses
import os import os
import config import config
from .agent import submit, log from .agent import submit, log
from tools.coder import set_current_workspace
def _build_visual(buffer, max_chars): def _build_visual(buffer, max_chars):
@ -174,7 +173,7 @@ def workspace_popup(app, stdscr):
if ws: if ws:
resolved = os.path.abspath(ws) resolved = os.path.abspath(ws)
if os.path.isdir(resolved): if os.path.isdir(resolved):
set_current_workspace(resolved) os.chdir(resolved)
log(app, "system", f"Workspace \u2192 {resolved}") log(app, "system", f"Workspace \u2192 {resolved}")
else: else:
log(app, "error", f"Invalid directory: {resolved}") log(app, "error", f"Invalid directory: {resolved}")

View File

@ -4,7 +4,7 @@
import curses import curses
import json import json
from tools.coder import get_current_workspace import os
# -- Color pair IDs (id 1-9, id 0 = default curses) -- # -- Color pair IDs (id 1-9, id 0 = default curses) --
C_HEADER = 1 # header bar: biru C_HEADER = 1 # header bar: biru
@ -377,7 +377,7 @@ def draw_status(app, stdscr):
# Status bar di baris h-9: mode, workspace, session # Status bar di baris h-9: mode, workspace, session
h, w = app.h, app.w h, w = app.h, app.w
y = h - 9 y = h - 9
ws = get_current_workspace() ws = os.getcwd()
session_tag = "" session_tag = ""
if app.current_session: if app.current_session:

View File

@ -193,7 +193,7 @@ class TelegramClient:
self._schedule_send(chat_id, 'Memulai sesi baru. Ada yang bisa dibantu?', reply_to_msg_id) self._schedule_send(chat_id, 'Memulai sesi baru. Ada yang bisa dibantu?', reply_to_msg_id)
return return
if 'roleplayer' in self._skill and chat_type == 'private': if self._skill == 'roleplayer' and chat_type == 'private':
try: try:
results = ragroleplay.user_load(config.ragroleplay_db_path, unique_id=str(chat_id), character=personality.PERSONALITY.name) results = ragroleplay.user_load(config.ragroleplay_db_path, unique_id=str(chat_id), character=personality.PERSONALITY.name)
if results: if results:
@ -213,22 +213,20 @@ class TelegramClient:
) )
session.add_message('system', context) session.add_message('system', context)
else: else:
tg_id_str = str(chat_id) tg_info = f'ID: {chat_id}'
tg_user_str = tg_username or '' if tg_username:
tg_info += f', @{tg_username}'
session.add_message('system', session.add_message('system',
f'[User Context: Pengguna baru — belum ada di database]\n' f'[User Context: Pengguna baru — belum ada di database]\n'
f'[PENTING: Kamu BELUM mengenal user ini. WAJIB tanya nama sebagai pembuka. ' f'[PENTING: Kamu BELUM mengenal user ini. WAJIB tanya nama sebagai pembuka. '
f'Setelah nama diketahui, simpan via users_store. ' f'Setelah nama diketahui, simpan via users_store. '
f'Lanjutkan percakapan natural dan proaktif melengkapi data user lainnya di pesan berikutnya.]\n' f'Lanjutkan percakapan natural dan proaktif melengkapi data user lainnya di pesan berikutnya.]\n'
f'[Platform: Telegram]\n' f'[Platform: Telegram ({tg_info})]')
f'[PENTING: Saat memanggil users_store, gunakan nilai berikut:]\n'
f'[telegram_id: {tg_id_str}]\n'
f'[telegram_username: {tg_user_str}]')
except Exception: except Exception:
pass pass
session.add_message('user', body) session.add_message('user', body)
is_roleplay = 'roleplayer' in self._skill is_roleplay = self._skill == 'roleplayer'
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self._app.bot.send_chat_action(chat_id=chat_id, action='typing'), self._app.bot.send_chat_action(chat_id=chat_id, action='typing'),
@ -287,7 +285,7 @@ class TelegramClient:
msg = 'Max iterations reached without final answer.' msg = 'Max iterations reached without final answer.'
self._schedule_send(chat_id, msg, reply_to_msg_id) self._schedule_send(chat_id, msg, reply_to_msg_id)
if should_close and chat_type == 'private' and 'roleplayer' in self._skill: if should_close and chat_type == 'private' and self._skill == 'roleplayer':
print(f'[{_ts()}] Natural close triggered for {chat_id}', flush=True) print(f'[{_ts()}] Natural close triggered for {chat_id}', flush=True)
self._session_mgr.reset(str(chat_id)) self._session_mgr.reset(str(chat_id))
else: else:

View File

@ -297,7 +297,7 @@ class XMPPClient(ClientXMPP):
self._schedule_send(jid, 'Memulai sesi baru. Ada yang bisa di bantu?') self._schedule_send(jid, 'Memulai sesi baru. Ada yang bisa di bantu?')
return return
if 'roleplayer' in self._skill: if self._skill == 'roleplayer':
try: try:
results = ragroleplay.user_load(config.ragroleplay_db_path, unique_id=jid, character=personality.PERSONALITY.name) results = ragroleplay.user_load(config.ragroleplay_db_path, unique_id=jid, character=personality.PERSONALITY.name)
if results: if results:
@ -328,7 +328,7 @@ class XMPPClient(ClientXMPP):
session.add_message('user', body) session.add_message('user', body)
is_roleplay = 'roleplayer' in self._skill is_roleplay = self._skill == 'roleplayer'
if not is_roleplay: if not is_roleplay:
self._schedule_send(jid, f'> {body}\nThinking...') self._schedule_send(jid, f'> {body}\nThinking...')
@ -388,7 +388,7 @@ class XMPPClient(ClientXMPP):
self._schedule_send(jid, f'> {quote}\n{msg}', 'chat') self._schedule_send(jid, f'> {quote}\n{msg}', 'chat')
# Natural close: DM only, roleplayer only # Natural close: DM only, roleplayer only
if should_close and 'roleplayer' in self._skill: if should_close and self._skill == 'roleplayer':
print(f'[{_ts()}] Natural close triggered for {jid}', flush=True) print(f'[{_ts()}] Natural close triggered for {jid}', flush=True)
self._session_mgr.reset(jid) self._session_mgr.reset(jid)
else: else:
@ -424,7 +424,7 @@ class XMPPClient(ClientXMPP):
my_name = personality.PERSONALITY.name my_name = personality.PERSONALITY.name
quote = f'[{nick}] {body}' quote = f'[{nick}] {body}'
_is_roleplay = 'roleplayer' in self._skill _is_roleplay = self._skill == 'roleplayer'
def on_tool_calls(tnames): def on_tool_calls(tnames):
if not _is_roleplay: if not _is_roleplay:

View File

@ -3,19 +3,6 @@ import subprocess
import re import re
import glob as glob_module import glob as glob_module
# Global state to track the agent's current working directory
# This ensures that subsequent bash commands run in the correct directory
CURRENT_WORKSPACE = os.getcwd()
def get_current_workspace():
global CURRENT_WORKSPACE
return CURRENT_WORKSPACE
def set_current_workspace(path):
global CURRENT_WORKSPACE
CURRENT_WORKSPACE = path
os.chdir(path)
schema_read_file = { schema_read_file = {
"type": "function", "type": "function",
"function": { "function": {
@ -33,9 +20,7 @@ schema_read_file = {
def read_file(path): def read_file(path):
try: try:
# Use absolute path if it's relative to CURRENT_WORKSPACE with open(path, 'r', encoding='utf-8', errors='replace') as f:
full_path = os.path.join(get_current_workspace(), path) if not os.path.isabs(path) else path
with open(full_path, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines() lines = f.readlines()
return ''.join(f"{i+1}: {line}" for i, line in enumerate(lines)) return ''.join(f"{i+1}: {line}" for i, line in enumerate(lines))
except Exception as e: except Exception as e:
@ -59,10 +44,9 @@ schema_write_file = {
def write_file(path, content): def write_file(path, content):
try: try:
full_path = os.path.join(get_current_workspace(), path) if not os.path.isabs(path) else path with open(path, 'w', encoding='utf-8') as f:
with open(full_path, 'w', encoding='utf-8') as f:
f.write(content) f.write(content)
return f"Successfully wrote to {full_path}" return f"Successfully wrote to {path}"
except Exception as e: except Exception as e:
return f"Error writing file: {str(e)}" return f"Error writing file: {str(e)}"
@ -85,15 +69,14 @@ schema_edit_file = {
def edit_file(path, old_string, new_string): def edit_file(path, old_string, new_string):
try: try:
full_path = os.path.join(get_current_workspace(), path) if not os.path.isabs(path) else path with open(path, 'r', encoding='utf-8', errors='replace') as f:
with open(full_path, 'r', encoding='utf-8', errors='replace') as f:
content = f.read() content = f.read()
if old_string not in content: if old_string not in content:
return f"Error: old_string not found in {full_path}" return f"Error: old_string not found in {path}"
new_content = content.replace(old_string, new_string) new_content = content.replace(old_string, new_string)
with open(full_path, 'w', encoding='utf-8') as f: with open(path, 'w', encoding='utf-8') as f:
f.write(new_content) f.write(new_content)
return f"Successfully edited {full_path}" return f"Successfully edited {path}"
except Exception as e: except Exception as e:
return f"Error editing file: {str(e)}" return f"Error editing file: {str(e)}"
@ -101,7 +84,7 @@ schema_run_bash = {
"type": "function", "type": "function",
"function": { "function": {
"name" : "run_bash", "name" : "run_bash",
"description" : "Run a bash command. Returns stdout, stderr, and return code. Commands are executed relative to the current workspace.", "description" : "Run a bash command. Returns stdout, stderr, and return code.",
"parameters" : { "parameters" : {
"type" : "object", "type" : "object",
"properties" : { "properties" : {
@ -114,9 +97,7 @@ schema_run_bash = {
def run_bash(command): def run_bash(command):
try: try:
# Prepend 'cd' to the command to ensure it runs in the correct workspace result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
full_command = f"cd {get_current_workspace()} && {command}"
result = subprocess.run(full_command, shell=True, capture_output=True, text=True, timeout=30)
return f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}\nreturn code: {result.returncode}" return f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}\nreturn code: {result.returncode}"
except Exception as e: except Exception as e:
return f"Error running command: {str(e)}" return f"Error running command: {str(e)}"
@ -140,15 +121,12 @@ schema_search_code = {
def search_code(pattern, search_type, path="."): def search_code(pattern, search_type, path="."):
try: try:
# Resolve path relative to workspace
search_path = os.path.join(get_current_workspace(), path) if not os.path.isabs(path) else path
if search_type == "glob": if search_type == "glob":
files = glob_module.glob(f"{search_path}/**/{pattern}", recursive=True) files = glob_module.glob(f"{path}/**/{pattern}", recursive=True)
return "\n".join(files) if files else "No files found" return "\n".join(files) if files else "No files found"
elif search_type == "content": elif search_type == "content":
results = [] results = []
for root, dirs, files in os.walk(search_path): for root, dirs, files in os.walk(path):
for file in files: for file in files:
file_path = os.path.join(root, file) file_path = os.path.join(root, file)
try: try:
@ -184,39 +162,8 @@ schema_git_operation = {
def git_operation(args): def git_operation(args):
try: try:
# Execute git command from the current workspace result = subprocess.run(["git", *args], capture_output=True, text=True, timeout=10)
full_command = ["git", *args]
result = subprocess.run(full_command, cwd=get_current_workspace(), capture_output=True, text=True, timeout=10)
return f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}\nreturn code: {result.returncode}" return f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}\nreturn code: {result.returncode}"
except Exception as e: except Exception as e:
return f"Error running git command: {str(e)}" return f"Error running git command: {str(e)}"
schema_workspace_change = {
"type": "function",
"function": {
"name": "workspace_change",
"description": "Change the current working directory of the agent session. Supports tilde (~) expansion for the user home directory (/home/aji/).",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "The target directory path to change to."}
},
"required": ["path"]
}
}
}
def workspace_change(path):
try:
if path.startswith("~"):
target_path = os.path.expanduser(path)
else:
target_path = os.path.abspath(path)
if os.path.exists(target_path) and os.path.isdir(target_path):
set_current_workspace(target_path)
return f"Successfully changed workspace to: {target_path}"
else:
return f"Error: The path {target_path} does not exist or is not a directory."
except Exception as e:
return f"Error changing workspace: {str(e)}"

View File

@ -684,10 +684,6 @@ def user_load(query_name=None, unique_id=None, persona_keyword=None, user_id=Non
f"Character: {row.get('character', '-') or '-'}\n" f"Character: {row.get('character', '-') or '-'}\n"
f"Salutation: {row.get('salutation', '-') or '-'}\n" f"Salutation: {row.get('salutation', '-') or '-'}\n"
f"Persona: {row['persona']}\n" f"Persona: {row['persona']}\n"
f"Telegram ID: {row.get('telegram_id', '-') or '-'}\n"
f"Telegram Username: {row.get('telegram_username', '-') or '-'}\n"
f"XAMPP Username: {row.get('xampp_username', '-') or '-'}\n"
f"Alias: {row.get('alias', '-') or '-'}\n"
) )
output.append(user_info) output.append(user_info)