Compare commits

..

6 Commits

8 changed files with 97 additions and 60 deletions

View File

@ -7,9 +7,16 @@ Kamu dapat bertindak sebagai coding agent yang membantu software engineering tas
## Approach
- 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
- Selalu pertimbangkan error handling dan edge cases
- Jangan hanya mengubah file yang paling terlihat. Tangani juga edge case dan error yang relevan.
- 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
- 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
## Code Review Style
@ -28,35 +35,3 @@ Kamu dapat bertindak sebagai coding agent yang membantu software engineering tas
- 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.
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,6 +29,8 @@ tools_definition = [
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
gadget.tools_mapping( schema = carrack.schema_sendhttprequest, handler = carrack.sendhttprequest ),
@ -87,7 +89,7 @@ def main():
if not os.path.isdir(resolved):
print(f"Error: '{resolved}' is not a valid directory", flush=True)
sys.exit(1)
os.chdir(resolved)
coder.set_current_workspace(resolved)
llm_client = LLMClient(config.llm_baseurl, config.llm_model, config.llm_api_key, config.llm_timeout)

View File

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

View File

@ -4,7 +4,7 @@
import curses
import json
import os
from tools.coder import get_current_workspace
# -- Color pair IDs (id 1-9, id 0 = default curses) --
C_HEADER = 1 # header bar: biru
@ -377,7 +377,7 @@ def draw_status(app, stdscr):
# Status bar di baris h-9: mode, workspace, session
h, w = app.h, app.w
y = h - 9
ws = os.getcwd()
ws = get_current_workspace()
session_tag = ""
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)
return
if self._skill == 'roleplayer' and chat_type == 'private':
if 'roleplayer' in self._skill and chat_type == 'private':
try:
results = ragroleplay.user_load(config.ragroleplay_db_path, unique_id=str(chat_id), character=personality.PERSONALITY.name)
if results:
@ -213,20 +213,22 @@ class TelegramClient:
)
session.add_message('system', context)
else:
tg_info = f'ID: {chat_id}'
if tg_username:
tg_info += f', @{tg_username}'
tg_id_str = str(chat_id)
tg_user_str = tg_username or ''
session.add_message('system',
f'[User Context: Pengguna baru — belum ada di database]\n'
f'[PENTING: Kamu BELUM mengenal user ini. WAJIB tanya nama sebagai pembuka. '
f'Setelah nama diketahui, simpan via users_store. '
f'Lanjutkan percakapan natural dan proaktif melengkapi data user lainnya di pesan berikutnya.]\n'
f'[Platform: Telegram ({tg_info})]')
f'[Platform: Telegram]\n'
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:
pass
session.add_message('user', body)
is_roleplay = self._skill == 'roleplayer'
is_roleplay = 'roleplayer' in self._skill
asyncio.run_coroutine_threadsafe(
self._app.bot.send_chat_action(chat_id=chat_id, action='typing'),
@ -285,7 +287,7 @@ class TelegramClient:
msg = 'Max iterations reached without final answer.'
self._schedule_send(chat_id, msg, reply_to_msg_id)
if should_close and chat_type == 'private' and self._skill == 'roleplayer':
if should_close and chat_type == 'private' and 'roleplayer' in self._skill:
print(f'[{_ts()}] Natural close triggered for {chat_id}', flush=True)
self._session_mgr.reset(str(chat_id))
else:

View File

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

View File

@ -3,6 +3,19 @@ import subprocess
import re
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 = {
"type": "function",
"function": {
@ -20,7 +33,9 @@ schema_read_file = {
def read_file(path):
try:
with open(path, 'r', encoding='utf-8', errors='replace') as f:
# Use absolute path if it's relative to CURRENT_WORKSPACE
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()
return ''.join(f"{i+1}: {line}" for i, line in enumerate(lines))
except Exception as e:
@ -44,9 +59,10 @@ schema_write_file = {
def write_file(path, content):
try:
with open(path, 'w', encoding='utf-8') as f:
full_path = os.path.join(get_current_workspace(), path) if not os.path.isabs(path) else path
with open(full_path, 'w', encoding='utf-8') as f:
f.write(content)
return f"Successfully wrote to {path}"
return f"Successfully wrote to {full_path}"
except Exception as e:
return f"Error writing file: {str(e)}"
@ -69,14 +85,15 @@ schema_edit_file = {
def edit_file(path, old_string, new_string):
try:
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:
content = f.read()
if old_string not in content:
return f"Error: old_string not found in {path}"
return f"Error: old_string not found in {full_path}"
new_content = content.replace(old_string, new_string)
with open(path, 'w', encoding='utf-8') as f:
with open(full_path, 'w', encoding='utf-8') as f:
f.write(new_content)
return f"Successfully edited {path}"
return f"Successfully edited {full_path}"
except Exception as e:
return f"Error editing file: {str(e)}"
@ -84,7 +101,7 @@ schema_run_bash = {
"type": "function",
"function": {
"name" : "run_bash",
"description" : "Run a bash command. Returns stdout, stderr, and return code.",
"description" : "Run a bash command. Returns stdout, stderr, and return code. Commands are executed relative to the current workspace.",
"parameters" : {
"type" : "object",
"properties" : {
@ -97,7 +114,9 @@ schema_run_bash = {
def run_bash(command):
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
# Prepend 'cd' to the command to ensure it runs in the correct workspace
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}"
except Exception as e:
return f"Error running command: {str(e)}"
@ -121,12 +140,15 @@ schema_search_code = {
def search_code(pattern, search_type, path="."):
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":
files = glob_module.glob(f"{path}/**/{pattern}", recursive=True)
files = glob_module.glob(f"{search_path}/**/{pattern}", recursive=True)
return "\n".join(files) if files else "No files found"
elif search_type == "content":
results = []
for root, dirs, files in os.walk(path):
for root, dirs, files in os.walk(search_path):
for file in files:
file_path = os.path.join(root, file)
try:
@ -162,8 +184,39 @@ schema_git_operation = {
def git_operation(args):
try:
result = subprocess.run(["git", *args], capture_output=True, text=True, timeout=10)
# Execute git command from the current workspace
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}"
except Exception as 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,6 +684,10 @@ def user_load(query_name=None, unique_id=None, persona_keyword=None, user_id=Non
f"Character: {row.get('character', '-') or '-'}\n"
f"Salutation: {row.get('salutation', '-') or '-'}\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)