beast-agent 2.19.0 → 2.20.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.19.0",
4
+ "version": "2.20.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -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 };
@@ -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
- else if (r.t === 'meta2' || r.t === 'bot' || r.t === 'todo') out.push(l);
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
- return r.t === 'meta' || r.t === 'meta2' || r.t === 'bot' || r.t === 'todo';
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ı) ajanlar-arası mesajlaşma aracı verilir */
3468
- if (session && (session.bgJob || (session.finance && session.bgTitle))) {
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) || String(name).startsWith('mcp__')) &&
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
  ) {
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.botId && !s.bgJob) {
8735
+ if (s && !s.bgJob && (!s.botId || s.botId === 'beast')) {
8703
8736
  if (financeState.mode) {
8704
- engine.markFinance(sid, false); /* kalıcı etiket: finance geçmişine girer */
8705
- finApplyTraderFields(s);
8706
- s.financeTrader = false; /* chat copilot'ı — trader değil */
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
- engine.unmarkFinance(sid); /* normal sohbete döner */
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*) */
@@ -8725,7 +8766,17 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
8725
8766
  const p = patch || {};
8726
8767
  if (p.symbols !== undefined) {
8727
8768
  const arr = Array.isArray(p.symbols) ? p.symbols : String(p.symbols).split(/[,\s]+/);
8728
- f.symbols = arr.map((s) => String(s).trim().toUpperCase()).filter(Boolean).slice(0, 20);
8769
+ const next = arr.map((s) => String(s).trim().toUpperCase()).filter(Boolean).slice(0, 20);
8770
+ /* SEMBOL GEÇMİŞİ: eklenen/silinen her sembol hatırlanır — izleme listesi
8771
+ tamamen silinse bile semboller geçmişte kalır, seçicide TEK TIKLA
8772
+ tekrar eklenir (brokere search'e gerek kalmaz). */
8773
+ const hist = Array.isArray(f.symbolHistory) ? f.symbolHistory.slice() : [];
8774
+ for (const s of [...(f.symbols || []), ...next]) {
8775
+ const u = String(s).toUpperCase();
8776
+ if (u && !hist.includes(u)) hist.push(u);
8777
+ }
8778
+ f.symbolHistory = hist.slice(-40);
8779
+ f.symbols = next;
8729
8780
  }
8730
8781
  if (p.intervalSec !== undefined) f.intervalSec = Math.max(30, Math.min(3600, Math.round(Number(p.intervalSec) || 120)));
8731
8782
  if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
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'),
@@ -22,6 +22,16 @@
22
22
  </div>
23
23
  <div id="watchPaneList"></div>
24
24
  </div>
25
+ <div id="toolBox">
26
+ <div id="toolBoxHead">
27
+ <span id="toolBoxTitle">TOOLLAR</span>
28
+ <span class="bot-bar-btns">
29
+ <button id="toolFolderBtn" title="Toollar klasörünü aç (%APPDATA%\beast\tools)"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/></svg></button>
30
+ <button id="toolAdd" title="Yeni tool yaz">+</button>
31
+ </span>
32
+ </div>
33
+ <div id="toolList"></div>
34
+ </div>
25
35
  <div id="botBar">
26
36
  <div id="botBarHead">
27
37
  <span id="botBarTitle" data-i18n="bot_head">BOTLAR</span>
@@ -173,6 +183,7 @@
173
183
  <div class="fin-card-title">
174
184
  PİYASA
175
185
  <span class="file-spacer"></span>
186
+ <button id="finWatchClear" title="İzleme listesini TAMAMEN sil — semboller geçmişe düşer, sonra tek tıkla geri eklenir">&#x2715;&#xFE0E;</button>
176
187
  <button id="finSymAdd" title="Sembol ekle/çıkar — MT5 sembol listesinden seç">&#43;</button>
177
188
  </div>
178
189
  <div id="finSymList"><div class="fin-empty">—</div></div>
@@ -362,10 +373,39 @@
362
373
  </aside>
363
374
  </div>
364
375
 
376
+ <div id="toolOverlay" hidden>
377
+ <div id="toolDialog">
378
+ <div id="toolDialogHead">
379
+ <span id="toolDialogTitle">YENİ TOOL</span>
380
+ <button id="toolClose" title="Kapat">&#x2715;&#xFE0E;</button>
381
+ </div>
382
+ <div class="tool-form">
383
+ <label>Tool adı (ajan tool__<ad> olarak çağırır)</label>
384
+ <input id="toolName" placeholder="örn: gold_emir" spellcheck="false" autocomplete="off" />
385
+ <label>Açıklama — ajan bu metni görüp ne zaman çağıracağını anlar</label>
386
+ <input id="toolDesc" placeholder="örn: GOLD için özel emir açar (side, lot, sl, tp)" spellcheck="false" autocomplete="off" />
387
+ <label>Parametre şeması (JSON)</label>
388
+ <textarea id="toolParams" rows="6" spellcheck="false" placeholder='{"type":"object","properties":{"side":{"type":"string"},"lot":{"type":"number"}},"required":["side"]}'></textarea>
389
+ <label>run.js kodu — stdin: JSON args · stdout: JSON sonuç (console.log)</label>
390
+ <textarea id="toolCode" rows="12" spellcheck="false" placeholder="const args = JSON.parse(...); console.log(JSON.stringify({ ok: true }));"></textarea>
391
+ <div id="toolFormErr" class="sub" style="color:var(--err)" hidden></div>
392
+ </div>
393
+ <div id="toolTestRow">
394
+ <input id="toolTestArgs" placeholder='deneme argümanları (JSON): {"mesaj":"merhaba"}' spellcheck="false" autocomplete="off" />
395
+ <button id="toolTestBtn">&#9654;&#xFE0E; Çalıştır</button>
396
+ </div>
397
+ <pre id="toolTestOut" hidden></pre>
398
+ <div id="toolDialogBtns">
399
+ <button id="toolDeleteBtn" class="tool-danger" hidden>Sil</button>
400
+ <span class="file-spacer"></span>
401
+ <button id="toolSaveBtn" class="tool-primary">Kaydet</button>
402
+ </div>
403
+ </div>
404
+ </div>
405
+
365
406
  <div id="settingsOverlay" hidden>
366
407
  <div id="settingsDialog">
367
- <aside id="setTabs">
368
- <div class="set-tabs-title" data-i18n="set_title">AYARLAR</div>
408
+ <aside id="setTabs"> <div class="set-tabs-title" data-i18n="set_title">AYARLAR</div>
369
409
  <nav class="set-tabs-list">
370
410
  <button class="tab active" data-tab="lang" data-i18n="tab_lang">Dil</button>
371
411
  <button class="tab" data-tab="provider" data-i18n="tab_provider">Provider</button>
@@ -48,6 +48,22 @@ const els = {
48
48
  watchBtn: $('#watchBtn'),
49
49
  watchPaneList: $('#watchPaneList'),
50
50
  watchPaneOpen: $('#watchPaneOpen'),
51
+ toolBoxAdd: $('#toolAdd'),
52
+ toolList: $('#toolList'),
53
+ toolFolderBtn: $('#toolFolderBtn'),
54
+ toolOverlay: $('#toolOverlay'),
55
+ toolClose: $('#toolClose'),
56
+ toolDialogTitle: $('#toolDialogTitle'),
57
+ toolName: $('#toolName'),
58
+ toolDesc: $('#toolDesc'),
59
+ toolParams: $('#toolParams'),
60
+ toolCode: $('#toolCode'),
61
+ toolFormErr: $('#toolFormErr'),
62
+ toolTestArgs: $('#toolTestArgs'),
63
+ toolTestBtn: $('#toolTestBtn'),
64
+ toolTestOut: $('#toolTestOut'),
65
+ toolSaveBtn: $('#toolSaveBtn'),
66
+ toolDeleteBtn: $('#toolDeleteBtn'),
51
67
  watchPaneLog: $('#watchPaneLog'),
52
68
  cronBtn: $('#cronBtn'),
53
69
  watchOverlay: $('#watchOverlay'),
@@ -150,6 +166,7 @@ const els = {
150
166
  finInterval: $('#finInterval'),
151
167
  finMaxLot: $('#finMaxLot'),
152
168
  finSymBtn: $('#finSymBtn'),
169
+ finWatchClear: $('#finWatchClear'),
153
170
  finAllow: $('#finAllow'),
154
171
  finTraderBtn: $('#finTraderBtn'),
155
172
  finTraderDot: $('#finTraderDot'),
@@ -5109,6 +5126,192 @@ if (els.dmClear) {
5109
5126
  });
5110
5127
  }
5111
5128
 
5129
+ /* ---------------- TOOLS konsolu (kişisel araçlar) ----------------
5130
+ %APPDATA%\beast\tools\<ad>\tool.json + run.js — kullanıcı kendi araçlarını
5131
+ yazar; finance dahil TÜM oturumlar tool__<ad> olarak çağırır. Sol panelde
5132
+ buradan görüntülenir, yazılır, test edilir, silinir. */
5133
+
5134
+ const toolUi = {
5135
+ tools: [],
5136
+ editId: null,
5137
+ logRows: [],
5138
+ };
5139
+
5140
+ const TOOL_EXAMPLE_CODE =
5141
+ "'use strict';\n" +
5142
+ "// KİŞİSEL TOOL — stdin: JSON args · stdout: JSON sonuç\n" +
5143
+ "const fs = require('fs');\n" +
5144
+ "let raw = '';\n" +
5145
+ "try { raw = fs.readFileSync(0, 'utf8'); } catch {}\n" +
5146
+ "let args = {};\n" +
5147
+ "try { args = JSON.parse(raw || '{}'); } catch {}\n" +
5148
+ "// Buraya kendi işini yaz (MT5 köprüsü, özel API, hesap, sinyal…)\n" +
5149
+ "console.log(JSON.stringify({ ok: true, echo: args }));\n";
5150
+
5151
+ let toolRenderTimer = null;
5152
+ function scheduleToolRender() {
5153
+ if (toolRenderTimer) return;
5154
+ toolRenderTimer = setTimeout(() => {
5155
+ toolRenderTimer = null;
5156
+ renderToolBox();
5157
+ }, 500);
5158
+ }
5159
+
5160
+ async function renderToolBox() {
5161
+ const list = els.toolList;
5162
+ if (!list) return;
5163
+ try { toolUi.tools = (await beast.toolsList()) || []; } catch { toolUi.tools = []; }
5164
+ list.textContent = '';
5165
+ if (!toolUi.tools.length) {
5166
+ const d = document.createElement('div');
5167
+ d.className = 'tool-empty';
5168
+ d.textContent = 'Tool yok — + ile kendi aracını yaz (Finance dahil tüm oturumlarda çalışır)';
5169
+ list.appendChild(d);
5170
+ return;
5171
+ }
5172
+ for (const t of toolUi.tools) {
5173
+ const row = document.createElement('div');
5174
+ row.className = 'tool-row';
5175
+ const last = toolUi.logRows.filter((l) => l.toolId === t.id).slice(-1)[0];
5176
+ const st = last ? (last.ok ? 'ok' : 'err') : 'idle';
5177
+ const tip = last
5178
+ ? 'son çalışma ' + dmTime(last.at) + (last.error ? ' · hata: ' + last.error : ' · ' + last.ms + ' ms')
5179
+ : 'hiç çalışmadı';
5180
+ row.innerHTML =
5181
+ '<span class="tool-dot ' + st + '" title="' + escapeHtml(tip) + '"></span>' +
5182
+ '<span class="tool-name" title="' + escapeHtml(t.description || '') + '">' + escapeHtml(t.name) + '</span>' +
5183
+ '<span class="tool-btns">' +
5184
+ '<button class="tb-run" title="Aç & test çalıştır">&#9654;&#xFE0E;</button>' +
5185
+ '<button class="tb-edit" title="Düzenle">&#9998;&#xFE0E;</button>' +
5186
+ '<button class="tb-del" title="Sil">&#10005;&#xFE0E;</button>' +
5187
+ '</span>';
5188
+ row.querySelector('.tb-run').addEventListener('click', () => openToolDialog(t));
5189
+ row.querySelector('.tb-edit').addEventListener('click', () => openToolDialog(t));
5190
+ row.querySelector('.tb-del').addEventListener('click', async () => {
5191
+ if (!(await uiConfirm('"' + t.name + '" toolu silinsin mi? (ajanlar artık çağıramaz)', 'Sil'))) return;
5192
+ const r = await beast.toolDelete(t.id).catch(() => ({ ok: false }));
5193
+ if (r && r.ok) {
5194
+ toast('Tool silindi');
5195
+ renderToolBox();
5196
+ } else toast((r && r.error) || 'Silinemedi');
5197
+ });
5198
+ list.appendChild(row);
5199
+ }
5200
+ }
5201
+
5202
+ function openToolDialog(t) {
5203
+ if (!els.toolOverlay) return;
5204
+ toolUi.editId = t ? t.id : null;
5205
+ els.toolDialogTitle.textContent = t ? 'TOOL · ' + t.name : 'YENİ TOOL';
5206
+ els.toolName.value = t ? t.name : '';
5207
+ els.toolDesc.value = t ? t.description || '' : '';
5208
+ els.toolParams.value = t
5209
+ ? JSON.stringify(t.parameters || { type: 'object', properties: {} }, null, 2)
5210
+ : '{\n "type": "object",\n "properties": {},\n "required": []\n}';
5211
+ els.toolCode.value = t ? t.code || TOOL_EXAMPLE_CODE : TOOL_EXAMPLE_CODE;
5212
+ els.toolTestArgs.value = t && t.parameters && Array.isArray(t.parameters.required) && t.parameters.required.length
5213
+ ? JSON.stringify(Object.fromEntries(t.parameters.required.map((k) => [k, ''])), null, 1)
5214
+ : '';
5215
+ els.toolTestOut.hidden = true;
5216
+ els.toolTestOut.textContent = '';
5217
+ els.toolFormErr.hidden = true;
5218
+ els.toolDeleteBtn.hidden = !t;
5219
+ els.toolOverlay.hidden = false;
5220
+ els.toolName.focus();
5221
+ }
5222
+
5223
+ function closeToolDialog() {
5224
+ if (els.toolOverlay) els.toolOverlay.hidden = true;
5225
+ }
5226
+
5227
+ function toolErr(msg) {
5228
+ if (els.toolFormErr) {
5229
+ els.toolFormErr.textContent = msg;
5230
+ els.toolFormErr.hidden = !msg;
5231
+ }
5232
+ }
5233
+
5234
+ if (els.toolBoxAdd) els.toolBoxAdd.addEventListener('click', () => openToolDialog(null));
5235
+ if (els.toolFolderBtn) els.toolFolderBtn.addEventListener('click', () => beast.toolsOpenFolder());
5236
+ if (els.toolClose) els.toolClose.addEventListener('click', closeToolDialog);
5237
+ if (els.toolOverlay) {
5238
+ els.toolOverlay.addEventListener('click', (e) => {
5239
+ if (e.target === els.toolOverlay) closeToolDialog();
5240
+ });
5241
+ els.toolOverlay.addEventListener('keydown', (e) => {
5242
+ if (e.key === 'Escape') { e.stopPropagation(); closeToolDialog(); }
5243
+ });
5244
+ }
5245
+ if (els.toolSaveBtn) {
5246
+ els.toolSaveBtn.addEventListener('click', async () => {
5247
+ toolErr('');
5248
+ const name = String(els.toolName.value || '').trim();
5249
+ if (!name) return toolErr('Tool adı gerekli');
5250
+ let parameters = null;
5251
+ try {
5252
+ parameters = JSON.parse(els.toolParams.value || '{}');
5253
+ } catch {
5254
+ return toolErr('Parametre şeması geçerli JSON değil');
5255
+ }
5256
+ const code = String(els.toolCode.value || '').trim();
5257
+ if (!code) return toolErr('run.js kodu boş olamaz');
5258
+ const r = await beast
5259
+ .toolSave({ id: toolUi.editId, name, description: String(els.toolDesc.value || ''), parameters, code })
5260
+ .catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
5261
+ if (r && r.ok) {
5262
+ closeToolDialog();
5263
+ toast('Tool kaydedildi — ajanlar tool__' + r.id + ' olarak çağırabilir (Finance dahil)');
5264
+ renderToolBox();
5265
+ } else {
5266
+ toolErr((r && r.error) || 'Kaydedilemedi');
5267
+ }
5268
+ });
5269
+ }
5270
+ if (els.toolDeleteBtn) {
5271
+ els.toolDeleteBtn.addEventListener('click', async () => {
5272
+ if (!toolUi.editId) return;
5273
+ const t = toolUi.tools.find((x) => x.id === toolUi.editId);
5274
+ if (!(await uiConfirm('"' + ((t && t.name) || toolUi.editId) + '" toolu silinsin mi?', 'Sil'))) return;
5275
+ const r = await beast.toolDelete(toolUi.editId).catch(() => ({ ok: false }));
5276
+ if (r && r.ok) {
5277
+ closeToolDialog();
5278
+ toast('Tool silindi');
5279
+ renderToolBox();
5280
+ } else toast((r && r.error) || 'Silinemedi');
5281
+ });
5282
+ }
5283
+ if (els.toolTestBtn) {
5284
+ els.toolTestBtn.addEventListener('click', async () => {
5285
+ toolErr('');
5286
+ let args = {};
5287
+ try {
5288
+ args = JSON.parse(els.toolTestArgs.value || '{}');
5289
+ } catch {
5290
+ return toolErr('Test argümanları geçerli JSON değil');
5291
+ }
5292
+ const id = toolUi.editId || String(els.toolName.value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '_').replace(/^_+|_+$/g, '');
5293
+ if (!id) return toolErr('Önce tool adı gir');
5294
+ /* önce kaydet (yeni tool ilk testte de diskte olsun) */
5295
+ const sr = await beast
5296
+ .toolSave({
5297
+ id: toolUi.editId,
5298
+ name: String(els.toolName.value || id),
5299
+ description: String(els.toolDesc.value || ''),
5300
+ parameters: (() => {
5301
+ try { return JSON.parse(els.toolParams.value || '{}'); } catch { return null; }
5302
+ })() || { type: 'object', properties: {} },
5303
+ code: String(els.toolCode.value || ''),
5304
+ })
5305
+ .catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
5306
+ if (!sr || !sr.ok) return toolErr((sr && sr.error) || 'Tool kaydedilemedi');
5307
+ els.toolTestOut.hidden = false;
5308
+ els.toolTestOut.textContent = 'çalışıyor…';
5309
+ const r = await beast.toolRun(sr.id, args).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
5310
+ els.toolTestOut.textContent = JSON.stringify(r, null, 2).slice(0, 8000);
5311
+ renderToolBox();
5312
+ });
5313
+ }
5314
+
5112
5315
  function renderAgentsPane() {
5113
5316
  const pane = $('#tab-agents');
5114
5317
  if (!pane) return;
@@ -5267,6 +5470,20 @@ function onEvent(ev) {
5267
5470
  if ($('#dmRail') && !$('#dmRail').classList.contains('dm-hidden')) renderDmRail();
5268
5471
  return;
5269
5472
  }
5473
+ /* KİŞİSEL TOOL çalışma kaydı — TOOLS konsolunun nokta göstergeleri */
5474
+ if (ev.type === 'tool-log') {
5475
+ toolUi.logRows.push({
5476
+ at: ev.at,
5477
+ tool: ev.tool,
5478
+ toolId: ev.toolId,
5479
+ ok: ev.ok,
5480
+ ms: ev.ms,
5481
+ error: ev.error,
5482
+ });
5483
+ if (toolUi.logRows.length > 60) toolUi.logRows.splice(0, toolUi.logRows.length - 60);
5484
+ scheduleToolRender();
5485
+ return;
5486
+ }
5270
5487
  /* OFFLINE MESAJ KUYRUĞU: bağlantı + kuyruk olayları (sessionId filtresinden önce) */
5271
5488
  if (ev.type === 'net' || ev.type === 'netQueue') { onNetEvent(ev); return; }
5272
5489
  /* Sandbox klonlama progress'i (sessionId'siz global olay) */
@@ -6414,6 +6631,7 @@ async function init() {
6414
6631
  };
6415
6632
 
6416
6633
  refreshAgentsPane();
6634
+ renderToolBox(); // TOOLS konsolu: kişisel araçlar sol panelde
6417
6635
 
6418
6636
  els.sendBtn.addEventListener('click', sendCurrent);
6419
6637
  els.stopBtn.addEventListener('click', () => {
@@ -8502,6 +8720,10 @@ function finSymBtnUpdate() {
8502
8720
  function finTraderInputsSet(cfg) {
8503
8721
  if (!cfg) return;
8504
8722
  if (!finWatchDirty) finWatchSet(cfg.symbols);
8723
+ /* sembol geçmişi (silinenler dahil) — seçicide "SON SEMBOLLER" olarak çıkar */
8724
+ finSymHist = Array.isArray(cfg.symbolHistory)
8725
+ ? cfg.symbolHistory.map((s) => String(s).trim().toUpperCase()).filter(Boolean).slice(0, 40)
8726
+ : [];
8505
8727
  const ae = document.activeElement;
8506
8728
  if (els.finInterval && ae !== els.finInterval) els.finInterval.value = cfg.intervalSec || 120;
8507
8729
  if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
@@ -8611,7 +8833,15 @@ async function setFinanceMode(on) {
8611
8833
  const brandSub = document.querySelector('#brand .brand-sub');
8612
8834
  if (brandSub) brandSub.textContent = on ? 'Finance' : 'Agent';
8613
8835
  try { localStorage.setItem('beast.financeMode', on ? '1' : '0'); } catch {}
8614
- beast.financeMode(!!on, activeId).catch(() => {});
8836
+ const fm = await beast.financeMode(!!on, activeId).catch(() => ({}));
8837
+ /* finance modu açıldı ama aktif oturum NORMAL (örn. WhatsApp sohbeti) ise
8838
+ ASLA o oturumu finance'e çevirme — kendine ÖZEL finance oturumu aç */
8839
+ if (on && fm && fm.needNew) {
8840
+ try {
8841
+ const created = await beast.createSession();
8842
+ if (created && created.id) await openSession(created.id);
8843
+ } catch {}
8844
+ }
8615
8845
  /* sohbet geçmişi listesi değişir: finance modunda yalnız finance sohbetleri */
8616
8846
  try {
8617
8847
  await refreshSessions();
@@ -8695,6 +8925,7 @@ if (els.finTraderBtn) {
8695
8925
  ekle/çıkar. PİYASA kartı yalnız seçili sembolleri gösterir. */
8696
8926
 
8697
8927
  let finWatch = []; /* anlık izleme listesi (snapshot cfg'den senkron) */
8928
+ let finSymHist = []; /* sembol geçmişi — silinenler dahil, seçicide tek tıkla geri ekleme */
8698
8929
  let finSymSearchTimer = null;
8699
8930
  let finSymRefreshTimer = null;
8700
8931
 
@@ -8718,12 +8949,41 @@ function finRenderPickRows(rows, q) {
8718
8949
  if (!els.finSymPick) return;
8719
8950
  els.finSymPick.textContent = '';
8720
8951
  const term = String(q || '').trim().toUpperCase();
8952
+ /* arama boşken ÜSTTE geçmiş bölümü: silinen semboller TEK TIKLA geri eklenir */
8953
+ if (!term && finSymHist.length) {
8954
+ const sec = document.createElement('div');
8955
+ sec.className = 'fin-sym-sec';
8956
+ sec.textContent = 'SON SEMBOLLER (sildiklerin dahil — tek tıkla geri ekle)';
8957
+ els.finSymPick.appendChild(sec);
8958
+ for (const name of finSymHist) {
8959
+ const row = document.createElement('div');
8960
+ row.className = 'fin-sym-row';
8961
+ const on = finWatch.includes(name);
8962
+ row.innerHTML =
8963
+ '<span class="fsr-check">' + (on ? '✓' : '') + '</span>' +
8964
+ '<span class="fsr-name">' + escapeHtml(name) + '</span>' +
8965
+ '<span class="fsr-desc">' + (on ? 'izlemede' : 'silinmiş — eklemek için tıkla') + '</span>';
8966
+ if (on) row.classList.add('on');
8967
+ row.addEventListener('click', () => {
8968
+ const added = finToggleWatch(name);
8969
+ row.classList.toggle('on', added);
8970
+ row.querySelector('.fsr-check').textContent = added ? '✓' : '';
8971
+ row.querySelector('.fsr-desc').textContent = added ? 'izlemede' : 'silinmiş — eklemek için tıkla';
8972
+ });
8973
+ els.finSymPick.appendChild(row);
8974
+ }
8975
+ const sep = document.createElement('div');
8976
+ sep.className = 'fin-sym-sec';
8977
+ sep.textContent = 'TÜM MT5 SEMBOLLERİ';
8978
+ els.finSymPick.appendChild(sep);
8979
+ }
8721
8980
  const matches = rows.filter((r) => {
8722
8981
  if (!term) return true;
8723
8982
  return (String(r.name || '').toUpperCase().includes(term) || String(r.desc || '').toUpperCase().includes(term));
8724
8983
  });
8725
8984
  if (els.finSymCount) els.finSymCount.textContent = matches.length ? matches.length + ' sembol' : '';
8726
8985
  if (!matches.length) {
8986
+ if (!term && finSymHist.length) return; /* geçmiş zaten gösterildi */
8727
8987
  const d = document.createElement('div');
8728
8988
  d.className = 'fin-empty';
8729
8989
  d.textContent = term ? 'Eşleşme yok' : 'Sembol listesi boş';
@@ -8788,6 +9048,26 @@ function finSymPickerClose() {
8788
9048
  }
8789
9049
 
8790
9050
  if (els.finSymAdd) els.finSymAdd.addEventListener('click', finSymPickerOpen);
9051
+ if (els.finWatchClear) {
9052
+ els.finWatchClear.addEventListener('click', async () => {
9053
+ if (!finWatch.length) {
9054
+ toast('İzleme listesi zaten boş');
9055
+ return;
9056
+ }
9057
+ const ok = await uiConfirm(
9058
+ 'İzleme listesindeki TÜM semboller silinsin mi? (' + finWatch.join(', ') + ')\nSilinenler geçmişte kalır — sembol seçicide TEK TIKLA geri ekleyebilirsin.',
9059
+ 'Tümünü Sil',
9060
+ 'Vazgeç'
9061
+ );
9062
+ if (!ok) return;
9063
+ finWatchSet([]);
9064
+ finSaveCfg({ symbols: [] });
9065
+ finSymBtnUpdate();
9066
+ clearTimeout(finSymRefreshTimer);
9067
+ finSymRefreshTimer = setTimeout(() => finSnapshot(), 700);
9068
+ toast('İzleme listesi temizlendi — semboller geçmişte duruyor');
9069
+ });
9070
+ }
8791
9071
  if (els.finSymClose) els.finSymClose.addEventListener('click', finSymPickerClose);
8792
9072
  if (els.finSymOverlay) {
8793
9073
  els.finSymOverlay.addEventListener('click', (e) => {
@@ -2206,6 +2206,20 @@ body.rail-hidden #dmRailHead { padding-right: 148px; }
2206
2206
  .fin-sym:hover .fs-agent { visibility: visible; color: var(--accent); }
2207
2207
  .fin-sym .fs-agent:hover { background: var(--accent-dim); }
2208
2208
 
2209
+ /* sembol seçicide bölüm başlıkları (geçmiş / tüm semboller) */
2210
+ .fin-sym-sec {
2211
+ font-size: 10px;
2212
+ font-weight: 800;
2213
+ letter-spacing: 0.6px;
2214
+ color: var(--accent);
2215
+ padding: 8px 4px 3px;
2216
+ border-bottom: 1px solid var(--border);
2217
+ margin-bottom: 3px;
2218
+ white-space: nowrap;
2219
+ overflow: hidden;
2220
+ text-overflow: ellipsis;
2221
+ }
2222
+
2209
2223
  /* ---------- BOT SİSTEMİ (sol panel alt bölümü + bot sayfası) ---------- */
2210
2224
 
2211
2225
  /* üst bardaki aktif bot rozeti */
@@ -2260,6 +2274,225 @@ body.rail-hidden #dmRailHead { padding-right: 148px; }
2260
2274
  letter-spacing: 1.5px;
2261
2275
  color: var(--muted);
2262
2276
  }
2277
+
2278
+ /* ---------- sol panel: TOOLS konsolu (kişisel araçlar) ---------- */
2279
+ #toolBox {
2280
+ flex: none;
2281
+ max-height: 32%;
2282
+ display: none;
2283
+ flex-direction: column;
2284
+ border-top: 1px solid var(--border);
2285
+ padding: 6px 8px 6px;
2286
+ }
2287
+ body:not(.ide-mode):not(.studio-mode):not(.sandbox-mode) #toolBox { display: flex; }
2288
+ #toolBoxHead {
2289
+ display: flex;
2290
+ align-items: center;
2291
+ justify-content: space-between;
2292
+ padding: 2px 2px 6px;
2293
+ font-size: 10.5px;
2294
+ font-weight: 800;
2295
+ letter-spacing: 1.5px;
2296
+ color: var(--muted);
2297
+ }
2298
+ #toolList {
2299
+ min-height: 40px;
2300
+ max-height: 200px;
2301
+ overflow-y: auto;
2302
+ display: flex;
2303
+ flex-direction: column;
2304
+ gap: 2px;
2305
+ }
2306
+ .tool-empty {
2307
+ color: var(--muted);
2308
+ font-size: 10.5px;
2309
+ line-height: 1.5;
2310
+ padding: 6px 2px;
2311
+ }
2312
+ .tool-row {
2313
+ display: flex;
2314
+ align-items: center;
2315
+ gap: 6px;
2316
+ min-width: 0;
2317
+ padding: 3px 4px;
2318
+ border-radius: 6px;
2319
+ cursor: pointer;
2320
+ color: var(--text);
2321
+ }
2322
+ .tool-row:hover { background: var(--panel2); }
2323
+ .tool-dot {
2324
+ flex: none;
2325
+ width: 7px;
2326
+ height: 7px;
2327
+ border-radius: 50%;
2328
+ background: var(--border);
2329
+ }
2330
+ .tool-dot.ok { background: #43c463; }
2331
+ .tool-dot.err { background: var(--err); }
2332
+ .tool-name {
2333
+ flex: 1;
2334
+ min-width: 0;
2335
+ font-size: 12px;
2336
+ font-weight: 600;
2337
+ white-space: nowrap;
2338
+ overflow: hidden;
2339
+ text-overflow: ellipsis;
2340
+ }
2341
+ .tool-btns {
2342
+ flex: none;
2343
+ display: flex;
2344
+ gap: 2px;
2345
+ visibility: hidden;
2346
+ }
2347
+ .tool-row:hover .tool-btns { visibility: visible; }
2348
+ .tool-btns button {
2349
+ border: none;
2350
+ background: none;
2351
+ color: var(--muted);
2352
+ font-size: 11px;
2353
+ cursor: pointer;
2354
+ padding: 2px 5px;
2355
+ border-radius: 4px;
2356
+ }
2357
+ .tool-btns button:hover { background: var(--accent-dim); color: var(--accent); }
2358
+ .tool-btns .tb-del:hover { color: var(--err); background: rgba(220, 60, 60, 0.12); }
2359
+
2360
+ /* ---------- TOOL editör dialogu ---------- */
2361
+ #toolOverlay {
2362
+ position: fixed;
2363
+ inset: 0;
2364
+ background: rgba(0, 0, 0, 0.45);
2365
+ z-index: 300;
2366
+ display: flex;
2367
+ align-items: center;
2368
+ justify-content: center;
2369
+ }
2370
+ #toolOverlay[hidden] { display: none; }
2371
+ #toolDialog {
2372
+ width: min(760px, 92vw);
2373
+ max-height: 88vh;
2374
+ overflow-y: auto;
2375
+ background: var(--panel);
2376
+ border: 1px solid var(--border);
2377
+ border-radius: 12px;
2378
+ padding: 14px 16px;
2379
+ display: flex;
2380
+ flex-direction: column;
2381
+ gap: 10px;
2382
+ }
2383
+ #toolDialogHead {
2384
+ display: flex;
2385
+ align-items: center;
2386
+ justify-content: space-between;
2387
+ font-size: 13px;
2388
+ font-weight: 800;
2389
+ letter-spacing: 1px;
2390
+ color: var(--text);
2391
+ }
2392
+ #toolDialogHead button {
2393
+ border: none;
2394
+ background: none;
2395
+ color: var(--muted);
2396
+ font-size: 13px;
2397
+ cursor: pointer;
2398
+ padding: 2px 6px;
2399
+ border-radius: 6px;
2400
+ }
2401
+ #toolDialogHead button:hover { color: var(--err); background: rgba(220, 60, 60, 0.1); }
2402
+ .tool-form {
2403
+ display: flex;
2404
+ flex-direction: column;
2405
+ gap: 4px;
2406
+ }
2407
+ .tool-form label {
2408
+ font-size: 10.5px;
2409
+ font-weight: 700;
2410
+ letter-spacing: 0.5px;
2411
+ color: var(--muted);
2412
+ margin-top: 4px;
2413
+ }
2414
+ .tool-form input,
2415
+ .tool-form textarea {
2416
+ width: 100%;
2417
+ background: var(--bg);
2418
+ color: var(--text);
2419
+ border: 1px solid var(--border);
2420
+ border-radius: 8px;
2421
+ padding: 7px 9px;
2422
+ font-size: 12px;
2423
+ font-family: Consolas, monospace;
2424
+ }
2425
+ .tool-form input { font-family: inherit; }
2426
+ .tool-form textarea { resize: vertical; min-height: 60px; }
2427
+ .tool-form input:focus,
2428
+ .tool-form textarea:focus { outline: none; border-color: var(--accent); }
2429
+ #toolTestRow {
2430
+ display: flex;
2431
+ gap: 6px;
2432
+ }
2433
+ #toolTestArgs {
2434
+ flex: 1;
2435
+ background: var(--bg);
2436
+ color: var(--text);
2437
+ border: 1px solid var(--border);
2438
+ border-radius: 8px;
2439
+ padding: 7px 9px;
2440
+ font-size: 12px;
2441
+ font-family: Consolas, monospace;
2442
+ }
2443
+ #toolTestArgs:focus { outline: none; border-color: var(--accent); }
2444
+ #toolTestBtn {
2445
+ border: none;
2446
+ background: var(--accent-dim);
2447
+ color: var(--accent);
2448
+ font-weight: 800;
2449
+ font-size: 12px;
2450
+ border-radius: 8px;
2451
+ padding: 7px 12px;
2452
+ cursor: pointer;
2453
+ }
2454
+ #toolTestBtn:hover { filter: brightness(1.1); }
2455
+ #toolTestOut {
2456
+ margin: 0;
2457
+ max-height: 220px;
2458
+ overflow: auto;
2459
+ background: var(--bg);
2460
+ border: 1px solid var(--border);
2461
+ border-radius: 8px;
2462
+ padding: 8px 10px;
2463
+ font-size: 11px;
2464
+ line-height: 1.5;
2465
+ white-space: pre-wrap;
2466
+ word-break: break-word;
2467
+ color: var(--text);
2468
+ }
2469
+ #toolDialogBtns {
2470
+ display: flex;
2471
+ align-items: center;
2472
+ gap: 8px;
2473
+ }
2474
+ .tool-primary {
2475
+ border: none;
2476
+ background: var(--accent);
2477
+ color: #fff;
2478
+ font-weight: 800;
2479
+ font-size: 12.5px;
2480
+ border-radius: 8px;
2481
+ padding: 8px 16px;
2482
+ cursor: pointer;
2483
+ }
2484
+ .tool-primary:hover { filter: brightness(1.08); }
2485
+ .tool-danger {
2486
+ border: none;
2487
+ background: rgba(220, 60, 60, 0.12);
2488
+ color: var(--err);
2489
+ font-weight: 700;
2490
+ font-size: 12px;
2491
+ border-radius: 8px;
2492
+ padding: 8px 14px;
2493
+ cursor: pointer;
2494
+ }
2495
+ .tool-danger:hover { background: rgba(220, 60, 60, 0.2); }
2263
2496
  .bot-bar-btns { display: flex; gap: 4px; }
2264
2497
  .bot-bar-btns button {
2265
2498
  border: none; background: none; color: var(--muted);