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/scripts/release.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* Beast Agent — Dağıtım / Senkron Sistemi
|
|
4
|
-
Tek komutla tam sürüm akışı (EXE/INSTALLER YOK — npm + kaynak dağıtımı):
|
|
5
|
-
npm run release → otomatik patch bump (0.20.0 → 0.20.1)
|
|
6
|
-
npm run release -- minor → 0.21.0
|
|
7
|
-
npm run release -- 0.22.0 → belirli sürüm
|
|
8
|
-
|
|
9
|
-
Adımlar:
|
|
10
|
-
1) package.json sürümü yükselt
|
|
11
|
-
2) commit + tag + push (main + tag)
|
|
12
|
-
3) GitHub Release (yalnız kaynak — exe/asset yüklenmez)
|
|
13
|
-
4) npm publish (NPM_TOKEN env var ise; yoksa atlar ve uyarır)
|
|
14
|
-
5) OneDrive yedek klasörüne kaynak kopyası (beast-v< sürüm >)
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
const { execSync } = require('child_process');
|
|
20
|
-
|
|
21
|
-
const ROOT = path.join(__dirname, '..');
|
|
22
|
-
const ONE_DRIVE_DIR = process.env.BEAST_BACKUP_DIR
|
|
23
|
-
|| 'C:\\Users\\batuh\\OneDrive\\Masaüstü\\Beast Agent';
|
|
24
|
-
|
|
25
|
-
const step = (msg) => console.log('\n\x1b[1m▶ ' + msg + '\x1b[0m');
|
|
26
|
-
const ok = (msg) => console.log(' \x1b[32m✓\x1b[0m ' + msg);
|
|
27
|
-
const warn = (msg) => console.log(' \x1b[33m!\x1b[0m ' + msg);
|
|
28
|
-
const fail = (msg) => { console.error(' \x1b[31m✗ ' + msg + '\x1b[0m'); process.exit(1); };
|
|
29
|
-
|
|
30
|
-
function run(cmd, opts = {}) {
|
|
31
|
-
/* stdio:'inherit' iken execSync null döner — trim patlamasın */
|
|
32
|
-
return String(execSync(cmd, { cwd: ROOT, stdio: opts.inherit ? 'inherit' : 'pipe', encoding: 'utf8', ...opts }) ?? '').trim();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function bump(version, kind) {
|
|
36
|
-
const [ma, mi, pa] = version.split('.').map(Number);
|
|
37
|
-
if (kind === 'major') return `${ma + 1}.0.0`;
|
|
38
|
-
if (kind === 'minor') return `${ma}.${mi + 1}.0`;
|
|
39
|
-
return `${ma}.${mi}.${pa + 1}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/* ---------- argümanlar ---------- */
|
|
43
|
-
const arg = process.argv[2] || 'patch';
|
|
44
|
-
const pkgPath = path.join(ROOT, 'package.json');
|
|
45
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
46
|
-
const current = pkg.version;
|
|
47
|
-
const next = ['major', 'minor', 'patch'].includes(arg) ? bump(current, arg) : arg.replace(/^v/, '');
|
|
48
|
-
if (!/^\d+\.\d+\.\d+$/.test(next)) fail('geçersiz sürüm: ' + next);
|
|
49
|
-
const tag = 'v' + next;
|
|
50
|
-
|
|
51
|
-
console.log(`\x1b[1mBeast Agent release: ${current} → ${next}\x1b[0m`);
|
|
52
|
-
|
|
53
|
-
/* ---------- 1) sürüm bump ---------- */
|
|
54
|
-
step('1/5 sürüm yükselt: ' + current + ' → ' + next);
|
|
55
|
-
pkg.version = next;
|
|
56
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + '\n');
|
|
57
|
-
ok('package.json → ' + next);
|
|
58
|
-
|
|
59
|
-
/* ---------- 2) commit + push ---------- */
|
|
60
|
-
step('2/5 git: commit + tag + push');
|
|
61
|
-
try {
|
|
62
|
-
run('git add -A');
|
|
63
|
-
run(`git commit -m "v${next}"`);
|
|
64
|
-
} catch { warn('commit edilecek değişiklik yoktu'); }
|
|
65
|
-
try { run(`git tag ${tag} -f`); } catch {}
|
|
66
|
-
run('git push origin main');
|
|
67
|
-
run(`git push origin ${tag}`);
|
|
68
|
-
ok('pushed: main + ' + tag);
|
|
69
|
-
|
|
70
|
-
/* ---------- 3) GitHub Release (kaynak — exe yok) ---------- */
|
|
71
|
-
step('3/5 GitHub Release ' + tag);
|
|
72
|
-
const gh = process.env.GH || 'gh';
|
|
73
|
-
try {
|
|
74
|
-
const notes = [
|
|
75
|
-
`## Beast Agent ${tag}`,
|
|
76
|
-
'',
|
|
77
|
-
'- \`npm i -g beast-agent@\`' + next,
|
|
78
|
-
'',
|
|
79
|
-
'Dağıtım npm üzerinden yapılır — exe/installer sürümü kaldırıldı.',
|
|
80
|
-
'Tam değişiklik listesi: commit geçmişi.',
|
|
81
|
-
].join('\n');
|
|
82
|
-
const notesFile = path.join(ROOT, 'dist', 'release-notes.md');
|
|
83
|
-
fs.mkdirSync(path.dirname(notesFile), { recursive: true });
|
|
84
|
-
fs.writeFileSync(notesFile, notes);
|
|
85
|
-
try { run(`${gh} release delete ${tag} --yes --cleanup-tag`); } catch {}
|
|
86
|
-
run(`${gh} release create ${tag} --title "Beast Agent ${tag}" --notes-file "${notesFile}"`, { inherit: true });
|
|
87
|
-
ok('release yayında: https://github.com/algokodcom/beast-agent/releases/tag/' + tag);
|
|
88
|
-
} catch (e) {
|
|
89
|
-
fail('GitHub release: ' + String(e));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/* ---------- 4) npm publish ---------- */
|
|
93
|
-
step('4/5 npm publish');
|
|
94
|
-
if (process.env.NPM_TOKEN) {
|
|
95
|
-
try {
|
|
96
|
-
run(`npm publish --//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}`, { inherit: true });
|
|
97
|
-
ok('npm: beast-agent@' + next);
|
|
98
|
-
} catch (e) {
|
|
99
|
-
warn('npm publish başarısız (sürüm zaten var olabilir): ' + String(e).slice(0, 200));
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
warn('NPM_TOKEN env yok — npm adımı atlandı.');
|
|
103
|
-
warn('Elle: set NPM_TOKEN=<token> && npm publish');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/* ---------- 5) OneDrive kaynak yedeği ---------- */
|
|
107
|
-
step('5/5 OneDrive yedek (beast-v' + next + ')');
|
|
108
|
-
try {
|
|
109
|
-
const dest = path.join(ONE_DRIVE_DIR, 'beast-v' + next);
|
|
110
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
111
|
-
run(`robocopy "${ROOT}" "${dest}" /E /XD node_modules dist "beast agent web" .git /NFL /NDL /NJH`);
|
|
112
|
-
const info = path.join(dest, `YEDEK-BILGI-v${next}.txt`);
|
|
113
|
-
fs.writeFileSync(info, `BEAST AGENT v${next} — ${new Date().toLocaleString('tr-TR')}\nKaynak: ${ROOT}\nGitHub: https://github.com/algokodcom/beast-agent/releases/tag/${tag}\nnpm: https://www.npmjs.com/package/beast-agent\n`);
|
|
114
|
-
ok(dest);
|
|
115
|
-
} catch (e) {
|
|
116
|
-
warn('OneDrive yedeği atlandı: ' + String(e).slice(0, 160));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
console.log(`\n\x1b[1m\x1b[32m✓ v${next} dağıtımı tamam.\x1b[0m`);
|
|
120
|
-
console.log(' GitHub : https://github.com/algokodcom/beast-agent/releases/tag/' + tag);
|
|
121
|
-
console.log(' npm : https://www.npmjs.com/package/beast-agent');
|
package/scripts/swap-electron.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* electron yer değiştirici:
|
|
4
|
-
- `deps` : electron'u devDependencies → dependencies taşır (npm publish öncesi;
|
|
5
|
-
global kurulumda electron runtime'ı inmesi için)
|
|
6
|
-
- `devdeps`: geri taşır (electron-builder yalnız devDependencies kabul eder;
|
|
7
|
-
repo normal durumu budur)
|
|
8
|
-
npm lifecycle: prepublishOnly → deps, postpublish → devdeps */
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
|
|
13
|
-
const mode = process.argv[2];
|
|
14
|
-
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
15
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
16
|
-
const VER = pkg.devDependencies && pkg.devDependencies.electron
|
|
17
|
-
? pkg.devDependencies.electron
|
|
18
|
-
: (pkg.dependencies && pkg.dependencies.electron);
|
|
19
|
-
|
|
20
|
-
if (!VER) {
|
|
21
|
-
console.error('electron sürümü package.json içinde bulunamadı');
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (mode === 'deps') {
|
|
26
|
-
delete pkg.devDependencies.electron;
|
|
27
|
-
pkg.dependencies = pkg.dependencies || {};
|
|
28
|
-
pkg.dependencies.electron = VER;
|
|
29
|
-
console.log('electron → dependencies (' + VER + ')');
|
|
30
|
-
} else if (mode === 'devdeps') {
|
|
31
|
-
delete pkg.dependencies.electron;
|
|
32
|
-
pkg.devDependencies = pkg.devDependencies || {};
|
|
33
|
-
pkg.devDependencies.electron = VER;
|
|
34
|
-
console.log('electron → devDependencies (' + VER + ')');
|
|
35
|
-
} else {
|
|
36
|
-
console.error('kullanım: node scripts/swap-electron.js deps|devdeps');
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + '\n');
|
package/store/skills.json
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"_doc": "Skills Store topluluk indeksi. Her kayıt: { id, name, description, version, tags[], author{username,avatar,beastId}, image(dataURL), files{'SKILL.md': ...}, installs, likes, createdAt, updatedAt }. Yüklemeler buraya PR ile eklenir; app raw URL'den çeker.",
|
|
4
|
-
"skills": []
|
|
5
|
-
}
|
package/tests/approval.test.js
DELETED
|
@@ -1,56 +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
|
-
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
|
-
});
|
package/tests/bg-jobs.test.js
DELETED
|
@@ -1,424 +0,0 @@
|
|
|
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 KAPALI; setCeoMode çalışır', () => {
|
|
25
|
-
const { eng } = tmpEngine();
|
|
26
|
-
assert.strictEqual(eng.ceoMode, false);
|
|
27
|
-
eng.setCeoMode(true);
|
|
28
|
-
assert.strictEqual(eng.ceoMode, true);
|
|
29
|
-
eng.setCeoMode(false);
|
|
30
|
-
assert.strictEqual(eng.ceoMode, false);
|
|
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
|
-
eng.setCeoMode(true);
|
|
48
|
-
const onSys = eng.buildSystem('merhaba');
|
|
49
|
-
assert.ok(onSys.includes('# CEO MODU'), 'ceo bloğu');
|
|
50
|
-
assert.ok(onSys.includes('PARALEL ajana devret'));
|
|
51
|
-
eng.setCeoMode(false);
|
|
52
|
-
const offSys = eng.buildSystem('merhaba');
|
|
53
|
-
assert.ok(!offSys.includes('# CEO MODU'));
|
|
54
|
-
assert.ok(offSys.includes('delegate_task ile devret'));
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('runBackground kayıt açar ve iş hata durumunda error olarak kapanır', async () => {
|
|
58
|
-
const { eng, events } = tmpEngine();
|
|
59
|
-
const r = eng.runBackground('oturum-1', 'klasördeki pdfleri listele', 'PDF Taraması');
|
|
60
|
-
assert.ok(r.ok);
|
|
61
|
-
assert.ok(r.backgroundId);
|
|
62
|
-
|
|
63
|
-
let jobs = eng.listBgJobs();
|
|
64
|
-
assert.strictEqual(jobs.length, 1);
|
|
65
|
-
assert.strictEqual(jobs[0].status, 'running');
|
|
66
|
-
assert.strictEqual(jobs[0].title, 'PDF Taraması');
|
|
67
|
-
assert.ok(jobs[0].task.includes('pdf'));
|
|
68
|
-
assert.strictEqual(jobs[0].parentId, 'oturum-1');
|
|
69
|
-
|
|
70
|
-
await sleep(200); // send tetiklenir → model yok → _run error yakalar
|
|
71
|
-
|
|
72
|
-
jobs = eng.listBgJobs();
|
|
73
|
-
assert.strictEqual(jobs[0].status, 'error');
|
|
74
|
-
assert.ok(/Model yapılandırılmadı/.test(jobs[0].error || ''), 'hata metni yakalandı: ' + jobs[0].error);
|
|
75
|
-
assert.ok(events.some((e) => e.type === 'agents' && Array.isArray(e.jobs)));
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('_execTool tasks_list + task_status + task_cancel yanıt verir', async () => {
|
|
79
|
-
const { eng } = tmpEngine();
|
|
80
|
-
eng.runBackground('p2', 'araştırma görevi', 'Araştırma');
|
|
81
|
-
await sleep(150); // iş biter (error)
|
|
82
|
-
|
|
83
|
-
const lst = JSON.parse(await eng._execTool('tasks_list', {}, {}));
|
|
84
|
-
assert.ok(lst.ok && lst.jobs.length >= 1);
|
|
85
|
-
const id = lst.jobs[0].id;
|
|
86
|
-
|
|
87
|
-
const det = JSON.parse(await eng._execTool('task_status', { id }, {}));
|
|
88
|
-
assert.ok(det.ok);
|
|
89
|
-
assert.strictEqual(det.job.id, id);
|
|
90
|
-
assert.ok(Array.isArray(det.messages));
|
|
91
|
-
|
|
92
|
-
/* çalışan ajan olmadığından cancel false döner */
|
|
93
|
-
const c = JSON.parse(await eng._execTool('task_cancel', { id }, {}));
|
|
94
|
-
assert.strictEqual(c.ok, false);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test('bgDetail kod ile de bulunur; mesaj özeti GÖREV/AJAN etiketlidir', async () => {
|
|
98
|
-
const { eng } = tmpEngine();
|
|
99
|
-
const r = eng.runBackground('p3', 'görev metni', 'Kodlu İş');
|
|
100
|
-
await sleep(150);
|
|
101
|
-
const job = eng.listBgJobs()[0];
|
|
102
|
-
const byId = eng.bgDetail(job.id);
|
|
103
|
-
assert.ok(byId.ok);
|
|
104
|
-
const byCode = eng.bgDetail(job.code);
|
|
105
|
-
assert.ok(byCode.ok && byCode.job.id === job.id);
|
|
106
|
-
const missing = eng.bgDetail('XXXXXX99');
|
|
107
|
-
assert.strictEqual(missing.ok, false);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
/* ---------- superyorizyon (#15) ---------- */
|
|
111
|
-
|
|
112
|
-
const MIN = 60000;
|
|
113
|
-
|
|
114
|
-
function fakeJob(over) {
|
|
115
|
-
return {
|
|
116
|
-
status: 'running',
|
|
117
|
-
startedAt: new Date(Date.now() - 10 * MIN).toISOString(),
|
|
118
|
-
lastActivityAt: new Date(Date.now() - 10 * MIN).toISOString(),
|
|
119
|
-
lastNudgeAt: null,
|
|
120
|
-
...over,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
test('superviseReason: takılan stuck, uzun koşan iş CEO kontrolü (long), yeni uyarılan null', () => {
|
|
125
|
-
assert.strictEqual(Engine.superviseReason(fakeJob(), Date.now()), 'stuck');
|
|
126
|
-
|
|
127
|
-
// süre sınırı KALDIRILDI: aktivite süren uzun iş artık kesilmez → CEO ara kontrolü (long)
|
|
128
|
-
assert.strictEqual(
|
|
129
|
-
Engine.superviseReason(fakeJob({ lastActivityAt: new Date(Date.now() - 30 * 1000).toISOString() }), Date.now()),
|
|
130
|
-
'long'
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
assert.strictEqual(
|
|
134
|
-
Engine.superviseReason(fakeJob({ startedAt: new Date(Date.now() - 1 * MIN).toISOString() }), Date.now()),
|
|
135
|
-
null
|
|
136
|
-
); // henüz çok yeni
|
|
137
|
-
|
|
138
|
-
assert.strictEqual(
|
|
139
|
-
Engine.superviseReason(fakeJob({ lastNudgeAt: new Date(Date.now() - 1 * MIN).toISOString() }), Date.now()),
|
|
140
|
-
null
|
|
141
|
-
); // az önce uyarıldı — cooldown
|
|
142
|
-
|
|
143
|
-
assert.strictEqual(Engine.superviseReason(fakeJob({ status: 'done' }), Date.now()), null);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('emit sarmalayıcı ajan olayında lastActivityAt günceller', () => {
|
|
147
|
-
const { eng } = tmpEngine();
|
|
148
|
-
eng.runBackground('pX', 'izleme', 'İz İş');
|
|
149
|
-
const job = eng.listBgJobs()[0];
|
|
150
|
-
/* zaman damgasını yapay eskilt — ms çözünürlüğü eşitliğe düşmesin */
|
|
151
|
-
eng._bgJobs.get(job.id).lastActivityAt = new Date(Date.now() - 5 * MIN).toISOString();
|
|
152
|
-
const eski = eng._bgJobs.get(job.id).lastActivityAt;
|
|
153
|
-
eng.emit({ type: 'tool-start', sessionId: job.id, name: 'run_command', args: {} });
|
|
154
|
-
const sonra = eng._bgJobs.get(job.id).lastActivityAt;
|
|
155
|
-
assert.ok(new Date(sonra) > new Date(eski), 'aktivite tazelendi');
|
|
156
|
-
/* yabancı oturum etkilenmez */
|
|
157
|
-
eng.emit({ type: 'token', sessionId: 'baskasi', delta: 'x' });
|
|
158
|
-
assert.ok(true);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('_supervise önce ajana ÖZ-KURTARMA verir, haklar bitince CEO\'ya uyarır', async () => {
|
|
162
|
-
const { eng } = tmpEngine();
|
|
163
|
-
const r = eng.runBackground('parentOk', 'uzun araştırma', 'Uzun İş');
|
|
164
|
-
const job = eng._bgJobs.get(r.backgroundId);
|
|
165
|
-
job.startedAt = new Date(Date.now() - 10 * MIN).toISOString();
|
|
166
|
-
job.lastActivityAt = job.startedAt;
|
|
167
|
-
eng._supervise();
|
|
168
|
-
|
|
169
|
-
/* 1. aşama: kurtarma emri ajanın KENDİ oturumuna düşer — CEO meşgul edilmez */
|
|
170
|
-
const bgS = eng._load(r.backgroundId);
|
|
171
|
-
assert.ok(bgS.messages.some((m) => String(m.content || '').includes('[ÖZ-KURTARMA 1/')), 'ajana öz-kurtarma düştü');
|
|
172
|
-
assert.strictEqual(job.checks, 1);
|
|
173
|
-
assert.strictEqual(job.fixes, 1);
|
|
174
|
-
const s0 = eng._load('parentOk');
|
|
175
|
-
assert.ok(!s0.messages.some((m) => String(m.content || '').includes('[SUPERYORIZON]')), 'CEO daha devreye girmedi');
|
|
176
|
-
|
|
177
|
-
/* cooldown: ikinci çağrı hemen tekrar müdahale etmez */
|
|
178
|
-
eng._supervise();
|
|
179
|
-
assert.strictEqual(job.checks, 1);
|
|
180
|
-
|
|
181
|
-
/* 2. aşama: öz-kurtarma hakları bitmiş + iş hâlâ koşuyor (aktivite sürüyor,
|
|
182
|
-
süre sınırı yok) → CEO uyarısı ('long') */
|
|
183
|
-
job.status = 'running';
|
|
184
|
-
job.endedAt = null;
|
|
185
|
-
job.fixes = 2;
|
|
186
|
-
job.lastActivityAt = new Date(Date.now() - 30 * 1000).toISOString();
|
|
187
|
-
job.lastNudgeAt = new Date(Date.now() - 5 * MIN).toISOString();
|
|
188
|
-
eng._supervise();
|
|
189
|
-
await sleep(250);
|
|
190
|
-
|
|
191
|
-
const s = eng._load('parentOk');
|
|
192
|
-
const got = s.messages.some((m) => String(m.content || '').includes('[SUPERYORIZON] "Uzun İş"'));
|
|
193
|
-
assert.ok(got, 'üst oturuma uyarı düştü');
|
|
194
|
-
assert.strictEqual(job.checks, 2);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
/* ---------- #17 kalıcı ajan geçmişi ---------- */
|
|
198
|
-
|
|
199
|
-
test('ajan geçmişi restart sonrası durur; sol listede GİZLİ, kayıt diske yazar; elle silinir', () => {
|
|
200
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-persist-'));
|
|
201
|
-
const eng1 = new Engine({}, { sessionsDir: dir, emit: () => {} });
|
|
202
|
-
const r = eng1.runBackground('p1', 'masaüstündeki dosyaları listele', 'Tarama İş');
|
|
203
|
-
|
|
204
|
-
/* ajan oturumu sol sohbet listesine karışmaz */
|
|
205
|
-
assert.ok(!eng1.listSessions().some((s) => s.id === r.backgroundId), 'sol liste ajan sohbetini göstermez');
|
|
206
|
-
|
|
207
|
-
/* uygulama yeniden başlaması: yeni motor örneği aynı dizinden */
|
|
208
|
-
const eng2 = new Engine({}, { sessionsDir: dir, emit: () => {} });
|
|
209
|
-
const jobs = eng2.listBgJobs();
|
|
210
|
-
assert.strictEqual(jobs.length, 1);
|
|
211
|
-
assert.strictEqual(jobs[0].status, 'aborted', 'running → aborted migrasyonu');
|
|
212
|
-
assert.ok(String(jobs[0].error || '').includes('kapatıldı'));
|
|
213
|
-
|
|
214
|
-
/* hâlâ sol listede yok */
|
|
215
|
-
assert.ok(!eng2.listSessions().some((s) => s.id === r.backgroundId), 'restart sonrası da gizli');
|
|
216
|
-
|
|
217
|
-
/* elle silme: oturum dosyası + iş kaydı birlikte gider, hayalet yazılmaz */
|
|
218
|
-
eng2.deleteSession(r.backgroundId);
|
|
219
|
-
assert.strictEqual(eng2.listBgJobs().length, 0);
|
|
220
|
-
assert.ok(!fs.existsSync(path.join(dir, r.backgroundId + '.jsonl')));
|
|
221
|
-
const raw = JSON.parse(fs.readFileSync(path.join(dir, 'bg-jobs.json'), 'utf8'));
|
|
222
|
-
assert.ok(!raw.jobs.some((j) => j.id === r.backgroundId), 'silinen iş diske geri yazılmaz');
|
|
223
|
-
|
|
224
|
-
/* gerçek uygulamada tek motor olur; burada eski örneği de uyumlaştır */
|
|
225
|
-
eng1._bgJobs.delete(r.backgroundId);
|
|
226
|
-
|
|
227
|
-
/* eski motorun geç tetiklenen send'i kaydı diriltmesin */
|
|
228
|
-
return new Promise((resolve) => setTimeout(resolve, 300)).then(() => {
|
|
229
|
-
const raw2 = JSON.parse(fs.readFileSync(path.join(dir, 'bg-jobs.json'), 'utf8'));
|
|
230
|
-
assert.ok(!raw2.jobs.some((j) => j.id === r.backgroundId), 'geç gelen _bgEmit hayır demedi');
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
/* ---------- #18 fan-out + #5 eşzamanlılık kuyruğu ---------- */
|
|
235
|
-
|
|
236
|
-
test('fan-out: adımlar aynı grupta açılır, hepsi bitince TEK birleşik rapor düşer', async () => {
|
|
237
|
-
const { eng } = tmpEngine();
|
|
238
|
-
const r = eng.runBackgroundMany(
|
|
239
|
-
'pg',
|
|
240
|
-
[
|
|
241
|
-
{ title: 'TR', task: 'tr haberlerini topla' },
|
|
242
|
-
{ title: 'DÜNYA', task: 'dünya haberlerini topla' },
|
|
243
|
-
{ title: 'EKONOMİ', task: 'ekonomi haberlerini topla' },
|
|
244
|
-
],
|
|
245
|
-
'Haber Taraması'
|
|
246
|
-
);
|
|
247
|
-
assert.ok(r.ok, 'fan-out başladı');
|
|
248
|
-
assert.strictEqual(r.ids.length, 3);
|
|
249
|
-
const groups = new Set([...eng._bgJobs.values()].map((j) => j.groupId));
|
|
250
|
-
assert.strictEqual(groups.size, 1);
|
|
251
|
-
for (const id of r.ids) await eng.reportBackgroundDone(id);
|
|
252
|
-
|
|
253
|
-
const s = eng._load('pg');
|
|
254
|
-
const got = s.messages.some((m) => String(m.content || '').includes('[ARKA PLAN GRUP BİTTİ: 3/3]'));
|
|
255
|
-
assert.ok(got, 'birleşik rapor düştü');
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test('#5 eşzamanlılık limiti: fazlası kuyrukta bekler, slot boşalınca FIFO başlar', async () => {
|
|
259
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-q-'));
|
|
260
|
-
const eng = new Engine({}, { sessionsDir: dir, emit: () => {}, bgLimit: 2 });
|
|
261
|
-
/* model yok → gerçek _run hata ile derhal bitmesin: gönderimi izlemeye al */
|
|
262
|
-
const sent = [];
|
|
263
|
-
eng.send = (sid, payload) => { sent.push(String(sid)); return true; };
|
|
264
|
-
const ids = [];
|
|
265
|
-
for (let i = 0; i < 4; i++) {
|
|
266
|
-
const r = eng.runBackground('pq', 'iş ' + i, 'İ' + i);
|
|
267
|
-
ids.push(r.backgroundId);
|
|
268
|
-
}
|
|
269
|
-
await sleep(120); // admit zamanlayıcıları
|
|
270
|
-
let st = [...eng._bgJobs.values()].map((j) => j.status).sort();
|
|
271
|
-
assert.deepEqual(st, ['queued', 'queued', 'running', 'running']);
|
|
272
|
-
assert.strictEqual(sent.length, 2);
|
|
273
|
-
|
|
274
|
-
/* slot açılır → sıradaki queued iş running olur ve gönderilir.
|
|
275
|
-
_bgFinish('aborted') artık üst sohbete [ARKA PLAN İPTAL + SEBEP] raporu da düşürür. */
|
|
276
|
-
eng._bgFinish(ids[0], 'aborted');
|
|
277
|
-
await sleep(30);
|
|
278
|
-
const queued = [...eng._bgJobs.values()].filter((j) => j.status === 'queued').length;
|
|
279
|
-
const running = [...eng._bgJobs.values()].filter((j) => j.status === 'running').length;
|
|
280
|
-
assert.strictEqual(queued, 1);
|
|
281
|
-
assert.strictEqual(running, 2);
|
|
282
|
-
/* 3 gönderim = kuyruktan başlayan iş + 1 rapor = üst sohbete düşen iptal sebebi */
|
|
283
|
-
assert.strictEqual(sent.length, 4);
|
|
284
|
-
assert.ok(eng._pendingReports.length === 0, 'iptal raporu üst sohbete düştü');
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
/* ---------- İPTAL SEBEBİ DİSİPLİNİ ---------- */
|
|
288
|
-
|
|
289
|
-
test('üst oturum hata verirse rapor TEK SEFER düşer — sonsuz _run zinciri yok', async () => {
|
|
290
|
-
/* model yok → bg işi error ile biter → rapor üst oturuma flush edilir;
|
|
291
|
-
üst oturum da model-yok ile SYNC çökerse flush zinciri SONSUZA dönmemeli
|
|
292
|
-
(gerçek hata: flush → send → _run sync-throw → finally → flush … stack overflow) */
|
|
293
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-flush-'));
|
|
294
|
-
let runCount = 0;
|
|
295
|
-
const eng = new Engine({}, { sessionsDir: dir, emit: () => {} });
|
|
296
|
-
const origRun = Engine.prototype._run;
|
|
297
|
-
eng._run = function (s) { runCount++; return origRun.call(this, s); };
|
|
298
|
-
eng.runBackground('pflush', 'iş', 'Rapor İş');
|
|
299
|
-
await sleep(700);
|
|
300
|
-
assert.ok(runCount <= 4, 'sonsuz _run zinciri yok (runCount=' + runCount + ')');
|
|
301
|
-
/* rapor tam olarak 1 kez üst oturuma gönderilmiş olmalı */
|
|
302
|
-
eng.deleteSession('pflush');
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test('takılı iş ZORLA kapatılır: grup tamamlanır, birleşik rapor düşer', async () => {
|
|
306
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-fc-'));
|
|
307
|
-
const eng = new Engine({}, { sessionsDir: dir, emit: () => {} });
|
|
308
|
-
const sent = [];
|
|
309
|
-
eng.send = (sid, payload) => { sent.push({ sid, text: String((payload && payload.text) || '') }); return true; };
|
|
310
|
-
const r = eng.runBackgroundMany(
|
|
311
|
-
'pfc',
|
|
312
|
-
[
|
|
313
|
-
{ title: 'A1', task: 'görev a' },
|
|
314
|
-
{ title: 'B1', task: 'görev b' },
|
|
315
|
-
],
|
|
316
|
-
'Zorla Grup'
|
|
317
|
-
);
|
|
318
|
-
assert.ok(r.ok);
|
|
319
|
-
/* A1 done; B1 takılı → force-close */
|
|
320
|
-
await eng.reportBackgroundDone(r.ids[0]);
|
|
321
|
-
const stuck = eng._bgJobs.get(r.ids[1]);
|
|
322
|
-
stuck.fixes = 99; /* öz-kurtarma hakları tükenmiş */
|
|
323
|
-
eng._bgForceClose(stuck, 'test: takıldı');
|
|
324
|
-
await sleep(30);
|
|
325
|
-
const j = eng._bgJobs.get(r.ids[1]);
|
|
326
|
-
assert.strictEqual(j.status, 'aborted', 'zorla kapatılan iş aborted');
|
|
327
|
-
assert.ok(String(j.error || '').includes('test: takıldı'), 'sebep kayda geçti');
|
|
328
|
-
const rep = sent.find((x) => x.sid === 'pfc' && x.text.includes('[ARKA PLAN GRUP BİTTİ'));
|
|
329
|
-
assert.ok(rep, 'birleşik rapor düştü — grup kilitlenmedi');
|
|
330
|
-
assert.ok(/B1/.test(rep.text) && /aborted/.test(rep.text), 'raporda takılan üye SEBEPİYLE görünüyor');
|
|
331
|
-
eng.deleteSession('pfc');
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
test('iptal SEBEBİ zorunlu: interrupt sebebi iş kaydına ve üst sohbete yazar', async () => {
|
|
335
|
-
const { eng } = tmpEngine();
|
|
336
|
-
const sent = [];
|
|
337
|
-
eng.send = (sid, payload) => { sent.push({ sid, text: String((payload && payload.text) || '') }); return true; };
|
|
338
|
-
const r = eng.runBackground('pr', 'koşan iş', 'Sebep İş');
|
|
339
|
-
const id = r.backgroundId;
|
|
340
|
-
const c = new AbortController();
|
|
341
|
-
eng.ctrls.set(id, c);
|
|
342
|
-
|
|
343
|
-
eng.interrupt(id, 'kullanıcı panelden iptal etti');
|
|
344
|
-
assert.ok(c.signal.aborted, 'ctrl kesildi');
|
|
345
|
-
assert.strictEqual(eng._abortReasons.get(id), 'kullanıcı panelden iptal etti', 'sebep haritada');
|
|
346
|
-
|
|
347
|
-
/* _run catch'inin yaptığı iş: haritadan sebebi okuyup _bgFinish'e verir */
|
|
348
|
-
const why = eng._abortReasons.get(id);
|
|
349
|
-
eng._abortReasons.delete(id);
|
|
350
|
-
eng._bgFinish(id, 'aborted', why);
|
|
351
|
-
|
|
352
|
-
const j = eng._bgJobs.get(id);
|
|
353
|
-
assert.strictEqual(j.status, 'aborted');
|
|
354
|
-
assert.strictEqual(j.error, 'kullanıcı panelden iptal etti', 'sebep iş kaydına yazıldı');
|
|
355
|
-
const rep = sent.find((x) => x.sid === 'pr' && x.text.includes('[ARKA PLAN İPTAL'));
|
|
356
|
-
assert.ok(rep, 'üst sohbete İPTAL raporu düştü');
|
|
357
|
-
assert.ok(/Sebep: kullanıcı panelden/.test(rep.text), 'rapor SEBEPİ içerir');
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
test('kuyruktaki ajan interrupt ile SEBEPİYLE kesilir', async () => {
|
|
361
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-qc-'));
|
|
362
|
-
const eng = new Engine({}, { sessionsDir: dir, emit: () => {} });
|
|
363
|
-
eng._bgLimit = 0; // yapıcı min 1 kelepçeler; sıfırla → her iş queued kalır
|
|
364
|
-
eng.send = () => true;
|
|
365
|
-
const r = eng.runBackground('pq2', 'kuyruk işi', 'Kuyruk İş');
|
|
366
|
-
await sleep(80); // admit → bgLimit 0 → queued
|
|
367
|
-
assert.strictEqual(eng._bgJobs.get(r.backgroundId).status, 'queued');
|
|
368
|
-
|
|
369
|
-
assert.strictEqual(eng.interrupt(r.backgroundId, 'sıra iptal edildi'), true);
|
|
370
|
-
const j = eng._bgJobs.get(r.backgroundId);
|
|
371
|
-
assert.strictEqual(j.status, 'aborted');
|
|
372
|
-
assert.strictEqual(j.error, 'sıra iptal edildi', 'kuyruktaki işin iptal sebebi kayda geçti');
|
|
373
|
-
eng.deleteSession(r.backgroundId);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
test('task_cancel reason ZORUNLU: sebep iş kaydına ve note mesajına düşer', async () => {
|
|
377
|
-
const { eng } = tmpEngine();
|
|
378
|
-
const r = eng.runBackground('pc', 'iş', 'İptal Edilen');
|
|
379
|
-
const id = r.backgroundId;
|
|
380
|
-
const c = new AbortController();
|
|
381
|
-
eng.ctrls.set(id, c);
|
|
382
|
-
|
|
383
|
-
const out = JSON.parse(await eng._execTool('task_cancel', { id, reason: 'yanlış kaynağa baktı' }, {}));
|
|
384
|
-
assert.ok(out.ok, 'cancel başarılı');
|
|
385
|
-
assert.ok(/yanlış kaynağa baktı/.test(out.note), 'note ceza sebep içerir');
|
|
386
|
-
assert.strictEqual(eng._abortReasons.get(id), 'yanlış kaynağa baktı');
|
|
387
|
-
assert.ok(c.signal.aborted);
|
|
388
|
-
|
|
389
|
-
/* reason verilmezse varsayılan sebep yine kayda düşer */
|
|
390
|
-
const id2 = eng.runBackground('pc', 'iş 2', 'İptal 2').backgroundId;
|
|
391
|
-
eng.ctrls.set(id2, new AbortController());
|
|
392
|
-
const out2 = JSON.parse(await eng._execTool('task_cancel', { id: id2 }, {}));
|
|
393
|
-
assert.ok(out2.ok);
|
|
394
|
-
assert.ok(eng._abortReasons.get(id2).includes('task_cancel'), 'varsayılan sebep CEO kaynağını söyler');
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
/* ---------- /stop anahtarı ---------- */
|
|
398
|
-
test('stopAll: koşan turları keser, paralel ajanı aborted işaretler', async () => { const { eng } = tmpEngine();
|
|
399
|
-
const r = eng.runBackground('pS', 'koşan iş', 'Koşu');
|
|
400
|
-
const id = r.backgroundId;
|
|
401
|
-
|
|
402
|
-
/* sahte koşan tur: ctrls'e AbortController koy */
|
|
403
|
-
const c1 = new AbortController();
|
|
404
|
-
const c2 = new AbortController();
|
|
405
|
-
eng.ctrls.set(id, c1); // paralel ajan
|
|
406
|
-
eng.ctrls.set('main-sid', c2); // masaüstü oturumu
|
|
407
|
-
/* bekleyen rapor kuyruğu da dolu olsun */
|
|
408
|
-
eng._pendingReports = [{ parentId: 'pS', text: 'eski rapor' }];
|
|
409
|
-
|
|
410
|
-
const n = eng.stopAll();
|
|
411
|
-
|
|
412
|
-
assert.strictEqual(n, 2);
|
|
413
|
-
assert.ok(c1.signal.aborted && c2.signal.aborted);
|
|
414
|
-
assert.strictEqual(eng._bgJobs.get(id).status, 'aborted');
|
|
415
|
-
assert.ok(eng._bgJobs.get(id).endedAt);
|
|
416
|
-
assert.strictEqual(eng._pendingReports.length, 0);
|
|
417
|
-
/* events'te agents yayını var */
|
|
418
|
-
assert.ok(eng.listBgJobs()[0].status === 'aborted');
|
|
419
|
-
|
|
420
|
-
/* ikinci çağrı idempotent: gerçek akışta _run finally ctrl'i siler */
|
|
421
|
-
eng.ctrls.delete(id);
|
|
422
|
-
eng.ctrls.delete('main-sid');
|
|
423
|
-
assert.strictEqual(eng.stopAll(), 0);
|
|
424
|
-
});
|