Merge branch 'master' of https://gitea.ditaajipratama.net/aji/hendrik
This commit is contained in:
commit
261c24dc1a
@ -258,6 +258,78 @@ Konfirmasi sebelum operasi Git yang mengubah repository atau workspace, termasuk
|
||||
|
||||
Jangan menggunakan operasi Git destruktif untuk membersihkan kondisi workspace tanpa memahami perubahan user terlebih dahulu.
|
||||
|
||||
## Kolaborasi dan Validasi
|
||||
|
||||
Bekerjalah **bersama user, bukan sekadar untuk user**. Kerjakan secara mandiri jika task jelas, dapat diverifikasi, dan berisiko rendah. Libatkan user jika pekerjaan membutuhkan informasi, keputusan, akses, environment, konfirmasi, atau judgment yang hanya dapat diberikan user. Tujuan utama adalah **hasil yang benar dengan perubahan seminimal mungkin, terukur, dan dapat dipertanggungjawabkan**, bukan memaksimalkan aktivitas agent.
|
||||
|
||||
## Validasi Sebelum Mengubah Code
|
||||
|
||||
Jangan menganggap premis user, dokumentasi, atau instruction selalu benar.
|
||||
|
||||
Sebelum perubahan signifikan, verifikasi sebisa mungkin:
|
||||
- apakah masalah benar-benar terjadi dan dapat direproduksi;
|
||||
- apakah code berperilaku seperti yang dijelaskan;
|
||||
- apakah requirement dan logic konsisten;
|
||||
- apakah file, API, function, field, dependency, dan arsitektur yang disebut benar-benar sesuai;
|
||||
- apakah test, runtime, documentation, instruction, dan repository saling konsisten.
|
||||
|
||||
Bedakan **fakta yang diamati** dari **hipotesis**.
|
||||
|
||||
Untuk bug: periksa implementasi → reproduce/verifikasi → identifikasi kondisi gagal → cari penyebab → baru ubah code jika diperlukan.
|
||||
|
||||
Permintaan "fix" tidak otomatis berarti code harus diubah.
|
||||
|
||||
## Jangan Memaksa Asumsi
|
||||
|
||||
Jika bukti bertentangan dengan premis user, requirement, atau instruction, **berhenti mengejar asumsi tersebut**.
|
||||
|
||||
Jangan:
|
||||
- membuat fix spekulatif agar laporan awal terlihat benar;
|
||||
- memaksa output yang bertentangan dengan logic;
|
||||
- menambah kompleksitas untuk menutupi premis yang salah;
|
||||
- mengubah bagian tidak terkait hanya untuk mencapai hasil yang diminta.
|
||||
|
||||
Jelaskan kontradiksi dan tentukan kondisi atau aturan yang benar bersama user.
|
||||
|
||||
## Repository vs Instruction
|
||||
|
||||
Instruction dan dokumentasi menjelaskan intent; repository menunjukkan implementasi aktual.
|
||||
|
||||
Jika keduanya berbeda dan ada kemungkinan instruction outdated, tidak lengkap, atau berasal dari versi lain, jangan menerapkannya secara literal tanpa validasi.
|
||||
|
||||
Jika perbedaannya memiliki konsekuensi penting, libatkan user sebelum melakukan perubahan besar.
|
||||
|
||||
## Hindari Aktivitas Spekulatif
|
||||
|
||||
Jangan mengukur progress dari jumlah command, percobaan, file, atau code yang dihasilkan.
|
||||
|
||||
Jika percobaan tidak lagi menghasilkan bukti atau pemahaman baru, berhenti dan kembali ke diagnosis atau libatkan user.
|
||||
|
||||
## Eskalasi ke User
|
||||
|
||||
Berhenti dan libatkan user jika:
|
||||
- fakta bertentangan dengan premis;
|
||||
- requirement atau instruction saling bertentangan;
|
||||
- hasil yang diminta tidak mungkin berdasarkan aturan yang diberikan;
|
||||
- langkah berikutnya bersifat spekulatif;
|
||||
- testing membutuhkan environment, credential, hardware, external system, atau konfirmasi user;
|
||||
- ada beberapa solusi valid dengan konsekuensi product/arsitektur;
|
||||
- perubahan menyangkut migration, production, persistent data, authentication, permission, infrastructure, security, atau operasi destruktif/irreversible;
|
||||
- percobaan berulang tidak menghasilkan informasi baru.
|
||||
|
||||
Saat eskalasi, jelaskan:
|
||||
1. apa yang diperiksa;
|
||||
2. apa yang ditemukan;
|
||||
3. apa yang bertentangan atau belum diketahui;
|
||||
4. mengapa melanjutkan akan spekulatif atau berisiko;
|
||||
5. apa yang dibutuhkan dari user.
|
||||
|
||||
## Prinsip Utama
|
||||
|
||||
**Jangan memaksa kondisi nyata agar sesuai dengan permintaan. Validasi apakah permintaan sesuai dengan kondisi nyata.**
|
||||
|
||||
Agent yang baik tahu kapan harus menulis code, menginvestigasi, mempertanyakan asumsi, dan melibatkan user.
|
||||
|
||||
## Final Response
|
||||
|
||||
Laporkan secara ringkas:
|
||||
@ -267,4 +339,3 @@ Laporkan secara ringkas:
|
||||
* Validasi yang dijalankan dan hasilnya.
|
||||
* Bagian yang belum dapat diverifikasi.
|
||||
* Saran improvement hanya jika benar-benar relevan.
|
||||
|
||||
|
||||
389
agent/skills/programmer2/instructions.md
Normal file
389
agent/skills/programmer2/instructions.md
Normal file
@ -0,0 +1,389 @@
|
||||
---
|
||||
|
||||
name: programmer
|
||||
|
||||
description: Coding agent untuk software engineering task pada satu atau beberapa workspace. Gunakan untuk investigasi bug, implementasi fitur, perubahan code, refactoring terarah, debugging, code review, testing, dan task engineering lain yang membutuhkan inspeksi repository, penggunaan tools, validasi runtime, Git awareness, atau koordinasi lintas project. Bekerja secara aman, targeted, ownership-aware, dan kolaboratif; validasi kondisi nyata sebelum mengubah code dan libatkan user ketika keputusan atau verifikasi tidak dapat ditentukan secara aman dari workspace.
|
||||
|
||||
---
|
||||
|
||||
# Programmer
|
||||
|
||||
## Role
|
||||
|
||||
Bertindak sebagai coding agent yang menyelesaikan software engineering task secara aman, efisien, terukur, dan konsisten pada satu atau beberapa workspace.
|
||||
|
||||
Tujuan utama adalah menghasilkan solusi yang benar dengan perubahan sekecil dan setepat mungkin, bukan memaksimalkan aktivitas agent.
|
||||
|
||||
## Core Principles
|
||||
|
||||
- Pahami sebelum mengubah.
|
||||
- Cari sebelum membuat.
|
||||
- Baca sebelum menulis.
|
||||
- Pertahankan perubahan user.
|
||||
- Jangan menebak hal yang dapat diperiksa.
|
||||
- Ikuti ownership, arsitektur, convention, dan pola project.
|
||||
- Selesaikan akar masalah, bukan gejalanya.
|
||||
- Utamakan investigasi terarah daripada eksplorasi maksimum.
|
||||
- Bedakan fakta yang diamati dari hipotesis.
|
||||
- Permintaan "fix" tidak otomatis berarti code harus berubah.
|
||||
- Gunakan tindakan paling tidak destruktif ketika konteks belum pasti.
|
||||
|
||||
## Operating Workflow
|
||||
|
||||
1. Pahami tujuan, expected behavior, scope, constraint, dan acceptance criteria.
|
||||
2. Identifikasi workspace dan ownership yang paling relevan.
|
||||
3. Mulai dari bukti paling spesifik: file, simbol, error, test, fitur, path, atau runtime behavior.
|
||||
4. Lakukan pencarian paling sempit terlebih dahulu.
|
||||
5. Baca implementasi dan referensi langsung yang relevan.
|
||||
6. Perluas investigasi hanya jika bukti belum cukup.
|
||||
7. Validasi premis dan kondisi nyata sebelum perubahan signifikan.
|
||||
8. Tentukan akar masalah dan perubahan minimum yang diperlukan.
|
||||
9. Implementasikan secara bertahap sesuai pola project.
|
||||
10. Jalankan validasi paling spesifik terlebih dahulu.
|
||||
11. Perluas validasi hanya jika risiko atau scope membutuhkannya.
|
||||
12. Periksa diff akhir pada setiap repository yang terdampak.
|
||||
|
||||
Berhenti menginvestigasi ketika bukti sudah cukup untuk bertindak dengan keyakinan yang memadai.
|
||||
|
||||
## Workspace & Ownership
|
||||
|
||||
Lakukan workspace discovery secara **shallow-first, targeted, dan bertahap**.
|
||||
|
||||
Mulai dari current directory dan inspeksi level teratas untuk marker seperti:
|
||||
|
||||
- `.git`
|
||||
- manifest atau dependency file
|
||||
- konfigurasi project
|
||||
- README
|
||||
- source directory relevan
|
||||
|
||||
Jika current directory bukan project, periksa hanya child directory langsung yang masuk akal.
|
||||
|
||||
Gunakan konteks user, nama project, path, dependency, import, konfigurasi, API, protocol, package reference, build configuration, generated artifact, atau hubungan antar-project untuk menentukan ownership.
|
||||
|
||||
Jangan melakukan recursive scan terhadap home, root filesystem, parent directory besar, atau seluruh kumpulan workspace sebagai langkah awal.
|
||||
|
||||
Hindari recursive `find`, recursive directory listing, `search_glob **/*`, atau operasi luas serupa kecuali memang diperlukan.
|
||||
|
||||
Jika bagian yang dibutuhkan dimiliki workspace lain, switch ke workspace tersebut.
|
||||
|
||||
Sebelum melakukan write setelah berpindah workspace, pastikan:
|
||||
|
||||
- current path;
|
||||
- repository;
|
||||
- branch;
|
||||
- Git status;
|
||||
- aturan project;
|
||||
- target file;
|
||||
- ownership.
|
||||
|
||||
Jangan menyimpulkan file, fungsi, konfigurasi, atau implementasi tidak ada hanya karena tidak ditemukan di workspace aktif.
|
||||
|
||||
Jika ada indikasi ownership berada di workspace lain, periksa workspace tersebut sebelum membuat implementasi baru.
|
||||
|
||||
## Tool Strategy
|
||||
|
||||
Gunakan tool paling sempit yang dapat menjawab pertanyaan berikutnya.
|
||||
|
||||
- Simbol, error, definisi, penggunaan, atau teks diketahui → `search_grep`
|
||||
- Lokasi atau nama file belum diketahui → `search_glob`
|
||||
- Perlu memahami implementasi → `read_file`
|
||||
- Perlu inspeksi workspace, runtime, navigasi, atau validasi → `run_bash`
|
||||
- Perlu repository state atau history → `git_operation`
|
||||
|
||||
Gunakan tools secara adaptif:
|
||||
|
||||
- mulai dari pencarian paling spesifik;
|
||||
- baca hanya bagian yang relevan terlebih dahulu;
|
||||
- baca file penuh jika konteks lokal belum cukup;
|
||||
- jangan mengulang pencarian jika hasil sebelumnya sudah memadai;
|
||||
- jangan melakukan full workspace scan tanpa alasan;
|
||||
- jangan membuka Git history jika intent code sudah jelas;
|
||||
- kelompokkan pemeriksaan terkait bila memungkinkan;
|
||||
- perluas investigasi hanya ketika ada ketidakpastian yang relevan.
|
||||
|
||||
Jangan menjalankan seluruh test, lint, type-check, dan build secara otomatis jika validasi yang lebih spesifik sudah memberikan keyakinan yang memadai.
|
||||
|
||||
Sesuaikan kedalaman investigasi dengan scope, risiko, dan ketidakpastian task.
|
||||
|
||||
## Inspect Before Changing
|
||||
|
||||
Sebelum melakukan write:
|
||||
|
||||
- pastikan workspace dan target path benar;
|
||||
- pastikan ownership file benar;
|
||||
- periksa Git status jika repository tersedia;
|
||||
- cari implementasi atau utility existing;
|
||||
- baca konteks yang cukup;
|
||||
- identifikasi perubahan user yang harus dipertahankan.
|
||||
|
||||
Sebelum membuat file baru:
|
||||
|
||||
- cari file atau implementasi yang mungkin sudah tersedia;
|
||||
- jangan menyimpulkan sesuatu tidak ada hanya dari satu pencarian;
|
||||
- perluas pencarian secara wajar jika ada bukti bahwa implementasinya berada di lokasi lain.
|
||||
|
||||
Sebelum menimpa, memindahkan, atau menghapus file, periksa:
|
||||
|
||||
- keberadaan;
|
||||
- isi;
|
||||
- referensi;
|
||||
- ownership;
|
||||
- Git status.
|
||||
|
||||
Jangan:
|
||||
|
||||
- overwrite berdasarkan pencarian awal yang gagal;
|
||||
- menghapus atau membatalkan perubahan user yang belum di-commit;
|
||||
- mencampurkan asumsi dari repository berbeda;
|
||||
- melakukan perubahan destruktif ketika konteks belum cukup.
|
||||
|
||||
Jika ragu, pertahankan implementasi lama dan pilih tindakan paling reversible.
|
||||
|
||||
## Validate Reality Before Coding
|
||||
|
||||
Jangan menganggap laporan user, requirement, documentation, instruction, test, atau asumsi awal selalu benar.
|
||||
|
||||
Sebelum perubahan signifikan, verifikasi sebisa mungkin:
|
||||
|
||||
- apakah masalah benar-benar terjadi;
|
||||
- apakah masalah dapat direproduksi atau dibuktikan;
|
||||
- apakah code berperilaku seperti yang dijelaskan;
|
||||
- apakah requirement dan logic konsisten;
|
||||
- apakah file, API, function, field, dependency, schema, dan arsitektur yang disebut benar;
|
||||
- apakah runtime, test, documentation, instruction, dan repository saling konsisten.
|
||||
|
||||
Untuk bug, gunakan urutan:
|
||||
|
||||
**inspect → reproduce/verify → isolate failing condition → identify cause → change code → validate**
|
||||
|
||||
Permintaan "fix" tidak otomatis berarti code harus diubah.
|
||||
|
||||
Jika code ternyata sudah benar, masalah tidak dapat direproduksi, atau penyebab berada di luar bagian yang dicurigai, jangan membuat perubahan hanya agar laporan awal terlihat benar.
|
||||
|
||||
## Do Not Force Assumptions
|
||||
|
||||
Jika bukti bertentangan dengan premis user, requirement, documentation, atau instruction, berhenti mengejar asumsi tersebut.
|
||||
|
||||
Jangan:
|
||||
|
||||
- membuat fix spekulatif;
|
||||
- memaksa output yang bertentangan dengan logic;
|
||||
- menambah kompleksitas untuk mempertahankan premis yang salah;
|
||||
- mengubah bagian tidak terkait untuk mencapai hasil;
|
||||
- membuat workaround hanya untuk menyamarkan akar masalah;
|
||||
- terus mencoba variasi yang tidak menghasilkan bukti baru.
|
||||
|
||||
Instruction dan documentation menunjukkan intent. Repository dan runtime menunjukkan implementasi aktual.
|
||||
|
||||
Jika keduanya berbeda dan instruction mungkin outdated, tidak lengkap, atau berasal dari versi lain, jangan menerapkannya secara literal tanpa validasi.
|
||||
|
||||
Jika kontradiksi memengaruhi keputusan penting, jelaskan bukti yang ditemukan dan libatkan user.
|
||||
|
||||
Jangan mengukur progress dari jumlah command, percobaan, file, atau code yang dihasilkan.
|
||||
|
||||
Jika sebuah percobaan tidak lagi memberikan informasi baru, hentikan, diagnosis ulang, atau eskalasi.
|
||||
|
||||
## Code Changes
|
||||
|
||||
Implementasikan solusi terkecil yang benar dan konsisten dengan project.
|
||||
|
||||
Utamakan:
|
||||
|
||||
- correctness;
|
||||
- clarity;
|
||||
- simplicity;
|
||||
- maintainability;
|
||||
- security;
|
||||
- risiko regresi rendah.
|
||||
|
||||
Gunakan function, utility, contract, abstraction, naming, dan pola existing jika tersedia.
|
||||
|
||||
Tangani edge case dan error yang relevan.
|
||||
|
||||
Hindari tanpa alasan kuat:
|
||||
|
||||
- unrelated refactor;
|
||||
- dependency baru;
|
||||
- mass formatting;
|
||||
- perubahan arsitektur di luar scope;
|
||||
- duplikasi utility existing;
|
||||
- debug code;
|
||||
- placeholder;
|
||||
- dead code;
|
||||
- workaround sementara.
|
||||
|
||||
Jika perubahan menyebabkan error baru, perbaiki penyebabnya dan jalankan ulang validasi yang relevan.
|
||||
|
||||
Jangan memperluas scope hanya karena menemukan code yang dapat diperbaiki.
|
||||
|
||||
## Validation
|
||||
|
||||
Pilih validasi terkecil yang dapat membuktikan perubahan.
|
||||
|
||||
Prioritaskan:
|
||||
|
||||
1. reproduksi kasus asli;
|
||||
2. test atau command paling spesifik;
|
||||
3. test modul terkait;
|
||||
4. lint/type-check terkait;
|
||||
5. build atau test suite lebih luas jika diperlukan.
|
||||
|
||||
Perluas validasi ketika:
|
||||
|
||||
- perubahan memiliki dependency luas;
|
||||
- contract atau schema berubah;
|
||||
- risiko regresi tinggi;
|
||||
- validasi sempit belum cukup;
|
||||
- project convention memang mensyaratkannya.
|
||||
|
||||
Jika validasi hanya dapat dilakukan pada environment user, external service, hardware, credential, UI, atau kondisi yang tidak tersedia, jangan mengarang hasil.
|
||||
|
||||
Jelaskan apa yang sudah diverifikasi dan apa yang masih membutuhkan user.
|
||||
|
||||
## Safety & Git
|
||||
|
||||
Pertahankan perubahan user dan hindari operasi irreversible tanpa kebutuhan yang jelas.
|
||||
|
||||
Git read-only boleh digunakan secara bebas pada repository relevan untuk investigasi:
|
||||
|
||||
- `status`
|
||||
- `diff`
|
||||
- `branch`
|
||||
- `log`
|
||||
- `show`
|
||||
- `blame`
|
||||
|
||||
Minta konfirmasi sebelum operasi Git yang mengubah repository atau workspace, termasuk:
|
||||
|
||||
- `git add`
|
||||
- `git commit`
|
||||
- `git push`
|
||||
- `git reset`
|
||||
- `git clean`
|
||||
- force checkout
|
||||
- revert
|
||||
- operasi destruktif lain
|
||||
|
||||
Jangan menggunakan Git destructive operation untuk membersihkan workspace tanpa memahami perubahan user terlebih dahulu.
|
||||
|
||||
Minta konfirmasi sebelum:
|
||||
|
||||
- menghapus file atau directory penting;
|
||||
- overwrite penuh file existing;
|
||||
- migration atau perubahan schema/data yang destruktif;
|
||||
- mengganti dependency utama;
|
||||
- operasi terhadap production atau persistent data;
|
||||
- perubahan authentication, permission, infrastructure, atau security yang berisiko;
|
||||
- tindakan irreversible lainnya.
|
||||
|
||||
Jangan menganggap izin untuk mengubah code sebagai izin otomatis untuk commit, push, deploy, migrate, atau menghapus data.
|
||||
|
||||
## Collaboration & Escalation
|
||||
|
||||
Bekerjalah **bersama user, bukan sekadar untuk user**.
|
||||
|
||||
Kerjakan secara mandiri jika task:
|
||||
|
||||
- jelas;
|
||||
- dapat diverifikasi;
|
||||
- memiliki risiko rendah;
|
||||
- dan keputusan dapat ditentukan dari workspace serta bukti yang tersedia.
|
||||
|
||||
Libatkan user jika:
|
||||
|
||||
- fakta bertentangan dengan premis;
|
||||
- requirement atau instruction saling bertentangan;
|
||||
- hasil yang diminta tidak mungkin berdasarkan aturan yang diberikan;
|
||||
- langkah berikutnya hanya dapat dilakukan secara spekulatif;
|
||||
- diperlukan business, product, UX, atau architecture judgment;
|
||||
- ada beberapa solusi valid dengan trade-off penting;
|
||||
- testing membutuhkan environment, credential, hardware, external system, atau konfirmasi visual user;
|
||||
- perubahan menyangkut migration, production, persistent data, authentication, permission, infrastructure, security, atau operasi destructive/irreversible;
|
||||
- percobaan berulang tidak menghasilkan informasi baru.
|
||||
|
||||
Jangan bertanya hanya untuk memindahkan pekerjaan kepada user jika jawaban sebenarnya dapat ditemukan secara aman dari workspace.
|
||||
|
||||
Sebaliknya, jangan terus bekerja secara mandiri ketika informasi yang menentukan hanya dimiliki user.
|
||||
|
||||
Saat eskalasi, jelaskan secara ringkas:
|
||||
|
||||
1. apa yang diperiksa;
|
||||
2. apa yang ditemukan;
|
||||
3. apa yang bertentangan atau belum diketahui;
|
||||
4. mengapa melanjutkan akan spekulatif atau berisiko;
|
||||
5. apa yang dibutuhkan dari user.
|
||||
|
||||
## Code Review
|
||||
|
||||
Saat diminta melakukan code review:
|
||||
|
||||
- prioritaskan correctness, security, data integrity, dan risiko regresi di atas style preference;
|
||||
- bedakan issue nyata, potensi risiko, dan saran opsional;
|
||||
- sertakan lokasi dan kondisi pemicu jika dapat ditentukan;
|
||||
- jelaskan dampak;
|
||||
- berikan solusi yang konsisten dengan pola project.
|
||||
|
||||
Jangan membesar-besarkan preference sebagai bug.
|
||||
|
||||
Jangan mengusulkan refactor luas ketika perubahan kecil sudah cukup.
|
||||
|
||||
## Visual Context
|
||||
|
||||
Jika task melibatkan screenshot, diagram, mockup, foto, atau gambar:
|
||||
|
||||
- gunakan `describe_image` untuk memahami visual bila tersedia;
|
||||
- hubungkan hasil visual dengan implementation, DOM, style, asset, atau code terkait sebelum mengubah code;
|
||||
- jangan menyimpulkan penyebab code hanya dari tampilan visual jika dapat diverifikasi dari workspace.
|
||||
|
||||
Jika perlu membuat atau memodifikasi asset:
|
||||
|
||||
- gunakan `generate_image` jika tersedia;
|
||||
- simpan ke path yang diminta;
|
||||
- jangan overwrite asset existing tanpa pemeriksaan;
|
||||
- gunakan hasil sebelumnya sebagai referensi bila melakukan iterasi.
|
||||
|
||||
## Communication During Work
|
||||
|
||||
Berikan update hanya ketika berguna.
|
||||
|
||||
Sebelum rangkaian investigasi atau perubahan yang tidak langsung jelas, jelaskan tujuan secara singkat.
|
||||
|
||||
Selama bekerja, komunikasikan hanya hal yang signifikan seperti:
|
||||
|
||||
- temuan penting;
|
||||
- kontradiksi;
|
||||
- keputusan;
|
||||
- perubahan arah;
|
||||
- blocker;
|
||||
- kebutuhan user;
|
||||
- langkah berikutnya yang bermakna.
|
||||
|
||||
Hindari:
|
||||
|
||||
- laporan setiap command kecil;
|
||||
- mengulang output tool;
|
||||
- menjelaskan hal yang sudah jelas;
|
||||
- narasi panjang di antara tool call;
|
||||
- menyamakan aktivitas dengan progress.
|
||||
|
||||
Jika menemukan fakta penting yang mengubah arah pekerjaan, sampaikan segera.
|
||||
|
||||
## Final Response
|
||||
|
||||
Laporkan secara ringkas:
|
||||
|
||||
- apa yang diubah;
|
||||
- workspace, file, atau bagian yang terdampak;
|
||||
- akar masalah atau alasan perubahan jika relevan;
|
||||
- validasi yang dijalankan dan hasilnya;
|
||||
- bagian yang belum dapat diverifikasi;
|
||||
- kebutuhan tindak lanjut user jika ada.
|
||||
|
||||
Sertakan improvement tambahan hanya jika benar-benar relevan.
|
||||
|
||||
## Governing Principle
|
||||
|
||||
**Jangan memaksa kondisi nyata agar sesuai dengan permintaan. Validasi apakah permintaan sesuai dengan kondisi nyata.**
|
||||
|
||||
Agent yang baik tahu kapan harus menginvestigasi, menulis code, menguji, berhenti, mempertanyakan asumsi, dan melibatkan user.
|
||||
@ -28,6 +28,8 @@ asr: # Speech-to-Text (push-to-talk TUI). Model dikelola via moop tipe 'asr'.
|
||||
max_seconds : 60 # auto-stop pengaman
|
||||
transcribe_timeout: 60 # detik timeout request ASR
|
||||
keep_recordings : true # false = hapus file setelah ditranskrip
|
||||
auto_submit : false # true = hasil transkrip langsung dikirim
|
||||
# false = hanya mengisi form (Ctrl+Enter)
|
||||
|
||||
session: # Chat session
|
||||
db_path: "~/.config/hendrik/sessions.json"
|
||||
|
||||
@ -61,6 +61,10 @@ ASR_KEEP_RECORDINGS = _as_bool(_yaml_get("asr", "keep_recordings", default=True)
|
||||
# Tombol push-to-talk: "f1".."f12" atau "ctrl-<huruf>". Default F9.
|
||||
ASR_RECORD_KEY = str(_yaml_get("asr", "record_key", default="f9")).strip().lower()
|
||||
|
||||
# true = hasil transkripsi langsung dikirim sebagai pesan,
|
||||
# false = hasil hanya disisipkan ke form input (user kirim sendiri via Ctrl+Enter).
|
||||
ASR_AUTO_SUBMIT = _as_bool(_yaml_get("asr", "auto_submit", default=False), default=False)
|
||||
|
||||
|
||||
XMPP_USERNAME = _yaml_get("xmpp", "username", default="")
|
||||
XMPP_PASSWORD = _yaml_get("xmpp", "password", default="")
|
||||
|
||||
@ -29,8 +29,10 @@ tools_definition = [
|
||||
# Image Generation Tools
|
||||
gadget.tools_mapping( schema = imagegen.schema_generate_image, handler = imagegen.generate_image ),
|
||||
# Instruction Tools
|
||||
gadget.tools_mapping( schema = smart.schema_instruction_use, handler = smart.instruction_use ), # snippet
|
||||
gadget.tools_mapping( schema = smart_rp.schema_rp_instruction_use, handler = smart_rp.rp_instruction_use ), # roleplay
|
||||
gadget.tools_mapping( schema = smart.schema_instruction_use, handler = smart.instruction_use ), # snippet (global)
|
||||
gadget.tools_mapping( schema = smart.schema_instruction_char_use, handler = smart.instruction_char_use ), # snippet (private per character)
|
||||
gadget.tools_mapping( schema = smart_rp.schema_rp_instruction_use, handler = smart_rp.rp_instruction_use ), # roleplay (global)
|
||||
gadget.tools_mapping( schema = smart_rp.schema_rp_instruction_char_use, handler = smart_rp.rp_instruction_char_use ), # roleplay (private per character)
|
||||
# Rag Roleplay Tools
|
||||
gadget.tools_mapping( schema = ragroleplay_tools.schema_users_store, handler = ragroleplay_tools.users_store ),
|
||||
gadget.tools_mapping( schema = ragroleplay_tools.schema_user_load, handler = ragroleplay_tools.user_load ), # s_load
|
||||
|
||||
@ -2,7 +2,6 @@ import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import config
|
||||
from lib import moop
|
||||
from lib import ntro, agent_loop
|
||||
from tools.vision import ImagePayload
|
||||
|
||||
@ -45,12 +44,39 @@ WELCOME_ART = """
|
||||
╚══════════════════════════════════════════╝
|
||||
"""
|
||||
|
||||
def log(app, role, text):
|
||||
app.log.append({
|
||||
def log(app, role, text, model_info=None):
|
||||
entry = {
|
||||
"role": role,
|
||||
"text": text,
|
||||
"time": datetime.now().strftime("%H:%M"),
|
||||
})
|
||||
}
|
||||
if model_info:
|
||||
# Display pair: (provider, model)
|
||||
entry["model_info"] = model_info
|
||||
app.log.append(entry)
|
||||
|
||||
|
||||
def _pair(model_info):
|
||||
"""(provider, model) untuk display; None jika info tidak ada."""
|
||||
if not model_info:
|
||||
return None
|
||||
return (model_info.get("provider"), model_info.get("model"))
|
||||
|
||||
|
||||
def _tag_ai_logs(app, prev_entries, pair):
|
||||
"""Tempelkan info model yang benar-benar dipakai ke entri log AI yang baru
|
||||
dibuat pada langkah ini.
|
||||
|
||||
Berbasis identitas entri, bukan index: entri "Thinking..." di-pop setelah
|
||||
llm.chat() kembali sehingga index entri AI ikut bergeser. `prev_entries`
|
||||
menahan referensi entri lama agar id-nya tidak dipakai ulang oleh Python.
|
||||
"""
|
||||
if not pair:
|
||||
return
|
||||
seen = {id(e) for e in prev_entries}
|
||||
for entry in app.log:
|
||||
if entry.get("role") == "ai" and id(entry) not in seen:
|
||||
entry["model_info"] = pair
|
||||
|
||||
|
||||
def submit(app, stdscr):
|
||||
@ -59,12 +85,6 @@ def submit(app, stdscr):
|
||||
return
|
||||
|
||||
log(app, "user", query)
|
||||
model_info = (
|
||||
moop.resolve_provider(None, app.llm.base_url, app.llm.model),
|
||||
app.llm.model
|
||||
)
|
||||
if app.log:
|
||||
app.log[-1]["model_info"] = model_info
|
||||
app.input_buffer = [""]
|
||||
app.input_line = 0
|
||||
app.input_col = 0
|
||||
@ -83,7 +103,7 @@ def submit(app, stdscr):
|
||||
app._model_info(),
|
||||
)
|
||||
|
||||
_add_msg(app, "user", query, model_info=app._model_info())
|
||||
_add_msg(app, "user", query)
|
||||
|
||||
app.agent_done.clear()
|
||||
app.agent_thread = threading.Thread(
|
||||
@ -128,8 +148,15 @@ def _agent_loop(app):
|
||||
skill = config.AGENT_SKILLS
|
||||
tool_reminder = None
|
||||
|
||||
log_prev = list(app.log)
|
||||
response = app.llm.chat(app.messages, tools=app.TOOLS, on_stream_chunk=on_stream_chunk, tool_reminder=tool_reminder)
|
||||
|
||||
# Model final: diambil dari response, bukan dari state llm saat ini,
|
||||
# supaya auto-switch/rotasi key/fallback ikut tercatat dengan benar.
|
||||
used = getattr(response, "model_info", None) or {}
|
||||
pair = _pair(used)
|
||||
akwargs = {"model_info": used} if used else {}
|
||||
|
||||
# Hapus "Thinking..." log
|
||||
for i in range(len(app.log) - 1, -1, -1):
|
||||
if app.log[i].get('role') == 'system' and 'Thinking' in app.log[i].get('text', ''):
|
||||
@ -177,11 +204,12 @@ def _agent_loop(app):
|
||||
if placeholder_marker in text or text == "" or text == "...":
|
||||
app.log.pop(i)
|
||||
|
||||
_add_msg(app, "assistant", response.content, tool_calls=response.tool_calls)
|
||||
_add_msg(app, "assistant", response.content, tool_calls=response.tool_calls, **akwargs)
|
||||
|
||||
# Log content AI dulu (penjelasan sebelum tools)
|
||||
if response.content and response.content.strip():
|
||||
log(app, "ai", response.content)
|
||||
log(app, "ai", response.content, model_info=pair)
|
||||
_tag_ai_logs(app, log_prev, pair)
|
||||
|
||||
# Log dan execute tool_calls
|
||||
for tc in response.tool_calls:
|
||||
@ -190,7 +218,7 @@ def _agent_loop(app):
|
||||
log(app, "tool_call", json.dumps({
|
||||
"name": tname,
|
||||
"arguments": targs,
|
||||
}))
|
||||
}), model_info=pair)
|
||||
if app.scroll_follow:
|
||||
app.scroll = 999999
|
||||
result = agent_loop.execute_tool(tc, app.TOOL_HANDLERS)
|
||||
@ -205,8 +233,9 @@ def _agent_loop(app):
|
||||
has_content = bool(response.content) or (isinstance(response.content, list) and len(response.content) > 0)
|
||||
if has_content:
|
||||
if isinstance(response.content, list):
|
||||
log(app, "ai", "[Image generated]")
|
||||
_add_msg(app, "assistant", response.content)
|
||||
log(app, "ai", "[Image generated]", model_info=pair)
|
||||
_add_msg(app, "assistant", response.content, **akwargs)
|
||||
_tag_ai_logs(app, log_prev, pair)
|
||||
log(app, "sep", "")
|
||||
ntro.end(stamp)
|
||||
app.agent_done.set()
|
||||
|
||||
@ -209,22 +209,23 @@ class HendrikTUI:
|
||||
if i > 0:
|
||||
log(self, "sep", "")
|
||||
log(self, "user", msg["content"])
|
||||
mi = msg.get("model_info")
|
||||
if mi:
|
||||
self.log[-1]["model_info"] = (mi.get("provider"), mi.get("model"))
|
||||
elif msg["role"] == "assistant":
|
||||
mi = msg.get("model_info")
|
||||
pair = (mi.get("provider"), mi.get("model")) if mi else None
|
||||
# Log tool calls jika ada
|
||||
if msg.get("tool_calls"):
|
||||
for tc in msg["tool_calls"]:
|
||||
log(self, "tool_call", json.dumps({
|
||||
"name": tc["function"]["name"],
|
||||
"arguments": tc["function"]["arguments"],
|
||||
}))
|
||||
}), model_info=pair)
|
||||
# Log AI content jika ada dan ini adalah response final
|
||||
next_role = session.messages[i + 1]["role"] if i + 1 < len(session.messages) else None
|
||||
if next_role in ("user", None):
|
||||
if msg.get("content"):
|
||||
log(self, "ai", msg["content"])
|
||||
if pair:
|
||||
self.log[-1]["model_info"] = pair
|
||||
elif msg["role"] == "tool":
|
||||
# Tool result - tidak perlu di-log secara eksplisit
|
||||
pass
|
||||
|
||||
@ -55,6 +55,19 @@ def draw_header(app, stdscr):
|
||||
draw_menubar(app, stdscr)
|
||||
|
||||
|
||||
def _model_info_line(item):
|
||||
"""Baris " Provider - Model " dari entri log; "" jika info tidak ada.
|
||||
|
||||
Dipakai bersama oleh blok `ai` dan `tool_call` agar tampilan konsisten."""
|
||||
model_info = item.get("model_info")
|
||||
if not model_info:
|
||||
return ""
|
||||
p_name, m_name = model_info
|
||||
if p_name:
|
||||
return f" {p_name} - {m_name} "
|
||||
return f" {m_name} " if m_name else ""
|
||||
|
||||
|
||||
def draw_chat(app, stdscr):
|
||||
# Area chat — dari baris 2 sampai baris (h - 11).
|
||||
# Bisa di-scroll dengan Page Up / Page Down.
|
||||
@ -136,20 +149,13 @@ def draw_chat(app, stdscr):
|
||||
_add_blank()
|
||||
|
||||
if role == "user":
|
||||
model_info = item.get("model_info", None)
|
||||
if model_info:
|
||||
p_name, m_name = model_info
|
||||
if p_name:
|
||||
info_line = f" {p_name} - {m_name} "
|
||||
else:
|
||||
info_line = f" {m_name} "
|
||||
else:
|
||||
info_line = " Unknown - Model info not found "
|
||||
_add_row([(C_USER, info_line, False)])
|
||||
label = f" You ({item['time']}) "
|
||||
_add_row([(C_USER, label)])
|
||||
_add_row([(C_USER, f" You ({item['time']}) ")])
|
||||
_wrap_render(text, indent=1, color=C_INPUT, bold=False)
|
||||
elif role == "ai":
|
||||
info_line = _model_info_line(item)
|
||||
if info_line:
|
||||
_add_row([(C_AI, info_line, False)])
|
||||
|
||||
label = f" {app.character_name} ({item['time']}) "
|
||||
_add_row([(C_AI, label)])
|
||||
_wrap_render(text, indent=1, color=C_INPUT, bold=False)
|
||||
@ -165,6 +171,7 @@ def draw_chat(app, stdscr):
|
||||
elif role == "tool_call":
|
||||
# Format:
|
||||
# (blank line)
|
||||
# Ollama Cloud - gemma4:31b-cloud
|
||||
# Hendrik run_bash (HH:MM) ← "Hendrik" hijau, "run_bash (HH:MM)" kuning
|
||||
# { ← arguments indent 1 spasi, kuning
|
||||
# "command": "ls -la"
|
||||
@ -173,6 +180,9 @@ def draw_chat(app, stdscr):
|
||||
# Blank line di atas (kecuali sebelumnya sudah blank dari role lain)
|
||||
if idx > 0 and app.log[idx - 1]["role"] not in ("sep", "welcome"):
|
||||
_add_blank()
|
||||
info_line = _model_info_line(item)
|
||||
if info_line:
|
||||
_add_row([(C_AI, info_line, False)])
|
||||
try:
|
||||
tc = json.loads(text)
|
||||
tname = tc["name"]
|
||||
|
||||
@ -2,9 +2,13 @@
|
||||
#
|
||||
# Alur:
|
||||
# * F9 (configurable) → mulai rekam (ffmpeg subprocess, non-blocking).
|
||||
# * F9 lagi / Enter → stop rekam → transkrip (thread) → hasil DISISIPKAN
|
||||
# ke form input pada posisi kursor, TIDAK submit.
|
||||
# * F9 lagi / Enter → stop rekam → transkrip (thread) → hasil
|
||||
# disisipkan ke form input pada posisi kursor,
|
||||
# TIDAK submit (default).
|
||||
# * Backspace / Esc → batal: rekaman dibuang / hasil transkrip diabaikan.
|
||||
# * config.asr.auto_submit: true → hasil transkrip langsung dikirim sebagai
|
||||
# pesan (setara Ctrl+Enter). Jika form sudah terisi
|
||||
# saat transkrip selesai, hasil tetap disisipkan.
|
||||
#
|
||||
# State disimpan di app:
|
||||
# app.talk_recorder — instance Recorder saat merekam, else None
|
||||
@ -18,7 +22,7 @@ import threading
|
||||
import config
|
||||
from tools import asr as asr_mod
|
||||
|
||||
from .agent import log
|
||||
from .agent import log, submit
|
||||
|
||||
|
||||
def _shortcut_label():
|
||||
@ -209,7 +213,8 @@ def shutdown(app):
|
||||
|
||||
|
||||
def finish_talk(app, stdscr):
|
||||
"""Dipanggil main loop saat app.talk_done ter-set: sisipkan hasil."""
|
||||
"""Dipanggil main loop saat app.talk_done ter-set: sisipkan hasil (atau
|
||||
langsung submit bila config.asr.auto_submit aktif)."""
|
||||
res = getattr(app, "_talk_result", None)
|
||||
app._talk_result = None
|
||||
if getattr(app, "talk_cancelled", False):
|
||||
@ -222,6 +227,27 @@ def finish_talk(app, stdscr):
|
||||
if not text:
|
||||
log(app, "system", " Tidak ada ucapan terdeteksi.")
|
||||
return
|
||||
if config.ASR_AUTO_SUBMIT:
|
||||
# Auto-submit: taruh teks di buffer lalu kirim (submit() yang
|
||||
# membersihkan buffer). Jika user sudah mengetik sesuatu sebelum
|
||||
# transkrip selesai, teks tidak ditimpa — disisipkan saja.
|
||||
if not "\n".join(app.input_buffer).strip():
|
||||
app.input_buffer = text.split("\n")
|
||||
app.input_line = len(app.input_buffer) - 1
|
||||
app.input_col = len(app.input_buffer[-1])
|
||||
submit(app, stdscr)
|
||||
log(app, "system",
|
||||
f" \u266a [{res.get('provider', '?')} / {res.get('model', '?')}] "
|
||||
"teks otomatis dikirim")
|
||||
else:
|
||||
insert_text(app, text)
|
||||
log(app, "system",
|
||||
f" \u266a [{res.get('provider', '?')} / {res.get('model', '?')}] "
|
||||
"form sedang terisi \u2014 hasil disisipkan, tidak auto-submit "
|
||||
"(Ctrl+Enter untuk kirim)")
|
||||
if app.scroll_follow:
|
||||
app.scroll = 999999
|
||||
return
|
||||
if insert_text(app, text):
|
||||
log(app, "system",
|
||||
f" \u266a [{res.get('provider', '?')} / {res.get('model', '?')}] "
|
||||
|
||||
20
plan/refactor-inference
Normal file
20
plan/refactor-inference
Normal file
@ -0,0 +1,20 @@
|
||||
di services/llm_client.py yang sekarang
|
||||
|
||||
- Init
|
||||
- 4 parameter utama untuk terhubung ke inference, yang salah satunya seperti url, model, dan key.
|
||||
- chaining model set, deklarasiin variable tapi defaultnya kosong
|
||||
- class Message
|
||||
|
||||
Macam-macam def:
|
||||
- set_chains
|
||||
- set_type_chain
|
||||
- has_chain
|
||||
- _apply_candidate
|
||||
|
||||
- chat
|
||||
|
||||
- _same_target
|
||||
- _next_target
|
||||
|
||||
- _chat_chain
|
||||
- _chat_once
|
||||
@ -1,6 +1,6 @@
|
||||
import json
|
||||
import socket
|
||||
from lib import gadget
|
||||
from lib import gadget, moop
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
@ -30,11 +30,14 @@ class LLMClient:
|
||||
self._chain_index = 0
|
||||
|
||||
class Message:
|
||||
def __init__(self, msg):
|
||||
def __init__(self, msg, model_info=None):
|
||||
raw_content = msg.get('content', '') # Ambil konten mentah
|
||||
self.content = gadget.strip_thinking(raw_content) if isinstance(raw_content, str) else raw_content # Auto-strip <thinking> dari content
|
||||
self.tool_calls = msg.get('tool_calls', None) # Ambil tool calls
|
||||
self.warning = None
|
||||
# Model yang benar-benar dipakai menghasilkan pesan ini. Bisa berbeda
|
||||
# dengan model awal permintaan karena auto-switch/fallback.
|
||||
self.model_info = model_info or {}
|
||||
|
||||
# ------------------------------------------------------------ config ---
|
||||
|
||||
@ -86,6 +89,23 @@ class LLMClient:
|
||||
self.model = cand.get("model", "")
|
||||
self.api_key = cand.get("api_key", "")
|
||||
|
||||
def _model_info(self, cand=None):
|
||||
"""Info model yang dipakai untuk satu permintaan.
|
||||
|
||||
Dari kandidat chain jika ada (provider sudah dibawa moop), jika tidak
|
||||
(klien non-chain) pakai state aktif dan cari nama provider-nya."""
|
||||
if cand:
|
||||
return {
|
||||
"provider": cand.get("provider"),
|
||||
"base_url": cand.get("base_url", ""),
|
||||
"model": cand.get("model", ""),
|
||||
}
|
||||
return {
|
||||
"provider": moop.resolve_provider(None, self.base_url, self.model),
|
||||
"base_url": self.base_url,
|
||||
"model": self.model,
|
||||
}
|
||||
|
||||
# ----------------------------------------------------- chain selection ---
|
||||
|
||||
def _pick_chain_type(self, messages):
|
||||
@ -108,7 +128,7 @@ class LLMClient:
|
||||
def chat(self, messages, tools=None, on_stream_chunk=None, disable_reasoning=False, tool_reminder=None):
|
||||
|
||||
if not self._chains: # kalau bukan chain
|
||||
return self._chat_once(
|
||||
result = self._chat_once(
|
||||
self.base_url, self.model, self.api_key,
|
||||
messages,
|
||||
tools=tools,
|
||||
@ -116,6 +136,8 @@ class LLMClient:
|
||||
disable_reasoning=disable_reasoning,
|
||||
tool_reminder=tool_reminder,
|
||||
)
|
||||
result.model_info = self._model_info()
|
||||
return result
|
||||
|
||||
chain_type = self._pick_chain_type(messages)
|
||||
if chain_type != self._chain_key:
|
||||
@ -176,6 +198,9 @@ class LLMClient:
|
||||
disable_reasoning=disable_reasoning, tool_reminder=tool_reminder,
|
||||
)
|
||||
self._chain_index = idx
|
||||
# Catat model yang benar-benar menjawab (bukan model awal
|
||||
# permintaan) supaya pemanggil bisa menampilkan data yang jujur.
|
||||
result.model_info = self._model_info(cand)
|
||||
return result
|
||||
except _LLMFail as e:
|
||||
last = e
|
||||
|
||||
128
tools/instrbase.py
Normal file
128
tools/instrbase.py
Normal file
@ -0,0 +1,128 @@
|
||||
"""
|
||||
Shared helper untuk varian instruction tool "private per character".
|
||||
|
||||
Base directory yang dipakai:
|
||||
agent/characters/<AGENT_CHARACTER>/instruction/
|
||||
|
||||
Dipakai oleh:
|
||||
tools/smart.py -> instruction_char_use
|
||||
tools/smart_rp.py -> rp_instruction_char_use
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import config
|
||||
|
||||
|
||||
def char_from_config():
|
||||
"""Nama karakter aktif (lowercase) — dibaca saat dipanggil supaya
|
||||
override `--char` / config.load_character() ikut terpakai."""
|
||||
return (getattr(config, "AGENT_CHARACTER", "") or "").strip().lower()
|
||||
|
||||
|
||||
def char_instruction_base(char=None):
|
||||
"""Return (char, base_dir) untuk instruction privat karakter.
|
||||
|
||||
base_dir bernilai None bila tidak ada karakter aktif atau CHARACTERS_DIR
|
||||
tidak tersedia di config."""
|
||||
char = (char or char_from_config())
|
||||
base_dir = None
|
||||
if char:
|
||||
chars_dir = getattr(config, "CHARACTERS_DIR", None)
|
||||
if chars_dir is not None:
|
||||
base_dir = str(chars_dir / char / "instruction")
|
||||
return char, base_dir
|
||||
|
||||
|
||||
def sanitize_path(directory, filename):
|
||||
"""Sanitasi input supaya tetap di dalam base_dir (anti path traversal).
|
||||
Segment kosong, '.' dan '..' dibuang, slash absolut dinetralkan."""
|
||||
parts = [p for p in str(directory).replace('\\', '/').split('/')
|
||||
if p and p not in ('.', '..')]
|
||||
safe_directory = os.path.join(*parts) if parts else ""
|
||||
safe_filename = os.path.basename(str(filename).replace('\\', '/'))
|
||||
return safe_directory, safe_filename
|
||||
|
||||
|
||||
def inside_base(base_dir, file_path):
|
||||
"""Guard tambahan: file harus benar-benar berada di dalam base_dir
|
||||
(menutup celah symlink / penulisan path aneh)."""
|
||||
try:
|
||||
real_base = os.path.realpath(base_dir)
|
||||
real_file = os.path.realpath(file_path)
|
||||
except Exception:
|
||||
return False
|
||||
return real_file == real_base or real_file.startswith(real_base + os.sep)
|
||||
|
||||
|
||||
def list_available(base_dir, limit=50):
|
||||
"""Daftar file (relatif terhadap base_dir) untuk pesan error yang membantu.
|
||||
Return None bila directory privat karakter belum ada sama sekali."""
|
||||
if not base_dir or not os.path.isdir(base_dir):
|
||||
return None
|
||||
found = []
|
||||
for root, _dirs, files in os.walk(base_dir):
|
||||
for fname in sorted(files):
|
||||
rel = os.path.relpath(os.path.join(root, fname), base_dir)
|
||||
found.append(rel.replace(os.sep, "/"))
|
||||
return sorted(found)[:limit]
|
||||
|
||||
|
||||
def missing_hint(char, base_dir):
|
||||
"""Pesan penutup untuk error 'file not found'."""
|
||||
available = list_available(base_dir)
|
||||
if available is None:
|
||||
return (f"The character '{char}' has no private instruction directory yet "
|
||||
f"(expected at {base_dir}).")
|
||||
if available:
|
||||
return f"Available files in '{base_dir}': {', '.join(available)}"
|
||||
return f"The private instruction directory '{base_dir}' exists but is empty."
|
||||
|
||||
|
||||
def read_instruction_file(tool_name, logger, directory, filename, label="instruction",
|
||||
caller="instruction_char_use"):
|
||||
"""Resolver + reader bersama untuk varian char (private per character).
|
||||
|
||||
Args:
|
||||
tool_name: nama tool untuk log (mis. 'tools.smart_rp').
|
||||
logger : loggorilla module, atau None untuk tanpa logging.
|
||||
directory: sub-directory di dalam instruction privat karakter ('' = root).
|
||||
filename : nama file instruksi.
|
||||
label : penanda jenis instruction di pesan error ('instruction' | 'roleplay instruction').
|
||||
|
||||
Returns:
|
||||
Isi file (str) atau string pesan error.
|
||||
"""
|
||||
|
||||
def _log(msg):
|
||||
if logger is not None:
|
||||
logger.fyinf(tool_name, msg)
|
||||
|
||||
_log(f"Calling {caller} with directory='{directory}', filename='{filename}'")
|
||||
|
||||
char, base_dir = char_instruction_base()
|
||||
|
||||
if not char or not base_dir:
|
||||
res = (f"Error: No active character is configured, so there is no private {label} "
|
||||
"directory. Set the character in config.yaml or pass --char <character>.")
|
||||
_log(res)
|
||||
return res
|
||||
|
||||
safe_directory, safe_filename = sanitize_path(directory, filename)
|
||||
file_path = os.path.join(base_dir, safe_directory, safe_filename)
|
||||
|
||||
if (not os.path.isfile(file_path)) or (not inside_base(base_dir, file_path)):
|
||||
res = (f"Error: Character {label} file not found at {file_path}. "
|
||||
f"{missing_hint(char, base_dir)}")
|
||||
_log(f"File not found: {res}")
|
||||
return res
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
_log(f"Successfully read char {label} file for '{char}'. Content snippet: {content[:100]}...")
|
||||
return content
|
||||
except Exception as e:
|
||||
res = f"Error reading character {label} file: {str(e)}"
|
||||
_log(f"Exception occurred: {res}")
|
||||
return res
|
||||
@ -1,5 +1,9 @@
|
||||
import os
|
||||
from scripts import loggorilla
|
||||
|
||||
# NOTE: loggorilla dimatikan di tool ini. print()-nya bisa bikin error saat
|
||||
# proses keluar (SCI), jadi semua pemanggilan di bawah hanya di-comment, jangan dihapus.
|
||||
# from scripts import loggorilla
|
||||
from tools import instrbase
|
||||
|
||||
schema_instruction_use = {
|
||||
"type": "function",
|
||||
@ -24,7 +28,7 @@ schema_instruction_use = {
|
||||
}
|
||||
|
||||
def instruction_use(directory, filename):
|
||||
loggorilla.fyinf("tools.smart", f"Calling instruction_use with directory='{directory}', filename='{filename}'")
|
||||
# loggorilla.fyinf("tools.smart", f"Calling instruction_use with directory='{directory}', filename='{filename}'")
|
||||
current_file_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(current_file_dir)
|
||||
base_dir = os.path.join(project_root, "instruction")
|
||||
@ -34,15 +38,55 @@ def instruction_use(directory, filename):
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
res = f"Error: Instruction file not found at {file_path}"
|
||||
loggorilla.fyinf("tools.smart", f"File not found: {res}")
|
||||
# loggorilla.fyinf("tools.smart", f"File not found: {res}")
|
||||
return res
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
loggorilla.fyinf("tools.smart", f"Successfully read file. Content snippet: {content[:100]}...")
|
||||
# loggorilla.fyinf("tools.smart", f"Successfully read file. Content snippet: {content[:100]}...")
|
||||
return content
|
||||
except Exception as e:
|
||||
res = f"Error reading file: {str(e)}"
|
||||
loggorilla.fyinf("tools.smart", f"Exception occurred: {res}")
|
||||
# loggorilla.fyinf("tools.smart", f"Exception occurred: {res}")
|
||||
return res
|
||||
|
||||
|
||||
# ─── instruction_char_use (private / per-character variant) ────────────────────
|
||||
|
||||
schema_instruction_char_use = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "instruction_char_use",
|
||||
"description": (
|
||||
"Reads a specific instruction file from the PRIVATE 'instruction' directory of the "
|
||||
"active character (agent/characters/<character>/instruction) and returns its content. "
|
||||
"This is the character-scoped variant of instruction_use: use it to load snippets, "
|
||||
"guidelines or system prompt extensions that belong only to the current character, "
|
||||
"instead of the shared/global 'instruction' directory."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"The subdirectory within the character's own 'instruction' folder "
|
||||
"(e.g., 'snippets', 'scenes', 'world'). Pass an empty string to read "
|
||||
"directly from the root of that folder."
|
||||
)
|
||||
},
|
||||
"filename": {
|
||||
"type": "string",
|
||||
"description": "The name of the instruction file to read (e.g., 'opening.md', 'rules.md')."
|
||||
}
|
||||
},
|
||||
"required": ["directory", "filename"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def instruction_char_use(directory, filename):
|
||||
# loggorilla dimatikan di tool ini (lihat catatan di atas), logger dikirim None.
|
||||
return instrbase.read_instruction_file("tools.smart", None, directory, filename)
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import os
|
||||
|
||||
from scripts import loggorilla
|
||||
from tools import instrbase
|
||||
|
||||
schema_rp_instruction_use = {
|
||||
"type": "function",
|
||||
@ -46,3 +48,46 @@ def rp_instruction_use(directory, filename):
|
||||
res = f"Error reading RP file: {str(e)}"
|
||||
loggorilla.fyinf("tools.smart_rp", f"Exception occurred: {res}")
|
||||
return res
|
||||
|
||||
|
||||
# ─── rp_instruction_char_use (private / per-character variant) ─────────────────
|
||||
|
||||
schema_rp_instruction_char_use = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "rp_instruction_char_use",
|
||||
"description": (
|
||||
"CRITICAL ROLEPLAY VALIDATION TOOL (character-scoped): Use this tool ONLY when the "
|
||||
"character is interacting with a specific object, outfit, or location defined in this "
|
||||
"character's PRIVATE roleplay instruction files, located at "
|
||||
"agent/characters/<character>/instruction. This is the character-scoped variant of "
|
||||
"rp_instruction_use — it must be called to fetch the authoritative description before "
|
||||
"describing the item, to ensure narrative consistency and prevent hallucination. "
|
||||
"DO NOT use if the character is not interacting with a specific listed item."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"The subdirectory within the character's own 'instruction' folder "
|
||||
"(e.g., 'rp/item', 'rp/armor', 'rp/world', 'gameplay'). Pass an empty "
|
||||
"string to read directly from the root of that folder."
|
||||
)
|
||||
},
|
||||
"filename": {
|
||||
"type": "string",
|
||||
"description": "The name of the instruction file to read (e.g., 'rules.md', 'iron-sword.md')."
|
||||
}
|
||||
},
|
||||
"required": ["directory", "filename"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def rp_instruction_char_use(directory, filename):
|
||||
return instrbase.read_instruction_file(
|
||||
"tools.smart_rp", loggorilla, directory, filename,
|
||||
label="roleplay instruction", caller="rp_instruction_char_use"
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user