@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 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>]...` | 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` | Percakapan interaktif yang juga bisa bertindak di tengah obrolan; riwayat dipangkas otomatis dan sadar-tool (maks ±4.000 token, hanya pada batas giliran utuh); `/keluar` untuk berhenti |
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 konfirmasi(deskripsi))) {
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 konfirmasi(deskripsi))) {
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 konfirmasi(deskripsi))) {
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.jejak?.(`langkah ${langkah}: ${p.function.name}${argRingkas ? ` (${argRingkas})` : ""}`);
356
- const hasilTool = await eksekusiTool(p, { cwd: opsi.cwd, konfirmasi: opsi.konfirmasi });
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.3.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
- // Pangkas SADAR-TOOL: hanya pada batas giliran utuh, tidak pernah
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 tugas = args.join(" ").trim();
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
- console.log("Tool baca berjalan langsung; tulis/edit file dan jalankan perintah meminta persetujuanmu satu per satu.\n");
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":
package/package.json CHANGED
@@ -30,7 +30,7 @@
30
30
  "test": "npm run build && node --test test/*.test.mjs"
31
31
  },
32
32
  "type": "module",
33
- "version": "0.3.0",
33
+ "version": "0.4.0",
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  }