beast-agent 2.19.1 → 2.21.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 +1 -1
- package/src/agent/customtools.js +242 -0
- package/src/agent/engine.js +47 -12
- package/src/agent/skills.js +83 -1
- package/src/main.js +47 -6
- package/src/preload.js +5 -0
- package/src/renderer/index.html +56 -2
- package/src/renderer/renderer.js +340 -42
- package/src/renderer/style.css +285 -4
package/package.json
CHANGED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* KİŞİSEL TOOLLAR: %APPDATA%\beast\tools\<slug>\tool.json + run.js
|
|
4
|
+
Kullanıcı (ya da ajan) Node ile koşan kendi araçlarını yazar; TÜM oturumlar
|
|
5
|
+
— BEAST FINANCE dahil — bunları model'e açılan gerçek araç olarak çağırır
|
|
6
|
+
(tool__<slug>). Sözleşme:
|
|
7
|
+
- stdin : JSON argümanlar
|
|
8
|
+
- stdout : JSON sonuç (son satır JSON'a çevrilmeye çalışılır)
|
|
9
|
+
- çalışma klasörü: tool'un kendi klasörü (%APPDATA%\beast\tools\<slug>)
|
|
10
|
+
Sonuç model'e { ok, ... } olarak döner; çocuk proses izoledir (uygulama
|
|
11
|
+
çökmez), 90 sn zaman aşımı vardır. */
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { spawn } = require('child_process');
|
|
16
|
+
const { beastRoot } = require('./memory');
|
|
17
|
+
|
|
18
|
+
let notify = () => {};
|
|
19
|
+
function setNotify(fn) {
|
|
20
|
+
notify = typeof fn === 'function' ? fn : () => {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function dir() {
|
|
24
|
+
return path.join(beastRoot(), 'tools');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function slugify(name) {
|
|
28
|
+
return String(name || '')
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/[^a-z0-9_-]+/g, '_')
|
|
31
|
+
.replace(/^_+|_+$/g, '')
|
|
32
|
+
.slice(0, 24);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let cache = null;
|
|
36
|
+
let dirty = true;
|
|
37
|
+
|
|
38
|
+
function invalidate() {
|
|
39
|
+
dirty = true;
|
|
40
|
+
cache = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readTool(d) {
|
|
44
|
+
try {
|
|
45
|
+
const json = JSON.parse(fs.readFileSync(path.join(d, 'tool.json'), 'utf8'));
|
|
46
|
+
if (!json || typeof json !== 'object' || !json.name) return null;
|
|
47
|
+
const runJs = path.join(d, 'run.js');
|
|
48
|
+
if (!fs.existsSync(runJs)) return null;
|
|
49
|
+
return {
|
|
50
|
+
id: path.basename(d),
|
|
51
|
+
name: String(json.name).slice(0, 40),
|
|
52
|
+
description: String(json.description || '').slice(0, 300),
|
|
53
|
+
parameters:
|
|
54
|
+
json.parameters && typeof json.parameters === 'object'
|
|
55
|
+
? json.parameters
|
|
56
|
+
: { type: 'object', properties: {} },
|
|
57
|
+
script: runJs,
|
|
58
|
+
code: fs.readFileSync(runJs, 'utf8').slice(0, 20000),
|
|
59
|
+
updatedAt: json.updatedAt || null,
|
|
60
|
+
};
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function scan() {
|
|
67
|
+
const out = [];
|
|
68
|
+
try {
|
|
69
|
+
for (const e of fs.readdirSync(dir(), { withFileTypes: true })) {
|
|
70
|
+
if (!e.isDirectory()) continue;
|
|
71
|
+
const t = readTool(path.join(dir(), e.name));
|
|
72
|
+
if (t) out.push(t);
|
|
73
|
+
}
|
|
74
|
+
} catch {}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function list() {
|
|
79
|
+
return scan();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* engine _chatTurn'e OpenAI function formatında şemalar */
|
|
83
|
+
function definitions() {
|
|
84
|
+
if (dirty || !cache) {
|
|
85
|
+
cache = scan();
|
|
86
|
+
dirty = false;
|
|
87
|
+
}
|
|
88
|
+
return cache.map((t) => ({
|
|
89
|
+
type: 'function',
|
|
90
|
+
function: {
|
|
91
|
+
name: 'tool__' + t.id,
|
|
92
|
+
description:
|
|
93
|
+
(t.description || 'kişisel tool') + ` (kişisel tool — args stdin JSON, sonuç stdout JSON)`,
|
|
94
|
+
parameters: t.parameters,
|
|
95
|
+
},
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function names() {
|
|
100
|
+
if (dirty || !cache) {
|
|
101
|
+
cache = scan();
|
|
102
|
+
dirty = false;
|
|
103
|
+
}
|
|
104
|
+
return cache.map((t) => 'tool__' + t.id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const TOOL_TIMEOUT_MS = 90 * 1000;
|
|
108
|
+
|
|
109
|
+
/* Tool'u çocuk node prosesinde koşturur — uygulama asla çökmez */
|
|
110
|
+
function call(name, args) {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const id = String(name || '').replace(/^tool__/, '');
|
|
113
|
+
if (!id) return resolve({ ok: false, error: 'tool adı gerekli' });
|
|
114
|
+
const t = scan().find((x) => x.id === id);
|
|
115
|
+
if (!t) return resolve({ ok: false, error: 'tool bulunamadı: ' + id });
|
|
116
|
+
const t0 = Date.now();
|
|
117
|
+
let child = null;
|
|
118
|
+
let out = '';
|
|
119
|
+
let err = '';
|
|
120
|
+
let done = false;
|
|
121
|
+
const finish = (r) => {
|
|
122
|
+
if (done) return;
|
|
123
|
+
done = true;
|
|
124
|
+
clearTimeout(killer);
|
|
125
|
+
try {
|
|
126
|
+
notify({
|
|
127
|
+
at: new Date().toISOString(),
|
|
128
|
+
tool: t.name,
|
|
129
|
+
toolId: id,
|
|
130
|
+
ok: !!r.ok,
|
|
131
|
+
ms: Date.now() - t0,
|
|
132
|
+
error: r && r.error ? String(r.error).slice(0, 200) : null,
|
|
133
|
+
});
|
|
134
|
+
} catch {}
|
|
135
|
+
resolve(r);
|
|
136
|
+
};
|
|
137
|
+
let killer = setTimeout(() => {
|
|
138
|
+
try { child && child.kill(); } catch {}
|
|
139
|
+
finish({ ok: false, error: 'tool zaman aşımı (' + Math.round(TOOL_TIMEOUT_MS / 1000) + ' sn)' });
|
|
140
|
+
}, TOOL_TIMEOUT_MS);
|
|
141
|
+
try {
|
|
142
|
+
child = spawn(process.execPath, [t.script], { cwd: t.path, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return finish({ ok: false, error: String((e && e.message) || e) });
|
|
145
|
+
}
|
|
146
|
+
child.stdout.on('data', (d) => { out += d; });
|
|
147
|
+
child.stderr.on('data', (d) => { err += d; });
|
|
148
|
+
child.on('error', (e) => finish({ ok: false, error: String((e && e.message) || e) }));
|
|
149
|
+
child.on('close', (code) => {
|
|
150
|
+
const raw = String(out || '').trim();
|
|
151
|
+
let data = null;
|
|
152
|
+
try { data = raw ? JSON.parse(raw) : null; } catch {}
|
|
153
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data) || typeof data.ok !== 'boolean') {
|
|
154
|
+
data = { ok: code === 0, result: data === null ? raw.slice(0, 4000) : data };
|
|
155
|
+
}
|
|
156
|
+
if (code !== 0) {
|
|
157
|
+
if (data.ok !== false) data.ok = false;
|
|
158
|
+
if (!data.error) data.error = String(err || '').slice(0, 400) || 'çıkış kodu ' + code;
|
|
159
|
+
}
|
|
160
|
+
finish(data);
|
|
161
|
+
});
|
|
162
|
+
try {
|
|
163
|
+
child.stdin.write(JSON.stringify(args || {}));
|
|
164
|
+
child.stdin.end();
|
|
165
|
+
} catch {}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function save({ id, name, description, parameters, code }) {
|
|
170
|
+
const slug = id && /^[a-z0-9_-]{2,24}$/.test(String(id)) ? String(id) : slugify(name);
|
|
171
|
+
if (!slug) return { ok: false, error: 'tool adı gerekli (a-z 0-9 _ -)' };
|
|
172
|
+
const d = path.join(dir(), slug);
|
|
173
|
+
try {
|
|
174
|
+
fs.mkdirSync(d, { recursive: true });
|
|
175
|
+
fs.writeFileSync(
|
|
176
|
+
path.join(d, 'tool.json'),
|
|
177
|
+
JSON.stringify(
|
|
178
|
+
{
|
|
179
|
+
name: String(name || slug).slice(0, 40),
|
|
180
|
+
description: String(description || '').slice(0, 300),
|
|
181
|
+
parameters:
|
|
182
|
+
parameters && typeof parameters === 'object' && !Array.isArray(parameters)
|
|
183
|
+
? parameters
|
|
184
|
+
: { type: 'object', properties: {} },
|
|
185
|
+
updatedAt: new Date().toISOString(),
|
|
186
|
+
},
|
|
187
|
+
null,
|
|
188
|
+
2
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
const js = String(code || '').trim();
|
|
192
|
+
if (!js) return { ok: false, error: 'run.js kodu boş olamaz' };
|
|
193
|
+
fs.writeFileSync(path.join(d, 'run.js'), js.slice(0, 20000));
|
|
194
|
+
invalidate();
|
|
195
|
+
return { ok: true, id: slug };
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function remove(id) {
|
|
202
|
+
const slug = String(id || '');
|
|
203
|
+
if (!/^[a-z0-9_-]{2,24}$/.test(slug)) return { ok: false, error: 'geçersiz tool kimliği' };
|
|
204
|
+
try {
|
|
205
|
+
fs.rmSync(path.join(dir(), slug), { recursive: true, force: true });
|
|
206
|
+
invalidate();
|
|
207
|
+
return { ok: true };
|
|
208
|
+
} catch (e) {
|
|
209
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* Örnek tool: klasör boşsa biçimi göstermek için bir adet kur */
|
|
214
|
+
function seedIfEmpty() {
|
|
215
|
+
try {
|
|
216
|
+
if (scan().length) return;
|
|
217
|
+
save({
|
|
218
|
+
name: 'ornek_echo',
|
|
219
|
+
description:
|
|
220
|
+
'Örnek kişisel tool — gelen argümanları geri döndürür. Bu klasörü kopyalayarak kendi toollarını yaz; agent finance dahil tüm oturumlarda tool__<ad> olarak çağırır.',
|
|
221
|
+
parameters: {
|
|
222
|
+
type: 'object',
|
|
223
|
+
properties: {
|
|
224
|
+
mesaj: { type: 'string', description: 'geri döndürülecek deneme mesajı' },
|
|
225
|
+
},
|
|
226
|
+
required: ['mesaj'],
|
|
227
|
+
},
|
|
228
|
+
code:
|
|
229
|
+
"'use strict';\n" +
|
|
230
|
+
"// KİŞİSEL TOOL — stdin: JSON args · stdout: JSON sonuç\n" +
|
|
231
|
+
"// Bu klasörü kopyalayıp kendi araçlarını yaz (örn. MT5 emir köprüsü, özel API çağrısı…)\n" +
|
|
232
|
+
"const fs = require('fs');\n" +
|
|
233
|
+
"let raw = '';\n" +
|
|
234
|
+
"try { raw = fs.readFileSync(0, 'utf8'); } catch {}\n" +
|
|
235
|
+
"let args = {};\n" +
|
|
236
|
+
"try { args = JSON.parse(raw || '{}'); } catch {}\n" +
|
|
237
|
+
"console.log(JSON.stringify({ ok: true, echo: args, not: 'run.js ve tool.json düzenlenebilir' }));\n",
|
|
238
|
+
});
|
|
239
|
+
} catch {}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = { setNotify, list, scan, definitions, names, call, save, remove, seedIfEmpty, invalidate, dir, slugify };
|
package/src/agent/engine.js
CHANGED
|
@@ -13,6 +13,7 @@ const tools = require('./tools');
|
|
|
13
13
|
let financetools = null;
|
|
14
14
|
try { financetools = require('./financetools'); } catch {}
|
|
15
15
|
const FINANCE_TOOL_SET = new Set(financetools ? financetools.NAMES : []);
|
|
16
|
+
const customtools = require('./customtools');
|
|
16
17
|
const research = require('./research');
|
|
17
18
|
const agentdefs = require('./agentdefs');
|
|
18
19
|
const memory = require('./memory');
|
|
@@ -1232,7 +1233,14 @@ class Engine {
|
|
|
1232
1233
|
let r;
|
|
1233
1234
|
try { r = JSON.parse(l); } catch { continue; }
|
|
1234
1235
|
if (r.t === 'meta') out.push(JSON.stringify({ t: 'meta', id: s.id, code: s.code, createdAt: s.createdAt }));
|
|
1235
|
-
|
|
1236
|
+
/* KALICİ ETİKETLER compact'ta ASLA düşmez — özellikle 'fin'/'finoff':
|
|
1237
|
+
silinirse finance oturumu etiketini KAYBEDER ve mt5_* araçları
|
|
1238
|
+
"unknown tool" olur (bayat compact kaybı bu yüzden) */
|
|
1239
|
+
else if (
|
|
1240
|
+
r.t === 'meta2' || r.t === 'bot' || r.t === 'todo' ||
|
|
1241
|
+
r.t === 'fin' || r.t === 'finoff' ||
|
|
1242
|
+
r.t === 'agent' || r.t === 'bcws' || r.t === 'botdm'
|
|
1243
|
+
) out.push(l);
|
|
1236
1244
|
}
|
|
1237
1245
|
if (lastNotesLine) out.push(lastNotesLine);
|
|
1238
1246
|
/* korunan pencere: son N mesaj */
|
|
@@ -1419,7 +1427,12 @@ class Engine {
|
|
|
1419
1427
|
const kept = lines.filter((l) => {
|
|
1420
1428
|
try {
|
|
1421
1429
|
const r = JSON.parse(l);
|
|
1422
|
-
|
|
1430
|
+
/* kalıcı etiketler korunur (fin/finoff dahil — finance bayrağı kaybolmasın) */
|
|
1431
|
+
return (
|
|
1432
|
+
r.t === 'meta' || r.t === 'meta2' || r.t === 'bot' || r.t === 'todo' ||
|
|
1433
|
+
r.t === 'fin' || r.t === 'finoff' ||
|
|
1434
|
+
r.t === 'agent' || r.t === 'bcws' || r.t === 'botdm'
|
|
1435
|
+
);
|
|
1423
1436
|
} catch { return false; }
|
|
1424
1437
|
});
|
|
1425
1438
|
const tmp = file + '.tmp';
|
|
@@ -3453,6 +3466,9 @@ class Engine {
|
|
|
3453
3466
|
toolsList = await mcp.mergeTools(toolsList);
|
|
3454
3467
|
/* Beast Apps: kurulu app'lerin araçları (app__<id>__<tool>) modele açılır */
|
|
3455
3468
|
toolsList = apps.mergeTools(toolsList);
|
|
3469
|
+
/* KİŞİSEL TOOLLAR: %APPDATA%\beast\tools\<ad>\ — kullanıcı yazmışsa
|
|
3470
|
+
TÜM oturumlara (BEAST FINANCE dahil) tool__<ad> olarak açılır */
|
|
3471
|
+
toolsList = [...toolsList, ...customtools.definitions()];
|
|
3456
3472
|
/* Beast Finance: skill aracı da açık — kataloğun SKILL.md gövdesi
|
|
3457
3473
|
modele açılır (skill handler'ı zaten tüm oturumlar için çalışır,
|
|
3458
3474
|
definition yalnız BC'de vardı; finance chat copilot + trader ikisi de)
|
|
@@ -3464,8 +3480,13 @@ class Engine {
|
|
|
3464
3480
|
if (skillDef) toolsList = [...toolsList, skillDef];
|
|
3465
3481
|
}
|
|
3466
3482
|
/* AJAN DM: koşan ajan oturumlarına (arka plan işleri + finance
|
|
3467
|
-
ajanları)
|
|
3468
|
-
|
|
3483
|
+
ajanları) VE ana sohbete verilir — chat Beast'i de ajana DM atabilir;
|
|
3484
|
+
müşteri bot oturumları (botId ≠ beast) ve botlar arası DM hariç */
|
|
3485
|
+
if (
|
|
3486
|
+
session &&
|
|
3487
|
+
!session.isBotDm &&
|
|
3488
|
+
(session.bgJob || session.finance || !session.botId || session.botId === 'beast')
|
|
3489
|
+
) {
|
|
3469
3490
|
toolsList = [...toolsList, AGENT_DM_DEF];
|
|
3470
3491
|
}
|
|
3471
3492
|
/* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
|
|
@@ -3890,6 +3911,8 @@ class Engine {
|
|
|
3890
3911
|
// Paralel yürütme — bağımsız çağrılar beklemesin
|
|
3891
3912
|
session.toolsSinceReflect = (session.toolsSinceReflect || 0) + res.toolCalls.length;
|
|
3892
3913
|
if (!this._knownToolNames) this._knownToolNames = new Set([...TOOLS.map((t) => t.function.name), ...opencode.toolmap.names()]);
|
|
3914
|
+
/* kişisel tool sonradan yazılabilir — isim havuzunu canlı tazele */
|
|
3915
|
+
for (const n of customtools.names()) this._knownToolNames.add(n);
|
|
3893
3916
|
await Promise.all(
|
|
3894
3917
|
res.toolCalls.map(async (tc) => {
|
|
3895
3918
|
let name = tc.function && tc.function.name;
|
|
@@ -4765,6 +4788,10 @@ const skills = require('./skills');
|
|
|
4765
4788
|
if (name === 'agent_dm') {
|
|
4766
4789
|
return JSON.stringify(this._agentDmSend(sessionId, args));
|
|
4767
4790
|
}
|
|
4791
|
+
/* KİŞİSEL TOOL: %APPDATA%\beast\tools\ — çocuk node prosesinde izole koşar */
|
|
4792
|
+
if (String(name).startsWith('tool__')) {
|
|
4793
|
+
return JSON.stringify(await customtools.call(name, args));
|
|
4794
|
+
}
|
|
4768
4795
|
if (isBc) {
|
|
4769
4796
|
/* opencode permission akışı (tool/external-directory.ts + her aracın
|
|
4770
4797
|
ctx.ask'ı): önce workspace DIŞI erişim izni, sonra araç izni.
|
|
@@ -4796,7 +4823,9 @@ const skills = require('./skills');
|
|
|
4796
4823
|
"always" onaylı araçlar doğrudan geçer. (BC dışı oturumlar — opencode
|
|
4797
4824
|
izin sistemi yalnız Beast Code oturumlarında koşar) */
|
|
4798
4825
|
if (
|
|
4799
|
-
(Engine.RISKY_TOOLS.has(name) ||
|
|
4826
|
+
(Engine.RISKY_TOOLS.has(name) ||
|
|
4827
|
+
String(name).startsWith('mcp__') ||
|
|
4828
|
+
String(name).startsWith('tool__')) && /* kişisel tool = kullanıcı kodu koşturur */
|
|
4800
4829
|
!this.alwaysAllowTools.has(name) &&
|
|
4801
4830
|
this.approvals && typeof this.approvals.request === 'function'
|
|
4802
4831
|
) {
|
|
@@ -5288,23 +5317,25 @@ function emitSafe(engine, sessionId, ev) {
|
|
|
5288
5317
|
|
|
5289
5318
|
/* AJAN DM aracı: paralel/finance ajanlarının birbirine kısa mesaj atması.
|
|
5290
5319
|
Salt metin — hedef ajanın oturumuna [AJAN DM] bloğu olarak düşer; AJAN DM
|
|
5291
|
-
panelinde iki yönlü görünür.
|
|
5320
|
+
panelinde iki yönlü görünür. topic verirsen aynı konudaki mesajlar aynı
|
|
5321
|
+
oturumda toplanır (cevap zinciri tek thread'de devam eder). */
|
|
5292
5322
|
const AGENT_DM_DEF = {
|
|
5293
5323
|
type: 'function',
|
|
5294
5324
|
function: {
|
|
5295
5325
|
name: 'agent_dm',
|
|
5296
5326
|
description:
|
|
5297
|
-
'Send a short DM to another
|
|
5327
|
+
'Send a short DM to another running agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). `topic` = short subject label — replies to the same topic stay in the SAME conversation thread, so ALWAYS reuse the topic you were DMed with when replying.',
|
|
5298
5328
|
parameters: {
|
|
5299
5329
|
type: 'object',
|
|
5300
5330
|
properties: {
|
|
5301
5331
|
to: { type: 'string', description: 'Target agent session id or a title keyword' },
|
|
5332
|
+
topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
|
|
5302
5333
|
message: { type: 'string', description: 'Short message (1-3 sentences)' },
|
|
5303
5334
|
},
|
|
5304
5335
|
required: ['to', 'message'],
|
|
5305
5336
|
},
|
|
5306
5337
|
},
|
|
5307
|
-
};
|
|
5338
|
+
};
|
|
5308
5339
|
|
|
5309
5340
|
const TOOLS = [
|
|
5310
5341
|
...tools.definitions,
|
|
@@ -6121,12 +6152,15 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
|
|
|
6121
6152
|
if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
|
|
6122
6153
|
const fromJob = jobs.get(String(fromSid));
|
|
6123
6154
|
const toJob = jobs.get(target);
|
|
6155
|
+
/* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
|
|
6156
|
+
const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
|
|
6124
6157
|
const dm = {
|
|
6125
6158
|
at: new Date().toISOString(),
|
|
6126
6159
|
from: String(fromSid),
|
|
6127
6160
|
fromTitle: (fromJob && fromJob.title) || 'Ajan',
|
|
6128
6161
|
to: String(target),
|
|
6129
6162
|
toTitle: (toJob && toJob.title) || 'Ajan',
|
|
6163
|
+
topic,
|
|
6130
6164
|
text,
|
|
6131
6165
|
};
|
|
6132
6166
|
this._agentDms = this._agentDms || [];
|
|
@@ -6136,15 +6170,16 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
|
|
|
6136
6170
|
fs.writeFileSync(this._agentDmsFile, JSON.stringify({ dms: this._agentDms }, null, 2));
|
|
6137
6171
|
} catch {}
|
|
6138
6172
|
emitSafe(this, target, { type: 'agent-dm', ...dm });
|
|
6139
|
-
/* teslim: hedef meşgulse pending kuyruğunda bekler, done olunca düşer
|
|
6173
|
+
/* teslim: hedef meşgulse pending kuyruğunda bekler, done olunca düşer.
|
|
6174
|
+
Cevap zinciri AYNI thread'den sürsün diye topic geri bildirilir. */
|
|
6140
6175
|
(this._pendingReports = this._pendingReports || []).push({
|
|
6141
6176
|
parentId: target,
|
|
6142
6177
|
text:
|
|
6143
|
-
`[AJAN DM — ${dm.fromTitle}]\n${text}\n` +
|
|
6144
|
-
`(Cevabını agent_dm aracıyla ver — to: "${dm.fromTitle}")`,
|
|
6178
|
+
`[AJAN DM — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
|
|
6179
|
+
`(Cevabını agent_dm aracıyla ver — to: "${dm.fromTitle}", topic: "${topic}")`,
|
|
6145
6180
|
});
|
|
6146
6181
|
this.flushPendingReports(target);
|
|
6147
|
-
return { ok: true, to: target, toTitle: dm.toTitle };
|
|
6182
|
+
return { ok: true, to: target, toTitle: dm.toTitle, topic };
|
|
6148
6183
|
} catch (e) {
|
|
6149
6184
|
return { ok: false, error: String((e && e.message) || e) };
|
|
6150
6185
|
}
|
package/src/agent/skills.js
CHANGED
|
@@ -129,7 +129,8 @@ Get-Content "$env:APPDATA\\beast\\sessions\\bg-jobs.json" | ConvertFrom-Json | %
|
|
|
129
129
|
| \`agent/agentdefs.js\` | Özel ajan tanımları: %APPDATA%\beast\agents\\*.md (frontmatter: model/tools/steps/mode + prompt gövdesi); /agent ile bağlanır, run_background(agent:) ile paralel ajana verilir |
|
|
130
130
|
| \`agent/searxng.js\` | SearXNG entegrasyonu: gömülü Python'a kurulum + 127.0.0.1:8888 başlatıcı + JSON arama motoru |
|
|
131
131
|
| \`agent/bots.js\` | Bot CRUD, 5 haneli bot kodu, izolasyon; ad-ilk-harf doğrulaması |
|
|
132
|
-
| \`agent/skills.js\` | Builtin tohumlar (help, email, python-web-search, pdf, gold-trading + superpowers metodoloji paketi: brainstorming/writing-plans/executing-plans/TDD/paralel-ajan/alt-ajan-geliştirme/sistematik-debugging/doğrulama/skill-yazma) + SKILL.md taraması + taslaklar |
|
|
132
|
+
| \`agent/skills.js\` | Builtin tohumlar (help, email, python-web-search, pdf, gold-trading, tool-yazma + superpowers metodoloji paketi: brainstorming/writing-plans/executing-plans/TDD/paralel-ajan/alt-ajan-geliştirme/sistematik-debugging/doğrulama/skill-yazma) + SKILL.md taraması + taslaklar |
|
|
133
|
+
| \`agent/customtools.js\` | Kişisel toollar: %APPDATA%\\beast\\tools\\<ad>\\tool.json + run.js (stdin JSON → stdout JSON) — tool__<ad> olarak TÜM oturumlara açılır; sol panel TOOLLAR kutusunda yazılır/test edilir. Yeni tool yazarken \`tool-yazma\` skill'ini oku. |
|
|
133
134
|
| \`agent/memory.js\`, \`agent/kb.js\` | Kalıcı hafıza + bilgi bankası |
|
|
134
135
|
| \`cron.js\` (src kökü), \`agent/watchers.js\`, \`agent/bus.js\` | Cron, izleyici, olay merkezi — /stop bunlara DOKUNMAZ |
|
|
135
136
|
| \`agent/usage.js\` | Token/maliyet sayaçları |
|
|
@@ -456,6 +457,87 @@ const { createWorker } = require('tesseract.js');
|
|
|
456
457
|
| read_file boş metin döndü | taranmış PDF → 4. adım (görselleştir) + 5. adım (OCR) |
|
|
457
458
|
| tesseract dil verisi hatası | ilk indirme internet ister; tekrar dene |`,
|
|
458
459
|
},
|
|
460
|
+
{
|
|
461
|
+
folder: 'tool-yazma',
|
|
462
|
+
force: true,
|
|
463
|
+
body: `---
|
|
464
|
+
name: tool-yazma
|
|
465
|
+
description: Kişisel TOOL yazma/düzenleme rehberi — %APPDATA%\\beast\\tools\\<ad>\\ altına tool.json + run.js yazılacak veya mevcut bir tool editlenecekse ÖNCE bu skill'i oku. Tool'un sözleşmesi: stdin JSON argüman → stdout JSON sonuç; ajan tool__<ad> olarak çağırır (Beast Finance dahil tüm oturumlarda çalışır).
|
|
466
|
+
version: 1.0.0
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
# Kişisel TOOL Yazma
|
|
470
|
+
|
|
471
|
+
## TOOL NEDİR / NE ZAMAN YAZILIR
|
|
472
|
+
|
|
473
|
+
Kişisel tool = modelin alet çantasına GİREN gerçek bir araçtır: %APPDATA%\\beast\\tools\\<slug>\\ klasöründe **tool.json** (tanım) + **run.js** (kod) bulunur. Tüm oturumlarda — Beast Finance chat/trader/işçiler dahil — \`tool__<slug>\` adıyla çağrılır.
|
|
474
|
+
|
|
475
|
+
Yaz ZAMAN: mevcut araçlar işi görmüyorsa (örn. broker sembol adlandırması mt5_trade'i düşürüyorsa), özel bir API'ye bağlanman, tekrarlı bir hesap/işlem rutini gerekiyorsa. Basit işler için YAZMA — önce yerleşik araçları (mt5_*, web_search, python_run, run_command) dene.
|
|
476
|
+
|
|
477
|
+
## SÖZLEŞME (birebir uygula)
|
|
478
|
+
|
|
479
|
+
- **stdin**: JSON argümanlar (tek satırda, güvenli parse)
|
|
480
|
+
- **stdout**: SADECE JSON sonuç — son satır JSON'a çevrilmeye çalışılır; günlük/metin yazarsan JSON bozulur, \`console.error\` kullan
|
|
481
|
+
- **çalışma klasörü**: tool'un kendi klasörü (yanına dosya yazabilirsin)
|
|
482
|
+
- **zaman aşımı**: 90 sn — uzun işleri kendin parçalama, \`run_background\`'a devret
|
|
483
|
+
- **sonuç biçimi**: \`{ "ok": true, ... }\` ya da \`{ "ok": false, "error": "sebep" }\` — ok alanı ZORUNLU
|
|
484
|
+
|
|
485
|
+
## YENİ TOOL OLUŞTURMA (2 dosya, write_file ile)
|
|
486
|
+
|
|
487
|
+
\`%APPDATA%\\beast\\tools\\<slug>\\tool.json\`:
|
|
488
|
+
\`\`\`json
|
|
489
|
+
{
|
|
490
|
+
"name": "gold_emir",
|
|
491
|
+
"description": "GOLD için özel emir açar — side (buy/sell), lot, sl, tp alır; sonucu ticket ile döndürür.",
|
|
492
|
+
"parameters": {
|
|
493
|
+
"type": "object",
|
|
494
|
+
"properties": {
|
|
495
|
+
"side": { "type": "string", "description": "buy | sell" },
|
|
496
|
+
"lot": { "type": "number", "description": "0.01-1.0" },
|
|
497
|
+
"sl": { "type": "number" },
|
|
498
|
+
"tp": { "type": "number" }
|
|
499
|
+
},
|
|
500
|
+
"required": ["side", "lot"]
|
|
501
|
+
},
|
|
502
|
+
"updatedAt": "2026-09-09T00:00:00.000Z"
|
|
503
|
+
}
|
|
504
|
+
\`\`\`
|
|
505
|
+
|
|
506
|
+
\`%APPDATA%\\beast\\tools\\<slug>\\run.js\`:
|
|
507
|
+
\`\`\`javascript
|
|
508
|
+
'use strict';
|
|
509
|
+
// stdin: JSON args · stdout: JSON sonuç (SADECE JSON yaz!)
|
|
510
|
+
const fs = require('fs');
|
|
511
|
+
let args = {};
|
|
512
|
+
try { args = JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); } catch {}
|
|
513
|
+
(function main() {
|
|
514
|
+
const { side, lot, sl, tp } = args;
|
|
515
|
+
if (!side || !lot) {
|
|
516
|
+
console.log(JSON.stringify({ ok: false, error: 'side ve lot zorunlu' }));
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
// --- kendi işin burada: HTTP çağrısı, MT5 köprüsü, dosya, hesap... ---
|
|
520
|
+
// Örn: https modülüyle özel endpoint'e istek at, sonucu ok alanıyla sar.
|
|
521
|
+
console.log(JSON.stringify({ ok: true, side, lot, sl: sl || 0, tp: tp || 0, ticket: 0 }));
|
|
522
|
+
})();
|
|
523
|
+
\`\`\`
|
|
524
|
+
|
|
525
|
+
Slug kuralları: a-z, 0-9, alt çizgi, tire — 2-24 karakter (örn: gold_emir, haber_ozet). Hazır taslak: %APPDATA%\\beast\\tools\\ornek_echo klasörünü referans al; yeni tool'u o biçimle yaz.
|
|
526
|
+
|
|
527
|
+
## DÜZENLEME / DOĞRULAMA
|
|
528
|
+
|
|
529
|
+
1. Mevcut toolu değiştirme: \`edit_file\` ile tool.json / run.js düzenle — ASLA tüm klasörü silip baştan yazma.
|
|
530
|
+
2. Kayıttan sonra tool ANINDA aktifleşir (yeniden başlatma gerekmez) — bir sonraki turda \`tool__<slug>\` çağrılabilir.
|
|
531
|
+
3. Doğrula: \`run_command\` ile \`echo \\"{\\\\\\"mesaj\\\\\\":\\\\\\"test\\\\\\"}\\" | node \\"%APPDATA%\\\\beast\\\\tools\\\\<slug>\\\\run.js\\"\` — çıktı JSON olmalı ve \`ok\` alanı içermeli.
|
|
532
|
+
4. Hata çıktısı \`ok:false + error\` olsun — model hatayı okuyup düzeltir; çıplak stack yasak.
|
|
533
|
+
|
|
534
|
+
## GÜVENLİK + STİL
|
|
535
|
+
|
|
536
|
+
- Şifre/API anahtarını ASLA stdout'a, log'a, skill'e yazma.
|
|
537
|
+
- Ağ çağrılarında timeout kullan (\`https\` modülü + \`req.setTimeout\` veya fetch); sonsuz bekleyen script yazma.
|
|
538
|
+
- Tek sorumluluk: bir tool = bir iş. Çok işli dev tool yazma — parçala.
|
|
539
|
+
- Kullanıcıya tool'u nasıl yöneteceğini söyle: sol panelde TOOLLAR kutusunda test/düzenle/sil + 📂 klasör.`,
|
|
540
|
+
},
|
|
459
541
|
];
|
|
460
542
|
|
|
461
543
|
function writeSeed(seed) {
|
package/src/main.js
CHANGED
|
@@ -3605,6 +3605,7 @@ app.whenReady().then(() => {
|
|
|
3605
3605
|
reloadBackend();
|
|
3606
3606
|
syncWhitelist(); // bot sistemi: whitelist.json aynası ilk açılışta garanti
|
|
3607
3607
|
try { bots.ensureBotCodes(); } catch {} // her bota benzersiz 5 haneli kod garanti
|
|
3608
|
+
try { customtools.seedIfEmpty(); } catch {} // kişisel toollar klasörü boşsa örnek tool kur
|
|
3608
3609
|
createSplash();
|
|
3609
3610
|
createWindow();
|
|
3610
3611
|
log.info('main', 'Beast Agent başlatıldı');
|
|
@@ -5892,6 +5893,27 @@ ipcMain.handle('agents:cancel', (_e, id) => {
|
|
|
5892
5893
|
/* AJAN DM paneli: ajanlar arası mesaj arşivi */
|
|
5893
5894
|
ipcMain.handle('agent-dms:list', () => (engine ? engine.agentDmsList() : []));
|
|
5894
5895
|
ipcMain.handle('agent-dms:clear', () => (engine ? engine.agentDmsClear() : { ok: false }));
|
|
5896
|
+
|
|
5897
|
+
/* ---------- KİŞİSEL TOOLLAR (%APPDATA%\beast\tools\) ---------- */
|
|
5898
|
+
ipcMain.handle('tools:list', () => customtools.list());
|
|
5899
|
+
ipcMain.handle('tools:save', (_e, tool) => customtools.save(tool || {}));
|
|
5900
|
+
ipcMain.handle('tools:delete', (_e, id) => customtools.remove(id));
|
|
5901
|
+
ipcMain.handle('tools:run', async (_e, payload) => {
|
|
5902
|
+
const id = String((payload && payload.id) || '');
|
|
5903
|
+
const args = (payload && payload.args) || {};
|
|
5904
|
+
const r = await customtools.call('tool__' + id, args);
|
|
5905
|
+
return r;
|
|
5906
|
+
});
|
|
5907
|
+
ipcMain.handle('tools:openFolder', () => {
|
|
5908
|
+
try {
|
|
5909
|
+
const d = customtools.dir();
|
|
5910
|
+
fs.mkdirSync(d, { recursive: true });
|
|
5911
|
+
require('electron').shell.openPath(d);
|
|
5912
|
+
return { ok: true };
|
|
5913
|
+
} catch (e) {
|
|
5914
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
5915
|
+
}
|
|
5916
|
+
});
|
|
5895
5917
|
ipcMain.handle('ceo:get', () => !!engine.ceoMode);
|
|
5896
5918
|
ipcMain.handle('ceo:set', (_e, v) => {
|
|
5897
5919
|
settings.ceoMode = !!v;
|
|
@@ -8273,6 +8295,7 @@ function studioWatchStop() {
|
|
|
8273
8295
|
oturumu, tur tur piyasa tarayıp (farklı API/model seçilebilir) işlem kovalar. */
|
|
8274
8296
|
const mt5bridge = require('./mt5bridge');
|
|
8275
8297
|
const financetools = require('./agent/financetools');
|
|
8298
|
+
const customtools = require('./agent/customtools');
|
|
8276
8299
|
|
|
8277
8300
|
const financeState = {
|
|
8278
8301
|
mode: false, /* chat oturumları finance bayrağı alıyor mu */
|
|
@@ -8337,6 +8360,15 @@ try {
|
|
|
8337
8360
|
});
|
|
8338
8361
|
} catch {}
|
|
8339
8362
|
|
|
8363
|
+
/* KİŞİSEL TOOLLAR: çalışma kaydı renderer'a (TOOLS konsolu buradan beslenir) */
|
|
8364
|
+
try {
|
|
8365
|
+
customtools.setNotify((entry) => {
|
|
8366
|
+
try {
|
|
8367
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'tool-log', ...entry });
|
|
8368
|
+
} catch {}
|
|
8369
|
+
});
|
|
8370
|
+
} catch {}
|
|
8371
|
+
|
|
8340
8372
|
function financeEnsureBridge() {
|
|
8341
8373
|
const f = finCfg();
|
|
8342
8374
|
const candidates = [String(f.pythonPath || '').trim(), 'python', 'py -3'].filter(Boolean);
|
|
@@ -8695,23 +8727,32 @@ ipcMain.handle('finance:snapshot', async () => {
|
|
|
8695
8727
|
ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
8696
8728
|
financeState.mode = !!(payload && payload.on);
|
|
8697
8729
|
const sid = String((payload && payload.sessionId) || '');
|
|
8730
|
+
let needNew = false;
|
|
8698
8731
|
if (sid && engine) {
|
|
8699
8732
|
try {
|
|
8700
8733
|
let s = engine.cache.get(sid);
|
|
8701
8734
|
if (!s) { try { s = engine._load(sid); } catch {} }
|
|
8702
|
-
if (s && !s.
|
|
8735
|
+
if (s && !s.bgJob && (!s.botId || s.botId === 'beast')) {
|
|
8703
8736
|
if (financeState.mode) {
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8737
|
+
if (s.finance) {
|
|
8738
|
+
/* zaten finance oturumu — aynen sürdür */
|
|
8739
|
+
finApplyTraderFields(s);
|
|
8740
|
+
s.financeTrader = false; /* chat copilot'ı — trader değil */
|
|
8741
|
+
} else {
|
|
8742
|
+
/* NORMAL oturum (örn. WhatsApp sohbeti) finance modundayken ASLA
|
|
8743
|
+
finance'e çevrilmez — ayrı bir finance oturumu açılır.
|
|
8744
|
+
(eski davranış WhatsApp oturumunu da finance listesine karıştırıyordu) */
|
|
8745
|
+
needNew = true;
|
|
8746
|
+
}
|
|
8707
8747
|
} else {
|
|
8708
|
-
|
|
8748
|
+
/* mod kapandı: aktif finance oturumu normal sohbete döner */
|
|
8749
|
+
if (s.finance) engine.unmarkFinance(sid);
|
|
8709
8750
|
}
|
|
8710
8751
|
engine.cache.set(sid, s);
|
|
8711
8752
|
}
|
|
8712
8753
|
} catch {}
|
|
8713
8754
|
}
|
|
8714
|
-
return { ok: true, mode: financeState.mode };
|
|
8755
|
+
return { ok: true, mode: financeState.mode, needNew };
|
|
8715
8756
|
});
|
|
8716
8757
|
|
|
8717
8758
|
/* MT5 sembol seçici: terminaldeki tüm semboller (isteğe bağlı *filter*) */
|
package/src/preload.js
CHANGED
|
@@ -22,6 +22,11 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
22
22
|
agentsCancel: (id) => ipcRenderer.invoke('agents:cancel', id),
|
|
23
23
|
agentDmsList: () => ipcRenderer.invoke('agent-dms:list'),
|
|
24
24
|
agentDmsClear: () => ipcRenderer.invoke('agent-dms:clear'),
|
|
25
|
+
toolsList: () => ipcRenderer.invoke('tools:list'),
|
|
26
|
+
toolSave: (t) => ipcRenderer.invoke('tools:save', t),
|
|
27
|
+
toolDelete: (id) => ipcRenderer.invoke('tools:delete', id),
|
|
28
|
+
toolRun: (id, args) => ipcRenderer.invoke('tools:run', { id, args }),
|
|
29
|
+
toolsOpenFolder: () => ipcRenderer.invoke('tools:openFolder'),
|
|
25
30
|
getCeoMode: () => ipcRenderer.invoke('ceo:get'),
|
|
26
31
|
setCeoMode: (v) => ipcRenderer.invoke('ceo:set', v),
|
|
27
32
|
listNotes: () => ipcRenderer.invoke('notes:list'),
|