@gilangranggaid/kilua-agent 1.1.2 → 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.
- package/bin/kilua.js +79 -12
- package/bin/kilua.mjs +79 -12
- 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:
|
|
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
|
-
|
|
240
|
-
1.
|
|
241
|
-
2.
|
|
242
|
-
3.
|
|
243
|
-
4.
|
|
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
|
-
|
|
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
|
-
|
|
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 <
|
|
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
|
|
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 <
|
|
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
|
|
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:
|
|
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
|
-
|
|
240
|
-
1.
|
|
241
|
-
2.
|
|
242
|
-
3.
|
|
243
|
-
4.
|
|
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
|
-
|
|
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
|
-
|
|
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 <
|
|
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
|
|
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 <
|
|
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
|
|
437
|
+
res = await fetchWithRetry(endpoint, {
|
|
371
438
|
method: "POST",
|
|
372
439
|
headers: {
|
|
373
440
|
"Content-Type": "application/json",
|