beast-agent 0.30.0 → 1.0.1
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 +8 -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/src/agent/scripts/__pycache__/websearch.cpython-312.pyc +0 -0
- 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/tools.test.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
const tools = require('../src/agent/tools');
|
|
7
|
-
|
|
8
|
-
/* ---------- SSRF guard ---------- */
|
|
9
|
-
|
|
10
|
-
test('public URL kabul edilir', () => {
|
|
11
|
-
const u = tools.assertPublicHttpUrl('https://example.com/x?y=1');
|
|
12
|
-
assert.ok(u.startsWith('https://example.com'));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('localhost ve özel ağlar engellenir', () => {
|
|
16
|
-
const bad = [
|
|
17
|
-
'http://localhost/a',
|
|
18
|
-
'http://127.0.0.1/',
|
|
19
|
-
'http://10.0.0.5/',
|
|
20
|
-
'http://192.168.1.1/',
|
|
21
|
-
'http://172.16.0.1/',
|
|
22
|
-
'http://172.31.255.255/',
|
|
23
|
-
'http://169.254.169.254/meta',
|
|
24
|
-
'http://pc.local/',
|
|
25
|
-
'file:///C:/Windows',
|
|
26
|
-
'ftp://example.com',
|
|
27
|
-
'http://[::1]/',
|
|
28
|
-
'http://user:pass@example.com/',
|
|
29
|
-
];
|
|
30
|
-
for (const u of bad) assert.throws(() => tools.assertPublicHttpUrl(u));
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('geçersiz URL atar', () => {
|
|
34
|
-
assert.throws(() => tools.assertPublicHttpUrl('not a url'));
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
/* ---------- DDG parser ---------- */
|
|
38
|
-
|
|
39
|
-
test('parseDdgResults sonuçları ayrıştırır', () => {
|
|
40
|
-
const html = `
|
|
41
|
-
<div class="result results_links">
|
|
42
|
-
<a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.com%2Fa&rut=abc">Örnek <b>Başlık</b></a>
|
|
43
|
-
<a class="result__snippet" href="#">Açıklama & özet</a>
|
|
44
|
-
</div>
|
|
45
|
-
<div class="result">
|
|
46
|
-
<a class="result__a" href="https://direct.com/b">Direkt</a>
|
|
47
|
-
<a class="result__snippet">snpt</a>
|
|
48
|
-
</div>`;
|
|
49
|
-
const out = tools.parseDdgResults(html);
|
|
50
|
-
assert.equal(out.length, 2);
|
|
51
|
-
assert.equal(out[0].url, 'https://example.com/a');
|
|
52
|
-
assert.equal(out[0].title, 'Örnek Başlık');
|
|
53
|
-
assert.ok(out[0].snippet.includes('özet'));
|
|
54
|
-
assert.equal(out[1].url, 'https://direct.com/b');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('parseDdgResults boş HTML\u0027de boş döner', () => {
|
|
58
|
-
assert.deepEqual(tools.parseDdgResults('<html></html>'), []);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
/* ---------- htmlToText ---------- */
|
|
62
|
-
|
|
63
|
-
test('htmlToText script/style temizler', () => {
|
|
64
|
-
const t = tools.htmlToText('<head><style>p{color:red}</style><script>evil()</script></head><body><p>Merhaba</p> dünya</body>');
|
|
65
|
-
assert.ok(t.includes('Merhaba'));
|
|
66
|
-
assert.ok(!t.includes('evil'));
|
|
67
|
-
assert.ok(!t.includes('color:red'));
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
/* ---------- dosya araçları ---------- */
|
|
71
|
-
|
|
72
|
-
const fs = require('fs');
|
|
73
|
-
const os = require('os');
|
|
74
|
-
const path = require('path');
|
|
75
|
-
|
|
76
|
-
test('exec write/read/list turu', async () => {
|
|
77
|
-
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-tools-'));
|
|
78
|
-
const w = JSON.parse(await tools.exec('write_file', { path: 'alt/dosya.txt', content: 'içerik' }, { cwd }));
|
|
79
|
-
assert.ok(w.ok);
|
|
80
|
-
const r = JSON.parse(await tools.exec('read_file', { path: 'alt/dosya.txt' }, { cwd }));
|
|
81
|
-
assert.equal(r.content, 'içerik');
|
|
82
|
-
const l = JSON.parse(await tools.exec('list_dir', {}, { cwd }));
|
|
83
|
-
assert.ok(l.entries.split('\n')[0].includes('alt'));
|
|
84
|
-
});
|
package/tests/usage.test.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
const usageMod = require('../src/agent/usage');
|
|
7
|
-
|
|
8
|
-
test('usage: kayıt + bugün/ay raporu + maliyet hesabı', () => {
|
|
9
|
-
usageMod.reset();
|
|
10
|
-
|
|
11
|
-
/* maliyetsiz çağrı */
|
|
12
|
-
usageMod.record({ providerId: 'p1', model: 'm1', promptTokens: 1000, completionTokens: 500 });
|
|
13
|
-
/* fiyatlı çağrı: $2/M giriş, $8/M çıkış → (2000*2 + 1000*8)/1e6 = $0.012 */
|
|
14
|
-
usageMod.record({
|
|
15
|
-
providerId: 'p1',
|
|
16
|
-
model: 'm2',
|
|
17
|
-
promptTokens: 2000,
|
|
18
|
-
completionTokens: 1000,
|
|
19
|
-
costIn: 2,
|
|
20
|
-
costOut: 8,
|
|
21
|
-
});
|
|
22
|
-
/* sıfır/eksik token — sayılmaz */
|
|
23
|
-
usageMod.record({});
|
|
24
|
-
usageMod.record({ providerId: 'pX', model: 'mY' });
|
|
25
|
-
|
|
26
|
-
const rep = usageMod.report();
|
|
27
|
-
assert.equal(rep.today.total.calls, 2);
|
|
28
|
-
assert.equal(rep.today.total.pin, 3000);
|
|
29
|
-
assert.equal(rep.today.total.pout, 1500);
|
|
30
|
-
assert.ok(Math.abs(rep.today.total.cost - 0.012) < 1e-9);
|
|
31
|
-
|
|
32
|
-
const byModel = Object.fromEntries(rep.today.models.map((m) => [m.model, m]));
|
|
33
|
-
assert.equal(byModel['p1::m1'].calls, 1);
|
|
34
|
-
assert.equal(byModel['p1::m1'].cost, 0);
|
|
35
|
-
assert.equal(byModel['p1::m2'].calls, 1);
|
|
36
|
-
|
|
37
|
-
/* ay raporu aynı günü kapsar */
|
|
38
|
-
assert.equal(rep.month.total.calls, 2);
|
|
39
|
-
|
|
40
|
-
/* reset gerçekten temizler */
|
|
41
|
-
usageMod.reset();
|
|
42
|
-
const rep2 = usageMod.report();
|
|
43
|
-
assert.equal(rep2.today.total.calls, 0);
|
|
44
|
-
});
|
package/tests/v11.test.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
const Engine = require('../src/agent/engine');
|
|
7
|
-
|
|
8
|
-
/* parseReflectionJson: düz / fence'li / gömülü JSON + bozuk girdi */
|
|
9
|
-
test('reflection JSON ayrıştırıcı', () => {
|
|
10
|
-
const good = Engine.parseReflectionJson(
|
|
11
|
-
'{"create": true, "name": "rapor-al", "description": "d", "body": "# Rapor\\n- adım"}'
|
|
12
|
-
);
|
|
13
|
-
assert.equal(good.create, true);
|
|
14
|
-
assert.equal(good.name, 'rapor-al');
|
|
15
|
-
|
|
16
|
-
const fenced = Engine.parseReflectionJson(
|
|
17
|
-
'İşte öneri:\n```json\n{"create": false}\n```\nbitti'
|
|
18
|
-
);
|
|
19
|
-
assert.equal(fenced.create, false);
|
|
20
|
-
|
|
21
|
-
const embedded = Engine.parseReflectionJson(
|
|
22
|
-
'önce metin {"create": true, "name": "x", "description": "y", "body": "z"} sonra metin'
|
|
23
|
-
);
|
|
24
|
-
assert.equal(embedded.name, 'x');
|
|
25
|
-
|
|
26
|
-
assert.equal(Engine.parseReflectionJson('JSON yok burada'), null);
|
|
27
|
-
assert.equal(Engine.parseReflectionJson('{bozuk'), null);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
/* skills taslak yaşam döngüsü */
|
|
31
|
-
test('skills draft: add → list → accept → scan\u2019de görünür', () => {
|
|
32
|
-
const skills = require('../src/agent/skills');
|
|
33
|
-
const r = skills.addDraft({
|
|
34
|
-
name: 'Test Yetenek',
|
|
35
|
-
description: 'deneme taslağı',
|
|
36
|
-
body: '# Test Yetenek\n- adım bir',
|
|
37
|
-
});
|
|
38
|
-
assert.ok(r.ok);
|
|
39
|
-
|
|
40
|
-
const drafts = skills.listDrafts();
|
|
41
|
-
const d = drafts.find((x) => x.id === 'test-yetenek');
|
|
42
|
-
assert.ok(d, 'taslak listede olmalı');
|
|
43
|
-
|
|
44
|
-
// scan taslağı GÖRMEMELİ
|
|
45
|
-
assert.ok(!skills.scan().some((s) => s.name === 'Test Yetenek'));
|
|
46
|
-
|
|
47
|
-
// kabul → kurulur, taslak silinir
|
|
48
|
-
const acc = skills.acceptDraft('test-yetenek');
|
|
49
|
-
assert.ok(acc.ok);
|
|
50
|
-
assert.ok(skills.scan().some((s) => s.name === 'Test Yetenek'));
|
|
51
|
-
assert.ok(!skills.listDrafts().some((x) => x.id === 'test-yelenek' || x.id === 'test-yetenek'));
|
|
52
|
-
|
|
53
|
-
// temizlik
|
|
54
|
-
const folder = require('path').join(skills.dir(), 'test-yetenek');
|
|
55
|
-
require('fs').rmSync(folder, { recursive: true, force: true });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
/* kural hattı (#3) */
|
|
59
|
-
test('rules: ekle/liste/çıkar + skill\u2019e madde', () => {
|
|
60
|
-
const memory = require('../src/agent/memory');
|
|
61
|
-
const before = memory.listRules();
|
|
62
|
-
const r = memory.addRule('Terminal çıktısını asla kısaltma');
|
|
63
|
-
assert.ok(r.ok);
|
|
64
|
-
const after = memory.listRules();
|
|
65
|
-
assert.equal(after.length, before.length + 1);
|
|
66
|
-
assert.ok(after.some((x) => x.includes('Terminal çıktısını')));
|
|
67
|
-
|
|
68
|
-
// dup
|
|
69
|
-
const dup = memory.addRule('Terminal çıktısını asla kısaltma');
|
|
70
|
-
assert.ok(dup.duplicate);
|
|
71
|
-
|
|
72
|
-
// kaldır
|
|
73
|
-
const rm = memory.removeRule('Terminal çıktısını asla kısaltma');
|
|
74
|
-
assert.ok(rm.ok);
|
|
75
|
-
assert.ok(!memory.listRules().some((x) => x.includes('Terminal çıktısını')));
|
|
76
|
-
});
|
package/tests/watchers.test.js
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
const watchers = require('../src/agent/watchers');
|
|
7
|
-
|
|
8
|
-
const T0 = new Date('2026-08-26T12:00:00Z').getTime();
|
|
9
|
-
|
|
10
|
-
test('normalize: web izleyicisi minimal alanlarla kurulur', () => {
|
|
11
|
-
const r = watchers.normalize({ name: 'BTC', kind: 'web', url: 'https://api.x.com/p', value: 100000 });
|
|
12
|
-
assert.ok(!r.error);
|
|
13
|
-
assert.equal(r.watcher.kind, 'web');
|
|
14
|
-
assert.equal(r.watcher.op, 'lte'); // varsayılan
|
|
15
|
-
assert.equal(r.watcher.everyMin, 15);
|
|
16
|
-
assert.equal(r.watcher.enabled, true);
|
|
17
|
-
assert.equal(r.watcher.armed, true);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('normalize: battery için url gerekmez, web için zorunlu', () => {
|
|
21
|
-
const b = watchers.normalize({ name: 'Pil', kind: 'battery', value: 20 });
|
|
22
|
-
assert.ok(!b.error);
|
|
23
|
-
const w = watchers.normalize({ name: 'X', kind: 'web', value: 1 });
|
|
24
|
-
assert.ok(w.error);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('normalize: geçersiz kind / regex / eksik eşik reddedilir', () => {
|
|
28
|
-
assert.ok(watchers.normalize({ name: 'x', kind: 'uzay' }).error);
|
|
29
|
-
assert.ok(watchers.normalize({ name: 'x', kind: 'web', url: 'https://a.b', re: '[' }).error);
|
|
30
|
-
assert.ok(watchers.normalize({ name: 'x', kind: 'web', url: 'https://a.b', op: 'lt' }).error); // value yok
|
|
31
|
-
assert.ok(watchers.normalize({ kind: 'battery', value: 1 }).error); // isim yok
|
|
32
|
-
assert.ok(watchers.normalize({ name: 'x', kind: 'web', url: 'ftp://a.b' }).error);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('normalize: changed opsiyonu value olmadan çalışır, aralık kelepçelenir', () => {
|
|
36
|
-
const r = watchers.normalize({ name: 'sayfa', kind: 'web', url: 'https://a.b', op: 'changed', everyMin: 1, cooldownMin: 99999 });
|
|
37
|
-
assert.ok(!r.error);
|
|
38
|
-
assert.equal(r.watcher.everyMin, 1); // #22: 1 dakika artık geçerli
|
|
39
|
-
assert.equal(r.watcher.cooldownMin, 10080);
|
|
40
|
-
/* saniye aralığı: everySec esas alınır, everyMin uyumluluk için türetilir */
|
|
41
|
-
const s = watchers.normalize({ name: 'sn', kind: 'web', url: 'https://a.b', op: 'changed', everySec: 30 });
|
|
42
|
-
assert.ok(!s.error);
|
|
43
|
-
assert.equal(s.watcher.everySec, 30);
|
|
44
|
-
assert.equal(s.watcher.everyMin, 1);
|
|
45
|
-
const s2 = watchers.normalize({ name: 'sn2', kind: 'web', url: 'https://a.b', op: 'changed', everySec: 3 });
|
|
46
|
-
assert.equal(s2.watcher.everySec, 10); // min 10 sn
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('compare: sayısal zorlama ve değişmez karşılaştırma', () => {
|
|
50
|
-
assert.ok(watchers.compare('lte', '19.9', 20));
|
|
51
|
-
assert.ok(watchers.compare('gt', 30, 29));
|
|
52
|
-
assert.ok(watchers.compare('eq', 100, '100'));
|
|
53
|
-
assert.ok(watchers.compare('neq', 5, 6));
|
|
54
|
-
assert.ok(!watchers.compare('lt', 'abc', 10)); // sayıya dönmez
|
|
55
|
-
assert.ok(watchers.compare('changed', 'yeni', 'eski'));
|
|
56
|
-
assert.ok(!watchers.compare('changed', 'aynı', 'aynı'));
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('applyCheck: kenar tetiklemeli — bir kez ateşlenir, normale dönünce yeniden kurulur', () => {
|
|
60
|
-
const w = { op: 'lte', value: 20, armed: true, lastTriggeredAt: null };
|
|
61
|
-
const a = watchers.applyCheck(w, 18, T0);
|
|
62
|
-
assert.ok(a.triggered);
|
|
63
|
-
assert.equal(a.patch.armed, false);
|
|
64
|
-
// koşul hâlâ doğru ama armed=false → tekrar ateşlemez
|
|
65
|
-
const b = watchers.applyCheck({ ...w, ...a.patch }, 19, T0 + 1);
|
|
66
|
-
assert.ok(!b.triggered);
|
|
67
|
-
// normale döndü → armed=true
|
|
68
|
-
const c = watchers.applyCheck({ ...w, ...a.patch }, 55, T0 + 2);
|
|
69
|
-
assert.ok(!c.triggered);
|
|
70
|
-
assert.equal(c.patch.armed, true);
|
|
71
|
-
// tekrar eşik altına indi → yine ateşler
|
|
72
|
-
const d = watchers.applyCheck({ ...w, ...c.patch }, 10, T0 + 3);
|
|
73
|
-
assert.ok(d.triggered);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('isDue: hiç kontrol edilmemişse vakti geldi, yeni kontrol edildiyse gelmedi', () => {
|
|
77
|
-
const fresh = { enabled: true, everyMin: 15, lastCheckAt: null };
|
|
78
|
-
assert.ok(watchers.isDue(fresh, T0));
|
|
79
|
-
const recent = { enabled: true, everyMin: 15, lastCheckAt: new Date(T0 - 5 * 60000).toISOString() };
|
|
80
|
-
assert.ok(!watchers.isDue(recent, T0));
|
|
81
|
-
const old = { enabled: true, everyMin: 15, lastCheckAt: new Date(T0 - 16 * 60000).toISOString() };
|
|
82
|
-
assert.ok(watchers.isDue(old, T0));
|
|
83
|
-
assert.ok(!watchers.isDue({ ...fresh, enabled: false }, T0));
|
|
84
|
-
/* #22 saniye aralığı */
|
|
85
|
-
const sn = { enabled: true, everySec: 30, lastCheckAt: new Date(T0 - 20000).toISOString() };
|
|
86
|
-
assert.ok(!watchers.isDue(sn, T0));
|
|
87
|
-
assert.ok(watchers.isDue({ ...sn, lastCheckAt: new Date(T0 - 31000).toISOString() }, T0));
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('cooldownActive: süre dolmadan aktif', () => {
|
|
91
|
-
const w = { cooldownMin: 60, lastTriggeredAt: new Date(T0 - 30 * 60000).toISOString() };
|
|
92
|
-
assert.ok(watchers.cooldownActive(w, T0));
|
|
93
|
-
const expired = { cooldownMin: 60, lastTriggeredAt: new Date(T0 - 61 * 60000).toISOString() };
|
|
94
|
-
assert.ok(!watchers.cooldownActive(expired, T0));
|
|
95
|
-
assert.ok(!watchers.cooldownActive({ cooldownMin: 60, lastTriggeredAt: null }, T0));
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
/* ---------- arka plan tick ---------- */
|
|
99
|
-
|
|
100
|
-
const { extractValue } = watchers;
|
|
101
|
-
|
|
102
|
-
test('extractValue: JSON path ve regex yolu', () => {
|
|
103
|
-
const w1 = { path: 'data.0.price.usd' };
|
|
104
|
-
assert.equal(extractValue('{"data":[{"price":{"usd":2377.5}}]}', w1), 2377.5);
|
|
105
|
-
assert.equal(extractValue('{"data":[]}', { path: 'data.0.price' }), null);
|
|
106
|
-
|
|
107
|
-
const w2 = { re: '<span id="p">([\\d.]+)</span>' };
|
|
108
|
-
assert.equal(extractValue('x<span id="p">99.9</span>y', w2), '99.9');
|
|
109
|
-
|
|
110
|
-
assert.throws(() => extractValue('{"a":1}', { kind: 'web' }), /path/);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test('tickOnce: kenar tetikleme + hata disiplini + changed semantiği', async () => {
|
|
114
|
-
// depoyu temizle
|
|
115
|
-
for (const w of watchers.list()) watchers.remove(w.id);
|
|
116
|
-
|
|
117
|
-
let fakeValue = 100;
|
|
118
|
-
let throws = false;
|
|
119
|
-
|
|
120
|
-
const r = watchers.add({
|
|
121
|
-
name: 'fiyat alarımı',
|
|
122
|
-
kind: 'web',
|
|
123
|
-
url: 'https://api.example.com/x',
|
|
124
|
-
path: 'price',
|
|
125
|
-
op: 'lte',
|
|
126
|
-
value: 90,
|
|
127
|
-
everyMin: 15,
|
|
128
|
-
cooldownMin: 0, // akışı gerçek zamanla bağımsız kılmak için kapalı
|
|
129
|
-
});
|
|
130
|
-
assert.ok(r.ok);
|
|
131
|
-
|
|
132
|
-
const deps = {
|
|
133
|
-
check: async () => {
|
|
134
|
-
if (throws) throw new Error('ağ koptu');
|
|
135
|
-
return fakeValue;
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
const rewDue = () =>
|
|
139
|
-
watchers.patch(r.watcher.id, { lastCheckAt: new Date(Date.now() - 16 * 60000).toISOString() });
|
|
140
|
-
|
|
141
|
-
// 1) eşik üstü → tetik yok
|
|
142
|
-
assert.equal((await watchers.tickOnce(deps)).length, 0);
|
|
143
|
-
assert.equal(watchers.list()[0].lastValue, 100);
|
|
144
|
-
|
|
145
|
-
// 2) henüz due değil (az önce kontrol edildi)
|
|
146
|
-
assert.equal((await watchers.tickOnce(deps)).length, 0);
|
|
147
|
-
|
|
148
|
-
// 3) eşiğe iner → bir kez tetik
|
|
149
|
-
fakeValue = 85;
|
|
150
|
-
rewDue();
|
|
151
|
-
const hit = await watchers.tickOnce(deps);
|
|
152
|
-
assert.equal(hit.length, 1);
|
|
153
|
-
assert.equal(hit[0].value, 85);
|
|
154
|
-
|
|
155
|
-
// 4) armed=false — koşul doğruyken tekrar tetiklemez
|
|
156
|
-
rewDue();
|
|
157
|
-
assert.equal((await watchers.tickOnce(deps)).length, 0);
|
|
158
|
-
|
|
159
|
-
// 5) normale döner (re-arm) ama hata modunda lastError yazılır
|
|
160
|
-
fakeValue = 120;
|
|
161
|
-
throws = true;
|
|
162
|
-
rewDue();
|
|
163
|
-
assert.equal((await watchers.tickOnce(deps)).length, 0);
|
|
164
|
-
assert.match(watchers.list()[0].lastError, /ağ koptu/);
|
|
165
|
-
|
|
166
|
-
// 6) hata düzelir → lastError temizlenir
|
|
167
|
-
throws = false;
|
|
168
|
-
rewDue();
|
|
169
|
-
assert.equal((await watchers.tickOnce(deps)).length, 0);
|
|
170
|
-
assert.equal(watchers.list()[0].lastError, '');
|
|
171
|
-
|
|
172
|
-
/* --- changed: sayfa değişikliği izleme --- */
|
|
173
|
-
const r2 = watchers.add({
|
|
174
|
-
name: 'sayfa değişimi',
|
|
175
|
-
kind: 'web',
|
|
176
|
-
url: 'https://a.b/c',
|
|
177
|
-
op: 'changed',
|
|
178
|
-
everyMin: 2,
|
|
179
|
-
cooldownMin: 0,
|
|
180
|
-
});
|
|
181
|
-
assert.ok(r2.ok, JSON.stringify(r2));
|
|
182
|
-
let html = '';
|
|
183
|
-
const deps2 = { check: async () => html };
|
|
184
|
-
|
|
185
|
-
// kuruluştaki referans boş — ilk kontrolde içerik görünür → tetik
|
|
186
|
-
html = '<h1>v1</h1>';
|
|
187
|
-
let evs = await watchers.tickOnce(deps2);
|
|
188
|
-
assert.equal(evs.length, 1);
|
|
189
|
-
|
|
190
|
-
// içerik sabitken tekrar tetiklenmez
|
|
191
|
-
await watchers.patch(r2.watcher.id, { lastCheckAt: new Date(Date.now() - 5 * 60000).toISOString() });
|
|
192
|
-
assert.equal((await watchers.tickOnce(deps2)).length, 0);
|
|
193
|
-
|
|
194
|
-
// içeriği eski haline getir (referans değerine dön) → re-arm
|
|
195
|
-
html = '';
|
|
196
|
-
await watchers.patch(r2.watcher.id, { lastCheckAt: new Date(Date.now() - 5 * 60000).toISOString() });
|
|
197
|
-
await watchers.tickOnce(deps2);
|
|
198
|
-
|
|
199
|
-
// tekrar değişir → yeniden tetik
|
|
200
|
-
html = '<h1>v2</h1>';
|
|
201
|
-
await watchers.patch(r2.watcher.id, { lastCheckAt: new Date(Date.now() - 5 * 60000).toISOString() });
|
|
202
|
-
evs = await watchers.tickOnce(deps2);
|
|
203
|
-
assert.equal(evs.length, 1);
|
|
204
|
-
|
|
205
|
-
for (const id of watchers.list().map((w) => w.id)) watchers.remove(id);
|
|
206
|
-
});
|
package/tests/watext.test.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
const { waCleanText, waToolLine } = require('../src/agent/watext');
|
|
7
|
-
|
|
8
|
-
/* #16: TÜM markdown işaretleri temizlenir — WhatsApp'ta bold/başlık yok */
|
|
9
|
-
test('markdown işaretleri tamamen temizlenir (#16)', () => {
|
|
10
|
-
assert.equal(waCleanText('**selam** dünya'), 'selam dünya');
|
|
11
|
-
assert.equal(waCleanText('*vurgu* bitti'), 'vurgu bitti');
|
|
12
|
-
assert.equal(waCleanText('***üç***'), 'üç');
|
|
13
|
-
assert.equal(waCleanText('## Başlık\nmetin'), 'Başlık\nmetin');
|
|
14
|
-
assert.ok(!waCleanText('a *b* c #d').includes('*'));
|
|
15
|
-
assert.ok(!waCleanText('a *b* c #d').includes('#'));
|
|
16
|
-
assert.ok(waCleanText('fiyat #kolon başlığı').length > 0); // kareli metin bozulmadan gelir, işaret gider
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('başlık kareleri ve alıntı işaretleri gider', () => {
|
|
20
|
-
assert.equal(waCleanText('> alıntı satırı'), 'alıntı satırı');
|
|
21
|
-
assert.match(waToolLine('run_command', { command: 'npm test' }), /Terminal: npm test/);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('liste tireleri madde imine döner', () => {
|
|
25
|
-
assert.equal(waCleanText('- bir\n- iki'), '\u2022 bir\n\u2022 iki');
|
|
26
|
-
assert.equal(waCleanText('* elma\n+ armut'), '\u2022 elma\n\u2022 armut');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('snake_case alt çizgisi korunur, _italik_ temizlenir', () => {
|
|
30
|
-
assert.equal(waCleanText('file_name değişmedi'), 'file_name değişmedi');
|
|
31
|
-
assert.equal(waCleanText('bu _önemli_ bir konu'), 'bu önemli bir konu');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('linkler düz metne döner', () => {
|
|
35
|
-
assert.equal(waCleanText('[site](https://x.com) bak'), 'site (https://x.com) bak');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('kod bloğu fence gider, içerik kalır', () => {
|
|
39
|
-
const out = waCleanText('```js\nconsole.log(1)\n```');
|
|
40
|
-
assert.equal(out, 'console.log(1)');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('yatay çizgiler ve fazla boşluklar silinir', () => {
|
|
44
|
-
assert.equal(waCleanText('üst\n---\nalt'), 'üst\nalt');
|
|
45
|
-
assert.equal(waCleanText('a\n\n\n\nb'), 'a\n\nb');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('araç satırları düz metin ve kırpılır', () => {
|
|
49
|
-
assert.equal(waToolLine('read_file', { path: 'C:\\a.txt' }), '\u203A Dosya oku: C:\\a.txt');
|
|
50
|
-
assert.equal(waToolLine('web_search', { query: 'btc fiyat' }), '\u203A Web arama: btc fiyat');
|
|
51
|
-
assert.match(waToolLine('bilinmeyen_arac', {}), /\u203A bilinmeyen_arac/);
|
|
52
|
-
assert.ok(waToolLine('run_command', { command: 'x'.repeat(500) }).length < 180);
|
|
53
|
-
});
|
package/tests/whatsapp.test.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./setup');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert');
|
|
6
|
-
const { WhatsAppBridge, statusLabel } = require('../src/agent/whatsapp');
|
|
7
|
-
|
|
8
|
-
function makeBridge() {
|
|
9
|
-
const events = [];
|
|
10
|
-
const wa = new WhatsAppBridge({ authDir: '/tmp/x', emit: (ev) => events.push(ev) });
|
|
11
|
-
return { wa, events };
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
test('statusLabel bilinen durumları etiketler', () => {
|
|
15
|
-
assert.equal(statusLabel(2), 'gönderildi ✓');
|
|
16
|
-
assert.equal(statusLabel(3), 'teslim ✓✓');
|
|
17
|
-
assert.equal(statusLabel(4), 'okundu ✓✓');
|
|
18
|
-
assert.equal(statusLabel(0), 'hata');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('statusLabel bilinmeyen durum', () => {
|
|
22
|
-
assert.match(statusLabel(99), /bilinmeyen\(99\)/);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('_trackOutgoing send eventi yayar ve takibe alır', () => {
|
|
26
|
-
const { wa, events } = makeBridge();
|
|
27
|
-
wa._trackOutgoing('90555@s.whatsapp.net', 'merhaba dunya', { key: { id: 'M1' } });
|
|
28
|
-
assert.equal(events.length, 1);
|
|
29
|
-
assert.equal(events[0].type, 'send');
|
|
30
|
-
assert.equal(events[0].id, 'M1');
|
|
31
|
-
assert.ok(wa._tracked.has('M1'));
|
|
32
|
-
assert.equal(wa._tracked.get('M1').preview, 'merhaba dunya');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('_trackOutgoing key yoksa sessizce atlar', () => {
|
|
36
|
-
const { wa, events } = makeBridge();
|
|
37
|
-
wa._trackOutgoing('90555@s.whatsapp.net', 'x', null);
|
|
38
|
-
assert.equal(events.length, 0);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('tick: durum değişince event, aynı durumda tekrar yok', () => {
|
|
42
|
-
const { wa, events } = makeBridge();
|
|
43
|
-
wa._tracked.set('A', { jid: '90555@s.whatsapp.net', preview: 'selam', ts: 1, status: 1, receiptDetail: '' });
|
|
44
|
-
wa._handleStatusUpdates([{ key: { id: 'A' }, update: { status: 3 } }]);
|
|
45
|
-
assert.equal(events.length, 1);
|
|
46
|
-
assert.equal(events[0].type, 'tick');
|
|
47
|
-
assert.equal(events[0].label, 'teslim ✓✓');
|
|
48
|
-
events.length = 0;
|
|
49
|
-
wa._handleStatusUpdates([{ key: { id: 'A' }, update: { status: 3 } }]);
|
|
50
|
-
assert.equal(events.length, 0);
|
|
51
|
-
// okundu'ya yükselir
|
|
52
|
-
wa._handleStatusUpdates([{ key: { id: 'A' }, update: { status: 4 } }]);
|
|
53
|
-
assert.equal(events.length, 1);
|
|
54
|
-
assert.equal(events[0].label, 'okundu ✓✓');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('tick: izlenmeyen mesajlar yok sayılır', () => {
|
|
58
|
-
const { wa, events } = makeBridge();
|
|
59
|
-
wa._handleStatusUpdates([{ key: { id: 'BILINMEYEN' }, update: { status: 4 } }]);
|
|
60
|
-
assert.equal(events.length, 0);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('receipt: okundu zamanı detay olarak gelir, tekrarı bastırılır', () => {
|
|
64
|
-
const { wa, events } = makeBridge();
|
|
65
|
-
wa._tracked.set('C', { jid: '90555@s.whatsapp.net', preview: 's', ts: 1, status: 3, receiptDetail: '' });
|
|
66
|
-
const readAt = 1700000000000;
|
|
67
|
-
wa._handleReceipts([{ key: { id: 'C' }, receipt: { readAt } }]);
|
|
68
|
-
assert.equal(events.length, 1);
|
|
69
|
-
assert.equal(events[0].type, 'receipt');
|
|
70
|
-
assert.match(events[0].detail, /^okundu=/);
|
|
71
|
-
events.length = 0;
|
|
72
|
-
wa._handleReceipts([{ key: { id: 'C' }, receipt: { readAt } }]);
|
|
73
|
-
assert.equal(events.length, 0);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('takip haritası CAP ile sınırlı kalır', () => {
|
|
77
|
-
const { wa } = makeBridge();
|
|
78
|
-
for (let i = 0; i < 600; i++) wa._trackOutgoing(`j${i}@s.whatsapp.net`, `m${i}`, { key: { id: `ID${i}` } });
|
|
79
|
-
assert.ok(wa._tracked.size <= 500);
|
|
80
|
-
assert.ok(!wa._tracked.has('ID0'));
|
|
81
|
-
assert.ok(wa._tracked.has('ID599'));
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('setWatchJids sock yokken güvenli', async () => {
|
|
85
|
-
const { wa } = makeBridge();
|
|
86
|
-
wa.setWatchJids(['90555@s.whatsapp.net']);
|
|
87
|
-
assert.equal(wa._watchJids.size, 1);
|
|
88
|
-
await wa._subscribePresence(); // patlamamalı
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('presence: composing etiketlenir, bilinmeyen durum yok sayılır', () => {
|
|
92
|
-
const { wa, events } = makeBridge();
|
|
93
|
-
wa._handlePresence({
|
|
94
|
-
id: '90555@s.whatsapp.net',
|
|
95
|
-
presences: { '90555:0@s.whatsapp.net': { lastKnownPresence: 'composing' } },
|
|
96
|
-
});
|
|
97
|
-
assert.equal(events.length, 1);
|
|
98
|
-
assert.equal(events[0].type, 'presence');
|
|
99
|
-
assert.equal(events[0].label, 'yazıyor');
|
|
100
|
-
events.length = 0;
|
|
101
|
-
wa._handlePresence({ id: 'x@s.whatsapp.net', presences: { p: { lastKnownPresence: 'uzaylı' } } });
|
|
102
|
-
assert.equal(events.length, 0);
|
|
103
|
-
});
|
package/tests/wherewasi.test.js
DELETED
|
@@ -1,40 +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
|
-
test('#5 nerede kaldım: son oturum + yarım todo döner', async () => {
|
|
12
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-wwi-'));
|
|
13
|
-
const eng = new Engine({}, { sessionsDir: dir });
|
|
14
|
-
|
|
15
|
-
// iki oturum: eskisi bugün çalışsın, yenisi en son kalan olsun
|
|
16
|
-
const old1 = eng.createSession();
|
|
17
|
-
const act = eng.createSession();
|
|
18
|
-
|
|
19
|
-
eng.send(act.id, { text: 'rapor hazırla ve maili kontrol et' });
|
|
20
|
-
await new Promise((r) => setTimeout(r, 80)); // _run başlasın bitmesin önemli değil
|
|
21
|
-
eng.interrupt(act.id);
|
|
22
|
-
await new Promise((r) => setTimeout(r, 60));
|
|
23
|
-
|
|
24
|
-
// todo yaz (doğrudan araç yolu yerine state enjeksiyonu)
|
|
25
|
-
eng._execTool('todo_write', { items: [
|
|
26
|
-
{ title: 'raporu toparla', status: 'done' },
|
|
27
|
-
{ title: 'mail taslağı hazırla', status: 'active' },
|
|
28
|
-
{ title: 'tabloyu güncelle', status: 'pending' },
|
|
29
|
-
] }, null, act.id);
|
|
30
|
-
|
|
31
|
-
const w = eng.lastWhereWasI();
|
|
32
|
-
assert.ok(w, 'özet gelmeli');
|
|
33
|
-
assert.equal(w.sessionId, act.id); // en yeni oturum
|
|
34
|
-
assert.ok(w.code && w.code.length === 6);
|
|
35
|
-
assert.ok(w.pendingTodos.length >= 2, 'yarım todolar listelenmeli');
|
|
36
|
-
assert.equal(w.doneCount, 1);
|
|
37
|
-
assert.ok(w.lastUser.includes('rapor'));
|
|
38
|
-
|
|
39
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
40
|
-
});
|