beast-agent 0.30.0 → 1.0.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/package.json +6 -3
- package/src/agent/engine.js +463 -21
- package/src/agent/llm.js +52 -3
- package/src/agent/obscura.js +26 -10
- package/src/agent/skills.js +2 -2
- package/src/agent/tools.js +519 -7
- package/src/main.js +28 -13
- package/src/renderer/i18n.js +7 -7
- package/src/renderer/index.html +5 -2
- package/src/renderer/renderer.js +154 -5
- package/src/renderer/style.css +45 -4
- package/.env.example +0 -8
- package/config.example.yaml +0 -39
- package/eng.traineddata +0 -0
- package/scripts/fix-electron.js +0 -126
- package/scripts/release.js +0 -121
- package/scripts/swap-electron.js +0 -40
- package/store/skills.json +0 -5
- package/tests/approval.test.js +0 -56
- package/tests/bg-jobs.test.js +0 -424
- package/tests/bots-name.test.js +0 -42
- package/tests/bus.test.js +0 -46
- package/tests/computeruse-context.test.js +0 -69
- package/tests/cron.test.js +0 -95
- package/tests/engine.test.js +0 -203
- package/tests/eval.test.js +0 -91
- package/tests/fallout.test.js +0 -188
- package/tests/llm.test.js +0 -205
- package/tests/memory.test.js +0 -33
- package/tests/memoryloop.test.js +0 -26
- package/tests/notegen.test.js +0 -63
- package/tests/obscura.test.js +0 -124
- package/tests/owner.test.js +0 -32
- package/tests/python.test.js +0 -76
- package/tests/research.test.js +0 -116
- package/tests/scenarios.json +0 -81
- package/tests/sessioncode.test.js +0 -50
- package/tests/setup.js +0 -9
- package/tests/tokens.test.js +0 -42
- package/tests/tools.test.js +0 -84
- package/tests/usage.test.js +0 -44
- package/tests/v11.test.js +0 -76
- package/tests/watchers.test.js +0 -206
- package/tests/watext.test.js +0 -53
- package/tests/whatsapp.test.js +0 -103
- package/tests/wherewasi.test.js +0 -40
package/tests/bots-name.test.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* Bot adı disiplini: İLK KARAKTER HARF ZORUNLU (ekleme + güncelleme) */
|
|
4
|
-
|
|
5
|
-
const test = require('node:test');
|
|
6
|
-
const assert = require('node:assert');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
/* BEAST_DATA modül yüklenmeden önce ayarlanmalı (REG cache'i tek dosyada) */
|
|
12
|
-
process.env.BEAST_DATA = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-bots-name-'));
|
|
13
|
-
const bots = require('../src/agent/bots');
|
|
14
|
-
|
|
15
|
-
test('bot ekleme: ad harf ile başlamalı', () => {
|
|
16
|
-
for (const bad of ['1Muhasebe', '9bot', '-abc', '_x', ' 3BoT']) {
|
|
17
|
-
const r = bots.add({ name: bad, prompt: '' });
|
|
18
|
-
assert.strictEqual(r.ok, false, bad + ' reddedilmeli');
|
|
19
|
-
assert.ok(/harf ile başlamalı/.test(r.error), r.error);
|
|
20
|
-
}
|
|
21
|
-
const okNum = bots.add({ name: 'Muhasebe', prompt: '' });
|
|
22
|
-
assert.ok(okNum.ok, 'harf ile başlayan ad kabul: ' + (okNum.error || ''));
|
|
23
|
-
const okTr = bots.add({ name: 'Çağrı', prompt: '' });
|
|
24
|
-
assert.ok(okTr.ok, 'Türkçe harf (Ç) kabul: ' + (okTr.error || ''));
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('bot güncelleme: geçersiz ad diğer alanlara dokunmadan reddedilir', () => {
|
|
28
|
-
const created = bots.add({ name: 'Depo', prompt: 'eski' });
|
|
29
|
-
assert.ok(created.ok);
|
|
30
|
-
const id = created.bot.id;
|
|
31
|
-
|
|
32
|
-
const bad = bots.update(id, { name: '3Depo', prompt: 'yeni' });
|
|
33
|
-
assert.strictEqual(bad.ok, false, 'geçersiz ad reddedildi');
|
|
34
|
-
const after = bots.get(id);
|
|
35
|
-
assert.strictEqual(after.name, 'Depo', 'eski ad korundu');
|
|
36
|
-
assert.strictEqual(after.prompt, 'eski', 'prompt değişmedi');
|
|
37
|
-
|
|
38
|
-
const good = bots.update(id, { name: 'Anbar2', prompt: 'yeni' });
|
|
39
|
-
assert.ok(good.ok, 'harf ile başlayan güncelleme kabul: ' + (good.error || ''));
|
|
40
|
-
assert.strictEqual(bots.get(id).prompt, 'yeni');
|
|
41
|
-
assert.strictEqual(bots.get(id).name, 'Anbar2');
|
|
42
|
-
});
|
package/tests/bus.test.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
|
|
7
|
-
/* bus saf parçaları: decide/compareOp/emitEvent (hooks.notify stub'lı) */
|
|
8
|
-
const bus = require('../src/agent/bus');
|
|
9
|
-
|
|
10
|
-
test('bus: sayısal filtre op\u2019ları', () => {
|
|
11
|
-
assert.ok(bus.compareOp('lt', 2500, 2600));
|
|
12
|
-
assert.ok(bus.compareOp('gte', 2600, 2600));
|
|
13
|
-
assert.ok(!bus.compareOp('gt', 2599, 2600));
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('bus: decide — filtre + cooldown mantığı', () => {
|
|
17
|
-
const sub = { type: 'price:tick', op: 'lte', value: 2600, cooldownMin: 10, lastNotifiedAt: 0 };
|
|
18
|
-
const now = Date.now();
|
|
19
|
-
// koşul dışı
|
|
20
|
-
assert.equal(bus.decide(sub, { type: 'price:tick', value: 2700 }, now).hit, false);
|
|
21
|
-
// yanlış tip
|
|
22
|
-
assert.equal(bus.decide(sub, { type: 'mail:new' }, now).hit, false);
|
|
23
|
-
// koşul içi ilk kez → tetik
|
|
24
|
-
const d1 = bus.decide(sub, { type: 'price:tick', value: 2550 }, now);
|
|
25
|
-
assert.ok(d1.hit && !d1.cooled);
|
|
26
|
-
// hemen ardından → cooldown'a takılır ama hit'tir
|
|
27
|
-
sub.lastNotifiedAt = now;
|
|
28
|
-
const d2 = bus.decide(sub, { type: 'price:tick', value: 2500 }, now + 60000);
|
|
29
|
-
assert.ok(d2.hit && d2.cooled);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('bus: addSub doğrulama + emitEvent dağıtımı', () => {
|
|
33
|
-
assert.equal(bus.addSub({ type: 'yok', sessionId: 's1' }).ok, false);
|
|
34
|
-
assert.equal(bus.addSub({ type: 'price:tick', sessionId: 's1' }).ok, true); // filtresiz genel abonelik
|
|
35
|
-
|
|
36
|
-
let notified = 0;
|
|
37
|
-
// notify hook'u start ile gelir; emitEvent hooks.notify'i kullanır
|
|
38
|
-
const before = bus.listSubs().length;
|
|
39
|
-
assert.ok(before >= 1);
|
|
40
|
-
// fiyat tick yolla — cooldown zaten 10dk varsayılan; ikinci çağrı cooled olmalı
|
|
41
|
-
const fired1 = bus.emitEvent('price:tick', { symbol: 'PAXGUSDT', price: 100 }, Date.now());
|
|
42
|
-
const fired2 = bus.emitEvent('price:tick', { symbol: 'PAXGUSDT', price: 101 }, Date.now() + 1000);
|
|
43
|
-
assert.ok(fired1 >= fired2); // en az ilk seferde tetiklenmiş olmalı
|
|
44
|
-
// temizle
|
|
45
|
-
for (const s of bus.listSubs()) bus.removeSub(s.id);
|
|
46
|
-
});
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const Engine = require('../src/agent/engine');
|
|
10
|
-
|
|
11
|
-
/* computeruse: modül surface + koordinat/girdi kırpma (network'süz) */
|
|
12
|
-
test('#4 computer use: op doğrulama ve sınırlar', async () => {
|
|
13
|
-
const cu = require('../src/agent/computeruse');
|
|
14
|
-
const r1 = await cu.act('yokboyleop', {});
|
|
15
|
-
assert.equal(r1.ok, false);
|
|
16
|
-
const r2 = await cu.act('type', { text: '' });
|
|
17
|
-
assert.equal(r2.ok, false);
|
|
18
|
-
const r3 = await cu.act('key', { combo: '' });
|
|
19
|
-
assert.equal(r3.ok, false);
|
|
20
|
-
const r4 = await cu.act('scroll', { dy: 0 });
|
|
21
|
-
assert.equal(r4.ok, false);
|
|
22
|
-
/* bilinmeyen op dışındaki gerçek aksiyonlar PowerShell ister; çağrılmaz */
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
/* #6 bağlam sıkıştırma: notlar öze dönüşünce eski mesajlar diskten de düşer */
|
|
26
|
-
test('#6 compaction: _compactToNotes dosyayı metaya+notlara+son pencereye indirger', () => {
|
|
27
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-cx-'));
|
|
28
|
-
const eng = new Engine({}, { sessionsDir: dir });
|
|
29
|
-
const s = eng.createSession();
|
|
30
|
-
/* createSession bir meta satırı yazdı; cache'i atla — ham dosyaya mesaj ekleyip
|
|
31
|
-
YENİ engine ile yüklüyoruz (cache trick yok) */
|
|
32
|
-
const file = path.join(dir, s.id + '.jsonl');
|
|
33
|
-
const lines = [JSON.stringify({ t: 'meta', id: s.id, code: 'ABC234', createdAt: new Date().toISOString() })];
|
|
34
|
-
for (let i = 0; i < 20; i++) {
|
|
35
|
-
lines.push(JSON.stringify({ t: 'msg', role: i % 2 ? 'assistant' : 'user', content: 'mesaj-' + i }));
|
|
36
|
-
}
|
|
37
|
-
fs.writeFileSync(file, lines.join('\n') + '\n');
|
|
38
|
-
|
|
39
|
-
const eng2 = new Engine({}, { sessionsDir: dir }); // taze cache — dosyadan okur
|
|
40
|
-
const sess = eng2._load(s.id);
|
|
41
|
-
assert.equal(sess.messages.length, 20);
|
|
42
|
-
sess.notes = 'eski önemli noktalar:\n- karar A\n- karar B';
|
|
43
|
-
sess.code = sess.code || 'ABC234';
|
|
44
|
-
eng2._compactToNotes(sess, 14);
|
|
45
|
-
|
|
46
|
-
// dosyayı yeniden oku
|
|
47
|
-
const raw = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
48
|
-
const metas = raw.filter((r) => r.t === 'meta');
|
|
49
|
-
const notesRows = raw.filter((r) => r.t === 'notes');
|
|
50
|
-
const msgRows = raw.filter((r) => r.t === 'msg');
|
|
51
|
-
const compacted = raw.filter((r) => r.t === 'compacted');
|
|
52
|
-
assert.equal(metas.length, 1);
|
|
53
|
-
assert.ok(metas[0].code, 'kod korunmalı');
|
|
54
|
-
assert.equal(notesRows.length, 1);
|
|
55
|
-
assert.match(notesRows[0].text, /karar A/);
|
|
56
|
-
assert.equal(notesRows[0].at, 0); // notesAt sıfırlandı
|
|
57
|
-
assert.ok(msgRows.length <= 6, `korunan pencere ≤6=KEEP_RECENT*2 (gerçek ${msgRows.length})`);
|
|
58
|
-
assert.ok(msgRows.every((m) => Number(m.content.split('-')[1]) >= 14), 'yalnız cut sonrası kaldı');
|
|
59
|
-
assert.equal(compacted.length, 1);
|
|
60
|
-
|
|
61
|
-
// yeniden yükleme tutarlı
|
|
62
|
-
const reloaded = new Engine({}, { sessionsDir: dir });
|
|
63
|
-
const again = reloaded._load(s.id);
|
|
64
|
-
assert.equal(again.code, 'ABC234');
|
|
65
|
-
assert.ok(again.messages.length <= 12);
|
|
66
|
-
assert.match(String(again.notes || ''), /karar A/);
|
|
67
|
-
|
|
68
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
69
|
-
});
|
package/tests/cron.test.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
const cron = require('../src/cron');
|
|
7
|
-
|
|
8
|
-
test('geçersiz cron ifadeleri reddedilir', () => {
|
|
9
|
-
assert.equal(cron.parseCron('* * * *'), null);
|
|
10
|
-
assert.equal(cron.parseCron('61 * * * *'), null);
|
|
11
|
-
assert.equal(cron.parseCron('* 25 * * *'), null);
|
|
12
|
-
assert.equal(cron.parseCron('a b c d e'), null);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('geçerli cron ifadeleri ayrışır', () => {
|
|
16
|
-
assert.ok(cron.parseCron('* * * * *'));
|
|
17
|
-
assert.ok(cron.parseCron('*/15 * * * *'));
|
|
18
|
-
assert.ok(cron.parseCron('0 9 * * 1-5'));
|
|
19
|
-
assert.ok(cron.parseCron('30 21 1,15 * *'));
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
/* Sabit tarih: 2026-08-25 Salı 10:00 */
|
|
23
|
-
function at(min, h, day, mon) {
|
|
24
|
-
return new Date(2026, mon - 1, day, h, min, 0);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
test('nextRunFrom sıradaki uyarın anını bulur', () => {
|
|
28
|
-
// Her saat başı: 10:00'dan sonraki = 11:00
|
|
29
|
-
const n = new Date(cron.nextRunFrom('0 * * * *', at(0, 10, 25, 8)));
|
|
30
|
-
assert.equal(n.getHours(), 11);
|
|
31
|
-
assert.equal(n.getMinutes(), 0);
|
|
32
|
-
|
|
33
|
-
// Günlük 09:00: salı 10:00'dan sonra → çarşamba 09:00
|
|
34
|
-
const d = new Date(cron.nextRunFrom('0 9 * * *', at(0, 10, 25, 8)));
|
|
35
|
-
assert.equal(d.getDate(), 26);
|
|
36
|
-
assert.equal(d.getHours(), 9);
|
|
37
|
-
|
|
38
|
-
// Hafta içi (Pzt-Cum): salıdan sonraki aynı gün 21:00
|
|
39
|
-
const w = new Date(cron.nextRunFrom('0 21 * * 1-5', at(30, 20, 25, 8)));
|
|
40
|
-
assert.equal(w.getDay(), 2); // salı
|
|
41
|
-
assert.equal(w.getHours(), 21);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('add: bozuk schedule reddedilir, düzgün olan nextRun alır', () => {
|
|
45
|
-
const bad = cron.add({ name: 'x', schedule: 'yok', prompt: 'p' });
|
|
46
|
-
assert.equal(bad.ok, false);
|
|
47
|
-
const good = cron.add({ name: 'deneme', schedule: '*/5 * * * *', prompt: 'saati söyle' });
|
|
48
|
-
assert.equal(good.ok, true);
|
|
49
|
-
assert.ok(good.job.nextRunAt > Date.now());
|
|
50
|
-
assert.equal(cron.list().some((j) => j.id === good.job.id), true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('toggle + remove çalışır', () => {
|
|
54
|
-
const r = cron.add({ name: 't2', schedule: '0 12 * * *', prompt: 'p' });
|
|
55
|
-
const id = r.job.id;
|
|
56
|
-
const t = cron.toggle(id);
|
|
57
|
-
assert.equal(t.job.enabled, false);
|
|
58
|
-
assert.equal(t.job.nextRunAt, null);
|
|
59
|
-
assert.equal(cron.remove(id).ok, true);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
/* ---------- reminderSchedule: tekrarlı hatırlatma ---------- */
|
|
63
|
-
|
|
64
|
-
test('reminderSchedule: presetler when\u0027den saat üretir', () => {
|
|
65
|
-
// 2026-08-26 Çarşamba 09:00
|
|
66
|
-
const w = '2026-08-26T09:00';
|
|
67
|
-
|
|
68
|
-
const daily = cron.reminderSchedule(w, 'daily');
|
|
69
|
-
assert.equal(daily.ok, true);
|
|
70
|
-
assert.equal(daily.schedule, '0 9 * * *');
|
|
71
|
-
|
|
72
|
-
const weekdays = cron.reminderSchedule(w, 'weekdays');
|
|
73
|
-
assert.equal(weekdays.schedule, '0 9 * * 1-5');
|
|
74
|
-
|
|
75
|
-
const weekly = cron.reminderSchedule(w, 'weekly');
|
|
76
|
-
assert.equal(weekly.schedule, '0 9 * * 3'); // çarşamba
|
|
77
|
-
|
|
78
|
-
const monthly = cron.reminderSchedule(w, 'monthly');
|
|
79
|
-
assert.equal(monthly.schedule, '0 9 26 * *');
|
|
80
|
-
|
|
81
|
-
// dakika korunmalı
|
|
82
|
-
const dm = cron.reminderSchedule('2026-08-26T09:30', 'daily');
|
|
83
|
-
assert.equal(dm.schedule, '30 9 * * *');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('reminderSchedule: doğrudan cron kabul, bozuk ve bilinmeyen reddedilir', () => {
|
|
87
|
-
const raw = cron.reminderSchedule(null, '*/30 * * * *');
|
|
88
|
-
assert.equal(raw.ok, true);
|
|
89
|
-
assert.equal(raw.schedule, '*/30 * * * *');
|
|
90
|
-
|
|
91
|
-
assert.equal(cron.reminderSchedule('2026-08-26T09:00', '').ok, false);
|
|
92
|
-
assert.equal(cron.reminderSchedule('2026-08-26T09:00', 'hergun').ok, false);
|
|
93
|
-
assert.equal(cron.reminderSchedule('zaman değil', 'daily').ok, false);
|
|
94
|
-
assert.equal(cron.reminderSchedule(null, '61 * * * *').ok, false);
|
|
95
|
-
});
|
package/tests/engine.test.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
|
|
10
|
-
const { Engine, sanitizeTodoItems } = require('../src/agent/engine');
|
|
11
|
-
|
|
12
|
-
function makeEngine(extra = {}) {
|
|
13
|
-
const sessionsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-sess-'));
|
|
14
|
-
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-ws-'));
|
|
15
|
-
return new Engine(
|
|
16
|
-
{},
|
|
17
|
-
{ sessionsDir, workspace, customProviders: [], emit: () => {}, ...extra }
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/* ---------- sanitizeTodoItems ---------- */
|
|
22
|
-
|
|
23
|
-
test('todo: boş/geçersiz girdiler elenir', () => {
|
|
24
|
-
const out = sanitizeTodoItems([
|
|
25
|
-
{ title: 'Planla', status: 'active' },
|
|
26
|
-
{ title: '' },
|
|
27
|
-
null,
|
|
28
|
-
'Sadece string başlık',
|
|
29
|
-
{ title: 'Planla', status: 'done' }, // duplicate title
|
|
30
|
-
{ title: 'Bitir', status: 'saçma' },
|
|
31
|
-
]);
|
|
32
|
-
assert.deepEqual(out, [
|
|
33
|
-
{ title: 'Planla', status: 'active' },
|
|
34
|
-
{ title: 'Sadece string başlık', status: 'pending' },
|
|
35
|
-
{ title: 'Bitir', status: 'pending' },
|
|
36
|
-
]);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('todo: 20 ile sınırlı', () => {
|
|
40
|
-
const items = Array.from({ length: 50 }, (_, i) => ({ title: 'görev ' + i }));
|
|
41
|
-
assert.equal(sanitizeTodoItems(items).length, 20);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
/* ---------- payload builder ---------- */
|
|
45
|
-
|
|
46
|
-
test('tool_call/tool mesajları birlikte tutulur', () => {
|
|
47
|
-
const eng = makeEngine();
|
|
48
|
-
const msgs = [
|
|
49
|
-
{ role: 'user', content: 'merhaba' },
|
|
50
|
-
{ role: 'assistant', content: '', tool_calls: [{ id: '1', type: 'function', function: { name: 'list_dir', arguments: '{}' } }] },
|
|
51
|
-
{ role: 'tool', tool_call_id: '1', name: 'list_dir', content: '{"ok":true}' },
|
|
52
|
-
{ role: 'assistant', content: 'sonuç şu' },
|
|
53
|
-
{ role: 'user', content: 'tekrar' },
|
|
54
|
-
];
|
|
55
|
-
const payload = eng._buildPayload('sys', msgs);
|
|
56
|
-
assert.equal(payload[0].role, 'system');
|
|
57
|
-
const idxAssistant = payload.findIndex((m) => m.role === 'assistant');
|
|
58
|
-
assert.ok(idxAssistant >= 0);
|
|
59
|
-
assert.equal(payload[idxAssistant + 1].role, 'tool');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('token bütçesine uyar: eski mesajlar düşer, son mesaj kalır', () => {
|
|
63
|
-
const eng = makeEngine({ historyTokenBudget: 120 });
|
|
64
|
-
const big = 'kelime '.repeat(400); // ~700 token
|
|
65
|
-
const msgs = [];
|
|
66
|
-
for (let i = 0; i < 10; i++) msgs.push({ role: 'user', content: big + ' #' + i });
|
|
67
|
-
const payload = eng._buildPayload('sys', msgs);
|
|
68
|
-
// system + en az son kullanıcı mesajı
|
|
69
|
-
assert.equal(payload[0].role, 'system');
|
|
70
|
-
const last = payload[payload.length - 1];
|
|
71
|
-
assert.ok(last.role === 'user' && last.content.includes('#9'));
|
|
72
|
-
assert.ok(eng._payloadTokens(payload.slice(1)) <= 120 + estSlack(last));
|
|
73
|
-
function estSlack(m) {
|
|
74
|
-
return Math.ceil((m.content || '').length / 4) * 3; // tek mesaj taşabilir
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('browser_open/browser_read kancaları çağrılır', async () => {
|
|
79
|
-
const calls = [];
|
|
80
|
-
const eng = makeEngine({
|
|
81
|
-
browser: {
|
|
82
|
-
openUrl: async (u) => { calls.push(['open', u]); return { ok: true, url: u, title: 'T' }; },
|
|
83
|
-
readText: async () => { calls.push(['read']); return { ok: true, content: 'sayfa metni' }; },
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
const r1 = JSON.parse(await eng._execTool('browser_open', { url: 'https://example.com' }, null, 's1'));
|
|
87
|
-
const r2 = JSON.parse(await eng._execTool('browser_read', {}, null, 's1'));
|
|
88
|
-
assert.ok(r1.ok && r1.title === 'T');
|
|
89
|
-
assert.ok(r2.ok && r2.content.includes('metni'));
|
|
90
|
-
assert.deepEqual(calls, [['open', 'https://example.com'], ['read']]);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test('browser kancası yoksa zarif hata döner', async () => {
|
|
94
|
-
const eng = makeEngine();
|
|
95
|
-
const r = JSON.parse(await eng._execTool('browser_open', { url: 'https://x.com' }, null, 's1'));
|
|
96
|
-
assert.equal(r.ok, false);
|
|
97
|
-
assert.match(r.error, /tarayıcı/);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('eski görseller metne indirilir, son mesajın görseli korunur', () => {
|
|
101
|
-
const eng = makeEngine();
|
|
102
|
-
const img = (u) => [{ type: 'text', text: 'bak' }, { type: 'image_url', image_url: { url: u } }];
|
|
103
|
-
const msgs = [
|
|
104
|
-
{ role: 'user', content: img('data:image/png;base64,AAAA') },
|
|
105
|
-
{ role: 'assistant', content: 'ok' },
|
|
106
|
-
{ role: 'user', content: img('data:image/png;base64,BBBB') },
|
|
107
|
-
];
|
|
108
|
-
const payload = eng._buildPayload('sys', msgs);
|
|
109
|
-
const firstUser = payload.find((m) => m.role === 'user');
|
|
110
|
-
assert.equal(typeof firstUser.content, 'string');
|
|
111
|
-
assert.ok(firstUser.content.includes('bağlam dışı'));
|
|
112
|
-
const lastUser = payload[payload.length - 1];
|
|
113
|
-
assert.ok(Array.isArray(lastUser.content));
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
/* ---------- oturum notlar� (ge�ici haf�za) ---------- */
|
|
117
|
-
|
|
118
|
-
test('oturum notlar�: dosyadan y�klenir, mesaj listesine kar��maz', () => {
|
|
119
|
-
const eng = makeEngine();
|
|
120
|
-
const id = eng.createSession().id;
|
|
121
|
-
for (let i = 0; i < 5; i++) {
|
|
122
|
-
fs.appendFileSync(eng._file(id), JSON.stringify({ t: 'msg', role: 'user', content: 'mesaj ' + i }) + '\n');
|
|
123
|
-
}
|
|
124
|
-
fs.appendFileSync(eng._file(id), JSON.stringify({ t: 'notes', text: 'hedef: v7 �al�smas�', at: 3 }) + '\n');
|
|
125
|
-
eng.cache.delete(id); // diskten taze y�kle
|
|
126
|
-
const s = eng._load(id);
|
|
127
|
-
assert.equal(s.notes, 'hedef: v7 �al�smas�');
|
|
128
|
-
assert.equal(s.notesAt, 3);
|
|
129
|
-
assert.equal(s.messages.length, 5);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('oturum notlar� varken payload s�k�la��r', () => {
|
|
133
|
-
const eng = makeEngine({ historyTokenBudget: 100000 });
|
|
134
|
-
const msgs = [];
|
|
135
|
-
for (let i = 0; i < 30; i++) msgs.push({ role: 'user', content: 'kisa mesaj #' + i });
|
|
136
|
-
const withoutNotes = eng._buildPayload('sys', msgs, null);
|
|
137
|
-
const withNotes = eng._buildPayload('sys', msgs, '�zet: test');
|
|
138
|
-
assert.ok(withNotes.length < withoutNotes.length);
|
|
139
|
-
assert.equal(withNotes[withNotes.length - 1].content, 'kisa mesaj #29'); // son mesaj hep kal�r
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
/* ---------- observe(): sessiz bağlam enjeksiyonu ---------- */
|
|
143
|
-
|
|
144
|
-
test('observe: mesaj geçmişe düşer ama run tetiklenmez', async () => {
|
|
145
|
-
const events = [];
|
|
146
|
-
const eng = makeEngine({ emit: (ev) => events.push(ev) });
|
|
147
|
-
const id = eng.createSession().id;
|
|
148
|
-
const ok = eng.observe(id, '[BAĞLAM — grup] ali: selam');
|
|
149
|
-
assert.ok(ok);
|
|
150
|
-
const s = eng._load(id);
|
|
151
|
-
assert.equal(s.messages.length, 1);
|
|
152
|
-
assert.equal(s.messages[0].role, 'user');
|
|
153
|
-
assert.ok(s.messages[0].content.startsWith('[BAĞLAM'));
|
|
154
|
-
// _run tetiklenmedi: LLM hatası/done olayı yok
|
|
155
|
-
await new Promise((r) => setTimeout(r, 60));
|
|
156
|
-
assert.ok(!events.some((e) => e.type === 'error' || e.type === 'done'));
|
|
157
|
-
// observe olayı yayıldı
|
|
158
|
-
assert.ok(events.some((e) => e.type === 'observe' && e.sessionId === id));
|
|
159
|
-
// boş metin false
|
|
160
|
-
assert.equal(eng.observe(id, ' '), false);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test('observe: ardışık bağlam mesajları tek mesajda birleşir', () => {
|
|
164
|
-
const eng = makeEngine();
|
|
165
|
-
const id = eng.createSession().id;
|
|
166
|
-
eng.observe(id, '[BAĞLAM — grup] ali: selam');
|
|
167
|
-
eng.observe(id, '[BAĞLAM — grup] ayşe: nasılsın');
|
|
168
|
-
const s = eng._load(id);
|
|
169
|
-
assert.equal(s.messages.length, 1);
|
|
170
|
-
assert.ok(s.messages[0].content.includes('ali: selam'));
|
|
171
|
-
assert.ok(s.messages[0].content.includes('ayşe: nasılsın'));
|
|
172
|
-
// dosyaya da tek satır yazıldı
|
|
173
|
-
const lines = fs.readFileSync(eng._file(id), 'utf8').split('\n').filter((l) => l.trim());
|
|
174
|
-
const msgLines = lines.filter((l) => { try { return JSON.parse(l).t === 'msg'; } catch { return false; } });
|
|
175
|
-
assert.equal(msgLines.length, 1);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test('observe: normal user mesajıyla birleşmez', () => {
|
|
179
|
-
const eng = makeEngine();
|
|
180
|
-
const id = eng.createSession().id;
|
|
181
|
-
const s = eng._load(id);
|
|
182
|
-
s.messages.push({ role: 'user', content: 'gerçek soru' });
|
|
183
|
-
fs.appendFileSync(eng._file(id), JSON.stringify({ t: 'msg', role: 'user', content: 'gerçek soru' }) + '\n');
|
|
184
|
-
eng.observe(id, '[BAĞLAM — grup] ali: selam');
|
|
185
|
-
assert.equal(s.messages.length, 2);
|
|
186
|
-
assert.equal(s.messages[0].content, 'gerçek soru');
|
|
187
|
-
assert.ok(s.messages[1].content.startsWith('[BAĞLAM'));
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('send: mention mesajı bekleyen bağlam mesajıyla birleşir ve run başlar', async () => {
|
|
191
|
-
const events = [];
|
|
192
|
-
const eng = makeEngine({ emit: (ev) => events.push(ev) });
|
|
193
|
-
const id = eng.createSession().id;
|
|
194
|
-
eng.observe(id, '[BAĞLAM — grup] ali: selam\nayşe: nasılsın');
|
|
195
|
-
eng.send(id, { text: '@beast bana bugün hava nasıl?' });
|
|
196
|
-
const s = eng._load(id);
|
|
197
|
-
assert.equal(s.messages.length, 1);
|
|
198
|
-
assert.ok(s.messages[0].content.includes('ayşe: nasılsın'));
|
|
199
|
-
assert.ok(s.messages[0].content.includes('@beast bana bugün hava nasıl?'));
|
|
200
|
-
// run başladı → sağlayıcı yoksa hata olayı düşer (tetiklenme kanıtı)
|
|
201
|
-
await new Promise((r) => setTimeout(r, 120));
|
|
202
|
-
assert.ok(events.some((e) => e.type === 'error' || e.type === 'done'));
|
|
203
|
-
});
|
package/tests/eval.test.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* #4 Eval düzeni: scenarios.json'daki senaryoları koşar.
|
|
4
|
-
Yeni modül eklerken buraya senaryo düşür — regresyon kalkanı olur. */
|
|
5
|
-
|
|
6
|
-
require('./setup');
|
|
7
|
-
const test = require('node:test');
|
|
8
|
-
const assert = require('node:assert');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
|
|
12
|
-
const SCEN = JSON.parse(fs.readFileSync(path.join(__dirname, 'scenarios.json'), 'utf8'));
|
|
13
|
-
const cron = require('../src/cron');
|
|
14
|
-
const Engine = require('../src/agent/engine');
|
|
15
|
-
const memory = require('../src/agent/memory');
|
|
16
|
-
const kb = require('../src/agent/kb');
|
|
17
|
-
const bus = require('../src/agent/bus');
|
|
18
|
-
const usageMod = require('../src/agent/usage');
|
|
19
|
-
|
|
20
|
-
function scenario(id) {
|
|
21
|
-
return SCEN.scenarios.find((s) => s.id === id);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
test('eval: cron-invalid-rejected', () => {
|
|
25
|
-
const s = scenario('cron-invalid-rejected');
|
|
26
|
-
assert.equal(cron[s.call.fn](...s.call.args), null);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('eval: reminder-daily-schedule', () => {
|
|
30
|
-
const s = scenario('reminder-daily-schedule');
|
|
31
|
-
const r = cron[s.call.fn](...s.call.args);
|
|
32
|
-
assert.equal(r.schedule, s.expectEquals.schedule);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('eval: kb-search-citation', () => {
|
|
36
|
-
const s = scenario('kb-search-citation');
|
|
37
|
-
kb.add('FxPro GOLD seansları', 'GOLD sembolünde seans 01:00-23:00 arası açıktır', { source: 'test' });
|
|
38
|
-
const rows = kb.search('gold seans');
|
|
39
|
-
assert.ok(rows.length >= 1);
|
|
40
|
-
for (const r of rows) {
|
|
41
|
-
assert.ok(r.citation && r.citation.includes('['), 'citation formatı');
|
|
42
|
-
assert.ok(r.title && r.snippet !== undefined);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('eval: memory-hygiene-dedup', () => {
|
|
47
|
-
const s = scenario('memory-hygiene-dedup');
|
|
48
|
-
assert.equal(s.module, 'memory');
|
|
49
|
-
memory.append('Kullanıcının adı Batuhan');
|
|
50
|
-
memory.append('kullanıcının adı batuhan'); // dup (fold edilmiş)
|
|
51
|
-
const r = memory.hygiene({ maxAgeDays: 0 }); // yalnız dedup, yaş silmesi yok
|
|
52
|
-
assert.ok(r.ok);
|
|
53
|
-
const kept = memory.entries().filter((l) => /batuhan/i.test(l));
|
|
54
|
-
assert.ok(kept.length <= 1, 'duplike tekilleşmeli');
|
|
55
|
-
// temizlik
|
|
56
|
-
for (const l of memory.entries()) if (/batuhan/i.test(l)) memory.removeRule('__none__'); // no-op koruması
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('eval: system-prompt-has-local-time', () => {
|
|
60
|
-
const os = require('os');
|
|
61
|
-
const eng = new Engine({}, { sessionsDir: fs.mkdtempSync(path.join(os.tmpdir(), 'beast-eval-')) });
|
|
62
|
-
const sys = eng.buildSystem('x');
|
|
63
|
-
assert.match(sys, new RegExp(scenario('system-prompt-has-local-time').regex));
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('eval: system-prompt-rules-injected', () => {
|
|
67
|
-
const os = require('os');
|
|
68
|
-
const eng = new Engine({}, { sessionsDir: fs.mkdtempSync(path.join(os.tmpdir(), 'beast-eval2-')) });
|
|
69
|
-
memory.addRule('Eval test kuralı ABCXYZ');
|
|
70
|
-
const sys = eng.buildSystem('x');
|
|
71
|
-
assert.match(sys, /KURALLAR/);
|
|
72
|
-
assert.match(sys, /Eval test kuralı ABCXYZ/);
|
|
73
|
-
memory.removeRule('Eval test kuralı ABCXYZ');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('eval: usage-cost-math kısa doğrulama', () => {
|
|
77
|
-
usageMod.reset();
|
|
78
|
-
usageMod.record({ providerId: 'e', model: 'm', promptTokens: 1000000, completionTokens: 0, costIn: 1, costOut: 0 });
|
|
79
|
-
assert.ok(Math.abs(usageMod.report().today.total.cost - 1) < 1e-6);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('eval: bus-price-filter', () => {
|
|
83
|
-
for (const s of bus.listSubs()) bus.removeSub(s.id);
|
|
84
|
-
const r = bus.addSub({ type: 'price:tick', sessionId: 'evalsestest', op: 'gte', value: 100, cooldownMin: 0 });
|
|
85
|
-
assert.ok(r.ok);
|
|
86
|
-
const fired = bus.emitEvent('price:tick', { value: 150 }, Date.now());
|
|
87
|
-
assert.equal(fired, 1); // sadece bu abonelik
|
|
88
|
-
const notFired = bus.emitEvent('price:tick', { value: 50 }, Date.now());
|
|
89
|
-
assert.equal(notFired, 0);
|
|
90
|
-
for (const s of bus.listSubs()) bus.removeSub(s.id);
|
|
91
|
-
});
|