@chispear/vibekit 0.3.0 → 0.4.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 +4 -2
- package/dist/agent.js +123 -6
- package/dist/cli.js +58 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,8 +61,8 @@ vibekit status
|
|
|
61
61
|
| Perintah | Fungsi |
|
|
62
62
|
|---|---|
|
|
63
63
|
| `vibekit init` | Buat `~/.vibekit/config.json` dari template (tidak menimpa yang sudah ada) |
|
|
64
|
-
| `vibekit ask "<pertanyaan>" [--file <path>]... [--dir <path>]
|
|
65
|
-
| `vibekit chat` | Percakapan interaktif yang juga bisa bertindak di tengah obrolan; riwayat
|
|
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
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) |
|
|
67
67
|
| `vibekit status` | Tabel per penyedia: model, status (aktif / cooldown s.d. jam / anggaran tercapai), token terpakai hari ini |
|
|
68
68
|
| `vibekit --help` / `--version` | Bantuan / versi |
|
|
@@ -93,6 +93,7 @@ Model bekerja langkah demi langkah lewat tool calling (API OpenAI-compatible yan
|
|
|
93
93
|
|
|
94
94
|
- **Hanya folder kerja (cwd).** Semua path di-resolve terhadap folder tempat perintah dijalankan; path yang keluar dari folder itu (mis. `../`) ditolak eksekutor, bahkan bila pengguna menyetujui.
|
|
95
95
|
- **Konfirmasi per aksi.** Setiap aksi yang mengubah file atau menjalankan perintah menampilkan deskripsi aksinya (path + pratinjau konten untuk tulis, potongan teks lama→baru untuk edit, perintah lengkap untuk shell) dan hanya berjalan bila Anda menjawab `y`/`ya` — selain itu dianggap menolak, dan penolakan itu dilaporkan ke model agar ia menyesuaikan rencana.
|
|
96
|
+
- **Mode auto-allow (opsional, sejak v0.4.0).** Flag `--auto` (alias `--yes`) pada `ask`, `chat`, dan `do` — atau perintah `/auto` di tengah sesi chat — melewati konfirmasi per aksi: semua aksi tulis/perintah langsung dijalankan. Pagar folder kerja TIDAK ikut dilepas (path traversal tetap ditolak), tetapi risikonya nyata: model bisa menulis atau menjalankan sesuatu tanpa sempat Anda periksa. Hanya aktifkan di folder yang aman (folder uji, proyek sekali pakai) dan untuk tugas yang Anda percayai.
|
|
96
97
|
- **Batas 12 langkah** per giliran/tugas, dan hasil tiap tool dipangkas (±4.000 karakter) sebelum dikirim balik ke model demi hemat token.
|
|
97
98
|
- **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.
|
|
98
99
|
|
|
@@ -109,6 +110,7 @@ Model bekerja langkah demi langkah lewat tool calling (API OpenAI-compatible yan
|
|
|
109
110
|
- **System prompt ringkas** — gaya `poin-penting` memerintahkan model menjawab hanya poin penting tanpa basa-basi; tersedia juga `normal` dan `detail`.
|
|
110
111
|
- **Batas jawaban keras** — `maksTokenJawaban` menjadi `max_tokens` di setiap permintaan.
|
|
111
112
|
- **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.
|
|
112
114
|
- **Anggaran & pencatatan** — pemakaian per penyedia per hari terlihat di `vibekit status`, sehingga boros selalu kelihatan.
|
|
113
115
|
|
|
114
116
|
## Pengembangan
|
package/dist/agent.js
CHANGED
|
@@ -112,7 +112,7 @@ export const SYSTEM_PROMPT_GABUNGAN = [
|
|
|
112
112
|
"Semua path bersifat relatif terhadap folder kerja; jangan pernah mencoba mengakses apa pun di luar folder itu.",
|
|
113
113
|
"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
114
|
"Jangan mengarang isi file: gunakan daftar_file/baca_file dulu bila perlu mengetahui isi folder/file.",
|
|
115
|
-
"Setelah bertindak, tutup giliran dengan balasan teks (tanpa tool) berisi ringkasan singkat: apa yang dilakukan dan hasilnya.",
|
|
115
|
+
"Setelah bertindak, tutup giliran dengan balasan teks (tanpa tool) berisi ringkasan singkat: apa yang dilakukan dan hasilnya. Ringkasan itu TIDAK BOLEH mengulang isi file yang ditulis atau dibaca — cukup sebutkan nama file dan hasilnya.",
|
|
116
116
|
].join(" ");
|
|
117
117
|
/**
|
|
118
118
|
* Resolve path masukan terhadap cwd. Mengembalikan null bila hasil resolve
|
|
@@ -152,6 +152,8 @@ function argumenRingkas(nama, arg) {
|
|
|
152
152
|
return potong(JSON.stringify(arg), 80);
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
+
/** Nama tool yang mengubah sesuatu — wajib konfirmasi kecuali mode auto-allow. */
|
|
156
|
+
const TOOL_MUTASI = new Set(["tulis_file", "edit_file", "jalankan_perintah"]);
|
|
155
157
|
/**
|
|
156
158
|
* Jalankan satu panggilan tool. Selalu mengembalikan teks hasil untuk model
|
|
157
159
|
* (tidak pernah throw): keberhasilan diawali "OK:", kegagalan "ERROR:",
|
|
@@ -167,6 +169,10 @@ export async function eksekusiTool(panggilan, konteks) {
|
|
|
167
169
|
return `ERROR: argumen untuk tool ${nama} bukan JSON yang valid.`;
|
|
168
170
|
}
|
|
169
171
|
const { cwd, konfirmasi } = konteks;
|
|
172
|
+
// Mode auto-allow: callback konfirmasi pengguna tidak dipanggil sama sekali —
|
|
173
|
+
// aksi mutasi langsung disetujui. Pagar cwd (resolusiAman) TIDAK berubah dan
|
|
174
|
+
// tetap menolak path traversal walau auto-allow aktif.
|
|
175
|
+
const konfirmasiEfektif = konteks.autoAllow ? async () => true : konfirmasi;
|
|
170
176
|
const ambilPath = (mentah) => {
|
|
171
177
|
const masukan = typeof mentah === "string" && mentah.trim() !== "" ? mentah : ".";
|
|
172
178
|
const tujuan = resolusiAman(cwd, masukan);
|
|
@@ -203,7 +209,7 @@ export async function eksekusiTool(panggilan, konteks) {
|
|
|
203
209
|
const konten = typeof arg.konten === "string" ? arg.konten : "";
|
|
204
210
|
const deskripsi = `tulis_file → ${p.rel} (${konten.length} karakter)\n` +
|
|
205
211
|
`Pratinjau konten:\n${potong(konten, 300)}`;
|
|
206
|
-
if (!(await
|
|
212
|
+
if (!(await konfirmasiEfektif(deskripsi))) {
|
|
207
213
|
return `DITOLAK: pengguna menolak aksi tulis_file ke ${p.rel}. File tidak ditulis. Jangan mengulang aksi yang sama; sesuaikan rencana atau akhiri dengan ringkasan.`;
|
|
208
214
|
}
|
|
209
215
|
await fs.mkdir(path.dirname(p.tujuan), { recursive: true });
|
|
@@ -224,7 +230,7 @@ export async function eksekusiTool(panggilan, konteks) {
|
|
|
224
230
|
const deskripsi = `edit_file → ${p.rel}\n` +
|
|
225
231
|
`Teks lama: ${potong(teksLama, 150)}\n` +
|
|
226
232
|
`Teks baru: ${potong(teksBaru, 150)}`;
|
|
227
|
-
if (!(await
|
|
233
|
+
if (!(await konfirmasiEfektif(deskripsi))) {
|
|
228
234
|
return `DITOLAK: pengguna menolak aksi edit_file di ${p.rel}. File tidak diubah. Jangan mengulang aksi yang sama; sesuaikan rencana atau akhiri dengan ringkasan.`;
|
|
229
235
|
}
|
|
230
236
|
const baru = isi.slice(0, idx) + teksBaru + isi.slice(idx + teksLama.length);
|
|
@@ -236,7 +242,7 @@ export async function eksekusiTool(panggilan, konteks) {
|
|
|
236
242
|
if (!perintah)
|
|
237
243
|
return "ERROR: argumen perintah kosong.";
|
|
238
244
|
const deskripsi = `jalankan_perintah → ${perintah}`;
|
|
239
|
-
if (!(await
|
|
245
|
+
if (!(await konfirmasiEfektif(deskripsi))) {
|
|
240
246
|
return `DITOLAK: pengguna menolak menjalankan perintah "${perintah}". Perintah tidak dijalankan. Jangan mengulang aksi yang sama; sesuaikan rencana atau akhiri dengan ringkasan.`;
|
|
241
247
|
}
|
|
242
248
|
const hasil = await new Promise((resolve) => {
|
|
@@ -352,8 +358,13 @@ export async function jalankanGiliran(opsi) {
|
|
|
352
358
|
catch {
|
|
353
359
|
argRingkas = "";
|
|
354
360
|
}
|
|
355
|
-
opsi.
|
|
356
|
-
|
|
361
|
+
const penandaAuto = opsi.autoAllow && TOOL_MUTASI.has(p.function.name) ? " (auto)" : "";
|
|
362
|
+
opsi.jejak?.(`langkah ${langkah}: ${p.function.name}${argRingkas ? ` (${argRingkas})` : ""}${penandaAuto}`);
|
|
363
|
+
const hasilTool = await eksekusiTool(p, {
|
|
364
|
+
cwd: opsi.cwd,
|
|
365
|
+
konfirmasi: opsi.konfirmasi,
|
|
366
|
+
autoAllow: opsi.autoAllow,
|
|
367
|
+
});
|
|
357
368
|
opsi.jejak?.(` → ${potong(hasilTool.split("\n")[0], 120)}`);
|
|
358
369
|
messages.push({
|
|
359
370
|
role: "tool",
|
|
@@ -416,6 +427,111 @@ export function pangkasRiwayatAman(messages, maksToken) {
|
|
|
416
427
|
}
|
|
417
428
|
return [...awalan, ...grup.flat()];
|
|
418
429
|
}
|
|
430
|
+
/** Target ringkas satu panggilan tool untuk pesan ringkasan kompaksi. */
|
|
431
|
+
function targetRingkasTool(panggilan) {
|
|
432
|
+
let arg = {};
|
|
433
|
+
try {
|
|
434
|
+
arg = JSON.parse(panggilan.function.arguments || "{}");
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
// Argumen rusak: target dikosongkan, ringkasan tetap dibuat dari nama tool.
|
|
438
|
+
}
|
|
439
|
+
if (panggilan.function.name === "jalankan_perintah") {
|
|
440
|
+
return potong(String(arg.perintah ?? ""), 40);
|
|
441
|
+
}
|
|
442
|
+
return String(arg.path ?? ".");
|
|
443
|
+
}
|
|
444
|
+
/** Status satu hasil tool dari awalannya: "OK" | "ERROR" | "DITOLAK" | kata pertama. */
|
|
445
|
+
function statusHasilTool(konten) {
|
|
446
|
+
const t = (konten ?? "").trimStart();
|
|
447
|
+
if (t.startsWith("OK"))
|
|
448
|
+
return "OK";
|
|
449
|
+
if (t.startsWith("ERROR"))
|
|
450
|
+
return "ERROR";
|
|
451
|
+
if (t.startsWith("DITOLAK"))
|
|
452
|
+
return "DITOLAK";
|
|
453
|
+
return t.split(/[:\s]/)[0] || "?";
|
|
454
|
+
}
|
|
455
|
+
/** Padatkan satu giliran LAMA (bukan giliran terakhir) — lihat kompaksiRiwayatAksi. */
|
|
456
|
+
function kompaksiSatuGiliran(giliran) {
|
|
457
|
+
const keluar = [];
|
|
458
|
+
let i = 0;
|
|
459
|
+
while (i < giliran.length) {
|
|
460
|
+
const m = giliran[i];
|
|
461
|
+
if (m.role === "assistant" && m.tool_calls && m.tool_calls.length > 0) {
|
|
462
|
+
// Kumpulkan pesan-pesan tool milik rangkaian ini.
|
|
463
|
+
const hasilTool = [];
|
|
464
|
+
let j = i + 1;
|
|
465
|
+
while (j < giliran.length && giliran[j].role === "tool") {
|
|
466
|
+
hasilTool.push(giliran[j]);
|
|
467
|
+
j++;
|
|
468
|
+
}
|
|
469
|
+
// Teks pendamping pada pesan assistant (jarang ada) dipertahankan.
|
|
470
|
+
if (typeof m.content === "string" && m.content.trim() !== "") {
|
|
471
|
+
keluar.push({ role: "assistant", content: m.content });
|
|
472
|
+
}
|
|
473
|
+
// Satu pesan ringkasan per panggilan tool, menggantikan seluruh
|
|
474
|
+
// rangkaian — argumen lengkap (termasuk isi file) dan thought_signature
|
|
475
|
+
// ikut terbuang di sini, memang itu tujuannya (hemat token).
|
|
476
|
+
for (const p of m.tool_calls) {
|
|
477
|
+
const pasangan = hasilTool.find((t) => t.tool_call_id === p.id) ?? hasilTool[0];
|
|
478
|
+
const status = statusHasilTool(pasangan?.content ?? null);
|
|
479
|
+
const target = targetRingkasTool(p);
|
|
480
|
+
keluar.push({
|
|
481
|
+
role: "assistant",
|
|
482
|
+
content: `[Aksi selesai: ${p.function.name}${target ? ` ${target}` : ""} — ${status}]`,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
i = j;
|
|
486
|
+
}
|
|
487
|
+
else if (m.role === "tool") {
|
|
488
|
+
// Pesan tool yatim tanpa rangkaian assistant pendahulu dalam giliran
|
|
489
|
+
// yang sama: buang (tidak sah dikirim ke API tanpa pendahulunya).
|
|
490
|
+
i++;
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
keluar.push(m);
|
|
494
|
+
i++;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return keluar;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Kompaksi riwayat aksi untuk chat: ganti rangkaian tool pada giliran-giliran
|
|
501
|
+
* LAMA menjadi ringkasan teks satu baris, agar isi file yang ditulis model
|
|
502
|
+
* (argumen tool_calls) tidak ikut dikirim ulang ke API pada setiap giliran
|
|
503
|
+
* berikutnya. Bentuk ringkasan: `[Aksi selesai: <tool> <target> — <status>]`.
|
|
504
|
+
*
|
|
505
|
+
* Aturan keras:
|
|
506
|
+
* - Giliran TERAKHIR tidak disentuh sama sekali — pesan assistant mentahnya
|
|
507
|
+
* (termasuk thought_signature Gemini di dalam tool_calls) wajib utuh
|
|
508
|
+
* selama giliran itu masih aktif/baru selesai.
|
|
509
|
+
* - Pesan sebelum "user" pertama (mis. system) dipertahankan apa adanya.
|
|
510
|
+
* - Pesan user dan assistant teks biasa dipertahankan apa adanya.
|
|
511
|
+
* - Mengembalikan array BARU; array masukan tidak diubah.
|
|
512
|
+
*/
|
|
513
|
+
export function kompaksiRiwayatAksi(messages) {
|
|
514
|
+
let awal = 0;
|
|
515
|
+
while (awal < messages.length && messages[awal].role !== "user")
|
|
516
|
+
awal++;
|
|
517
|
+
const awalan = messages.slice(0, awal);
|
|
518
|
+
const sisa = messages.slice(awal);
|
|
519
|
+
const grup = [];
|
|
520
|
+
for (const m of sisa) {
|
|
521
|
+
if (m.role === "user" || grup.length === 0)
|
|
522
|
+
grup.push([m]);
|
|
523
|
+
else
|
|
524
|
+
grup[grup.length - 1].push(m);
|
|
525
|
+
}
|
|
526
|
+
const hasil = [...awalan];
|
|
527
|
+
grup.forEach((giliran, gi) => {
|
|
528
|
+
if (gi === grup.length - 1)
|
|
529
|
+
hasil.push(...giliran);
|
|
530
|
+
else
|
|
531
|
+
hasil.push(...kompaksiSatuGiliran(giliran));
|
|
532
|
+
});
|
|
533
|
+
return hasil;
|
|
534
|
+
}
|
|
419
535
|
/**
|
|
420
536
|
* Mode tugas sekali jalan (`vibekit do`): satu giliran agent dengan system
|
|
421
537
|
* prompt pelaksana tugas, dibangun di atas jalankanGiliran. Perilaku dan
|
|
@@ -433,6 +549,7 @@ export async function jalankanAgent(opsi) {
|
|
|
433
549
|
lokasiState: opsi.lokasiState,
|
|
434
550
|
cwd: opsi.cwd,
|
|
435
551
|
konfirmasi: opsi.konfirmasi,
|
|
552
|
+
autoAllow: opsi.autoAllow,
|
|
436
553
|
fetchImpl: opsi.fetchImpl,
|
|
437
554
|
maksLangkah: opsi.maksLangkah,
|
|
438
555
|
jejak: opsi.jejak,
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import readline from "node:readline";
|
|
3
3
|
import { buatConfigGlobal, globalConfigPath, muatConfig, statePath, } from "./config.js";
|
|
4
|
-
import { jalankanAgent, jalankanGiliran, pangkasRiwayatAman, SYSTEM_PROMPT_GABUNGAN, } from "./agent.js";
|
|
4
|
+
import { jalankanAgent, jalankanGiliran, kompaksiRiwayatAksi, pangkasRiwayatAman, SYSTEM_PROMPT_GABUNGAN, } from "./agent.js";
|
|
5
5
|
import { teksBanner } from "./banner.js";
|
|
6
6
|
import { bangunKonteks } from "./context.js";
|
|
7
7
|
import { formatJam, muatState, sedangCooldown, statePenyedia, } from "./state.js";
|
|
8
8
|
import { formatAngka } from "./token.js";
|
|
9
|
-
const VERSI = "0.
|
|
9
|
+
const VERSI = "0.4.0";
|
|
10
10
|
const BATAS_TOKEN_RIWAYAT = 4000;
|
|
11
11
|
function bantuan() {
|
|
12
12
|
console.log(`VibeKit CLI v${VERSI} — router AI hemat token dengan failover otomatis
|
|
@@ -23,6 +23,12 @@ Penggunaan:
|
|
|
23
23
|
Opsi ask:
|
|
24
24
|
--file <path> Lampirkan file sebagai konteks (boleh diulang)
|
|
25
25
|
--dir <path> Lampirkan isi folder sebagai konteks (boleh diulang)
|
|
26
|
+
--auto, --yes Mode auto-allow: aksi tulis/perintah langsung dijalankan
|
|
27
|
+
tanpa persetujuan (tetap terkunci di folder kerja)
|
|
28
|
+
|
|
29
|
+
Opsi chat & do:
|
|
30
|
+
--auto, --yes Mode auto-allow seperti di atas. Di tengah sesi chat,
|
|
31
|
+
perintah /auto menyalakan/mematikan mode ini kapan saja.
|
|
26
32
|
|
|
27
33
|
Kemampuan agent (ask, chat, do):
|
|
28
34
|
Sejak v0.3.0 ketiga perintah itu bisa bertindak, bukan hanya menjawab:
|
|
@@ -31,6 +37,9 @@ Kemampuan agent (ask, chat, do):
|
|
|
31
37
|
baca_file) berjalan langsung; tool yang mengubah sesuatu (tulis_file,
|
|
32
38
|
edit_file, jalankan_perintah) selalu meminta persetujuanmu satu per satu
|
|
33
39
|
— jawaban selain y/ya berarti ditolak. Batas 12 langkah per giliran.
|
|
40
|
+
Mode auto-allow (--auto/--yes, atau /auto di chat) melewati persetujuan
|
|
41
|
+
itu: semua aksi langsung dijalankan, pagar folder kerja tetap berlaku.
|
|
42
|
+
Hanya aktifkan di folder yang aman dan untuk tugas yang kamu percayai.
|
|
34
43
|
Perbedaannya: ask = satu pertanyaan yang boleh berakhir dengan aksi,
|
|
35
44
|
chat = percakapan yang bisa bertindak di tengah obrolan,
|
|
36
45
|
do = mode tugas sekali jalan dengan jejak langkah & ringkasan akhir.
|
|
@@ -57,10 +66,15 @@ function perintahInit() {
|
|
|
57
66
|
}
|
|
58
67
|
return 0;
|
|
59
68
|
}
|
|
69
|
+
/** Satu baris peringatan yang dicetak di awal perintah saat mode auto-allow aktif. */
|
|
70
|
+
function cetakPeringatanAuto() {
|
|
71
|
+
console.error("[vibekit] Mode auto-allow AKTIF: aksi tulis file & menjalankan perintah akan dijalankan TANPA persetujuanmu. Agent tetap terkunci di folder kerja ini.");
|
|
72
|
+
}
|
|
60
73
|
function uraiArgAsk(args) {
|
|
61
74
|
const files = [];
|
|
62
75
|
const dirs = [];
|
|
63
76
|
const sisa = [];
|
|
77
|
+
let auto = false;
|
|
64
78
|
for (let i = 0; i < args.length; i++) {
|
|
65
79
|
const a = args[i];
|
|
66
80
|
if (a === "--file") {
|
|
@@ -81,6 +95,9 @@ function uraiArgAsk(args) {
|
|
|
81
95
|
else if (a.startsWith("--dir=")) {
|
|
82
96
|
dirs.push(a.slice("--dir=".length));
|
|
83
97
|
}
|
|
98
|
+
else if (a === "--auto" || a === "--yes") {
|
|
99
|
+
auto = true;
|
|
100
|
+
}
|
|
84
101
|
else {
|
|
85
102
|
sisa.push(a);
|
|
86
103
|
}
|
|
@@ -88,7 +105,7 @@ function uraiArgAsk(args) {
|
|
|
88
105
|
const pertanyaan = sisa.join(" ").trim();
|
|
89
106
|
if (!pertanyaan)
|
|
90
107
|
return { error: "Pertanyaan kosong. Contoh: vibekit ask \"apa itu API?\"" };
|
|
91
|
-
return { pertanyaan, files, dirs };
|
|
108
|
+
return { pertanyaan, files, dirs, auto };
|
|
92
109
|
}
|
|
93
110
|
function buatPembacaBaris() {
|
|
94
111
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -166,6 +183,8 @@ async function perintahAsk(args) {
|
|
|
166
183
|
{ role: "system", content: SYSTEM_PROMPT_GABUNGAN },
|
|
167
184
|
{ role: "user", content: isiUser },
|
|
168
185
|
];
|
|
186
|
+
if (urai.auto)
|
|
187
|
+
cetakPeringatanAuto();
|
|
169
188
|
const pembaca = buatPembacaBaris();
|
|
170
189
|
try {
|
|
171
190
|
const hasil = await jalankanGiliran({
|
|
@@ -174,6 +193,7 @@ async function perintahAsk(args) {
|
|
|
174
193
|
lokasiState: statePath(),
|
|
175
194
|
cwd: process.cwd(),
|
|
176
195
|
konfirmasi: buatKonfirmasi(pembaca),
|
|
196
|
+
autoAllow: urai.auto,
|
|
177
197
|
jejak: (pesan) => console.error(`[vibekit] ${pesan}`),
|
|
178
198
|
});
|
|
179
199
|
if (!hasil.ok) {
|
|
@@ -199,7 +219,7 @@ async function perintahAsk(args) {
|
|
|
199
219
|
pembaca.tutup();
|
|
200
220
|
}
|
|
201
221
|
}
|
|
202
|
-
async function perintahChat() {
|
|
222
|
+
async function perintahChat(args) {
|
|
203
223
|
let config;
|
|
204
224
|
try {
|
|
205
225
|
config = muatConfig().config;
|
|
@@ -208,8 +228,14 @@ async function perintahChat() {
|
|
|
208
228
|
console.error(`[vibekit] ${e.message}`);
|
|
209
229
|
return 2;
|
|
210
230
|
}
|
|
231
|
+
// Mode auto-allow bisa dinyalakan dari flag --auto/--yes, lalu diubah
|
|
232
|
+
// kapan saja di tengah sesi dengan perintah /auto.
|
|
233
|
+
let autoAktif = args.includes("--auto") || args.includes("--yes");
|
|
211
234
|
console.log("VibeKit chat — ketik pertanyaan, /keluar untuk berhenti.");
|
|
212
235
|
console.log("Chat ini juga bisa bertindak: minta saja dibuatkan file atau menjalankan perintah — setiap aksi meminta persetujuanmu dulu.");
|
|
236
|
+
console.log("Perintah /auto menyalakan/mematikan mode auto-allow (aksi tanpa persetujuan) di tengah sesi.");
|
|
237
|
+
if (autoAktif)
|
|
238
|
+
cetakPeringatanAuto();
|
|
213
239
|
const riwayat = [{ role: "system", content: SYSTEM_PROMPT_GABUNGAN }];
|
|
214
240
|
const pembaca = buatPembacaBaris();
|
|
215
241
|
const konfirmasi = buatKonfirmasi(pembaca);
|
|
@@ -223,8 +249,21 @@ async function perintahChat() {
|
|
|
223
249
|
break;
|
|
224
250
|
if (!baris)
|
|
225
251
|
continue;
|
|
252
|
+
if (baris === "/auto") {
|
|
253
|
+
autoAktif = !autoAktif;
|
|
254
|
+
console.log(autoAktif
|
|
255
|
+
? "[vibekit] Mode auto-allow: AKTIF — aksi tulis file & menjalankan perintah berikutnya dijalankan TANPA persetujuan. Agent tetap terkunci di folder kerja ini."
|
|
256
|
+
: "[vibekit] Mode auto-allow: NONAKTIF — aksi tulis file & menjalankan perintah kembali meminta persetujuanmu satu per satu.");
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
226
259
|
riwayat.push({ role: "user", content: baris });
|
|
227
|
-
//
|
|
260
|
+
// Kompaksi riwayat aksi DULU: rangkaian tool giliran-giliran lama
|
|
261
|
+
// diganti ringkasan satu baris agar isi file tidak terkirim ulang.
|
|
262
|
+
// Giliran terakhir (yang baru saja ditambahkan pesan user-nya ini
|
|
263
|
+
// menjadi giliran terakhir) tidak disentuh kompaksi.
|
|
264
|
+
const terkompaksi = kompaksiRiwayatAksi(riwayat);
|
|
265
|
+
riwayat.splice(0, riwayat.length, ...terkompaksi);
|
|
266
|
+
// Lalu Pangkas SADAR-TOOL: hanya pada batas giliran utuh, tidak pernah
|
|
228
267
|
// menyisakan pesan tool yatim atau tool_calls tanpa hasilnya.
|
|
229
268
|
const terpotong = pangkasRiwayatAman(riwayat, BATAS_TOKEN_RIWAYAT);
|
|
230
269
|
riwayat.splice(0, riwayat.length, ...terpotong);
|
|
@@ -234,6 +273,7 @@ async function perintahChat() {
|
|
|
234
273
|
lokasiState: statePath(),
|
|
235
274
|
cwd: process.cwd(),
|
|
236
275
|
konfirmasi,
|
|
276
|
+
autoAllow: autoAktif,
|
|
237
277
|
jejak: (pesan) => console.error(`[vibekit] ${pesan}`),
|
|
238
278
|
});
|
|
239
279
|
if (hasil.ok) {
|
|
@@ -264,7 +304,11 @@ async function perintahChat() {
|
|
|
264
304
|
return 0;
|
|
265
305
|
}
|
|
266
306
|
async function perintahDo(args) {
|
|
267
|
-
const
|
|
307
|
+
const auto = args.includes("--auto") || args.includes("--yes");
|
|
308
|
+
const tugas = args
|
|
309
|
+
.filter((a) => a !== "--auto" && a !== "--yes")
|
|
310
|
+
.join(" ")
|
|
311
|
+
.trim();
|
|
268
312
|
if (!tugas) {
|
|
269
313
|
console.error("[vibekit] Tugas kosong. Contoh: vibekit do \"buatkan file halo.txt berisi sapaan\"");
|
|
270
314
|
return 2;
|
|
@@ -280,7 +324,12 @@ async function perintahDo(args) {
|
|
|
280
324
|
const cwd = process.cwd();
|
|
281
325
|
console.log(`VibeKit agent — folder kerja: ${cwd}`);
|
|
282
326
|
console.log(`Tugas: ${tugas}`);
|
|
283
|
-
|
|
327
|
+
if (auto) {
|
|
328
|
+
cetakPeringatanAuto();
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
console.log("Tool baca berjalan langsung; tulis/edit file dan jalankan perintah meminta persetujuanmu satu per satu.\n");
|
|
332
|
+
}
|
|
284
333
|
const pembaca = buatPembacaBaris();
|
|
285
334
|
const konfirmasi = buatKonfirmasi(pembaca);
|
|
286
335
|
try {
|
|
@@ -290,6 +339,7 @@ async function perintahDo(args) {
|
|
|
290
339
|
lokasiState: statePath(),
|
|
291
340
|
cwd,
|
|
292
341
|
konfirmasi,
|
|
342
|
+
autoAllow: auto,
|
|
293
343
|
jejak: (pesan) => console.error(`[vibekit] ${pesan}`),
|
|
294
344
|
});
|
|
295
345
|
for (const c of hasil.catatan) {
|
|
@@ -340,7 +390,7 @@ async function utama() {
|
|
|
340
390
|
case "ask":
|
|
341
391
|
return perintahAsk(sisa);
|
|
342
392
|
case "chat":
|
|
343
|
-
return perintahChat();
|
|
393
|
+
return perintahChat(sisa);
|
|
344
394
|
case "do":
|
|
345
395
|
return perintahDo(sisa);
|
|
346
396
|
case "status":
|