@chispear/vibekit 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -8
- package/dist/agent.js +183 -8
- package/dist/brief.js +276 -0
- package/dist/cli.js +361 -23
- package/dist/config.js +47 -1
- package/dist/mcp.js +389 -0
- package/dist/prompt.js +4 -4
- package/dist/router.js +186 -12
- package/dist/sesi.js +77 -0
- package/dist/state.js +104 -0
- package/examples/vibekit.config.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,19 @@ Isi `apiKey` tiap penyedia (atau gunakan sintaks `"env:NAMA_VAR"` untuk membaca
|
|
|
47
47
|
|
|
48
48
|
Konfigurasi per proyek: file `./vibekit.config.json` di folder proyek **menimpa** konfigurasi global, jadi tiap proyek bisa punya susunan penyedia sendiri.
|
|
49
49
|
|
|
50
|
+
### Mengatur Plafon Jawaban (`gaya.maksTokenJawaban`)
|
|
51
|
+
|
|
52
|
+
`maksTokenJawaban` adalah **plafon** panjang satu respons model (dikirim sebagai `max_tokens`), bukan target: panjang jawaban sebenarnya diatur oleh `gaya.jawaban`, jadi plafon besar tidak otomatis boros. Plafon hanya berfungsi sebagai rem pengaman — tanpa batas sama sekali, tidak ada yang menghentikan model yang bertele-tele (atau menulis file tanpa henti) selain batas bawaan model itu sendiri.
|
|
53
|
+
|
|
54
|
+
| Nilai | Cocok untuk |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `800` (bawaan template) | Tanya-jawab ringan, pertanyaan singkat |
|
|
57
|
+
| `4000` | Kerja agent dan file berukuran sedang |
|
|
58
|
+
| `8000`–`16000` | Membangun aplikasi, menghasilkan file besar |
|
|
59
|
+
| `0` (atau hapus field-nya) | **Tanpa batas** — `max_tokens` tidak dikirim sama sekali; batas bawaan model yang berlaku |
|
|
60
|
+
|
|
61
|
+
Penting untuk mode agent: isi file yang ditulis model lewat tool `tulis_file`/`edit_file` **ikut dihitung dalam plafon respons yang sama**. File yang lebih besar dari plafon akan terpotong di tengah dan gagal ditulis — bila Anda menyuruh agent membangun aplikasi atau file besar, naikkan plafonnya (atau set `0`).
|
|
62
|
+
|
|
50
63
|
Lalu:
|
|
51
64
|
|
|
52
65
|
```bash
|
|
@@ -61,9 +74,13 @@ vibekit status
|
|
|
61
74
|
| Perintah | Fungsi |
|
|
62
75
|
|---|---|
|
|
63
76
|
| `vibekit init` | Buat `~/.vibekit/config.json` dari template (tidak menimpa yang sudah ada) |
|
|
64
|
-
| `vibekit ask "<pertanyaan>" [--file <path>]... [--dir <path>]... [--auto]` | Satu pertanyaan yang boleh berakhir dengan aksi; lampirkan file/folder sebagai konteks terseleksi. Ringkasan hemat token ditulis ke stderr, jawaban bersih di stdout, ditutup baris info model + token terpakai |
|
|
65
|
-
| `vibekit chat [--auto]` | Percakapan interaktif yang juga bisa bertindak di tengah obrolan; riwayat aksi lama dipadatkan jadi ringkasan lalu dipangkas sadar-tool (maks ±4.000 token, hanya pada batas giliran utuh); `/keluar` untuk berhenti, `/auto` untuk menyala-matikan mode auto-allow |
|
|
66
|
-
| `vibekit do "<tugas>"` | Mode agent sekali jalan: model mengerjakan tugas nyata dengan tool file/perintah di folder saat ini (lihat bagian Mode Agent di bawah) |
|
|
77
|
+
| `vibekit ask "<pertanyaan>" [--file <path>]... [--dir <path>]... [--auto] [--tanpa-brief]` | Satu pertanyaan yang boleh berakhir dengan aksi; lampirkan file/folder sebagai konteks terseleksi. Ringkasan hemat token ditulis ke stderr, jawaban bersih di stdout, ditutup baris info model + token terpakai |
|
|
78
|
+
| `vibekit chat [--auto] [--lanjut] [--tanpa-brief]` | Percakapan interaktif yang juga bisa bertindak di tengah obrolan; riwayat aksi lama dipadatkan jadi ringkasan lalu dipangkas sadar-tool (maks ±4.000 token, hanya pada batas giliran utuh); `/keluar` untuk berhenti, `/auto` untuk menyala-matikan mode auto-allow, `/sesi` menampilkan id sesi aktif. Sesi tersimpan otomatis dan bisa dilanjutkan dengan `--lanjut` |
|
|
79
|
+
| `vibekit do "<tugas>" [--auto] [--rencana] [--tanpa-brief]` | Mode agent sekali jalan: model mengerjakan tugas nyata dengan tool file/perintah di folder saat ini (lihat bagian Mode Agent di bawah). `--rencana`: susun rencana dulu, persetujuan SATU kali, lalu eksekusi |
|
|
80
|
+
| `vibekit mcp` | Sambungkan semua server MCP dari konfigurasi dan daftar tool yang mereka sediakan (lihat bagian Dukungan MCP di bawah) |
|
|
81
|
+
| `vibekit brief` | Susun ringkasan konteks proyek ke `.vibekit/brief.md`; brief dipakai otomatis sebagai konteks pada ask/chat/do (lihat bagian Brief Proyek) |
|
|
82
|
+
| `vibekit hemat` | Laporan pemakaian token per penyedia (hari ini & 7 hari terakhir) + token yang terukur dihemat kompaksi riwayat |
|
|
83
|
+
| `vibekit sesi` | Daftar sesi chat yang tersimpan (untuk dilanjutkan dengan `chat --lanjut`) |
|
|
67
84
|
| `vibekit status` | Tabel per penyedia: model, status (aktif / cooldown s.d. jam / anggaran tercapai), token terpakai hari ini |
|
|
68
85
|
| `vibekit --help` / `--version` | Bantuan / versi |
|
|
69
86
|
|
|
@@ -97,10 +114,112 @@ Model bekerja langkah demi langkah lewat tool calling (API OpenAI-compatible yan
|
|
|
97
114
|
- **Batas 12 langkah** per giliran/tugas, dan hasil tiap tool dipangkas (±4.000 karakter) sebelum dikirim balik ke model demi hemat token.
|
|
98
115
|
- **Riwayat chat yang aman untuk tool.** Pemangkasan riwayat chat hanya terjadi pada batas giliran utuh (satu pesan pengguna + seluruh rangkaian tool-nya), sehingga tidak pernah ada pesan hasil tool yang yatim atau panggilan tool yang kehilangan hasilnya.
|
|
99
116
|
|
|
117
|
+
## Dukungan MCP (Model Context Protocol)
|
|
118
|
+
|
|
119
|
+
Sejak v0.5.0, agent VibeKit bisa memakai **tool dari server MCP mana pun**. VibeKit berperan sebagai MCP *client*: daftarkan server lewat field `mcpServers` di konfigurasi, dan tool mereka tampil **berdampingan dengan 5 tool bawaan** pada `ask`, `chat`, dan `do`. Nama tool ter-namespace menjadi `mcp__<namaServer>__<namaTool>` agar tidak bertabrakan antar server maupun dengan tool bawaan.
|
|
120
|
+
|
|
121
|
+
Ada dua transport:
|
|
122
|
+
|
|
123
|
+
- **stdio** — server lokal dijalankan sebagai proses anak lewat `perintah` + `argumen` (pesan JSON-RPC per baris/NDJSON). Cocok untuk server MCP komunitas yang umumnya didistribusikan sebagai perintah `npx`/`node`/`python`.
|
|
124
|
+
- **HTTP (Streamable HTTP)** — server remote dipanggil lewat POST JSON-RPC ke `url`; header tambahan (mis. API key) diatur lewat `headers`. Jawaban JSON biasa maupun aliran SSE sama-sama didukung, dan `Mcp-Session-Id` dari server diteruskan antar-request.
|
|
125
|
+
|
|
126
|
+
### Contoh: server lokal (stdio)
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"mcpServers": [
|
|
131
|
+
{
|
|
132
|
+
"nama": "filesystem",
|
|
133
|
+
"perintah": "npx",
|
|
134
|
+
"argumen": ["-y", "@modelcontextprotocol/server-filesystem", "D:/proyek-saya"]
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Contoh: server remote (HTTP) — Google Stitch
|
|
141
|
+
|
|
142
|
+
Stitch (alat desain UI Google) menyediakan server MCP remote. Contoh konfigurasinya:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"mcpServers": [
|
|
147
|
+
{
|
|
148
|
+
"nama": "stitch",
|
|
149
|
+
"url": "https://stitch.googleapis.com/mcp",
|
|
150
|
+
"headers": { "X-Goog-Api-Key": "ISI_API_KEY_STITCH_MU" }
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
> **Catatan tegas:** contoh di atas hanya pola konfigurasinya. Anda memerlukan **akses dan API key Stitch milik Anda sendiri** — nilai `ISI_API_KEY_STITCH_MU` adalah placeholder yang harus diganti key Anda (atau simpan key di tempat aman dan jangan commit file konfigurasi yang sudah terisi). Hal yang sama berlaku untuk server remote lain: header autentikasi selalu kredensial Anda sendiri.
|
|
157
|
+
|
|
158
|
+
### Melihat tool yang tersedia
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
vibekit mcp
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Perintah ini menyambungkan semua server di `mcpServers`, mencetak daftar tool per server beserta deskripsi singkatnya, lalu memutuskan sambungan. Saat `ask`/`chat`/`do` berjalan, satu baris `MCP <nama>: N tool` dicetak per server yang berhasil tersambung; server yang gagal cukup dicatat sebagai peringatan — server lain tetap jalan.
|
|
165
|
+
|
|
166
|
+
### Keamanan tool MCP
|
|
167
|
+
|
|
168
|
+
Tool MCP adalah **aksi pihak ketiga** yang dijalankan atas nama Anda, jadi perlakuannya sama dengan tool mutasi bawaan: **selalu meminta konfirmasi Anda satu per satu** (pratinjau menampilkan nama tool + argumennya), dan penolakan dilaporkan ke model. Mode auto-allow (`--auto`/`--yes`/`/auto`) melewati konfirmasi itu juga untuk tool MCP — aktifkan hanya bila Anda mempercayai server MCP yang terdaftar dan tugasnya. Sambungkan hanya server MCP yang Anda kenal sumbernya.
|
|
169
|
+
|
|
170
|
+
## Laporan Pemakaian — `vibekit hemat`
|
|
171
|
+
|
|
172
|
+
Menampilkan pemakaian token per penyedia untuk **hari ini** dan **7 hari terakhir**, ditambah token yang **terukur** dibuang oleh kompaksi riwayat chat (hari ini + total tercatat):
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
vibekit hemat
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Soal kejujuran angka: hanya penghematan kompaksi yang diangkakan, karena memang bisa dihitung persis dari konten yang dibuang. Penghematan dari gaya jawaban ringkas dan konteks terseleksi bersifat *desain* (mencegah token terpakai sejak awal) dan dinyatakan sebagai catatan, bukan angka karangan.
|
|
179
|
+
|
|
180
|
+
## Streaming Jawaban
|
|
181
|
+
|
|
182
|
+
Jawaban pada `ask` dan `chat`, serta ringkasan akhir `do`, **dialirkan (streaming)**: teks tampil potongan demi potongan begitu model menulisnya, bukan menunggu selesai semua. Aturan failover-nya:
|
|
183
|
+
|
|
184
|
+
- Error HTTP **sebelum token pertama** (429 kuota, 503, dll.) diperlakukan persis seperti mode biasa: retry/cooldown/failover ke penyedia berikutnya.
|
|
185
|
+
- Error **di tengah aliran, setelah sebagian jawaban tampil**, TIDAK di-failover — mengulang ke penyedia lain akan menggandakan keluaran. Giliran dinyatakan gagal dengan pesan khusus, dan di chat giliran itu digulung balik seperti kegagalan lain.
|
|
186
|
+
- Pemakaian token tetap tercatat: dari `usage` di akhir aliran bila penyedia mengirimnya, atau estimasi tokenizer lokal atas teks yang terakumulasi.
|
|
187
|
+
|
|
188
|
+
## Mode Rencana — `vibekit do "<tugas>" --rencana`
|
|
189
|
+
|
|
190
|
+
Untuk tugas besar yang ingin Anda periksa dulu arahnya:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
vibekit do "rapikan struktur folder src dan perbarui README" --rencana
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Alurnya: (1) model menyusun **rencana langkah bernomor** (satu panggilan tanpa tool); (2) rencana dicetak dan Anda ditanya **satu kali**: `Jalankan rencana ini? (y/N)`; (3) ditolak → tidak ada satu pun tool yang berjalan, perintah selesai normal; (4) disetujui → rencana disuntikkan ke konteks tugas dan eksekusi berjalan **tanpa konfirmasi per langkah** (persetujuan rencana = persetujuan eksekusi). Pagar folder kerja dan batas 12 langkah tetap berlaku penuh.
|
|
197
|
+
|
|
198
|
+
## Sesi Chat Tersimpan — `chat --lanjut` & `vibekit sesi`
|
|
199
|
+
|
|
200
|
+
Setiap sesi `vibekit chat` **tersimpan otomatis** (di `~/.vibekit/sesi/`) setelah setiap giliran yang berhasil — riwayat internal disimpan apa adanya, termasuk pesan mentah tool, jadi sesi yang dilanjutkan tetap sah untuk model seperti Gemini 3.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
vibekit sesi # daftar sesi: id, waktu, jumlah pesan, cuplikan
|
|
204
|
+
vibekit chat --lanjut # lanjutkan sesi terakhir persis dari tempat berhenti
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Di tengah sesi, perintah `/sesi` menampilkan id sesi aktif. Tanpa `--lanjut`, `vibekit chat` selalu memulai sesi baru seperti biasa. Kompaksi dan pemangkasan aman tetap diterapkan per giliran pada sesi lanjutan.
|
|
208
|
+
|
|
209
|
+
## Brief Proyek — `vibekit brief`
|
|
210
|
+
|
|
211
|
+
Memindai folder kerja (pohon maks 3 tingkat, melewati `node_modules`/`.git`/`dist`/`build`/file biner) dan menyusun ringkasan konteks yang padat ke **`.vibekit/brief.md`**: nama proyek, bahasa dominan, stack & dependencies dari `package.json`, scripts, file konfigurasi penting yang terdeteksi (hanya nama — isi `.env` asli tidak pernah dibaca), ekstensi terbanyak, struktur folder, dan potongan awal README.
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
cd proyek-saya && vibekit brief
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Setelah brief ada, `ask`/`chat`/`do` **memakai brief itu otomatis** sebagai konteks tambahan (dicetak satu baris `(memakai brief proyek: ±N token)`; brief di atas 4.000 token dipotong dengan penanda). Lewatkan per perintah dengan flag `--tanpa-brief`. Jalankan ulang `vibekit brief` bila struktur proyek berubah banyak.
|
|
218
|
+
|
|
100
219
|
## Cara Failover Bekerja
|
|
101
220
|
|
|
102
221
|
1. Sebelum memanggil, router memeriksa state lokal (`~/.vibekit/state.json`): penyedia yang masih **cooldown** atau sudah mencapai **anggaran harian** dilewati tanpa dipanggil.
|
|
103
|
-
2. Saat memanggil, permintaan berisi system prompt hemat sesuai `gaya.jawaban` dan `max_tokens` = `gaya.maksTokenJawaban
|
|
222
|
+
2. Saat memanggil, permintaan berisi system prompt hemat sesuai `gaya.jawaban` dan `max_tokens` = `gaya.maksTokenJawaban` (hanya bila diatur > 0; nilai `0` atau field yang dihapus berarti `max_tokens` tidak dikirim sama sekali — tanpa batas). Objek `extraBody` per penyedia (bila ada) digabung ke body — berguna untuk kenop khas model, mis. menekan penalaran.
|
|
104
223
|
3. Bila respons menandakan kuota habis — HTTP **429/402** atau pola error `insufficient_quota` / `RESOURCE_EXHAUSTED` / saldo — penyedia ditandai **cooldown sampai akhir hari lokal** dan permintaan yang sama diulangi ke penyedia berikutnya. Rate-limit singkat (429 tanpa pola kuota) → cooldown `jedaCooldownMenit` (bawaan 15 menit). Khusus HTTP 503 (server penyedia sedang sibuk/penuh sesaat), penyedia yang sama dicoba ulang dulu sampai 2 kali dengan jeda 3 dan 8 detik, dan 503 tidak memicu cooldown.
|
|
105
224
|
4. Error lain (401 kunci salah, 400, jaringan) **tidak** dianggap kuota habis: dicatat apa adanya dan router tetap mencoba penyedia berikutnya.
|
|
106
225
|
5. Token terpakai dicatat dari field `usage` respons (atau estimasi tokenizer bila tidak ada). Bila semua penyedia gagal/habis, Anda menerima ringkasan status tiap penyedia dan jam paling awal untuk mencoba lagi.
|
|
@@ -108,10 +227,78 @@ Model bekerja langkah demi langkah lewat tool calling (API OpenAI-compatible yan
|
|
|
108
227
|
## Penghematan Token
|
|
109
228
|
|
|
110
229
|
- **System prompt ringkas** — gaya `poin-penting` memerintahkan model menjawab hanya poin penting tanpa basa-basi; tersedia juga `normal` dan `detail`.
|
|
111
|
-
- **
|
|
230
|
+
- **Plafon jawaban** — `maksTokenJawaban` menjadi `max_tokens` di setiap permintaan bila diatur > 0; `0` (atau field dihapus) berarti tanpa batas. Ini plafon, bukan target — lihat tabel anjuran di bagian Mulai Cepat.
|
|
112
231
|
- **Konteks terseleksi** — `--file/--dir` melewati file biner & file > 256 KB, memangkas baris komentar penuh dan baris kosong berlebih, dan membatasi total konteks (±16.000 token) dengan memotong berkas terbesar. Selisih token mentah vs terseleksi selalu ditampilkan.
|
|
113
|
-
- **Kompaksi riwayat aksi (chat)** — rangkaian tool giliran-giliran lama dipadatkan menjadi ringkasan satu baris (`[Aksi selesai: tulis_file index.html — OK]`) sebelum giliran baru dimulai, jadi isi file yang pernah ditulis/dibaca model tidak dikirim ulang ke API di setiap giliran. Giliran terakhir tidak pernah disentuh kompaksi, sehingga `thought_signature` Gemini tetap utuh selama dibutuhkan.
|
|
114
|
-
- **
|
|
232
|
+
- **Kompaksi riwayat aksi (chat)** — rangkaian tool giliran-giliran lama dipadatkan menjadi ringkasan satu baris (`[Aksi selesai: tulis_file index.html — OK]`) sebelum giliran baru dimulai, jadi isi file yang pernah ditulis/dibaca model tidak dikirim ulang ke API di setiap giliran. Giliran terakhir tidak pernah disentuh kompaksi, sehingga `thought_signature` Gemini tetap utuh selama dibutuhkan. Token yang dibuang kompaksi **diukur dan dicatat** — lihat laporannya di `vibekit hemat`.
|
|
233
|
+
- **Brief proyek** — `.vibekit/brief.md` memberi model konteks proyek yang padat dan stabil, jauh lebih murah daripada melampirkan banyak file mentah berulang kali.
|
|
234
|
+
- **Anggaran & pencatatan** — pemakaian per penyedia per hari terlihat di `vibekit status` dan laporan lengkapnya di `vibekit hemat`, sehingga boros selalu kelihatan.
|
|
235
|
+
|
|
236
|
+
## Referensi Konfigurasi — Nilai yang Didukung
|
|
237
|
+
|
|
238
|
+
Ringkasan semua field konfigurasi beserta nilai sahnya, supaya tidak perlu menebak.
|
|
239
|
+
|
|
240
|
+
### `gaya.jawaban`
|
|
241
|
+
|
|
242
|
+
Mengatur gaya system prompt hemat yang dikirim pada setiap permintaan. Tepat **tiga nilai** yang dikenal:
|
|
243
|
+
|
|
244
|
+
| Nilai | Perilaku |
|
|
245
|
+
|---|---|
|
|
246
|
+
| `poin-penting` | **(bawaan)** Jawaban wajib hanya poin-poin penting dalam format poin; kalimat pembuka/penutup, sapaan, dan basa-basi dilarang keras; kode diberikan sebagai blok kode dengan penjelasan pendamping maksimal satu baris. Paling hemat token. |
|
|
247
|
+
| `normal` | Ringkas dan langsung ke inti, tetapi kalimat biasa yang mengalir — tidak dipaksa bentuk poin, tidak sekaku `poin-penting`. |
|
|
248
|
+
| `detail` | Jawaban jelas dan terstruktur; boleh panjang bila memang perlu, dengan subjudul atau poin agar mudah dibaca. Paling longgar (dan paling boros token) di antara ketiganya. |
|
|
249
|
+
|
|
250
|
+
Nilai lain, nilai kosong, atau field yang tidak diisi **dianggap `poin-penting`**.
|
|
251
|
+
|
|
252
|
+
### `gaya.maksTokenJawaban`
|
|
253
|
+
|
|
254
|
+
Plafon token untuk **satu respons** model (dikirim sebagai `max_tokens`) — plafon, bukan target:
|
|
255
|
+
|
|
256
|
+
| Nilai | Arti |
|
|
257
|
+
|---|---|
|
|
258
|
+
| Angka bulat `> 0` (mis. `800`, `4000`, `16000`) | Plafon sebesar itu; respons yang melewatinya dipotong. Lihat tabel anjuran pemakaian di bagian Mengatur Plafon Jawaban. |
|
|
259
|
+
| `0`, atau field dihapus / tidak diisi | **Tanpa batas** — `max_tokens` tidak dikirim sama sekali; batas bawaan model yang berlaku. |
|
|
260
|
+
| Bawaan template `init` | `800`. |
|
|
261
|
+
|
|
262
|
+
### Field lain, sekilas
|
|
263
|
+
|
|
264
|
+
| Field | Nilai / arti |
|
|
265
|
+
|---|---|
|
|
266
|
+
| `providers` | Array penyedia; **urutan = prioritas** — entri pertama adalah bawaan, berikutnya cadangan failover. |
|
|
267
|
+
| `providers[].jedaCooldownMenit` | Menit istirahat sebuah penyedia setelah kena rate-limit singkat (HTTP 429 tanpa pola kuota). Bawaan `15`. Kuota yang benar-benar habis selalu cooldown sampai akhir hari lokal, berapa pun nilai field ini. |
|
|
268
|
+
| `providers[].anggaranHarian` | **Opsional.** Batas token per hari yang dicatat lokal; penyedia yang mencapai batas dilewati sebelum dipanggil. Tidak diisi = tanpa anggaran lokal (hanya kuota asli penyedia yang berlaku). |
|
|
269
|
+
| `providers[].apiKey` | Kunci API langsung, atau `env:NAMA_VAR` untuk membaca dari variabel lingkungan. |
|
|
270
|
+
| `providers[].extraBody` | **Opsional.** Objek bebas yang digabung apa adanya ke body permintaan (kenop khas model/penyedia). |
|
|
271
|
+
| `mcpServers` | **Opsional.** Daftar server MCP (lihat bagian Dukungan MCP): tiap entri wajib punya `nama` plus `perintah` (stdio lokal, dengan `argumen`/`env` opsional) atau `url` (HTTP remote, dengan `headers` opsional). |
|
|
272
|
+
|
|
273
|
+
## Pemecahan Masalah
|
|
274
|
+
|
|
275
|
+
### Windows PowerShell: `vibekit : The term 'vibekit' is not recognized as the name of a cmdlet...`
|
|
276
|
+
|
|
277
|
+
Setelah `npm install -g @chispear/vibekit` berhasil, menjalankan `vibekit init` (atau perintah `vibekit` lain) di PowerShell bisa gagal dengan error `CommandNotFoundException`:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
vibekit : The term 'vibekit' is not recognized as the name of a cmdlet, function, script file, or operable program.
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Penyebab:** folder tempat npm menyimpan perintah global — hasil `npm config get prefix`, umumnya `C:\Users\<nama>\AppData\Roaming\npm` — belum ada di PATH Windows, jadi PowerShell tidak menemukan perintahnya. Package-nya sendiri sebenarnya sudah terpasang dengan benar; ini murni soal PATH.
|
|
284
|
+
|
|
285
|
+
**Perbaikan cepat** (hanya untuk jendela PowerShell yang sedang terbuka):
|
|
286
|
+
|
|
287
|
+
```powershell
|
|
288
|
+
$env:Path += ";" + (npm config get prefix)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Perbaikan permanen** (sekali saja, lalu tutup dan buka lagi PowerShell):
|
|
292
|
+
|
|
293
|
+
```powershell
|
|
294
|
+
$prefix = npm config get prefix
|
|
295
|
+
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
296
|
+
[Environment]::SetEnvironmentVariable("Path", "$userPath;$prefix", "User")
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Verifikasi:** `vibekit --version` harus menampilkan nomor versi (mis. `0.4.1`).
|
|
300
|
+
|
|
301
|
+
**Alternatif tanpa mengubah PATH:** jalankan lewat npx — `npx @chispear/vibekit <perintah>` (mis. `npx @chispear/vibekit init`).
|
|
115
302
|
|
|
116
303
|
## Pengembangan
|
|
117
304
|
|
|
@@ -125,7 +312,7 @@ Keputusan repo: `dist/` masuk `.gitignore` dan dibangun saat publish (`prepublis
|
|
|
125
312
|
|
|
126
313
|
## Status Nama di npm
|
|
127
314
|
|
|
128
|
-
Package ini terbit di npm sebagai **`@chispear/vibekit`** (v0.1.0, 25 Oktober 2026; v0.2.0 menambahkan mode agent `vibekit do`; v0.3.0 menggabungkan kemampuan agent itu ke `ask` dan `chat`) karena nama tak-scoped `vibekit` sudah dipakai proyek lain. Nama perintah CLI tetap `vibekit`.
|
|
315
|
+
Package ini terbit di npm sebagai **`@chispear/vibekit`** (v0.1.0, 25 Oktober 2026; v0.2.0 menambahkan mode agent `vibekit do`; v0.3.0 menggabungkan kemampuan agent itu ke `ask` dan `chat`; v0.4.0 menambahkan mode auto-allow dan kompaksi riwayat aksi; v0.5.0 menambahkan dukungan MCP; v0.6.0 menambahkan laporan `hemat`, streaming jawaban, mode `--rencana`, sesi chat yang bisa dilanjutkan, dan brief proyek) karena nama tak-scoped `vibekit` sudah dipakai proyek lain. Nama perintah CLI tetap `vibekit`.
|
|
129
316
|
|
|
130
317
|
## Lisensi
|
|
131
318
|
|
package/dist/agent.js
CHANGED
|
@@ -95,6 +95,7 @@ export const DEFINISI_TOOL = [
|
|
|
95
95
|
const SYSTEM_PROMPT_AGENT = [
|
|
96
96
|
"Kamu adalah agent pelaksana tugas VibeKit yang bekerja di dalam SATU folder kerja pengguna.",
|
|
97
97
|
"Kerjakan tugas langkah demi langkah memakai tool yang tersedia: daftar_file, baca_file, tulis_file, edit_file, jalankan_perintah.",
|
|
98
|
+
"Tool tambahan dari server MCP (nama diawali \"mcp__\") mungkin juga tersedia — gunakan bila relevan; tool MCP juga selalu meminta persetujuan pengguna dan penolakan diperlakukan sama seperti tool bawaan.",
|
|
98
99
|
"Semua path bersifat relatif terhadap folder kerja; jangan pernah mencoba mengakses apa pun di luar folder itu.",
|
|
99
100
|
"Aksi tulis_file, edit_file, dan jalankan_perintah selalu meminta persetujuan pengguna dulu; bila pengguna menolak, jangan memaksa dan jangan mengulang aksi yang sama — sesuaikan rencana atau akhiri tugas.",
|
|
100
101
|
"Jangan mengarang isi file: gunakan daftar_file/baca_file dulu bila perlu mengetahui isi folder/file.",
|
|
@@ -109,6 +110,7 @@ export const SYSTEM_PROMPT_GABUNGAN = [
|
|
|
109
110
|
"Kamu adalah lawan bicara VibeKit yang bekerja di dalam SATU folder kerja pengguna.",
|
|
110
111
|
"Jawab pertanyaan biasa langsung sebagai teks, singkat dan hanya poin-poin penting.",
|
|
111
112
|
"Bila pengguna meminta dibuatkan/diubah file atau menjalankan perintah, gunakan tool yang tersedia: daftar_file, baca_file, tulis_file, edit_file, jalankan_perintah.",
|
|
113
|
+
"Tool tambahan dari server MCP (nama diawali \"mcp__\") mungkin juga tersedia — gunakan bila relevan; tool MCP juga selalu meminta persetujuan pengguna dan penolakan diperlakukan sama seperti tool bawaan.",
|
|
112
114
|
"Semua path bersifat relatif terhadap folder kerja; jangan pernah mencoba mengakses apa pun di luar folder itu.",
|
|
113
115
|
"Aksi tulis_file, edit_file, dan jalankan_perintah selalu meminta persetujuan pengguna dulu; bila pengguna menolak, jangan memaksa dan jangan mengulang aksi yang sama.",
|
|
114
116
|
"Jangan mengarang isi file: gunakan daftar_file/baca_file dulu bila perlu mengetahui isi folder/file.",
|
|
@@ -276,6 +278,40 @@ export async function eksekusiTool(panggilan, konteks) {
|
|
|
276
278
|
return `ERROR: ${e.message}`;
|
|
277
279
|
}
|
|
278
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Jalankan satu panggilan tool EKSTERNAL (non-bawaan, mis. dari server MCP).
|
|
283
|
+
* Tool eksternal selalu diperlakukan sebagai AKSI: wajib konfirmasi pengguna
|
|
284
|
+
* (pratinjau nama tool + argumen ringkas ±300 karakter), kecuali mode
|
|
285
|
+
* auto-allow — sama persis seperti tulis_file. Selalu mengembalikan teks
|
|
286
|
+
* hasil dengan awalan "OK:" / "ERROR:" / "DITOLAK:" seperti eksekusiTool.
|
|
287
|
+
*/
|
|
288
|
+
export async function eksekusiToolEkstra(panggilan, konteks) {
|
|
289
|
+
const nama = panggilan.function.name;
|
|
290
|
+
let arg;
|
|
291
|
+
try {
|
|
292
|
+
arg = JSON.parse(panggilan.function.arguments || "{}");
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return `ERROR: argumen untuk tool ${nama} bukan JSON yang valid.`;
|
|
296
|
+
}
|
|
297
|
+
if (!konteks.eksekutorEkstra) {
|
|
298
|
+
return `ERROR: tool ${nama} tidak tersedia (eksekutor eksternal tidak terpasang pada giliran ini).`;
|
|
299
|
+
}
|
|
300
|
+
const konfirmasiEfektif = konteks.autoAllow ? async () => true : konteks.konfirmasi;
|
|
301
|
+
const deskripsi = `${nama} (tool eksternal/MCP)\nArgumen: ${potong(JSON.stringify(arg), 300)}`;
|
|
302
|
+
if (!(await konfirmasiEfektif(deskripsi))) {
|
|
303
|
+
return `DITOLAK: pengguna menolak aksi ${nama}. Tool tidak dipanggil. Jangan mengulang aksi yang sama; sesuaikan rencana atau akhiri dengan ringkasan.`;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const hasil = (await konteks.eksekutorEkstra(nama, arg)).trim();
|
|
307
|
+
if (hasil.startsWith("ERROR:") || hasil.startsWith("DITOLAK:"))
|
|
308
|
+
return hasil;
|
|
309
|
+
return `OK: ${hasil || "(tool selesai tanpa keluaran teks)"}`;
|
|
310
|
+
}
|
|
311
|
+
catch (e) {
|
|
312
|
+
return `ERROR: ${e.message}`;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
279
315
|
/**
|
|
280
316
|
* Inti loop agent untuk SATU giliran: panggil router (dengan tools) → jalankan
|
|
281
317
|
* tool_calls yang diminta model lewat eksekutor aman → kirim hasil tool balik
|
|
@@ -290,6 +326,11 @@ export async function eksekusiTool(panggilan, konteks) {
|
|
|
290
326
|
export async function jalankanGiliran(opsi) {
|
|
291
327
|
const maksLangkah = opsi.maksLangkah ?? MAKS_LANGKAH_BAWAAN;
|
|
292
328
|
const messages = opsi.messages;
|
|
329
|
+
// Tool bawaan + tool ekstra (MCP) ditawarkan bersama; nama ekstra menentukan
|
|
330
|
+
// perutean eksekusi (eksekusiToolEkstra) dan perlakuan sebagai aksi.
|
|
331
|
+
const toolEkstra = opsi.toolEkstra ?? [];
|
|
332
|
+
const namaToolEkstra = new Set(toolEkstra.map((t) => t.function.name));
|
|
333
|
+
const semuaTool = [...DEFINISI_TOOL, ...toolEkstra];
|
|
293
334
|
const catatan = [];
|
|
294
335
|
let totalToken = 0;
|
|
295
336
|
let semuaUsage = true;
|
|
@@ -303,7 +344,10 @@ export async function jalankanGiliran(opsi) {
|
|
|
303
344
|
messages,
|
|
304
345
|
lokasiState: opsi.lokasiState,
|
|
305
346
|
fetchImpl: opsi.fetchImpl,
|
|
306
|
-
tools:
|
|
347
|
+
tools: semuaTool,
|
|
348
|
+
stream: opsi.stream,
|
|
349
|
+
onToken: opsi.onToken,
|
|
350
|
+
onTokenPertama: opsi.onTokenPertama,
|
|
307
351
|
});
|
|
308
352
|
catatan.push(...hasil.catatan);
|
|
309
353
|
if (!hasil.ok) {
|
|
@@ -316,6 +360,7 @@ export async function jalankanGiliran(opsi) {
|
|
|
316
360
|
jumlahLangkah: langkah,
|
|
317
361
|
catatan,
|
|
318
362
|
pesanGagal: hasil.pesan,
|
|
363
|
+
...(hasil.streamTerputus ? { streamTerputus: true } : {}),
|
|
319
364
|
};
|
|
320
365
|
}
|
|
321
366
|
totalToken += hasil.tokenTerpakai;
|
|
@@ -358,13 +403,20 @@ export async function jalankanGiliran(opsi) {
|
|
|
358
403
|
catch {
|
|
359
404
|
argRingkas = "";
|
|
360
405
|
}
|
|
361
|
-
const
|
|
406
|
+
const adalahEkstra = namaToolEkstra.has(p.function.name);
|
|
407
|
+
const penandaAuto = opsi.autoAllow && (TOOL_MUTASI.has(p.function.name) || adalahEkstra) ? " (auto)" : "";
|
|
362
408
|
opsi.jejak?.(`langkah ${langkah}: ${p.function.name}${argRingkas ? ` (${argRingkas})` : ""}${penandaAuto}`);
|
|
363
|
-
const hasilTool =
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
409
|
+
const hasilTool = adalahEkstra
|
|
410
|
+
? await eksekusiToolEkstra(p, {
|
|
411
|
+
konfirmasi: opsi.konfirmasi,
|
|
412
|
+
autoAllow: opsi.autoAllow,
|
|
413
|
+
eksekutorEkstra: opsi.eksekutorEkstra,
|
|
414
|
+
})
|
|
415
|
+
: await eksekusiTool(p, {
|
|
416
|
+
cwd: opsi.cwd,
|
|
417
|
+
konfirmasi: opsi.konfirmasi,
|
|
418
|
+
autoAllow: opsi.autoAllow,
|
|
419
|
+
});
|
|
368
420
|
opsi.jejak?.(` → ${potong(hasilTool.split("\n")[0], 120)}`);
|
|
369
421
|
messages.push({
|
|
370
422
|
role: "tool",
|
|
@@ -429,6 +481,13 @@ export function pangkasRiwayatAman(messages, maksToken) {
|
|
|
429
481
|
}
|
|
430
482
|
/** Target ringkas satu panggilan tool untuk pesan ringkasan kompaksi. */
|
|
431
483
|
function targetRingkasTool(panggilan) {
|
|
484
|
+
const nama = panggilan.function.name;
|
|
485
|
+
// Tool MCP (mcp__<server>__<tool>): targetnya adalah server/tool itu sendiri —
|
|
486
|
+
// argumennya tidak diasumsikan punya path/perintah.
|
|
487
|
+
if (nama.startsWith("mcp__")) {
|
|
488
|
+
const bagian = nama.split("__");
|
|
489
|
+
return bagian.length >= 3 ? `${bagian[1]}/${bagian.slice(2).join("__")}` : nama;
|
|
490
|
+
}
|
|
432
491
|
let arg = {};
|
|
433
492
|
try {
|
|
434
493
|
arg = JSON.parse(panggilan.function.arguments || "{}");
|
|
@@ -436,7 +495,7 @@ function targetRingkasTool(panggilan) {
|
|
|
436
495
|
catch {
|
|
437
496
|
// Argumen rusak: target dikosongkan, ringkasan tetap dibuat dari nama tool.
|
|
438
497
|
}
|
|
439
|
-
if (
|
|
498
|
+
if (nama === "jalankan_perintah") {
|
|
440
499
|
return potong(String(arg.perintah ?? ""), 40);
|
|
441
500
|
}
|
|
442
501
|
return String(arg.path ?? ".");
|
|
@@ -532,6 +591,19 @@ export function kompaksiRiwayatAksi(messages) {
|
|
|
532
591
|
});
|
|
533
592
|
return hasil;
|
|
534
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Varian terukur dari kompaksiRiwayatAksi: selain riwayat hasil, kembalikan
|
|
596
|
+
* jumlah token yang benar-benar dibuang (dihitung dengan tokenizer atas
|
|
597
|
+
* konten pesan yang digantikan/dibuang, dikurangi ringkasan penggantinya).
|
|
598
|
+
* Inilah angka penghematan yang dicatat `vibekit hemat` — terukur, bukan
|
|
599
|
+
* klaim desain.
|
|
600
|
+
*/
|
|
601
|
+
export function kompaksiRiwayatAksiTerukur(messages) {
|
|
602
|
+
const sebelum = messages.reduce((s, m) => s + tokenPesan(m), 0);
|
|
603
|
+
const riwayat = kompaksiRiwayatAksi(messages);
|
|
604
|
+
const sesudah = riwayat.reduce((s, m) => s + tokenPesan(m), 0);
|
|
605
|
+
return { riwayat, tokenTerbuang: Math.max(0, sebelum - sesudah) };
|
|
606
|
+
}
|
|
535
607
|
/**
|
|
536
608
|
* Mode tugas sekali jalan (`vibekit do`): satu giliran agent dengan system
|
|
537
609
|
* prompt pelaksana tugas, dibangun di atas jalankanGiliran. Perilaku dan
|
|
@@ -541,6 +613,7 @@ export async function jalankanAgent(opsi) {
|
|
|
541
613
|
const maksLangkah = opsi.maksLangkah ?? MAKS_LANGKAH_BAWAAN;
|
|
542
614
|
const messages = [
|
|
543
615
|
{ role: "system", content: SYSTEM_PROMPT_AGENT },
|
|
616
|
+
...(opsi.konteksSistem ? [{ role: "system", content: opsi.konteksSistem }] : []),
|
|
544
617
|
{ role: "user", content: opsi.tugas },
|
|
545
618
|
];
|
|
546
619
|
const hasil = await jalankanGiliran({
|
|
@@ -553,6 +626,11 @@ export async function jalankanAgent(opsi) {
|
|
|
553
626
|
fetchImpl: opsi.fetchImpl,
|
|
554
627
|
maksLangkah: opsi.maksLangkah,
|
|
555
628
|
jejak: opsi.jejak,
|
|
629
|
+
toolEkstra: opsi.toolEkstra,
|
|
630
|
+
eksekutorEkstra: opsi.eksekutorEkstra,
|
|
631
|
+
stream: opsi.stream,
|
|
632
|
+
onToken: opsi.onToken,
|
|
633
|
+
onTokenPertama: opsi.onTokenPertama,
|
|
556
634
|
});
|
|
557
635
|
if (hasil.pesanGagal !== undefined) {
|
|
558
636
|
return { ok: false, ringkasan: hasil.pesanGagal, jumlahLangkah: hasil.jumlahLangkah, catatan: hasil.catatan };
|
|
@@ -568,3 +646,100 @@ export async function jalankanAgent(opsi) {
|
|
|
568
646
|
}
|
|
569
647
|
return { ok: true, ringkasan: hasil.jawabanAkhir, jumlahLangkah: hasil.jumlahLangkah, catatan: hasil.catatan };
|
|
570
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* Mode rencana (`vibekit do --rencana`): dua tahap.
|
|
651
|
+
* 1. Susun rencana: SATU panggilan router TANPA tools — model hanya menulis
|
|
652
|
+
* rencana langkah bernomor yang ringkas (gaya dari konfigurasi tetap
|
|
653
|
+
* ditambahkan router).
|
|
654
|
+
* 2. Konfirmasi SATU kali lewat `konfirmasiRencana`. Ditolak → selesai
|
|
655
|
+
* tanpa menjalankan tool apa pun. Disetujui → rencana disuntikkan ke
|
|
656
|
+
* konteks tugas dan loop agent berjalan dengan autoAllow AKTIF
|
|
657
|
+
* (persetujuan rencana = persetujuan eksekusi); pagar cwd dan batas
|
|
658
|
+
* langkah tetap berlaku persis seperti mode biasa.
|
|
659
|
+
*/
|
|
660
|
+
export async function jalankanAgentRencana(opsi) {
|
|
661
|
+
const maksLangkah = opsi.maksLangkah ?? MAKS_LANGKAH_BAWAAN;
|
|
662
|
+
const hasilRencana = await tanyaRouter({
|
|
663
|
+
config: opsi.config,
|
|
664
|
+
messages: [
|
|
665
|
+
{
|
|
666
|
+
role: "user",
|
|
667
|
+
content: "Susun rencana langkah bernomor yang ringkas untuk tugas berikut. " +
|
|
668
|
+
"Tulis HANYA daftar rencananya — jangan menjalankan apa pun, jangan memanggil tool apa pun.\n\n" +
|
|
669
|
+
`Tugas: ${opsi.tugas}`,
|
|
670
|
+
},
|
|
671
|
+
],
|
|
672
|
+
lokasiState: opsi.lokasiState,
|
|
673
|
+
fetchImpl: opsi.fetchImpl,
|
|
674
|
+
});
|
|
675
|
+
if (!hasilRencana.ok) {
|
|
676
|
+
return {
|
|
677
|
+
status: "gagal",
|
|
678
|
+
rencana: "",
|
|
679
|
+
ringkasan: hasilRencana.pesan,
|
|
680
|
+
jumlahLangkah: 0,
|
|
681
|
+
catatan: hasilRencana.catatan,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
const rencana = hasilRencana.jawaban.trim();
|
|
685
|
+
if (!rencana) {
|
|
686
|
+
return {
|
|
687
|
+
status: "gagal",
|
|
688
|
+
rencana: "",
|
|
689
|
+
ringkasan: "Model tidak menghasilkan rencana (jawaban kosong).",
|
|
690
|
+
jumlahLangkah: 0,
|
|
691
|
+
catatan: hasilRencana.catatan,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const disetujui = await opsi.konfirmasiRencana(rencana);
|
|
695
|
+
if (!disetujui) {
|
|
696
|
+
return {
|
|
697
|
+
status: "dibatalkan",
|
|
698
|
+
rencana,
|
|
699
|
+
ringkasan: "Rencana ditolak pengguna — tidak ada tool yang dijalankan.",
|
|
700
|
+
jumlahLangkah: 0,
|
|
701
|
+
catatan: hasilRencana.catatan,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
const messages = [
|
|
705
|
+
{ role: "system", content: SYSTEM_PROMPT_AGENT },
|
|
706
|
+
...(opsi.konteksSistem ? [{ role: "system", content: opsi.konteksSistem }] : []),
|
|
707
|
+
{
|
|
708
|
+
role: "user",
|
|
709
|
+
content: `${opsi.tugas}\n\n` +
|
|
710
|
+
"Rencana berikut SUDAH DISETUJUI pengguna — ikuti rencana ini langkah demi langkah:\n" +
|
|
711
|
+
rencana,
|
|
712
|
+
},
|
|
713
|
+
];
|
|
714
|
+
const hasil = await jalankanGiliran({
|
|
715
|
+
config: opsi.config,
|
|
716
|
+
messages,
|
|
717
|
+
lokasiState: opsi.lokasiState,
|
|
718
|
+
cwd: opsi.cwd,
|
|
719
|
+
konfirmasi: opsi.konfirmasi,
|
|
720
|
+
autoAllow: true,
|
|
721
|
+
fetchImpl: opsi.fetchImpl,
|
|
722
|
+
maksLangkah: opsi.maksLangkah,
|
|
723
|
+
jejak: opsi.jejak,
|
|
724
|
+
toolEkstra: opsi.toolEkstra,
|
|
725
|
+
eksekutorEkstra: opsi.eksekutorEkstra,
|
|
726
|
+
stream: opsi.stream,
|
|
727
|
+
onToken: opsi.onToken,
|
|
728
|
+
onTokenPertama: opsi.onTokenPertama,
|
|
729
|
+
});
|
|
730
|
+
const catatan = [...hasilRencana.catatan, ...hasil.catatan];
|
|
731
|
+
if (hasil.pesanGagal !== undefined) {
|
|
732
|
+
return { status: "gagal", rencana, ringkasan: hasil.pesanGagal, jumlahLangkah: hasil.jumlahLangkah, catatan };
|
|
733
|
+
}
|
|
734
|
+
if (hasil.batasTercapai) {
|
|
735
|
+
return {
|
|
736
|
+
status: "batas",
|
|
737
|
+
rencana,
|
|
738
|
+
ringkasan: `Batas langkah tercapai (${maksLangkah} langkah) sebelum rencana selesai dijalankan. ` +
|
|
739
|
+
"Jalankan lagi perintah yang sama untuk melanjutkan, atau persempit tugasnya.",
|
|
740
|
+
jumlahLangkah: hasil.jumlahLangkah,
|
|
741
|
+
catatan,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
return { status: "selesai", rencana, ringkasan: hasil.jawabanAkhir, jumlahLangkah: hasil.jumlahLangkah, catatan };
|
|
745
|
+
}
|