@gilangranggaid/kilua-agent 1.1.1 → 1.1.3

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.
Files changed (3) hide show
  1. package/bin/kilua.js +79 -12
  2. package/bin/kilua.mjs +79 -12
  3. package/package.json +1 -1
package/bin/kilua.js CHANGED
@@ -9,6 +9,8 @@ import { execSync } from "node:child_process";
9
9
  const KILUA_DIR = path.join(os.homedir(), ".kilua");
10
10
  const SETTINGS_PATH = path.join(KILUA_DIR, "settings.json");
11
11
  const SKILLS_DIR = path.join(KILUA_DIR, "skills");
12
+ const MAX_STEPS = 40; // ponytail: naik dari 16; agent butuh banyak langkah untuk self-fix loop
13
+ const MAX_RETRIES = 4;
12
14
 
13
15
  function ensureKiluaDir() {
14
16
  if (!fs.existsSync(KILUA_DIR)) fs.mkdirSync(KILUA_DIR, { recursive: true });
@@ -88,6 +90,22 @@ const TOOLS_OPENAI = [
88
90
  parameters: { type: "object", properties: { path: { type: "string" } } }
89
91
  }
90
92
  },
93
+ {
94
+ type: "function",
95
+ function: {
96
+ name: "grep_search",
97
+ description: "Mencari pola teks (regex) di dalam file project. Lebih hemat token daripada membaca seluruh file. Hasil: path:baris:isi.",
98
+ parameters: {
99
+ type: "object",
100
+ properties: {
101
+ pattern: { type: "string", description: "Pola regex atau teks yang dicari." },
102
+ path: { type: "string", description: "Folder atau file awal. Default: folder aktif." },
103
+ glob: { type: "string", description: "Filter file, contoh: *.js atau **/*.ts" }
104
+ },
105
+ required: ["pattern"]
106
+ }
107
+ }
108
+ },
91
109
  {
92
110
  type: "function",
93
111
  function: {
@@ -129,8 +147,28 @@ function executeLocalTool(name, args) {
129
147
  const target = path.resolve(cwd, args.path || ".");
130
148
  return fs.readdirSync(target, { withFileTypes: true }).map(it => `${it.isDirectory() ? "[DIR]" : "[FILE]"} ${it.name}`).join("\n");
131
149
  }
150
+ if (name === "grep_search") {
151
+ const target = path.resolve(cwd, args.path || ".");
152
+ const pattern = args.pattern;
153
+ const glob = args.glob || "";
154
+ // ponytail: pakai findstr/Select-String bawaan OS, hindari dependency
155
+ const isWin = process.platform === "win32";
156
+ let cmd;
157
+ if (isWin) {
158
+ const p = target.replace(/\//g, "\\");
159
+ cmd = glob
160
+ ? `powershell -NoProfile -Command "Get-ChildItem -Path '${p}' -Recurse -File -Filter '${glob}' -ErrorAction SilentlyContinue | Select-String -Pattern '${pattern.replace(/'/g, "''")}' | Select-Object -First 200 | ForEach-Object { \\"$($_.Path):$($_.LineNumber):$($_.Line.Trim())\\" }"`
161
+ : `powershell -NoProfile -Command "Get-ChildItem -Path '${p}' -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.Length -lt 2MB -and $_.FullName -notmatch 'node_modules|\\\\.git\\\\' } | Select-String -Pattern '${pattern.replace(/'/g, "''")}' | Select-Object -First 200 | ForEach-Object { \\"$($_.Path):$($_.LineNumber):$($_.Line.Trim())\\" }"`;
162
+ } else {
163
+ cmd = glob
164
+ ? `grep -rn --include='${glob}' '${pattern.replace(/'/g, "'\\''")}' '${target}' | head -200`
165
+ : `grep -rn --exclude-dir=node_modules --exclude-dir=.git '${pattern.replace(/'/g, "'\\''")}' '${target}' | head -200`;
166
+ }
167
+ const out = execSync(cmd, { cwd, encoding: "utf8", timeout: 60000, stdio: ["pipe", "pipe", "pipe"] });
168
+ return out.trim() || "(tidak ada hasil)";
169
+ }
132
170
  if (name === "run_cmd") {
133
- const out = execSync(args.command, { cwd, encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"] });
171
+ const out = execSync(args.command, { cwd, encoding: "utf8", timeout: 180000, stdio: ["pipe", "pipe", "pipe"], maxBuffer: 10 * 1024 * 1024 });
134
172
  return out.trim() || "(Done with exit code 0)";
135
173
  }
136
174
  } catch (err) {
@@ -234,17 +272,46 @@ function buildSystemPrompt() {
234
272
  return `Kamu KILUA, AI Coding Agent otonom di terminal lokal. Folder aktif: ${process.cwd()}.
235
273
  KEMAMPUAN TOOLS:
236
274
  - read_file, write_file, edit_file, list_dir
275
+ - grep_search: mencari pola di seluruh file project (hemat token, pakai ini sebelum read_file untuk file besar)
237
276
  - run_cmd: menjalankan perintah terminal lokal (npm, npx, git, node, python, build, test, curl, install skill, dsb).
238
277
 
239
- ATURAN OTONOM KILUA (SELF-TESTING & AUTO-INSTALL SKILL):
240
- 1. Jika pengguna meminta membuat project/fitur/skrip, JANGAN langsung serahkan begitu saja. Kamu HARUS mencoba atau memvalidasinya sendiri terlebih dahulu menggunakan run_cmd (misal: jalankan skrip, test, build, atau cek sintaks).
241
- 2. Jika ada error saat kamu mencoba, perbaiki langsung menggunakan edit_file/write_file sampai berhasil.
242
- 3. Setelah berhasil dicoba dan dipastikan tidak error, barulah beri tahu pengguna hasil dan cara menjalankannya.
243
- 4. Jika pengguna di chat menyuruh kamu install skill (misal: "install skill vercel-labs/skills@find-skills" atau skill lain), jalankan run_cmd dengan perintah:
278
+ CARA KERJA (WAJIB DIIKUTI):
279
+ 1. Pahami dulu: list_dir + grep_search untuk menemukan file yang relevan. Jangan menebak isi file.
280
+ 2. Baca hanya file yang benar-benar perlu (read_file), bukan seluruh project.
281
+ 3. Tulis perubahan dengan edit_file (teks lama harus persis sama). Pakai write_file hanya untuk file baru.
282
+ 4. SETELAH mengubah kode, WAJIB verifikasi sendiri lewat run_cmd: jalankan file, test, atau build.
283
+ 5. Jika muncul error, perbaiki sendiri dengan edit_file lalu jalankan ulang. Ulangi sampai lolos.
284
+ 6. Baru setelah terbukti jalan tanpa error, laporkan hasil + cara menjalankannya ke pengguna.
285
+ 7. Jika pengguna menyuruh install skill di chat (misal: "install skill vercel-labs/skills@find-skills"), jalankan run_cmd:
244
286
  npx skills add <nama-skill> --cwd "${SKILLS_DIR}"
245
- Lalu beri tahu pengguna bahwa skill tersebut telah siap dipakai.${skillsInfo}
287
+ lalu beri tahu skill sudah siap dipakai.${skillsInfo}
288
+
289
+ Gaya: ringkas, langsung bertindak, jangan minta konfirmasi untuk langkah yang sudah jelas.`;
290
+ }
246
291
 
247
- Respon ringkas, padat, dan bertindak langsung.`;
292
+ // ponytail: retry untuk 429/5xx/network — provider gratis sering rate-limit
293
+ async function fetchWithRetry(url, init) {
294
+ let lastErr;
295
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
296
+ try {
297
+ const res = await fetch(url, init);
298
+ if (res.status === 429 || res.status === 503 || res.status >= 500) {
299
+ if (attempt === MAX_RETRIES) return res;
300
+ const wait = Math.min(30000, 1500 * Math.pow(2, attempt));
301
+ process.stdout.write(`\r\x1b[2K\x1b[33m[!] Server sibuk (${res.status}), ulang ${attempt + 1}/${MAX_RETRIES} dalam ${Math.round(wait / 1000)}s...\x1b[0m`);
302
+ await new Promise(r => setTimeout(r, wait));
303
+ continue;
304
+ }
305
+ return res;
306
+ } catch (e) {
307
+ lastErr = e;
308
+ if (attempt === MAX_RETRIES) throw e;
309
+ const wait = Math.min(20000, 1500 * Math.pow(2, attempt));
310
+ process.stdout.write(`\r\x1b[2K\x1b[33m[!] Koneksi gagal, ulang ${attempt + 1}/${MAX_RETRIES} dalam ${Math.round(wait / 1000)}s...\x1b[0m`);
311
+ await new Promise(r => setTimeout(r, wait));
312
+ }
313
+ }
314
+ if (lastErr) throw lastErr;
248
315
  }
249
316
 
250
317
  // Loop untuk Anthropic / 9router local (/v1/messages)
@@ -254,11 +321,11 @@ async function runAnthropicLoop(settings, convo) {
254
321
  let endpoint = settings.base_url.replace(/\/+$/, "");
255
322
  if (!endpoint.endsWith("/messages")) endpoint += "/messages";
256
323
 
257
- for (let step = 0; step < 16; step++) {
324
+ for (let step = 0; step < MAX_STEPS; step++) {
258
325
  process.stdout.write("\r\x1b[2K\x1b[35m[KILUA Berpikir...]\x1b[0m ");
259
326
  let res;
260
327
  try {
261
- res = await fetch(endpoint, {
328
+ res = await fetchWithRetry(endpoint, {
262
329
  method: "POST",
263
330
  headers: {
264
331
  "Content-Type": "application/json",
@@ -363,11 +430,11 @@ async function runOpenAiLoop(settings, convo) {
363
430
  endpoint += "/chat/completions";
364
431
  }
365
432
 
366
- for (let step = 0; step < 16; step++) {
433
+ for (let step = 0; step < MAX_STEPS; step++) {
367
434
  process.stdout.write("\r\x1b[2K\x1b[35m[KILUA Berpikir...]\x1b[0m ");
368
435
  let res;
369
436
  try {
370
- res = await fetch(endpoint, {
437
+ res = await fetchWithRetry(endpoint, {
371
438
  method: "POST",
372
439
  headers: {
373
440
  "Content-Type": "application/json",
package/bin/kilua.mjs CHANGED
@@ -9,6 +9,8 @@ import { execSync } from "node:child_process";
9
9
  const KILUA_DIR = path.join(os.homedir(), ".kilua");
10
10
  const SETTINGS_PATH = path.join(KILUA_DIR, "settings.json");
11
11
  const SKILLS_DIR = path.join(KILUA_DIR, "skills");
12
+ const MAX_STEPS = 40; // ponytail: naik dari 16; agent butuh banyak langkah untuk self-fix loop
13
+ const MAX_RETRIES = 4;
12
14
 
13
15
  function ensureKiluaDir() {
14
16
  if (!fs.existsSync(KILUA_DIR)) fs.mkdirSync(KILUA_DIR, { recursive: true });
@@ -88,6 +90,22 @@ const TOOLS_OPENAI = [
88
90
  parameters: { type: "object", properties: { path: { type: "string" } } }
89
91
  }
90
92
  },
93
+ {
94
+ type: "function",
95
+ function: {
96
+ name: "grep_search",
97
+ description: "Mencari pola teks (regex) di dalam file project. Lebih hemat token daripada membaca seluruh file. Hasil: path:baris:isi.",
98
+ parameters: {
99
+ type: "object",
100
+ properties: {
101
+ pattern: { type: "string", description: "Pola regex atau teks yang dicari." },
102
+ path: { type: "string", description: "Folder atau file awal. Default: folder aktif." },
103
+ glob: { type: "string", description: "Filter file, contoh: *.js atau **/*.ts" }
104
+ },
105
+ required: ["pattern"]
106
+ }
107
+ }
108
+ },
91
109
  {
92
110
  type: "function",
93
111
  function: {
@@ -129,8 +147,28 @@ function executeLocalTool(name, args) {
129
147
  const target = path.resolve(cwd, args.path || ".");
130
148
  return fs.readdirSync(target, { withFileTypes: true }).map(it => `${it.isDirectory() ? "[DIR]" : "[FILE]"} ${it.name}`).join("\n");
131
149
  }
150
+ if (name === "grep_search") {
151
+ const target = path.resolve(cwd, args.path || ".");
152
+ const pattern = args.pattern;
153
+ const glob = args.glob || "";
154
+ // ponytail: pakai findstr/Select-String bawaan OS, hindari dependency
155
+ const isWin = process.platform === "win32";
156
+ let cmd;
157
+ if (isWin) {
158
+ const p = target.replace(/\//g, "\\");
159
+ cmd = glob
160
+ ? `powershell -NoProfile -Command "Get-ChildItem -Path '${p}' -Recurse -File -Filter '${glob}' -ErrorAction SilentlyContinue | Select-String -Pattern '${pattern.replace(/'/g, "''")}' | Select-Object -First 200 | ForEach-Object { \\"$($_.Path):$($_.LineNumber):$($_.Line.Trim())\\" }"`
161
+ : `powershell -NoProfile -Command "Get-ChildItem -Path '${p}' -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.Length -lt 2MB -and $_.FullName -notmatch 'node_modules|\\\\.git\\\\' } | Select-String -Pattern '${pattern.replace(/'/g, "''")}' | Select-Object -First 200 | ForEach-Object { \\"$($_.Path):$($_.LineNumber):$($_.Line.Trim())\\" }"`;
162
+ } else {
163
+ cmd = glob
164
+ ? `grep -rn --include='${glob}' '${pattern.replace(/'/g, "'\\''")}' '${target}' | head -200`
165
+ : `grep -rn --exclude-dir=node_modules --exclude-dir=.git '${pattern.replace(/'/g, "'\\''")}' '${target}' | head -200`;
166
+ }
167
+ const out = execSync(cmd, { cwd, encoding: "utf8", timeout: 60000, stdio: ["pipe", "pipe", "pipe"] });
168
+ return out.trim() || "(tidak ada hasil)";
169
+ }
132
170
  if (name === "run_cmd") {
133
- const out = execSync(args.command, { cwd, encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"] });
171
+ const out = execSync(args.command, { cwd, encoding: "utf8", timeout: 180000, stdio: ["pipe", "pipe", "pipe"], maxBuffer: 10 * 1024 * 1024 });
134
172
  return out.trim() || "(Done with exit code 0)";
135
173
  }
136
174
  } catch (err) {
@@ -234,17 +272,46 @@ function buildSystemPrompt() {
234
272
  return `Kamu KILUA, AI Coding Agent otonom di terminal lokal. Folder aktif: ${process.cwd()}.
235
273
  KEMAMPUAN TOOLS:
236
274
  - read_file, write_file, edit_file, list_dir
275
+ - grep_search: mencari pola di seluruh file project (hemat token, pakai ini sebelum read_file untuk file besar)
237
276
  - run_cmd: menjalankan perintah terminal lokal (npm, npx, git, node, python, build, test, curl, install skill, dsb).
238
277
 
239
- ATURAN OTONOM KILUA (SELF-TESTING & AUTO-INSTALL SKILL):
240
- 1. Jika pengguna meminta membuat project/fitur/skrip, JANGAN langsung serahkan begitu saja. Kamu HARUS mencoba atau memvalidasinya sendiri terlebih dahulu menggunakan run_cmd (misal: jalankan skrip, test, build, atau cek sintaks).
241
- 2. Jika ada error saat kamu mencoba, perbaiki langsung menggunakan edit_file/write_file sampai berhasil.
242
- 3. Setelah berhasil dicoba dan dipastikan tidak error, barulah beri tahu pengguna hasil dan cara menjalankannya.
243
- 4. Jika pengguna di chat menyuruh kamu install skill (misal: "install skill vercel-labs/skills@find-skills" atau skill lain), jalankan run_cmd dengan perintah:
278
+ CARA KERJA (WAJIB DIIKUTI):
279
+ 1. Pahami dulu: list_dir + grep_search untuk menemukan file yang relevan. Jangan menebak isi file.
280
+ 2. Baca hanya file yang benar-benar perlu (read_file), bukan seluruh project.
281
+ 3. Tulis perubahan dengan edit_file (teks lama harus persis sama). Pakai write_file hanya untuk file baru.
282
+ 4. SETELAH mengubah kode, WAJIB verifikasi sendiri lewat run_cmd: jalankan file, test, atau build.
283
+ 5. Jika muncul error, perbaiki sendiri dengan edit_file lalu jalankan ulang. Ulangi sampai lolos.
284
+ 6. Baru setelah terbukti jalan tanpa error, laporkan hasil + cara menjalankannya ke pengguna.
285
+ 7. Jika pengguna menyuruh install skill di chat (misal: "install skill vercel-labs/skills@find-skills"), jalankan run_cmd:
244
286
  npx skills add <nama-skill> --cwd "${SKILLS_DIR}"
245
- Lalu beri tahu pengguna bahwa skill tersebut telah siap dipakai.${skillsInfo}
287
+ lalu beri tahu skill sudah siap dipakai.${skillsInfo}
288
+
289
+ Gaya: ringkas, langsung bertindak, jangan minta konfirmasi untuk langkah yang sudah jelas.`;
290
+ }
246
291
 
247
- Respon ringkas, padat, dan bertindak langsung.`;
292
+ // ponytail: retry untuk 429/5xx/network — provider gratis sering rate-limit
293
+ async function fetchWithRetry(url, init) {
294
+ let lastErr;
295
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
296
+ try {
297
+ const res = await fetch(url, init);
298
+ if (res.status === 429 || res.status === 503 || res.status >= 500) {
299
+ if (attempt === MAX_RETRIES) return res;
300
+ const wait = Math.min(30000, 1500 * Math.pow(2, attempt));
301
+ process.stdout.write(`\r\x1b[2K\x1b[33m[!] Server sibuk (${res.status}), ulang ${attempt + 1}/${MAX_RETRIES} dalam ${Math.round(wait / 1000)}s...\x1b[0m`);
302
+ await new Promise(r => setTimeout(r, wait));
303
+ continue;
304
+ }
305
+ return res;
306
+ } catch (e) {
307
+ lastErr = e;
308
+ if (attempt === MAX_RETRIES) throw e;
309
+ const wait = Math.min(20000, 1500 * Math.pow(2, attempt));
310
+ process.stdout.write(`\r\x1b[2K\x1b[33m[!] Koneksi gagal, ulang ${attempt + 1}/${MAX_RETRIES} dalam ${Math.round(wait / 1000)}s...\x1b[0m`);
311
+ await new Promise(r => setTimeout(r, wait));
312
+ }
313
+ }
314
+ if (lastErr) throw lastErr;
248
315
  }
249
316
 
250
317
  // Loop untuk Anthropic / 9router local (/v1/messages)
@@ -254,11 +321,11 @@ async function runAnthropicLoop(settings, convo) {
254
321
  let endpoint = settings.base_url.replace(/\/+$/, "");
255
322
  if (!endpoint.endsWith("/messages")) endpoint += "/messages";
256
323
 
257
- for (let step = 0; step < 16; step++) {
324
+ for (let step = 0; step < MAX_STEPS; step++) {
258
325
  process.stdout.write("\r\x1b[2K\x1b[35m[KILUA Berpikir...]\x1b[0m ");
259
326
  let res;
260
327
  try {
261
- res = await fetch(endpoint, {
328
+ res = await fetchWithRetry(endpoint, {
262
329
  method: "POST",
263
330
  headers: {
264
331
  "Content-Type": "application/json",
@@ -363,11 +430,11 @@ async function runOpenAiLoop(settings, convo) {
363
430
  endpoint += "/chat/completions";
364
431
  }
365
432
 
366
- for (let step = 0; step < 16; step++) {
433
+ for (let step = 0; step < MAX_STEPS; step++) {
367
434
  process.stdout.write("\r\x1b[2K\x1b[35m[KILUA Berpikir...]\x1b[0m ");
368
435
  let res;
369
436
  try {
370
- res = await fetch(endpoint, {
437
+ res = await fetchWithRetry(endpoint, {
371
438
  method: "POST",
372
439
  headers: {
373
440
  "Content-Type": "application/json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gilangranggaid/kilua-agent",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Autonomous AI Coding Agent for your terminal and VS Code",
5
5
  "type": "module",
6
6
  "main": "bin/kilua.js",