beast-agent 0.15.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/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/assets/app.ico +0 -0
- package/assets/tray.png +0 -0
- package/bin/beast-agent.js +23 -0
- package/config.example.yaml +26 -0
- package/eng.traineddata +0 -0
- package/package.json +101 -0
- package/scripts/swap-electron.js +40 -0
- package/src/agent/bus.js +384 -0
- package/src/agent/computeruse.js +200 -0
- package/src/agent/config.js +284 -0
- package/src/agent/engine.js +3118 -0
- package/src/agent/kb.js +123 -0
- package/src/agent/llm.js +219 -0
- package/src/agent/logger.js +90 -0
- package/src/agent/memory.js +383 -0
- package/src/agent/pdf.js +20 -0
- package/src/agent/scripts/__pycache__/websearch.cpython-312.pyc +0 -0
- package/src/agent/scripts/news.py +113 -0
- package/src/agent/scripts/websearch.py +225 -0
- package/src/agent/skills.js +522 -0
- package/src/agent/tokens.js +39 -0
- package/src/agent/tools.js +911 -0
- package/src/agent/usage.js +125 -0
- package/src/agent/watchers.js +312 -0
- package/src/agent/watext.js +75 -0
- package/src/agent/whatsapp.js +494 -0
- package/src/cron.js +245 -0
- package/src/main.js +3622 -0
- package/src/preload.js +122 -0
- package/src/renderer/browserPreload.js +73 -0
- package/src/renderer/i18n.js +757 -0
- package/src/renderer/index.html +227 -0
- package/src/renderer/renderer.js +3250 -0
- package/src/renderer/style.css +1559 -0
- package/tests/approval.test.js +56 -0
- package/tests/bg-jobs.test.js +315 -0
- package/tests/bus.test.js +46 -0
- package/tests/computeruse-context.test.js +69 -0
- package/tests/cron.test.js +95 -0
- package/tests/engine.test.js +140 -0
- package/tests/eval.test.js +91 -0
- package/tests/fallout.test.js +188 -0
- package/tests/llm.test.js +56 -0
- package/tests/memory.test.js +33 -0
- package/tests/memoryloop.test.js +26 -0
- package/tests/notegen.test.js +63 -0
- package/tests/owner.test.js +32 -0
- package/tests/python.test.js +75 -0
- package/tests/scenarios.json +81 -0
- package/tests/sessioncode.test.js +50 -0
- package/tests/setup.js +9 -0
- package/tests/tokens.test.js +42 -0
- package/tests/tools.test.js +84 -0
- package/tests/usage.test.js +44 -0
- package/tests/v11.test.js +76 -0
- package/tests/watchers.test.js +206 -0
- package/tests/watext.test.js +53 -0
- package/tests/whatsapp.test.js +103 -0
- package/tests/wherewasi.test.js +40 -0
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
function mkEngine(approvals) {
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
return new Engine(
|
|
12
|
+
{},
|
|
13
|
+
{
|
|
14
|
+
sessionsDir: path.join(os.tmpdir(), 'beast-appr-test-' + Date.now()),
|
|
15
|
+
approvals,
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test('onay kapısı: onay gelmezse riskli araç çalışmaz, onay gelirse çalışır', async () => {
|
|
21
|
+
let asked = null;
|
|
22
|
+
const eng = mkEngine({
|
|
23
|
+
request: async (q) => {
|
|
24
|
+
asked = q;
|
|
25
|
+
return false; // reddet
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// GATE: run_command dıştan görünür şekilde kapıda durmalı
|
|
30
|
+
assert.ok(Engine.RISKY_TOOLS.has('run_command'));
|
|
31
|
+
assert.ok(Engine.RISKY_TOOLS.has('write_file'));
|
|
32
|
+
assert.ok(Engine.RISKY_TOOLS.has('email_send'));
|
|
33
|
+
assert.ok(!Engine.RISKY_TOOLS.has('web_search')); // güvenli araç kapıya takılmaz
|
|
34
|
+
|
|
35
|
+
const out = await eng._execTool('run_command', { command: 'echo hi' }, null, 's1');
|
|
36
|
+
const obj = JSON.parse(out);
|
|
37
|
+
assert.equal(obj.ok, false);
|
|
38
|
+
assert.match(obj.error, /ONAYLAMADI/);
|
|
39
|
+
assert.equal(asked.tool, 'run_command');
|
|
40
|
+
assert.equal(asked.sessionId, 's1');
|
|
41
|
+
|
|
42
|
+
// izin verilirse araç gerçekten çalışır (echo)
|
|
43
|
+
const eng2 = mkEngine({ request: async () => true });
|
|
44
|
+
const out2 = await eng2._execTool('run_command', { command: 'echo ok' }, null, 's2');
|
|
45
|
+
const obj2 = JSON.parse(out2);
|
|
46
|
+
if (obj2.ok === false && /yok/.test(String(obj2.error))) {
|
|
47
|
+
assert.ok(true); // ortamda shell yoksa kayıtlı davranış
|
|
48
|
+
} else {
|
|
49
|
+
assert.ok(JSON.stringify(obj2).includes('ok'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// kapı hiç verilmediyse eski davranış — doğrudan çalışır
|
|
53
|
+
const eng3 = mkEngine(null);
|
|
54
|
+
const out3 = await eng3._execTool('run_command', { command: 'echo three' }, null, 's3');
|
|
55
|
+
assert.ok(out3.length > 0);
|
|
56
|
+
});
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* #14 paralel ajanlar + CEO modu testleri (ağ çağrısı yok) */
|
|
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
|
+
const Engine = require('../src/agent/engine');
|
|
11
|
+
|
|
12
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
13
|
+
|
|
14
|
+
function tmpEngine() {
|
|
15
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-bg-'));
|
|
16
|
+
const events = [];
|
|
17
|
+
const eng = new Engine({}, {
|
|
18
|
+
sessionsDir: dir,
|
|
19
|
+
emit: (ev) => events.push(ev),
|
|
20
|
+
});
|
|
21
|
+
return { eng, events, dir };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test('CEO modu varsayılan açık; setCeoMode çalışır', () => {
|
|
25
|
+
const { eng } = tmpEngine();
|
|
26
|
+
assert.strictEqual(eng.ceoMode, true);
|
|
27
|
+
eng.setCeoMode(false);
|
|
28
|
+
assert.strictEqual(eng.ceoMode, false);
|
|
29
|
+
eng.setCeoMode(true);
|
|
30
|
+
assert.strictEqual(eng.ceoMode, true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('CEO yasaklı araç seti uygulayıcıları kapsar, yönetici araçlarını değil', () => {
|
|
34
|
+
const E = Engine;
|
|
35
|
+
for (const t of ['run_command', 'write_file', 'read_file', 'web_search', 'browser_click', 'computer_act', 'delegate_task']) {
|
|
36
|
+
assert.ok(E.CEO_EXEC_TOOLS.has(t), t);
|
|
37
|
+
}
|
|
38
|
+
for (const t of ['memory_write', 'run_background', 'tasks_list', 'task_status', 'set_reminder']) {
|
|
39
|
+
assert.ok(!E.CEO_EXEC_TOOLS.has(t), t);
|
|
40
|
+
}
|
|
41
|
+
/* arka plan ajanı yönetim araçlarını görmemeli — sonsuz alt dal zinciri olmasın */
|
|
42
|
+
assert.ok(E.BG_HIDDEN_TOOLS.has('run_background'));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('buildSystem CEO modunda CEO bloğu içerir; kapalıyken içermez', () => {
|
|
46
|
+
const { eng } = tmpEngine();
|
|
47
|
+
const onSys = eng.buildSystem('merhaba');
|
|
48
|
+
assert.ok(onSys.includes('# CEO MODU'), 'ceo bloğu');
|
|
49
|
+
assert.ok(onSys.includes('PARALEL ajana devret'));
|
|
50
|
+
eng.setCeoMode(false);
|
|
51
|
+
const offSys = eng.buildSystem('merhaba');
|
|
52
|
+
assert.ok(!offSys.includes('# CEO MODU'));
|
|
53
|
+
assert.ok(offSys.includes('delegate_task ile devret'));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('runBackground kayıt açar ve iş hata durumunda error olarak kapanır', async () => {
|
|
57
|
+
const { eng, events } = tmpEngine();
|
|
58
|
+
const r = eng.runBackground('oturum-1', 'klasördeki pdfleri listele', 'PDF Taraması');
|
|
59
|
+
assert.ok(r.ok);
|
|
60
|
+
assert.ok(r.backgroundId);
|
|
61
|
+
|
|
62
|
+
let jobs = eng.listBgJobs();
|
|
63
|
+
assert.strictEqual(jobs.length, 1);
|
|
64
|
+
assert.strictEqual(jobs[0].status, 'running');
|
|
65
|
+
assert.strictEqual(jobs[0].title, 'PDF Taraması');
|
|
66
|
+
assert.ok(jobs[0].task.includes('pdf'));
|
|
67
|
+
assert.strictEqual(jobs[0].parentId, 'oturum-1');
|
|
68
|
+
|
|
69
|
+
await sleep(200); // send tetiklenir → model yok → _run error yakalar
|
|
70
|
+
|
|
71
|
+
jobs = eng.listBgJobs();
|
|
72
|
+
assert.strictEqual(jobs[0].status, 'error');
|
|
73
|
+
assert.ok(/Model yapılandırılmadı/.test(jobs[0].error || ''), 'hata metni yakalandı: ' + jobs[0].error);
|
|
74
|
+
assert.ok(events.some((e) => e.type === 'agents' && Array.isArray(e.jobs)));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('_execTool tasks_list + task_status + task_cancel yanıt verir', async () => {
|
|
78
|
+
const { eng } = tmpEngine();
|
|
79
|
+
eng.runBackground('p2', 'araştırma görevi', 'Araştırma');
|
|
80
|
+
await sleep(150); // iş biter (error)
|
|
81
|
+
|
|
82
|
+
const lst = JSON.parse(await eng._execTool('tasks_list', {}, {}));
|
|
83
|
+
assert.ok(lst.ok && lst.jobs.length >= 1);
|
|
84
|
+
const id = lst.jobs[0].id;
|
|
85
|
+
|
|
86
|
+
const det = JSON.parse(await eng._execTool('task_status', { id }, {}));
|
|
87
|
+
assert.ok(det.ok);
|
|
88
|
+
assert.strictEqual(det.job.id, id);
|
|
89
|
+
assert.ok(Array.isArray(det.messages));
|
|
90
|
+
|
|
91
|
+
/* çalışan ajan olmadığından cancel false döner */
|
|
92
|
+
const c = JSON.parse(await eng._execTool('task_cancel', { id }, {}));
|
|
93
|
+
assert.strictEqual(c.ok, false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('bgDetail kod ile de bulunur; mesaj özeti GÖREV/AJAN etiketlidir', async () => {
|
|
97
|
+
const { eng } = tmpEngine();
|
|
98
|
+
const r = eng.runBackground('p3', 'görev metni', 'Kodlu İş');
|
|
99
|
+
await sleep(150);
|
|
100
|
+
const job = eng.listBgJobs()[0];
|
|
101
|
+
const byId = eng.bgDetail(job.id);
|
|
102
|
+
assert.ok(byId.ok);
|
|
103
|
+
const byCode = eng.bgDetail(job.code);
|
|
104
|
+
assert.ok(byCode.ok && byCode.job.id === job.id);
|
|
105
|
+
const missing = eng.bgDetail('XXXXXX99');
|
|
106
|
+
assert.strictEqual(missing.ok, false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/* ---------- superyorizyon (#15) ---------- */
|
|
110
|
+
|
|
111
|
+
const MIN = 60000;
|
|
112
|
+
|
|
113
|
+
function fakeJob(over) {
|
|
114
|
+
return {
|
|
115
|
+
status: 'running',
|
|
116
|
+
startedAt: new Date(Date.now() - 10 * MIN).toISOString(),
|
|
117
|
+
lastActivityAt: new Date(Date.now() - 10 * MIN).toISOString(),
|
|
118
|
+
lastNudgeAt: null,
|
|
119
|
+
...over,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
test('superviseReason: takılan stuck, çalışan uzun iş once wrapup (wrapAt sonrası long), yeni uyarılan null', () => {
|
|
124
|
+
assert.strictEqual(Engine.superviseReason(fakeJob(), Date.now()), 'stuck');
|
|
125
|
+
|
|
126
|
+
assert.strictEqual(
|
|
127
|
+
Engine.superviseReason(fakeJob({ lastActivityAt: new Date(Date.now() - 30 * 1000).toISOString() }), Date.now()),
|
|
128
|
+
'wrapup'
|
|
129
|
+
); // aktivite sürüyor ama 10 dk koştu — önce zarif bitirme uyarısı
|
|
130
|
+
|
|
131
|
+
assert.strictEqual(
|
|
132
|
+
Engine.superviseReason(fakeJob({ wrapAt: new Date().toISOString(), lastActivityAt: new Date(Date.now() - 30 * 1000).toISOString() }), Date.now()),
|
|
133
|
+
'long'
|
|
134
|
+
); // wrap-up uyarısı verildi, hâlâ bitmedi → CEO ara kontrolü
|
|
135
|
+
|
|
136
|
+
assert.strictEqual(
|
|
137
|
+
Engine.superviseReason(fakeJob({ startedAt: new Date(Date.now() - 1 * MIN).toISOString() }), Date.now()),
|
|
138
|
+
null
|
|
139
|
+
); // henüz çok yeni
|
|
140
|
+
|
|
141
|
+
assert.strictEqual(
|
|
142
|
+
Engine.superviseReason(fakeJob({ lastNudgeAt: new Date(Date.now() - 1 * MIN).toISOString() }), Date.now()),
|
|
143
|
+
null
|
|
144
|
+
); // az önce uyarıldı — cooldown
|
|
145
|
+
|
|
146
|
+
assert.strictEqual(Engine.superviseReason(fakeJob({ status: 'done' }), Date.now()), null);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('emit sarmalayıcı ajan olayında lastActivityAt günceller', () => {
|
|
150
|
+
const { eng } = tmpEngine();
|
|
151
|
+
eng.runBackground('pX', 'izleme', 'İz İş');
|
|
152
|
+
const job = eng.listBgJobs()[0];
|
|
153
|
+
/* zaman damgasını yapay eskilt — ms çözünürlüğü eşitliğe düşmesin */
|
|
154
|
+
eng._bgJobs.get(job.id).lastActivityAt = new Date(Date.now() - 5 * MIN).toISOString();
|
|
155
|
+
const eski = eng._bgJobs.get(job.id).lastActivityAt;
|
|
156
|
+
eng.emit({ type: 'tool-start', sessionId: job.id, name: 'run_command', args: {} });
|
|
157
|
+
const sonra = eng._bgJobs.get(job.id).lastActivityAt;
|
|
158
|
+
assert.ok(new Date(sonra) > new Date(eski), 'aktivite tazelendi');
|
|
159
|
+
/* yabancı oturum etkilenmez */
|
|
160
|
+
eng.emit({ type: 'token', sessionId: 'baskasi', delta: 'x' });
|
|
161
|
+
assert.ok(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('_supervise önce ajana ÖZ-KURTARMA verir, haklar bitince CEO\'ya uyarır', async () => {
|
|
165
|
+
const { eng } = tmpEngine();
|
|
166
|
+
const r = eng.runBackground('parentOk', 'uzun araştırma', 'Uzun İş');
|
|
167
|
+
const job = eng._bgJobs.get(r.backgroundId);
|
|
168
|
+
job.startedAt = new Date(Date.now() - 10 * MIN).toISOString();
|
|
169
|
+
job.lastActivityAt = job.startedAt;
|
|
170
|
+
eng._supervise();
|
|
171
|
+
|
|
172
|
+
/* 1. aşama: kurtarma emri ajanın KENDİ oturumuna düşer — CEO meşgul edilmez */
|
|
173
|
+
const bgS = eng._load(r.backgroundId);
|
|
174
|
+
assert.ok(bgS.messages.some((m) => String(m.content || '').includes('[ÖZ-KURTARMA 1/')), 'ajana öz-kurtarma düştü');
|
|
175
|
+
assert.strictEqual(job.checks, 1);
|
|
176
|
+
assert.strictEqual(job.fixes, 1);
|
|
177
|
+
const s0 = eng._load('parentOk');
|
|
178
|
+
assert.ok(!s0.messages.some((m) => String(m.content || '').includes('[SUPERYORIZON]')), 'CEO daha devreye girmedi');
|
|
179
|
+
|
|
180
|
+
/* cooldown: ikinci çağrı hemen tekrar müdahale etmez */
|
|
181
|
+
eng._supervise();
|
|
182
|
+
assert.strictEqual(job.checks, 1);
|
|
183
|
+
|
|
184
|
+
/* 2. aşama: öz-kurtarma hakları bitmiş + iş hâlâ koşuyor (wrap-up zaten
|
|
185
|
+
verildi, aktif) → CEO uyarısı ('long') */
|
|
186
|
+
job.status = 'running';
|
|
187
|
+
job.endedAt = null;
|
|
188
|
+
job.fixes = 2;
|
|
189
|
+
job.wrapAt = new Date().toISOString();
|
|
190
|
+
job.lastActivityAt = new Date(Date.now() - 30 * 1000).toISOString();
|
|
191
|
+
job.lastNudgeAt = new Date(Date.now() - 5 * MIN).toISOString();
|
|
192
|
+
eng._supervise();
|
|
193
|
+
await sleep(250);
|
|
194
|
+
|
|
195
|
+
const s = eng._load('parentOk');
|
|
196
|
+
const got = s.messages.some((m) => String(m.content || '').includes('[SUPERYORIZON] "Uzun İş"'));
|
|
197
|
+
assert.ok(got, 'üst oturuma uyarı düştü');
|
|
198
|
+
assert.strictEqual(job.checks, 2);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
/* ---------- #17 kalıcı ajan geçmişi ---------- */
|
|
202
|
+
|
|
203
|
+
test('ajan geçmişi restart sonrası durur; sol listede GİZLİ, kayıt diske yazar; elle silinir', () => {
|
|
204
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-persist-'));
|
|
205
|
+
const eng1 = new Engine({}, { sessionsDir: dir, emit: () => {} });
|
|
206
|
+
const r = eng1.runBackground('p1', 'masaüstündeki dosyaları listele', 'Tarama İş');
|
|
207
|
+
|
|
208
|
+
/* ajan oturumu sol sohbet listesine karışmaz */
|
|
209
|
+
assert.ok(!eng1.listSessions().some((s) => s.id === r.backgroundId), 'sol liste ajan sohbetini göstermez');
|
|
210
|
+
|
|
211
|
+
/* uygulama yeniden başlaması: yeni motor örneği aynı dizinden */
|
|
212
|
+
const eng2 = new Engine({}, { sessionsDir: dir, emit: () => {} });
|
|
213
|
+
const jobs = eng2.listBgJobs();
|
|
214
|
+
assert.strictEqual(jobs.length, 1);
|
|
215
|
+
assert.strictEqual(jobs[0].status, 'aborted', 'running → aborted migrasyonu');
|
|
216
|
+
assert.ok(String(jobs[0].error || '').includes('kapatıldı'));
|
|
217
|
+
|
|
218
|
+
/* hâlâ sol listede yok */
|
|
219
|
+
assert.ok(!eng2.listSessions().some((s) => s.id === r.backgroundId), 'restart sonrası da gizli');
|
|
220
|
+
|
|
221
|
+
/* elle silme: oturum dosyası + iş kaydı birlikte gider, hayalet yazılmaz */
|
|
222
|
+
eng2.deleteSession(r.backgroundId);
|
|
223
|
+
assert.strictEqual(eng2.listBgJobs().length, 0);
|
|
224
|
+
assert.ok(!fs.existsSync(path.join(dir, r.backgroundId + '.jsonl')));
|
|
225
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, 'bg-jobs.json'), 'utf8'));
|
|
226
|
+
assert.ok(!raw.jobs.some((j) => j.id === r.backgroundId), 'silinen iş diske geri yazılmaz');
|
|
227
|
+
|
|
228
|
+
/* gerçek uygulamada tek motor olur; burada eski örneği de uyumlaştır */
|
|
229
|
+
eng1._bgJobs.delete(r.backgroundId);
|
|
230
|
+
|
|
231
|
+
/* eski motorun geç tetiklenen send'i kaydı diriltmesin */
|
|
232
|
+
return new Promise((resolve) => setTimeout(resolve, 300)).then(() => {
|
|
233
|
+
const raw2 = JSON.parse(fs.readFileSync(path.join(dir, 'bg-jobs.json'), 'utf8'));
|
|
234
|
+
assert.ok(!raw2.jobs.some((j) => j.id === r.backgroundId), 'geç gelen _bgEmit hayır demedi');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/* ---------- #18 fan-out + #5 eşzamanlılık kuyruğu ---------- */
|
|
239
|
+
|
|
240
|
+
test('fan-out: adımlar aynı grupta açılır, hepsi bitince TEK birleşik rapor düşer', async () => {
|
|
241
|
+
const { eng } = tmpEngine();
|
|
242
|
+
const r = eng.runBackgroundMany(
|
|
243
|
+
'pg',
|
|
244
|
+
[
|
|
245
|
+
{ title: 'TR', task: 'tr haberlerini topla' },
|
|
246
|
+
{ title: 'DÜNYA', task: 'dünya haberlerini topla' },
|
|
247
|
+
{ title: 'EKONOMİ', task: 'ekonomi haberlerini topla' },
|
|
248
|
+
],
|
|
249
|
+
'Haber Taraması'
|
|
250
|
+
);
|
|
251
|
+
assert.ok(r.ok, 'fan-out başladı');
|
|
252
|
+
assert.strictEqual(r.ids.length, 3);
|
|
253
|
+
const groups = new Set([...eng._bgJobs.values()].map((j) => j.groupId));
|
|
254
|
+
assert.strictEqual(groups.size, 1);
|
|
255
|
+
for (const id of r.ids) await eng.reportBackgroundDone(id);
|
|
256
|
+
|
|
257
|
+
const s = eng._load('pg');
|
|
258
|
+
const got = s.messages.some((m) => String(m.content || '').includes('[ARKA PLAN GRUP BİTTİ: 3/3]'));
|
|
259
|
+
assert.ok(got, 'birleşik rapor düştü');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('#5 eşzamanlılık limiti: fazlası kuyrukta bekler, slot boşalınca FIFO başlar', async () => {
|
|
263
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-q-'));
|
|
264
|
+
const eng = new Engine({}, { sessionsDir: dir, emit: () => {}, bgLimit: 2 });
|
|
265
|
+
/* model yok → gerçek _run hata ile derhal bitmesin: gönderimi izlemeye al */
|
|
266
|
+
const sent = [];
|
|
267
|
+
eng.send = (sid, payload) => { sent.push(String(sid)); return true; };
|
|
268
|
+
const ids = [];
|
|
269
|
+
for (let i = 0; i < 4; i++) {
|
|
270
|
+
const r = eng.runBackground('pq', 'iş ' + i, 'İ' + i);
|
|
271
|
+
ids.push(r.backgroundId);
|
|
272
|
+
}
|
|
273
|
+
await sleep(120); // admit zamanlayıcıları
|
|
274
|
+
let st = [...eng._bgJobs.values()].map((j) => j.status).sort();
|
|
275
|
+
assert.deepEqual(st, ['queued', 'queued', 'running', 'running']);
|
|
276
|
+
assert.strictEqual(sent.length, 2);
|
|
277
|
+
|
|
278
|
+
/* slot açılır → sıradaki queued iş running olur ve gönderilir */
|
|
279
|
+
eng._bgFinish(ids[0], 'aborted');
|
|
280
|
+
await sleep(30);
|
|
281
|
+
const queued = [...eng._bgJobs.values()].filter((j) => j.status === 'queued').length;
|
|
282
|
+
const running = [...eng._bgJobs.values()].filter((j) => j.status === 'running').length;
|
|
283
|
+
assert.strictEqual(queued, 1);
|
|
284
|
+
assert.strictEqual(running, 2);
|
|
285
|
+
assert.strictEqual(sent.length, 3);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
/* ---------- /stop anahtarı ---------- */test('stopAll: koşan turları keser, paralel ajanı aborted işaretler', async () => {
|
|
289
|
+
const { eng } = tmpEngine();
|
|
290
|
+
const r = eng.runBackground('pS', 'koşan iş', 'Koşu');
|
|
291
|
+
const id = r.backgroundId;
|
|
292
|
+
|
|
293
|
+
/* sahte koşan tur: ctrls'e AbortController koy */
|
|
294
|
+
const c1 = new AbortController();
|
|
295
|
+
const c2 = new AbortController();
|
|
296
|
+
eng.ctrls.set(id, c1); // paralel ajan
|
|
297
|
+
eng.ctrls.set('main-sid', c2); // masaüstü oturumu
|
|
298
|
+
/* bekleyen rapor kuyruğu da dolu olsun */
|
|
299
|
+
eng._pendingReports = [{ parentId: 'pS', text: 'eski rapor' }];
|
|
300
|
+
|
|
301
|
+
const n = eng.stopAll();
|
|
302
|
+
|
|
303
|
+
assert.strictEqual(n, 2);
|
|
304
|
+
assert.ok(c1.signal.aborted && c2.signal.aborted);
|
|
305
|
+
assert.strictEqual(eng._bgJobs.get(id).status, 'aborted');
|
|
306
|
+
assert.ok(eng._bgJobs.get(id).endedAt);
|
|
307
|
+
assert.strictEqual(eng._pendingReports.length, 0);
|
|
308
|
+
/* events'te agents yayını var */
|
|
309
|
+
assert.ok(eng.listBgJobs()[0].status === 'aborted');
|
|
310
|
+
|
|
311
|
+
/* ikinci çağrı idempotent: gerçek akışta _run finally ctrl'i siler */
|
|
312
|
+
eng.ctrls.delete(id);
|
|
313
|
+
eng.ctrls.delete('main-sid');
|
|
314
|
+
assert.strictEqual(eng.stopAll(), 0);
|
|
315
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
});
|