Format follows Keep a Changelog. Versioning follows SemVer.
[Unreleased]
[0.9.1] — 2026-09-27
Diperbaiki
- Gagal menjangkau server artifact kini menyebut sebab aslinya.
fetchdi Node selalu melemparfetch failed; sebab sebenarnya (ENOTFOUND,ECONNREFUSED,ECONNRESET, TLS) ada dierror.causedan dulu dibuang, sehingga model mengarang penjelasannya sendiri. Pesannya sekarang memuat kode dan pesan sebabnya, dan menyatakan bahwa masalahnya ada di jaringan antara mesin user dan server. - Satu percobaan ulang, hanya kalau tidak bisa menggandakan halaman: untuk
kode yang berarti permintaan tidak pernah terkirim (DNS, ditolak, tak
terjangkau), atau untuk kode jaringan apa pun kalau
slugdikirim. Kegagalan ambigu sepertiECONNRESETtanpaslugtidak lagi dilaporkan sebagai “Nothing was published” — permintaannya mungkin sudah sampai. Kegagalan TLS tidak dicoba ulang.
$schema menunjuk ke folder config, bukan ke paket
config.schema.jsonkini disalin ke~/.config/titah/dan disegarkan setiap kali isinya berbeda dari versi yang sedang jalan.titah initmenulis$schemake salinan itu, bukan lagi ke folder instalasi npm yang berpindah begitu Node, prefix, atau cara pasangnya berganti.- Config lama dimigrasi sendiri.
$schemadi config global yang menunjuk ke berkas lokal bernamaconfig.schema.jsondi tempat lain dipindah ke salinan baru, dengan komentar dan format tetap utuh. URL, nama berkas lain, config tanpa$schema, config yang tidak bisa diurai, dantitah.jsonproyek tidak disentuh. titah doctormenampilkan satu barisschema:di bagian Config.
Titah bisa menulis skill sendiri — skills.learn, mati secara bawaan
- Sesudah giliran yang selesai normal dan memakai ≥
minToolstool call (bawaan 8), satu panggilansmallModelmembaca digest ringkas giliran itu — bukan transkripnya — dan memutuskan apakah ada prosedur yang layak disimpan. - Skill ditulis ke
~/.config/titah/skills/learned/<nama>/SKILL.mddengan namespacelearned:, dan langsung terlihat di giliran berikutnya. - Titah hanya menimpa berkas bertanda
source: titah-learn. Nama, ukuran, dan bentuk rahasia yang umum divalidasi sebelum apa pun ditulis. - Setiap keputusan tercatat satu baris di
~/.config/titah/learn.log, beserta token yang dipakai.titah doctormenampilkan status dan jumlahnya. - Sumber skill tidak lagi memindai masuk ke root sumber lain, sehingga
~/.config/titah/skillsdiskills.pathstidak mendaftarkan skill yang dipelajari dua kali.
[0.9.0] — 2026-09-20
Artifact — agent bisa menerbitkan halaman, bukan cuma menuliskan teks
-
Tool baru
artifact. Menerbitkan satu dokumen HTML utuh ke dashboard akun dan mengembalikan URL yang bisa dibuka manusia di browser. Dipakai saat jawabannya lebih baik jadi halaman daripada paragraf — laporan, bagan, tabel yang ingin diurutkan. Ada di sumbu izinnetwork, sama sepertiwebfetch: ia mengirim dokumen keluar dari mesin. -
Halaman bersifat PRIVAT, dan CLI tidak bisa mengubahnya. Menyalakan link publik hanya bisa dilakukan manusia lewat dashboard; API menolak token CLI dengan 403. Ini yang membatasi skenario agent yang disetir oleh sesuatu yang ia baca: agent semacam itu bisa menerbitkan halaman — itu fiturnya bekerja — tapi tidak bisa menyodorkannya ke orang lain.
-
Satu slug, banyak versi. Mengoper
slugdari panggilan sebelumnya merevisi halaman yang sama, jadi URL yang sudah kamu kirim tetap URL yang benar meski agent beriterasi tiga puluh kali. Menerbitkan ulang byte yang identik tidak menulis versi baru — itu yang membuat percobaan ulang setelah timeout aman. -
Batas 512 KiB per dokumen, angka yang sama dengan batas transkrip. Dokumen kebesaran ditolak sebelum meninggalkan mesin, dan penolakannya menyebut kedua angkanya supaya model mengecilkan halamannya alih-alih mengirim yang sama lagi.
-
Salinan lokal di
~/.local/share/titah/artifacts/<slug>.html. Halaman terbitan adalah hal pertama yang Titah taruh di server yang mungkin tidak ada salinannya di tempat lain — transkrip cuma salinan sesi yang masih ada di mesinmu, halaman ini bukan salinan apa pun. -
Config
artifacts.enabled, bawaannyatrue— berbeda daritracking.syncyangfalse.syncmengunggah otomatis tanpa user di dalam putaran; penerbitan artifact selalu melewati dialog izin yang menyebut host, judul, dan jumlah byte.titah doctormenyebut sakelar mana yang mati, bukan sekadar “off”. -
Disajikan dari origin terpisah. Halaman terbitan dibuka di origin lain, di dalam iframe ber-
sandboxtanpaallow-same-origin, dengan CSP yang mematikanconnect-src,form-action, dan membatasiimg-srcke daftar host bernama. Akibatnya halaman yang memusuhi pembacanya bisa menggambar kotak “masukkan API key”, tapi tidak punya saluran untuk mengirimkan apa yang diketik. Isinya tidak pernah disaring atau ditulis ulang — penyaringan akan merusak fiturnya sekaligus memberi rasa aman palsu.
[0.8.1] — 2026-09-20
Diperbaiki
-
Enter di picker extension memasang lagi.
<leader>x, lalux, lalu Enter pada sebuah baris tidak melakukan apa pun — tidak memasang, tidak melempar, tidak mengatakan apa pun.runSuggestionmencari baris yang tersorot di dalamextensionRows, tapiextensionRowstidak ada di dep array-nya. Callback itu menutup atas nilai render PERTAMA — array kosong — jadi pencariannya selalu gagal dan cabangnyareturn undefined. Ini kejadian KEEMPAT dari kelas bug yang sudah dinamai di bagian atasapp.tsx.Yang membuatnya sulit dilihat:
DdanRbekerja, dan hint di bawah picker benar. Keduanya dibaca dariuseInput, yang didaftarkan ulang setiap render. Hanya Enter yang diam — tombol yang paling sering ditekan orang.runLeaderActionhilang dari daftar yang sama, dengan akibat yang lebih senyap: dep array-nya sendiri memuateffort,focusedSpec, danmouseCapture, jadi aksi leader yang dipilih lewat Enter di menu bekerja atas keadaan saat aplikasi baru menyala. Ikut diperbaiki, bersamainstallFromPicker,openChild,cwd, danexit. -
Fdi picker extension mengambil ulang daftar dari registry. Cache registry ber-TTL dua puluh empat jam, jadi registry yang baru diperbarui tidak terlihat sampai besok — dan satu-satunya jalan keluar adalah menghapus~/.cache/titah/registry.jsondengan tangan, yang tidak disebutkan di mana pun. Gejalanya sulit dibaca: picker menyebut versi lama dan tidak ada apa pun di layar yang menyebut cache.loadRegistrysudah menerimaforce, lengkap dengan test yang membuktikan ia melewati cache yang masih segar — tapi tidak ada satu pun pemanggil yang pernah mengirimkannya, sementara komentar di berkasnya menjanjikan “tombol refresh di picker” yang tidak pernah dibuat. Sekarang tombolnya ada, dan ada test yang menekanFsungguhan lalu menuntut permintaan registry benar-benar terjadi.Hurufnya
Fdan bukanR:Rsudah berarti cabut di picker itu, danctrl+rsudah dipegangeffort_cycle. Ia berlaku di setiap baris — yang dimuat ulang adalah daftarnya, bukan barisnya — dan hasilnya dikatakan, termasuk saat jaringan mati.
[0.8.0] — 2026-09-19
Panel bisa mengirim teks ke prompt utama — EXTENSION_API naik ke 0.5.0
KeyVerdictbertambahprompt.onKeydanonClicksekarang boleh menjawab{ prompt: { text, mode } }, dan Titah menyisipkan teksnya ke draft prompt user."append"(bawaan) menyisip di posisi kursor,"replace"menimpa seluruh draft. Sebelumnya satu-satunya jawaban yang mungkin adalah{ refresh }— panel bisa menggambar apa saja tapi tidak bisa membawa apa pun keluar dari dirinya.EXTENSION_APInaik dari0.4.0ke0.5.0, karena bentuk sebuah tipe publik berubah. Ini memutus setiap extension yang menyatakan^0.4.0sampai masing-masing menaikkanengines.titahke^0.5.0dan diterbitkan ulang. Yang terpengaruh:@titah/extension-git,@titah/extension-diff. Panel yang belum diperbarui ditolak saat load dengan kalimat yang menyebut sebabnya, terlihat di footer dan dititah doctor.- Teks dari extension dibersihkan seperti tempelan (
sanitizePaste): ia input pihak ketiga yang ikut terkirim ke model, jadi karakter kontrol dibuang sebelum masuk editor. - Penyisipan dari
onClicklewat ref, bukan closure. Penangan mouse berlangganan sekali (useEffect([mouse])) dan menutupi nilai render pertama, jadi menyisip langsung di sana akan memakaicursoryang selamanya 0 — teks selalu mendarat di awal draft, tanpa satu pun error. Ada test yang menjaganya, dengan draft yang sengaja TIDAK kosong.
Extension bisa dimatikan dan dicabut dari tempat ia dipasang
- Picker
<leader>xsekarang punyaDdanR.Dmematikan (enabled: false, kode tetap di disk) atau menyalakannya lagi;Rmencabut — entri config dibuang dan paketnya di-npm uninstall. Sebelumnya picker hanya bisa memasang, dan orang yang memasang dari dalam TUI harus keluar dari TUI untuk membuangnya. - Keduanya bertanya dulu, lewat dialog merah yang menyebut apa yang akan terjadi dan berkas mana yang akan disunting. Menyalakan kembali tidak bertanya — ia tidak menghapus apa pun.
- Keadaan keempat,
⊘ disabled, dengan penanda visual sendiri. Ia menang atas✓:enabled: falseberarti modulnya tidak pernah di-import, jadi baris yang mengaku terpasang untuk sesuatu yang tidak akan pernah dimuat adalah baris yang berbohong. <leader>xsaat sebuah panel fokus membuka picker dengan baris panel itu sudah tersorot. Tanpa keybind baru, dan tanpa menyentuh aturan bahwa tombol biasa milikonKeyextension selama panelnya fokus.- Popup kini mendahului panel yang fokus. Tanpa itu, picker yang dibuka dari panel tergambar di depan mata sementara papan ketiknya masih dipegang kotak di belakangnya.
- Panel yang sedang tampil tidak lenyap sesudah D atau R. Extension dimuat
sekali per sesi dan Node tidak bisa meng-un-
importmodul; kalimatnya menyebut restart, cermin dari kalimat yang sudah dipakaiinstall.
Diperbaiki
titah extension remove ./xbenar-benar mencabut. Ia selalu menyunting config global, padahalinstall ./xselalu menulis ke config proyek — jadi ia mencetak “Removed” untuk entri yang masih utuh, dan panelnya muncul lagi di sesi berikutnya. Sekarang yang disunting adalah setiap berkas yang benar-benar menyebut spec itu, dan spec yang tidak disebut di mana pun dikatakan begitu alih-alih dilaporkan sebagai keberhasilan.remove market:<id>menghapus kunci yang benar. Kunci config-nyamarket:<id>, bukan nama paketnya; yang dihapus sebelumnya adalah kunci yang tidak pernah ada.titah extension listmenyebut yang dimatikan.loadExtensions()melewatienabled: falsesebelum sempat melaporkannya, jadi extension yang sengaja dimatikan hilang diam-diam dari daftar — dan orang mengira ia sudah terhapus.
Ditambahkan
titah extension disable <pkg>dantitah extension enable <pkg>.enablemenghapus kuncienabledalih-alih menulistrue:trueadalah bawaan schema, dan baris yang tidak menyatakan apa pun tidak layak ditambahkan ke berkas yang dirawat tangan.
[0.7.0] — 2026-09-06
Extension bertahan melewati upgrade Titah
engines.titahsekarang diperiksa terhadap versi API, bukan versi produk. Titah mengumumkanEXTENSION_APIdarititah-code/extension— versi kontrak, yang hanya bergerak kalau kontrak itu sendiri berubah. Sebelumnya setiap kenaikan minor Titah mematikan setiap extension yang terpasang, padahalsrc/extension.tstidak berubah satu byte pun dari 0.4.0 sampai 0.6.1.- Nilai awalnya
0.4.0, dan itu disengaja: setiap extension yang sudah terbit hidup lagi tanpa diterbitkan ulang, karena semuanya menyatakan^0.4.0. - Pemeriksaannya dipindahkan, bukan dilemahkan. Kontrak yang benar-benar
tidak cocok tetap ditolak, dan kalimatnya menyebut kedua angka —
needs extension API ^9.0.0, but Titah 0.7.0 provides 0.4.0— karena satu angka saja terbaca seperti kerusakan. satisfiesEnginemengerti rentang majemuk:||, konjungsi berspasi (>=0.4.0 <1.0.0), dan operator<<=>. Sebelumnya satu-satunya bentuk lebar adalah>=0.4.0yang tanpa batas atas sama sekali. Rentang yang tidak dikenali tetap ditolak, dan bagian cacat di dalam konjungsi tetap menjatuhkan kelompoknya.titah extension updateikut memilih berdasarkan API, jadi ia berhenti menolak setiap versi terbit setiap kali Titah naik minor.titah upgradetidak ikut: ia membandingkan versi Titah sendiri di npm, pertanyaan yang berbeda, dan alasannya ditulis di tempatnya.
[0.6.2] — 2026-09-06
MCP dan LSP
- Server MCP atau language server yang mati saat sedang ditulisi tidak lagi
menjatuhkan seluruh Titah.
#writesudah memeriksa apakah anaknya masih hidup, tapi itu potret sesaat: muatan yang lebih besar dari buffer pipa 64 KB — hasil tool MCP,didOpenyang membawa satu berkas utuh — mendarat sesudah pemeriksaan itu. EPIPE-nya datang asinkron sebagai eventerrordi stream, dan tanpa listener Node memperlakukannya sebagaierrortak tertangani. - Permintaannya kini ditolak dengan sebab yang sungguhan, bukan digantung sampai timeout tiga puluh detik. Kalimatnya membawa kode keluar dan stderr server, dan menyebut kalau ada permintaan yang sedang ditulis saat ia mati — itu yang menjelaskan kenapa jawabannya tidak pernah datang.
- Kelas yang sama dengan perbaikan kait di 0.5.1, tapi penanganannya berbeda dan sengaja: di sana EPIPE diabaikan karena putusan sebuah kait datang dari kode keluarnya. Di sini permintaannya benar-benar hilang dan ada yang menunggu, jadi mengabaikannya diam-diam justru salah.
[0.6.1] — 2026-09-06
Extension yang ditolak
Tidak ada perubahan kebijakan: extension yang engines.titah-nya tidak memuat
versi Titah yang berjalan tetap ditolak. Yang berubah cara Titah mengatakannya.
- Sisi yang terbuka tapi tidak satu pun extension-nya berhasil dimuat kini
berbunyi
⚠ N failed, bukanNo extension. Yang kedua menyesatkan: bukan “tidak ada extension”, melainkan “ada dan ditolak” — dan bedanya menentukan ke mana orang mencari. - Penjelasannya bertahan. Sebelumnya hanya
flash()yang hilang setelah empat detik; sesudah itu satu-satunya yang tersisa di layar justru kalimat yang menyesatkan di atas. Footer kini membawa⚠ N side panels failed to load — run: titah extension listselama keadaan itu masih berlaku. titah doctorpunya bagian Extensions: manifest terbaca danengines.titahcocok, per extension yang dikonfigurasi. Sengaja TANPA meng-importapa pun — aturan yang sama dengan bagian MCP — dan batasannya dicetak, bukan disembunyikan: kegagalan yang baru muncul saat modulnya dijalankan tetap miliktitah extension list.
[0.6.0] — 2026-09-06
Sidebar
- Satu sisi kini menampung banyak extension. Sebelumnya yang kedua di sisi yang sama ditolak dengan “already taken” dan hanya dilaporkan lewat notice, jadi plafonnya dua extension untuk seluruh aplikasi — satu kiri, satu kanan. Kini mereka bertumpuk sebagai box terpisah, urut menurut config: satu-satunya urutan yang user bisa lihat dan ubah.
- Box bisa dilipat jadi satu baris judul ber-
▸— lewat<leader>zpada box yang sedang fokus, klik pada baris judulnya, atau"collapsed": truedi config. Satu baris dan bukan satu bingkai: box terlipat yang masih membayar bingkai hampir tidak menghemat apa pun. - Box terlipat tidak dirender sama sekali. Bukan sekadar disembunyikan —
panel git yang terlipat berhenti menjalankan
git statustiap refresh. - Di bawah
panel.boxFloor, box melipat sendiri, dari bawah ke atas, dan dilaporkan lewat notice. Cermin lantai lebar yang sudah ada: urutan config adalah urutan prioritas, jadi yang teratas bertahan paling lama. <leader>fkini berputar antar box, bukan memilih sisi — tombol yang diusulkan extension kedua di satu sisi sebelumnya tidak akan pernah sampai kepadanya.- Dua field config baru per extension:
rows(tinggi yang diminta; permintaan, bukan jaminan) dancollapsed(nilai awal, tidak ditulis balik saat dilipat lewat tombol). Pluspanel.boxFloor.
Di dalamnya
- Identitas panel pindah dari SISI ke extension.
panelHitmengembalikan spec box beserta apakah yang kena adalah baris judulnya, dan geometrinya disusunstackGeometrydari tumpukan yang sama dengan yang menggambar — bukan dihitung ulang di sisi render, yang selama ini jadi cara reservasi dan gambar menyimpang.
[0.5.1] — 2026-09-06
Kait
- Kait yang tidak membaca stdin tidak lagi bisa menjatuhkan seluruh proses.
pwd,true, dan formatter yang cuma melihat$TITAH_TOOLmenutup stdin lalu keluar; tulisan yang datang sesudahnya gagal dengan EPIPE, dan kegagalan itu tiba sebagai eventerrordi stream — bukan sebagai lemparan dariend(), jaditry/catchyang menjaganya tidak pernah melihatnya. Tanpa listener, Node memperlakukannya sebagai eventerrortak tertangani. - Bugnya tersembunyi karena muatan kecil muat seluruhnya di buffer pipa: kait selesai sebelum anaknya sempat keluar, dan tidak ada yang pernah gagal. Ia muncul ketika anaknya menang balapan — di bawah beban, atau pada muatan yang lebih besar dari buffer pipa, yang wajar karena masukan tool bisa memuat seluruh isi berkas. Test barunya memakai muatan 200 KB supaya kegagalannya pasti, bukan kadang-kadang.
[0.5.0] — 2026-09-06
Kursor prompt
- Panah atas/bawah menelusuri baris visual, bukan hanya
\n. Satu kalimat panjang yang dibungkus terminal tidak lagi dilompati sekaligus — sebelumnya satu tekanan panah di draft seperti itu menukar seluruh ketikan dengan entri histori. ctrl+←/ctrl+→melompat per kata, denganalt+←/alt+→danalt+b/alt+fsebagai alias: tidak ada satu sekuens yang dikirim semua terminal.ctrl+a/ctrl+eberhenti di awal/akhir baris, bukan di ujung seluruh draft — sesuai namanya, dan sesuai readline.- Tinggi kotak penyunting dihitung dari baris visual yang sama. Sebelumnya ia
dihitung dari jumlah
\nsementara Ink menggambarnya terbungkus, jadi draft satu baris yang panjang memakan baris riwayat paling bawah tanpa jejak.
Blok tool
- Blok tool terbuka sejak awal; klik menutupnya,
ctrl+x dmenutup semuanya dan tekanan kedua mengembalikannya. Hasil kerja yang baru saja diminta tidak lagi bersembunyi di balik satu tekanan lagi. - Blok penalaran tetap terlipat: ia biasanya jauh lebih panjang daripada
jawabannya. Keduanya memakai mekanisme lipat yang sama — yang berbeda hanya
bawaannya, dan himpunan
Expansionkini berarti “yang dibalik dari bawaan”.
Halaman transkrip sub-agent
Enteratau klik pada baris panel sub-agent membuka transkrip sub-agent itu sebagai halaman penuh, mengalir langsung selagi pekerjaannya berjalan.ctrl+x smembuka daftar seluruh sub-agent sesi ini, termasuk giliran yang sudah selesai — panel sendiri dikosongkan setiap prompt baru.- Rute baru
GET /session/:id/children. Sesi anak tetap disembunyikan dariGET /session: memilihnya di sana akan mengganti sesi aktif, dan prompt berikutnya ikut masuk ke sesi milik sub-agent. - Halamannya baca-saja. Super agent menjalankan CLI di luar Titah dan hanya mengirim jawaban akhirnya, jadi halamannya mengatakan itu alih-alih menggambar layar hampa.
[0.4.2] — 2026-08-25
Account server
- Default login/account server diganti dari
https://titah.akil.co.idkehttps://titah.dev. - Dokumentasi default server disesuaikan agar mengikuti domain baru.
[0.4.1] — 2026-08-23
titah extension update
Sampai sekarang tidak ada jalur update sama sekali. Lockfile bekerja persis
seperti yang seharusnya — install menghormatinya, jadi memasang ulang
mengembalikan versi yang sama selamanya — tapi tidak ada perintah untuk
menyatakan “saya mau bergerak maju”. Satu-satunya jalan adalah mengetahui nomor
versinya lalu install <pkg>@<versi>, yang berarti menghafal angka yang
seharusnya dicari Titah sendiri.
Perintah tersendiri, bukan perilaku install: install yang diam-diam
menaikkan versi membuat “kode yang sama di dua mesin” jadi harapan lagi.
- Memilih versi terbaru yang KOMPATIBEL, bukan
dist-tags.latest. Extension yang menuntut^0.5.0akan jadilatestdi npm sementara Titah masih 0.4.0; memasangnya berarti mengganti extension yang bekerja dengan yang tidak bisa dimuat, dan pesan kegagalannya baru muncul di sesi BERIKUTNYA — jauh dari perintah yang menyebabkannya.enginesada di packument, jadi ini diketahui sebelum mengunduh apa pun. - Versi yang lebih baru tapi diblokir tetap dikatakan, beserta versi Titah
yang dituntutnya. Tanpa itu,
updateyang tidak mengubah apa pun terlihat seperti tidak ada versi baru — padahal ada, dan yang menahannya adalah satu hal yang user bisa perbaiki. - Prerelease dan versi yang di-deprecate dilewati.
- Tanpa argumen, seluruh extension npm di config diperiksa. Path lokal dilewati dan disebutkan: ia dimuat langsung dari disk, jadi tidak ada yang bisa di-update dari sini.
Diperbaiki
docs/extensions.mdmenjanjikan tombolUdanXdi picker yang tidak pernah dibuat. Picker hari ini hanya punya Enter, dan dokumennya sekarang mengatakan itu. Penanda✓juga diluruskan: ia berarti “ada di disk”, BUKAN “versi terbaru” — picker tidak memanggil registry npm untuk setiap baris.
[0.4.0] — 2026-08-23
Panel bisa diklik, dan lebarnya bisa diubah tanpa keluar dari sesi
Dua pertanyaan sederhana — “bagaimana resize?” dan “apakah bisa diklik?” — menemukan bahwa jawabannya “tidak bisa” untuk keduanya, dan menemukan dua cacat yang dikirim di 0.3.0. Lihat bagian Diperbaiki.
onClick({ row })di permukaan publik.rowadalah indeks baris yang DIGAMBAR, bukan indeks data extension — pemetaannya milik extension, karena hanya ia yang tahu baris mana berarti apa. Klik tidak menuntut fokus (klik sudah menyebutkan sasarannya) tapi MEMINDAHKAN fokus, supaya tombol yang panelnya iklankan langsung bekerja sesudahnya.+/-/=saat panel fokus: lebarkan, sempitkan, kembalikan ke lebar config. Tiga tombol itu dipesan Titah dan tidak pernah diteruskan ke extension — kebalikannya membuat artinya bergantung pada extension mana yang sedang fokus. Pelebaran berhenti dipanel.floor: tanpa batas itu, satu tekanan lagi membuat panel yang sedang dilebarkan menutup sendiri.- Lebar hasil resize hidup di memori sesi, tidak ditulis ke config.
+/-ditekan berkali-kali dalam hitungan detik.
Diperbaiki
onKeytidak pernah dipanggil. Ada di permukaan publik, diimplementasikan extension referensi, danapp.tsxtidak pernah memanggilnya — jadi panel git menampilkan “b branches · r refresh” dengan kedua tombol mati. Sekarang lewat<leader>f, yang memisahkan BUKA dari FOKUS: panel samping dibuka untuk dilihat sambil bekerja, dan kalau membukanya berarti memakan tombol, tidak ada yang akan membukanya.- Panel tidak digambar di layar pembuka. Memasang extension lalu menekan
Ctrl+X ←sebelum prompt pertama tidak menghasilkan apa pun — dan itu terbaca sebagai extension yang gagal dipasang, bukan sebagai layar yang berbeda. - Pilihan logo layar pembuka diambil dari kolom tengah, bukan lebar terminal.
Catatan tentang kelas bug yang sama tiga kali
Ketiga cacat di atas berakar pada satu hal: callback dan listener yang dep
array-nya tidak memuat extensions menutup atas render PERTAMA, saat daftarnya
masih kosong. Gejalanya bukan error, melainkan tombol atau klik yang tidak
melakukan apa pun. Ketiganya sekarang lewat ref yang disegarkan tiap render —
pola yang sama dengan view.current, dan alasannya sama: hanya render yang tahu
apa yang sedang terlihat, sedangkan penanganan input datang belakangan.
[0.3.0] — 2026-08-23
Extension — panel samping yang disumbang paket npm
Titah punya plugin dan hooks untuk mengubah perilaku. Yang belum tertutup
adalah hal yang tidak berbentuk perilaku sama sekali: melihat branch git tanpa
keluar dari sesi, membaca daftar worktree, menengok diff yang baru ditulis agent.
Sebelum ini, semuanya berarti terminal kedua.
extensiondi config, bentuk kunci yang sama denganplugin(npm, path,market:<id>). Disebut satu per satu; tidak ada penemuan otomatis.- Panel kiri dan kanan,
<leader>←/<leader>→, dengan lantai: di bawahpanel.floorkolom panel tertutup sendiri alih-alih membuat riwayat tidak terbaca.<leader>emenyegarkan,<leader>xmembuka picker. - Permukaan publik
titah-code/extension— hanya tipe dan pemeriksa versi.exportsdipackage.jsonmenolak jalur lain, jadicore/permission.jsdancore/auth.jstidak bisa di-importextension sama sekali. Batasnya ditegakkan resolver Node, bukan dokumen. engines.titahdiperiksa saat load. Extension tanpa itu ditolak: selama API 0.x, paket yang tidak menyatakan versi targetnya tidak bisa dibedakan dari paket yang ditulis dua rilis lalu.- Dipasang ke
~/.local/share/titah/extension/, bukan kenode_modulesproyek user — extension adalah preferensi orang, bukan dependency proyek. Lockfile di~/.config/titah/extension-lock.jsonmengunci versi persis dan integrity hash, dan ia berkas yang memang ingin dibawa ke dotfiles. - Kegagalan tidak menjatuhkan TUI. Panel yang melempar menampilkan pesannya
di tempatnya; yang menggantung dibatalkan pada dua detik. Aturan yang sama
dengan
plugin. titah extension list|install|remove, dantitah upgradeyang MENCETAK perintah pemasangan alih-alih menjalankannya.
Batas yang disengaja: extension hanya sisi TUI, jadi attach, serve, dan web
tidak punya panel. titah.panel adalah satu field di antara beberapa yang
mungkin, supaya sisi server nanti jadi field baru — bukan perubahan bentuk yang
memutus config orang.
Rujukan implementasi: @titah/extension-git, repo terpisah, memakai hanya
permukaan publik. Index picker: titah-dev/titah-extensions.
Diperbaiki
- String menu leader yang masih Bahasa Indonesia diterjemahkan.
AGENTS.mdsudah mewajibkan UI berbahasa Inggris; sembilan barisLEADER_ACTIONStertinggal.
[0.2.1] — 2026-08-22
Packaging
- Published on npm as
titah-code, nottitah. npm’s typosquat filter rejects the shorter name as too similar totiag— acreate-next-appscaffold published once in March 2024 and never touched since.titahitself is unclaimed, so this is a false positive; a dispute is open with npm support. - The command did not change.
binis stilltitah,~/.config/titah/is still the config directory, and no migration is needed. Only the install name moved:npm i -g titah-code, ornpx titah-codefor a one-off run. - Code-identical to
[email protected]on the registry, which went out before this tag existed. This release exists so that the git tag and the published version finally describe the same tree — everything below shipped in0.2.0on npm despite living under[Unreleased]here.
Tracking
tracking— the CLI finally reports to the dashboard it has had since 0.1.0. titah-web’s endpoints, models and pages were built and tested; nothing ever called them, so/dashboard/projects/stayed empty for everyone who signed in. Metadata only: name, language, git, and the figurestitah statsalready prints. No file contents, no transcripts, no tool output.- Four ways off, widest first: not signed in,
tracking.enabled: falseglobally, the same key in a project’s./titah.json, or a path intracking.exclude. The per-project one is free — config merging already did it — and travels with the repo. excludereusespermission.allowlist’s matcher. One glob dialect in the config, not two.- Sent after a turn, at most once per five minutes per project, and never
blocking or failing a turn. The debounce lives in SQLite, not memory:
titah runis one process per turn, so an in-memory window never fires exactly where a script runs it a hundred times. - Silent, succeeding or failing. A “heartbeat sent” line would break
--output-format jsonfor the caller. One line per attempt goes to~/.config/titah/tracking.log, andtitah doctorgrew aTrackingsection naming which switch is in effect. - Defaults on when signed in, which is a deliberate exception to “every new axis is off by default”: signing in is itself the opt-in, and demanding a second step leaves the dashboard empty for someone who already took the only step that looks like consent.
Session sync
tracking.sync— transcripts reach the dashboard, and it takes two switches: this one and the per-project toggle on the server. A dashboard toggle is remote policy; anyone who reaches the account can flip it and cannot edit a file on your machine.- Off by default, unlike
tracking.enabled. Signing in is consent to be counted, not consent to be read. - Prompts, answers, and the order of tool names. Never tool arguments, never
tool output, never reasoning. Tool output is where secrets live, and a secret
filter you can rely on does not exist — one that catches
AKIA…and misses an internal token is worse than none, because it manufactures confidence. - 32 KB per message (the number already used for tool output) and 512 KB per transcript. Over that the oldest messages go, and a marker says how many: a silently truncated transcript looks complete.
- The server toggle is learned from the heartbeat response, which already
returned it. Without that the only way to know would be attempting an upload
and being refused — one wasted request per turn, for the life of a project
that never turns it on. A
403also switches the stored flag off, so it stops trying until told otherwise. - Only sessions that get a turn after you switch it on go up. No backfill.
titah doctorreports which of the switches is holding it.
Fixed
Session.session_idon titah-web wasmax_length=36while a Titah session id isses_+ a UUID = 40 characters. It would have failed on the first real upload; the column is 64 now. Trimming the prefix to fit would have been the wrong direction — a trimmed id matches nothing the user can look up locally.- The tracking request timeout went from 5s to 10s. Measured: a warm request finishes in ~200ms, but the first HTTPS connection on a cold machine (DNS plus TLS) exceeded five seconds and failed silently.
bus.subscribegrewclient: falsefor observers.listenerCountdecides auto-deny with no client attached (Q17), and it counted every subscriber — so a purely observing subscriber would have madetitah runin CI stop auto-denying and hang waiting for an answer nobody could give.
[0.2.0] — 2026-08-19
Two days of closing the distance to the other terminal agents, measured against
opencode 1.18.4 and Claude Code 2.1.233 rather than guessed. Six gaps were
written down; all six are closed here.
Nothing in 0.1.0 changed shape. Every new axis is off or absent by default, except the turn budget — which is derived from a number you already declared.
Structured output
titah run --output-format json— one object at the end, carryingok,agent,model,text,tools(with bothstatusandoutcome),usage, andnotices. In json modes nothing human touches stdout.--output-format stream-json— oneEventper line as it happens, then a final{"type":"result",…}. Deliberately not a new format: it is Titah’s own event union, so a second shape cannot drift from the first.--json-schema <file>— requires the answer to be JSON matching a subset of JSON Schema (type,required,properties,items,enum). Unknown keywords are skipped, not failed. Asked for in the prompt and checked locally, because most openai-compatible endpoints have no native structured output.- Exit codes split:
0done,1the turn failed,2the answer was the wrong shape. The fixes are different, so the codes are too.
Cost and limits
titah stats [--since 7d] [--all]— tokens and cost by model and by day. Titah has recorded per-turn usage since the first version and never had a way to read it back; 30.2M tokens were sitting in one database unreadable.provider.<p>.models.<m>.price— per 1M tokens, declared, never guessed. Titah ships no price table: prices change by region and contract, and a stale table produces numbers that look official and are wrong. Unpriced models still have their tokens counted and are named separately rather than counted free.limits.turnTokens— a token budget for one turn, replacing step count as the real bound. Defaults to 5× the model’scontextWindow. Declaring a window also lifts the step ceiling from 40 to 200.limits.sessionTokens— a budget across every turn in a session. Checked before a turn starts, never mid-turn: a turn stopped halfway leaves edited files and unrun tests.limits.continueTurns— continue automatically after a limit while the plan still has unchecked- [ ]items. A loop of bounded turns, not one unbounded turn: a fresh turn starts on a clean transcript and re-reads the whole plan. Default0.
Hooks
hooks."tool.before"andhooks."tool.after"— shell commands at the same hook points plugins already use, for rules that do not deserve an npm package.matchis a regex over the tool name; the event arrives on stdin as JSON.tool.beforerefuses the call on a non-zero exit, and a hook that fails to run or hangs also refuses.tool.aftercancels nothing but appends its stderr to the tool output, so the model learns the formatter failed.titah hooks listnames invalid regexes explicitly — a hook silent because its pattern is broken looks exactly like a hook that did not match.
Background turns
titah run --bgdetaches and returns the terminal immediately.titah bg list/logs <id> [-f]/stop <id>. There is no stored status column: the state is asked of the operating system on every read.logs -fstops on its own when the turn ends.stopkills the whole process group.
Web client
titah webstarts the server and opens a browser client. No new routes — it uses exactly the API the TUI uses, embedded as a string with no build step.- Permission requests and questions are answered on the page. Without that it would only be a reader, and every turn touching a file would hang.
/serves HTML to browsers and JSON to everything else;/healthstays JSON.
Sandbox
sandbox.bash— runs bash commands under Seatbelt (macOS) or bubblewrap (Linux). Reads stay free; writes are confined to the project and temp. This closes the one hole the permission axes cannot:bashnever passes through thedeleteaxis.- Fails closed. Enabled where no sandbox exists,
bashis refused rather than run unfenced.titah doctorreports which state you are in. - Off by default. Verified on macOS/seatbelt; the bubblewrap path is written but has not been run on Linux yet.
Agents, prompts, and defaults
delegation(ask|auto|always|never) plus four prompt fixes. Delegation was measured at 1 call in 5 before, 2 in 2 withalwaysafter — the tool inventory never listedtask, andbuildsaid to work “directly”.- Sub-agents inherit the parent’s permission instead of falling back to
global.
build-autoused to promise no confirmations and then ask forlsthrough every sub-agent it spawned. agent.<id>.effort(low|medium|high) — how much closing analysis an answer ends with. Unset means the model decides. Cycle it live withctrl+r.- Every answer now ends with a conclusion: what changed, what is verified, what is still open. Skipped where there is nothing to conclude.
buildandbuild-autoask before decisions that shape everything after — which datastore, how auth works, REST or GraphQL — with a recommendation and the reason drawn from the code just read. Measured 3/3 on a datastore choice and 0/3 on a trivial one.- Turns that stop at a limit say so, to the model and to you. Measured: 13.2% of real turns were hitting the old 20-step wall silently.
instructionsaccepts a string,{ "path": … }, or an array of either, and missing files are created on the first turn of a session. A starterAGENTS.mdis written for projects with no instruction file at all.scaffold: falseturns all of that off.
Fixed
TITAH.mdlost toAGENTS.mdin the same directory. The flat result list was reversed, which also reversed the order within a directory — so the file whose whole purpose is to override was read first and therefore lost.titah rundropped everysession.notice: no case for it, so context-window warnings, loop detection, and limit notices were invisible outside the TUI.- The TUI’s step spinner and the running-step glyph now differ in kind, not just shape, so two animations on screen are told apart without being read.
- Answers are marked with the agent that produced them, and the working indicator names the agent actually running — which is not always the one the footer shows after you press Tab mid-turn.
Notes
- Linux and macOS. Windows is still unsupported, and the sandbox is macOS-only in practice until the bubblewrap path is exercised.
- Requires Node.js ≥ 22.6.
[0.1.0] — 2026-08-19
First release. Titah works as a full coding agent, with delegation to other agent editors as the thing that sets it apart.
Agent & tools
- Streaming agent loop on AI SDK v7, with OpenAI-compatible providers as first-class citizens.
- Read tools:
read,list,glob,grep. All filesystem access is confined to the session working directory. - Change tools:
edit(exact string replace, must be unique, fails hard),write,bash(timeout + cancel). - Tool output above 32 KB is written to
tool-output/; the context only ever receives the head plus a pointer.
Permissions & undo
- Deny by default for
edit/write/bash, with a per-session allowlist, a config allowlist, and an--automode. - With no client connected, permission is auto-denied — never hanging, never silently allowing.
- A git snapshot is taken in a shadow repo before the first change of each turn.
titah undoreverts the whole turn, including deleting files it created. Your own.gitis never touched.
Context management
- Compaction runs automatically, between turns and mid-turn, so a single long agentic turn cannot overflow the window on its own.
- Bounded by the model’s declared
contextWindow. Undeclared means compaction is off for that model, said out loud once per session and listed bytitah doctor— a wrong window is more dangerous than no window, because a provider does not reject an oversized request, it truncates the oldest part and the model then answers confidently about what it can no longer see. - Old tool output is pruned before anything is summarised — free, and usually
enough. Sub-agent (
task) results are exempt from ordinary pruning: re-running one costs a whole nested turn, unlike re-reading a file. - The summariser’s own prompt is bounded as well. A transcript larger than the summariser’s window is summarised in chunks and the chunk summaries summarised in turn, rather than being silently truncated by the provider.
- Tunable:
compaction.auto,reserved,tailTurns,prune.reservedis capped at a quarter of the window, so the 8192 default does not swallow an 8k local model’s entire budget. /compactruns the same machinery on demand, and/compact <focus>keeps the named material in full detail.
Sub-agents
- Titah’s own model can dispatch several of its configured agents in parallel
inside one turn, with the
tasktool or/tim <task>. - Dispatch depth is capped at exactly one level: a sub-agent never receives
task, so nothing can spawn a tree that burns quota with no way to stop it. - Readers run concurrently; writers are serialised per working directory, so two sub-agents never race over the same shadow-git snapshot.
- An agent’s
delegateroutes it through an external CLI instead of Titah’s own loop — the same engine@claudeuses, reached fromtask. - A live panel (
ctrl+xthen↓) shows each sub-agent’s state and can cancel one without killing the coordinator’s turn.
Modes
- Three modes ship built in: Plan (refuses every change), Build Manual (confirm each step, the default), and Build Auto (no confirmations).
Tabswitches modes, same key as opencode.- Permissions can be overridden per agent, not just globally — that is what separates Build Auto from Build Manual.
Interface
- Ink TUI on the alternate screen buffer, with opencode’s default keybindings
(
ctrl+xleader).Esccancels a turn without corrupting history. - Opening screen centres the prompt under an ASCII logo, like opening nvim. After the first prompt the layout switches to info panel, history, and a prompt pinned to the bottom.
- Header panel shows the Titah mark beside session information, dropped automatically on short terminals.
/sessionresumes a saved session and/newstarts a fresh one, both from the palette or by typing.- Command palette on
Ctrl+P, with nested menus: picking/modellists the models from your config, and picking one switches the session model. - Autocomplete popups while typing:
@for agents and files,/for commands. ↑/↓scroll the history.- Assistant answers render as markdown: headings, bold, inline code, fenced
code blocks, lists, and quotes. Your own prompts stay raw, and underscores are
never treated as emphasis so
snake_casesurvives intact. - A spinner with elapsed time sits right above the prompt while work is running.
- HTTP + SSE server; the TUI is just one client,
curlis another. titah runfor headless and scripted use.
Delegation
@claudeand@opencodeas ordinary prompts. The answer joins your conversation; the full transcript goes totool-output/.- External sessions are mapped, so the next
@clauderesumes the same conversation. - External tokens and cost are shown separately from Titah’s own.
- Config-driven registry: adding a third agent touches no code.
Consensus, agents, commands, skills
/consensusfans one question out to every external agent in parallel, then synthesises and flags where they disagree.- Custom agents (prompt + tool filter + permission override + model override, plus
delegateto run one through an external CLI instead). - Custom commands (
/review src/a.ts) with{{.Input}}placeholders. - Markdown skills from
skills.paths, in two supported layouts — and discovered from the Claude Code and opencode registries (~/.claude,~/.config/opencode), so skills you already have work without configuration. - Skills are both passive (catalogued in the system prompt, loaded by the model
with the
skilltool) and active (/<skill-name>inserts one yourself). - Per-agent
stepslimits, and a text answer forced when the limit is reached rather than the misleading “the model stopped without giving an answer”.
Account (optional)
titah loginsigns a machine in through the browser, using the Device Authorization Grant (RFC 8628) rather than a loopback redirect — because a coding agent is very often run over SSH or in a container, where a redirect to127.0.0.1reaches the loopback interface of the wrong machine.- The first run on a new machine asks once: sign in, or continue without an account. The answer is recorded, so it is never asked again — and “without an account” is a real answer, because nothing in the agent needs one.
- A non-interactive run is never asked and never has a choice recorded for it, and a failed sign-in never stops the session.
titah whoamiandtitah doctor --probeverify against the server rather than trusting the file on disk, which is the only way to notice a token revoked from the dashboard.titah logoutrevokes remotely, then deletes locally whether or not the server could be reached./login,/logoutand/accountdo the same from inside the TUI;Esccancels a sign-in that is waiting.- The token lives in
account.jsonat mode 0600, kept separate fromauth.json: one holds your provider keys, the other your identity, and mixing them would lettitah auth removetake your login with it. - The server is chosen from
$TITAH_ACCOUNT_SERVER, thenaccount.server, then the default — and a token records which server issued it, so changing servers asks you to sign in again instead of silently reusing one that means nothing.
Setup & maintenance
titah initdetects keys from the environment and probes local endpoints before asking anything.--yesfor a non-interactive path.titah sessions prunesweeps database rows, orphaned tool-output blobs, and orphaned snapshot repos in one command.titah doctorchecks environment, config, credentials, and external agents.
Notes
- Credentials are never written to the config; they live in
auth.jsonwith mode 0600, or are referenced through${env:VAR}. - Linux and macOS. Windows is not supported yet.
- Requires Node.js ≥ 22.6.