beast-agent 2.70.0 → 2.72.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.70.0",
4
+ "version": "2.72.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -15,6 +15,11 @@ const path = require('path');
15
15
  const { spawn } = require('child_process');
16
16
  const { beastRoot } = require('./memory');
17
17
 
18
+ /* Varsayılan 90 sn; tool.json'daki "timeoutMs" ile araç başına uzatılabilir
19
+ (üst sınır 15 dk) — uzun işler için (ör. fastbrowse tarayıcı ajanı). */
20
+ const TOOL_TIMEOUT_MS = 90 * 1000;
21
+ const TOOL_TIMEOUT_MAX_MS = 15 * 60 * 1000;
22
+
18
23
  let notify = () => {};
19
24
  function setNotify(fn) {
20
25
  notify = typeof fn === 'function' ? fn : () => {};
@@ -80,6 +85,7 @@ function readTool(d) {
80
85
  : { type: 'object', properties: {} },
81
86
  script: runJs,
82
87
  code: fs.readFileSync(runJs, 'utf8').slice(0, 20000),
88
+ timeoutMs: Math.max(5000, Math.min(TOOL_TIMEOUT_MAX_MS, Number(json.timeoutMs) || TOOL_TIMEOUT_MS)),
83
89
  updatedAt: json.updatedAt || null,
84
90
  };
85
91
  } catch {
@@ -122,8 +128,6 @@ function names() {
122
128
  return cache.map((t) => 'tool__' + t.id);
123
129
  }
124
130
 
125
- const TOOL_TIMEOUT_MS = 90 * 1000;
126
-
127
131
  /* Tool'u çocuk node prosesinde koşturur — uygulama asla çökmez */
128
132
  function call(name, args, sid) {
129
133
  return new Promise((resolve) => {
@@ -157,8 +161,8 @@ function call(name, args, sid) {
157
161
  };
158
162
  let killer = setTimeout(() => {
159
163
  try { child && child.kill(); } catch {}
160
- finish({ ok: false, error: 'tool zaman aşımı (' + Math.round(TOOL_TIMEOUT_MS / 1000) + ' sn)' });
161
- }, TOOL_TIMEOUT_MS);
164
+ finish({ ok: false, error: 'tool zaman aşımı (' + Math.round(t.timeoutMs / 1000) + ' sn)' });
165
+ }, t.timeoutMs);
162
166
  try {
163
167
  /* Araç ortamı: BEAST_* değişkenleri — tool'lar köprü/python yolunu ve
164
168
  argümanları buradan güvenle çözebilir (stdin yanı sıra yedek kanal).
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Beast Agent <-> fastbrowse köprüsü (JEV seçer, LLM okur, kod uygular).
4
+
5
+ stdin : JSON argümanlar (tool__fastbrowse_task ile aynı şema)
6
+ stdout: TEK JSON sonuç
7
+
8
+ Kimlikler args'tan GEÇMEZ: TypeSafe anahtarı ve OpenAI-uyumlu sağlayıcı
9
+ (DeepSeek / z.ai ...) %APPDATA%\\beast\\settings.json'dan okunur — model hiç
10
+ görmez. LLM istemcisi fastbrowse'un OpenAICompatibleLLM'i; response_format
11
+ json_schema'yı desteklemeyen sağlayıcılar için istek gövdesi httpx transport
12
+ katmanında json_object'e indirilir ve şema sistem mesajına eklenir.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import json
19
+ import os
20
+ import re
21
+ import sys
22
+ from pathlib import Path
23
+
24
+
25
+ def _emit(obj: dict) -> None:
26
+ try:
27
+ sys.stdout.reconfigure(encoding="utf-8")
28
+ except Exception:
29
+ pass
30
+ sys.stdout.write(json.dumps(obj, ensure_ascii=False) + "\n")
31
+ sys.stdout.flush()
32
+
33
+
34
+ def _read_args() -> dict:
35
+ try:
36
+ raw = sys.stdin.read()
37
+ except Exception:
38
+ raw = ""
39
+ try:
40
+ obj = json.loads(raw or "{}")
41
+ return obj if isinstance(obj, dict) else {}
42
+ except Exception:
43
+ return {}
44
+
45
+
46
+ def _beast_root() -> Path:
47
+ env = os.environ.get("BEAST_ROOT")
48
+ if env:
49
+ return Path(env)
50
+ return Path(os.environ.get("APPDATA") or ".") / "beast"
51
+
52
+
53
+ def _load_settings() -> dict:
54
+ try:
55
+ return json.loads((_beast_root() / "settings.json").read_text(encoding="utf-8"))
56
+ except Exception:
57
+ return {}
58
+
59
+
60
+ def _pick_provider(settings: dict, want: str, model: str):
61
+ """Aktif modelin sağlayıcısı (modelOverride: custom:<id>::<model>) ya da
62
+ istenen ad/kimlik; yoksa ilk özel sağlayıcı. Anahtar yalnız burada çözülür."""
63
+ provs = settings.get("customProviders")
64
+ if not isinstance(provs, list):
65
+ provs = []
66
+ want = str(want or "").strip().lower()
67
+ mo = str(settings.get("modelOverride") or "")
68
+ m = re.match(r"custom:([^:]+)::(.+)$", mo)
69
+ mo_id = m.group(1) if m else ""
70
+ mo_model = m.group(2) if m else ""
71
+ chosen = None
72
+ if want:
73
+ for p in provs:
74
+ if not isinstance(p, dict):
75
+ continue
76
+ if want in (str(p.get("id") or "").lower(), str(p.get("name") or "").lower()):
77
+ chosen = p
78
+ break
79
+ if chosen is None and mo_id:
80
+ for p in provs:
81
+ if isinstance(p, dict) and str(p.get("id") or "") == mo_id:
82
+ chosen = p
83
+ break
84
+ if chosen is None and provs and isinstance(provs[0], dict):
85
+ chosen = provs[0]
86
+ if not chosen:
87
+ return None
88
+ base = str(chosen.get("baseUrl") or "").strip()
89
+ key = str(chosen.get("key") or "").strip()
90
+ mdl = str(model or "").strip()
91
+ if not mdl:
92
+ if mo_model and str(chosen.get("id") or "") == mo_id:
93
+ mdl = mo_model
94
+ else:
95
+ models = chosen.get("models")
96
+ if isinstance(models, list) and models:
97
+ mdl = str(models[0])
98
+ if not (base and key and mdl):
99
+ return None
100
+ return {
101
+ "name": str(chosen.get("name") or chosen.get("id") or "?"),
102
+ "baseUrl": base,
103
+ "apiKey": key,
104
+ "model": mdl,
105
+ }
106
+
107
+
108
+ def _make_http():
109
+ """httpx istemcisi: görsel soyma + json_schema indirgemesi (taşıma katmanı)."""
110
+ import httpx
111
+
112
+ class _SchemaDowngrade(httpx.AsyncBaseTransport):
113
+ def __init__(self, inner):
114
+ self._inner = inner
115
+
116
+ async def aclose(self):
117
+ await self._inner.aclose()
118
+
119
+ async def handle_async_request(self, request):
120
+ payload = None
121
+ is_llm = False
122
+ try:
123
+ if request.content and b"messages" in request.content:
124
+ body = json.loads(request.content.decode("utf-8"))
125
+ changed = False
126
+ msgs = body.get("messages")
127
+ if isinstance(msgs, list):
128
+ # 1) GÖRSEL SOYMA: sağlayıcı (GLM/DeepSeek) görsel kabul etmiyorsa
129
+ # image_url parçaları düşer, metin korunur ve kısa bir not eklenir.
130
+ for m in msgs:
131
+ if not isinstance(m, dict):
132
+ continue
133
+ c = m.get("content")
134
+ if isinstance(c, list):
135
+ texts = [
136
+ str(p.get("text") or "")
137
+ for p in c
138
+ if isinstance(p, dict) and p.get("type") == "text"
139
+ ]
140
+ had_image = any(
141
+ isinstance(p, dict) and p.get("type") == "image_url" for p in c
142
+ )
143
+ joined = "\n".join(t for t in texts if t)
144
+ if had_image:
145
+ joined += "\n[ekran görüntüsü bu sağlayıcıya gönderilemedi — metinle devam et]"
146
+ m["content"] = joined
147
+ changed = True
148
+ # 2) JSON ŞEMA İNDİRGEMESİ: json_schema → json_object + şema sistem mesajı
149
+ rf = body.get("response_format")
150
+ if isinstance(rf, dict) and rf.get("type") == "json_schema":
151
+ schema = (rf.get("json_schema") or {}).get("schema")
152
+ body["response_format"] = {"type": "json_object"}
153
+ msgs.insert(
154
+ 0,
155
+ {
156
+ "role": "system",
157
+ "content": "Yalnızca tek bir JSON nesnesi döndür; JSON dışında hiçbir metin yazma. Şu JSON Schema'ya uy: "
158
+ + json.dumps(schema, ensure_ascii=False),
159
+ },
160
+ )
161
+ changed = True
162
+ if changed:
163
+ body["messages"] = msgs
164
+ payload = json.dumps(body, ensure_ascii=False).encode("utf-8")
165
+ else:
166
+ payload = request.content
167
+ is_llm = True
168
+ except Exception:
169
+ pass
170
+ req = request
171
+ if payload is not None:
172
+ headers = [(k, v) for (k, v) in request.headers.raw if k.lower() != b"content-length"]
173
+ req = httpx.Request(
174
+ request.method,
175
+ request.url,
176
+ headers=headers,
177
+ content=payload,
178
+ extensions=request.extensions,
179
+ )
180
+ # NOT: 429/5xx yeniden deneme fastbrowse'un post_with_retry katmanında zaten var
181
+ # (Retry-After'a saygılı) — burada TEKRAR denenmez, yoksa girişimler çoğalır.
182
+ return await self._inner.handle_async_request(req)
183
+
184
+ return httpx.AsyncClient(timeout=60, transport=_SchemaDowngrade(httpx.AsyncHTTPTransport()))
185
+
186
+
187
+ async def _resolve_cdp(client, args: dict) -> str:
188
+ """Beast dahili Chromium CDP: DevToolsActivePort dosyası → /json/version → ws URL."""
189
+ url = str(args.get("cdp_url") or "").strip()
190
+ if url.startswith(("ws://", "wss://")):
191
+ return url
192
+ port = 0
193
+ try:
194
+ port = int(str(args.get("cdp_port") or "0") or 0)
195
+ except Exception:
196
+ port = 0
197
+ if not port:
198
+ appdata = Path(os.environ.get("APPDATA") or ".")
199
+ for name in ("Beast Agent", "beast-agent", "beast"):
200
+ f = appdata / name / "DevToolsActivePort"
201
+ try:
202
+ if f.exists():
203
+ port = int(f.read_text(encoding="utf-8").splitlines()[0].strip())
204
+ break
205
+ except Exception:
206
+ continue
207
+ if not port:
208
+ raise RuntimeError("DevToolsActivePort bulunamadı (Beast açık mı?)")
209
+ r = await client.get("http://127.0.0.1:%d/json/version" % port)
210
+ r.raise_for_status()
211
+ ws = (r.json() or {}).get("webSocketDebuggerUrl")
212
+ if not ws:
213
+ raise RuntimeError("CDP webSocketDebuggerUrl yok (port %d)" % port)
214
+ return str(ws)
215
+
216
+
217
+ _HINTS = {
218
+ "needs_confirmation": "Geri alınamaz aksiyonda durdu (gönder/öde/sil). Kullanıcıdan onay al; uygunsa authorize:true ile tekrar çağır.",
219
+ "needs_login": "Giriş duvarı var; kayıtlı kimlik çözülemedi. Kullanıcıdan giriş bilgisi/profil iste.",
220
+ "needs_input": "Form alanı için değer verilmedi (model uydurmaz). Kullanıcıdan değeri al.",
221
+ "blocked": "Bot kontrolü (CAPTCHA) geçilemedi; kullanıcının çözmesi gerekebilir.",
222
+ "stuck": "İlerleme olmadan takıldı; görevi/dilimi netleştirip tekrar dene.",
223
+ "budget_exceeded": "Adım/süre limiti doldu; max_steps/max_seconds artır ya da görevi böl.",
224
+ "observation_limit": "Sayfa Jev'in girdi limitini aştı; daha dar bir görev/hedef ver.",
225
+ "unverified": "Bitti sanıyor ama tüm iddialar kanıtlanamadı — answer'a temkinli yaklaş, sonucu doğrula.",
226
+ "error": "Çalıştırma hatası; error alanına bak.",
227
+ }
228
+
229
+
230
+ def _result_dict(res, provider: dict, browser: str) -> dict:
231
+ try:
232
+ cost = {"known_dollars": round(float(res.cost.known_dollars), 4), "unknown": bool(res.cost.has_unknown)}
233
+ except Exception:
234
+ cost = None
235
+ steps = []
236
+ try:
237
+ for s in list(res.steps)[-30:]:
238
+ steps.append(
239
+ {
240
+ "i": s.index,
241
+ "op": getattr(s.operation, "value", str(s.operation)),
242
+ "karar": getattr(s.decided_by, "value", ""),
243
+ "sonuc": getattr(s.outcome, "value", ""),
244
+ "hedef": s.target,
245
+ "url": s.url,
246
+ }
247
+ )
248
+ except Exception:
249
+ pass
250
+ evidence = []
251
+ try:
252
+ for e in list(res.evidence)[:10]:
253
+ quote = " ".join(str(e.quote or "").split())
254
+ evidence.append({"quote": quote[:400], "url": e.url})
255
+ except Exception:
256
+ pass
257
+ status = getattr(res.status, "value", str(res.status))
258
+ return {
259
+ "ok": status == "complete",
260
+ "status": status,
261
+ "answer": res.answer,
262
+ "final_url": res.final_url,
263
+ "evidence": evidence,
264
+ "steps": steps,
265
+ "cost": cost,
266
+ "browser": browser,
267
+ "provider": provider["name"] + ":" + provider["model"],
268
+ "error": res.error,
269
+ "hint": _HINTS.get(status, ""),
270
+ }
271
+
272
+
273
+ async def _run(args: dict) -> dict:
274
+ from fastbrowse import run_task
275
+ from fastbrowse.clients.openai_compatible import OpenAICompatibleLLM
276
+ from fastbrowse.clients.typesafe import TYPESAFE_URL, TypeSafeJevClient
277
+ from fastbrowse.jev import JEV_MODEL
278
+ from fastbrowse.models import Authorization, Limits, LLMPurpose, LocalChrome
279
+
280
+ settings = _load_settings()
281
+ ts = settings.get("typesafe") if isinstance(settings.get("typesafe"), dict) else {}
282
+ ts_key = str(ts.get("apiKey") or "").strip()
283
+ if not ts_key:
284
+ return {
285
+ "ok": False,
286
+ "status": "error",
287
+ "error": "TypeSafe (Jev) API anahtarı yok",
288
+ "hint": "Ayarlar → TypeSafe sekmesinden anahtarı gir.",
289
+ }
290
+ if ts.get("enabled") is False:
291
+ return {
292
+ "ok": False,
293
+ "status": "error",
294
+ "error": "TypeSafe ayarı KAPALI",
295
+ "hint": "Ayarlar → TypeSafe → anahtarı aç.",
296
+ }
297
+ provider = _pick_provider(settings, str(args.get("provider") or ""), str(args.get("model") or ""))
298
+ if not provider:
299
+ return {
300
+ "ok": False,
301
+ "status": "error",
302
+ "error": "OpenAI-uyumlu LLM sağlayıcısı bulunamadı (customProviders boş ya da anahtarsız)",
303
+ "hint": "Ayarlar → Provider'dan özel sağlayıcı ekle (ör. DeepSeek / z.ai) ve model seç.",
304
+ }
305
+
306
+ def _clamp(v, lo, hi, default):
307
+ try:
308
+ return max(lo, min(hi, type(default)(v)))
309
+ except Exception:
310
+ return default
311
+
312
+ max_steps = _clamp(args.get("max_steps"), 5, 80, 40)
313
+ max_seconds = _clamp(args.get("max_seconds"), 30.0, 600.0, 180.0)
314
+ limits = Limits(max_steps=max_steps, max_seconds=max_seconds, max_llm_calls=50)
315
+ authorization = Authorization(irreversible_actions=bool(args.get("authorize")))
316
+
317
+ def _local_chrome():
318
+ binary = str(args.get("chrome") or os.environ.get("FASTBROWSE_CHROME") or "").strip()
319
+ return LocalChrome(binary=binary or None, headed=bool(args.get("headed")))
320
+
321
+ http = _make_http()
322
+ try:
323
+ jev = TypeSafeJevClient(
324
+ ts_key,
325
+ http=http,
326
+ base_url=TYPESAFE_URL,
327
+ model=str(ts.get("model") or "").strip() or JEV_MODEL,
328
+ )
329
+ llm = OpenAICompatibleLLM(
330
+ provider["apiKey"],
331
+ http=http,
332
+ base_url=provider["baseUrl"],
333
+ models={purpose: provider["model"] for purpose in LLMPurpose},
334
+ reasoning_effort=None, # sağlayıcıya özel 'reasoning' alanı gönderilmez
335
+ )
336
+ kwargs = {"jev": jev, "llm": llm, "limits": limits, "authorization": authorization, "http": http}
337
+ mode = str(args.get("mode") or "cdp").strip().lower()
338
+ if mode == "chrome":
339
+ kwargs["chrome"] = _local_chrome()
340
+ browser = "chrome"
341
+ else:
342
+ try:
343
+ kwargs["cdp_url"] = await _resolve_cdp(http, args)
344
+ browser = "cdp (Beast dahili)"
345
+ except Exception as e:
346
+ kwargs["chrome"] = _local_chrome()
347
+ browser = "chrome (cdp yok: %s)" % str(e)[:80]
348
+ res = await run_task(
349
+ str(args.get("task") or "").strip(),
350
+ start=(str(args.get("start") or "").strip() or None),
351
+ **kwargs,
352
+ )
353
+ finally:
354
+ try:
355
+ await http.aclose()
356
+ except Exception:
357
+ pass
358
+ return _result_dict(res, provider, browser)
359
+
360
+
361
+ def main() -> None:
362
+ args = _read_args()
363
+ if not str(args.get("task") or "").strip():
364
+ _emit({"ok": False, "status": "error", "error": "task gerekli (doğal dil hedef)"})
365
+ return
366
+ try:
367
+ result = asyncio.run(_run(args))
368
+ except ModuleNotFoundError as e:
369
+ _emit(
370
+ {
371
+ "ok": False,
372
+ "status": "error",
373
+ "error": "fastbrowse yüklenemedi: " + str(e),
374
+ "hint": "uv kurulu ve internet erişimi var mı? (uv run fastbrowse==0.4.2 ortamı kurar)",
375
+ }
376
+ )
377
+ return
378
+ except Exception as e:
379
+ _emit({"ok": False, "status": "error", "error": type(e).__name__ + ": " + str(e)[:400]})
380
+ return
381
+ _emit(result)
382
+
383
+
384
+ if __name__ == "__main__":
385
+ main()
@@ -0,0 +1,119 @@
1
+ 'use strict';
2
+ // FASTBROWSE KÖPRÜSÜ — stdin: JSON args · stdout: JSON sonuç
3
+ // fastbrowse (Python 3.13) uv ile izole ortamda çalışır; anahtar/kimlik bilgileri
4
+ // args'tan GEÇMEZ — köprü %APPDATA%\beast\settings.json'dan okur (model hiç görmez).
5
+ const { spawn } = require('child_process');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ function readArgs() {
10
+ try { return JSON.parse(fs.readFileSync(0, 'utf8') || '{}') || {}; } catch { return {}; }
11
+ }
12
+
13
+ /* uv bul: PATH'te yoksa uv'un resmi kurulum yerleri denenir */
14
+ function findUv() {
15
+ const cands = [];
16
+ if (process.env.UV) cands.push(String(process.env.UV));
17
+ if (process.env.USERPROFILE) cands.push(path.join(process.env.USERPROFILE, '.local', 'bin', 'uv.exe'));
18
+ if (process.env.LOCALAPPDATA) cands.push(path.join(process.env.LOCALAPPDATA, 'Microsoft', 'WinGet', 'Links', 'uv.exe'));
19
+ for (const c of cands) { try { if (c && fs.existsSync(c)) return c; } catch {} }
20
+ return 'uv';
21
+ }
22
+
23
+ /* Chrome bul (chrome modu yedeği; köprü FASTBROWSE_CHROME'u kullanır) */
24
+ function findChrome() {
25
+ const cands = [];
26
+ if (process.env.LOCALAPPDATA) cands.push(path.join(process.env.LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe'));
27
+ cands.push('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe');
28
+ cands.push('C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe');
29
+ for (const c of cands) { try { if (c && fs.existsSync(c)) return c; } catch {} }
30
+ return '';
31
+ }
32
+
33
+ function emit(obj) {
34
+ process.stdout.write(JSON.stringify(obj) + '\n');
35
+ }
36
+
37
+ const args = readArgs();
38
+ const uv = findUv();
39
+ const bridge = path.join(__dirname, 'fastbrowse_bridge.py');
40
+ if (!fs.existsSync(bridge)) {
41
+ emit({ ok: false, status: 'error', error: 'köprü dosyası yok: ' + bridge, hint: 'Araç klasörünü yeniden kur (uygulamayı yeniden başlat).' });
42
+ process.exit(0);
43
+ }
44
+
45
+ const env = { ...process.env, PYTHONUTF8: '1', PYTHONIOENCODING: 'utf-8' };
46
+ if (!env.FASTBROWSE_CHROME) {
47
+ const chrome = findChrome();
48
+ if (chrome) env.FASTBROWSE_CHROME = chrome;
49
+ }
50
+
51
+ const UV_TIMEOUT_MS = 330000; /* customtools zaman aşımının (360 sn) altında kal */
52
+ let child = null;
53
+ let out = '';
54
+ let err = '';
55
+ let settled = false;
56
+ const finish = (obj) => {
57
+ if (settled) return;
58
+ settled = true;
59
+ clearTimeout(killer);
60
+ try { child && child.kill(); } catch {}
61
+ emit(obj);
62
+ };
63
+
64
+ const killer = setTimeout(() => {
65
+ finish({ ok: false, status: 'error', error: 'fastbrowse zaman aşımı (uv/Chrome yanıt vermedi)', stderr: String(err || '').slice(-500) });
66
+ }, UV_TIMEOUT_MS);
67
+
68
+ try {
69
+ child = spawn(uv, ['run', '--python', '3.13', '--with', 'fastbrowse==0.4.2', 'python', bridge], {
70
+ cwd: __dirname,
71
+ env,
72
+ windowsHide: true,
73
+ stdio: ['pipe', 'pipe', 'pipe'],
74
+ });
75
+ } catch (e) {
76
+ finish({
77
+ ok: false,
78
+ status: 'error',
79
+ error: 'uv başlatılamadı: ' + String((e && e.message) || e),
80
+ hint: 'uv kur: winget install astral-sh.uv (ya da https://docs.astral.sh/uv/)',
81
+ });
82
+ process.exit(0);
83
+ }
84
+
85
+ child.stdout.on('data', (d) => { out += d.toString('utf8'); });
86
+ child.stderr.on('data', (d) => { err += d.toString('utf8'); if (err.length > 20000) err = err.slice(-12000); });
87
+ child.on('error', (e) => {
88
+ finish({
89
+ ok: false,
90
+ status: 'error',
91
+ error: 'uv çalıştırılamadı: ' + String((e && e.message) || e),
92
+ hint: 'uv kur: winget install astral-sh.uv (ya da https://docs.astral.sh/uv/)',
93
+ });
94
+ });
95
+ child.on('close', (code) => {
96
+ /* uv/python kendi log'larını stderr'e yazar; stdout'tan SON geçerli JSON satırını al */
97
+ let result = null;
98
+ for (const line of String(out || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)) {
99
+ try {
100
+ const o = JSON.parse(line);
101
+ if (o && typeof o === 'object' && typeof o.ok === 'boolean') result = o;
102
+ } catch {}
103
+ }
104
+ if (!result) {
105
+ result = {
106
+ ok: false,
107
+ status: 'error',
108
+ error: 'fastbrowse sonuç üretemedi (çıkış kodu ' + code + ')',
109
+ stderr: String(err || '').slice(-800),
110
+ };
111
+ }
112
+ if (!result.ok && !result.hint && code !== 0) result.hint = String(err || '').slice(-200) || 'çıkış kodu ' + code;
113
+ finish(result);
114
+ });
115
+
116
+ try {
117
+ child.stdin.write(JSON.stringify(args));
118
+ child.stdin.end();
119
+ } catch {}
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "fastbrowse_task",
3
+ "description": "HIZLI TARAYICI AJANI (fastbrowse · Jev seçer, LLM okur, kod doğrular): doğal dilde TEK hedef ver; sayfayı element listesine indeksler, her adımı Jev (TypeSafe) seçer ve cevabı sayfadan BİREBİR ALINTILARLA döndürür. Çok adımlı işler (arama + form + filtre + tarih seçimi + sonuç okuma) için browser_click/browser_type zinciri yerine bunu tercih et. Varsayılan mode:'cdp' Beast'in DAHİLİ Chromium'una bağlanır (kendi sekmesini açar/kapatır, senin sekmelerine dokunmaz); mode:'chrome' kendi Chrome'unu açar. Geri alınamaz aksiyonlar (gönder/öde/sil) authorize:true verilmeden DURUR ve status:'needs_confirmation' döner. status:'complete' DEĞİLSE başarıyı iddia etme: dönen answer + evidence alıntılarını kullanıcıya aktar; needs_login/needs_input/blocked durumlarında kullanıcıdan bilgi iste. Aynı iş için elle browser_click/browser_type zinciri kurma — tek çağrıda hedefi ver.",
4
+ "parameters": {
5
+ "type": "object",
6
+ "properties": {
7
+ "task": {
8
+ "type": "string",
9
+ "description": "Doğal dil hedef: \"Hacker News'teki en üst haberin başlığını bul\" gibi. Tek net hedef ver; gereksiz 've' ile işleri birleştirme."
10
+ },
11
+ "start": {
12
+ "type": "string",
13
+ "description": "Başlangıç URL'i (ör. https://news.ycombinator.com/). Boşsa ilk adres görevden çıkarılır."
14
+ },
15
+ "mode": {
16
+ "type": "string",
17
+ "enum": ["cdp", "chrome"],
18
+ "description": "cdp: Beast dahili Chromium (varsayılan, giriş yapılmış oturumu kullanır); chrome: kendi Chrome'unu başlatır (Beast kapalıyken/yedek)."
19
+ },
20
+ "provider": {
21
+ "type": "string",
22
+ "description": "LLM sağlayıcı adı/kimliği (Ayarlar → Provider'daki özel sağlayıcı; boşsa aktif modelin sağlayıcısı)."
23
+ },
24
+ "model": {
25
+ "type": "string",
26
+ "description": "LLM modeli (boşsa sağlayıcının/aktif modelin varsayılanı)."
27
+ },
28
+ "authorize": {
29
+ "type": "boolean",
30
+ "description": "true: geri alınamaz aksiyonlara (gönder/öde/sil/onayla) izin ver. Varsayılan false — güvenli."
31
+ },
32
+ "headed": {
33
+ "type": "boolean",
34
+ "description": "chrome modunda pencereyi göster (varsayılan false = headless)."
35
+ },
36
+ "max_steps": {
37
+ "type": "number",
38
+ "description": "Adım tavanı (varsayılan 40, en çok 80)."
39
+ },
40
+ "max_seconds": {
41
+ "type": "number",
42
+ "description": "Süre tavanı saniye (varsayılan 180, en çok 600)."
43
+ }
44
+ },
45
+ "required": ["task"]
46
+ },
47
+ "timeoutMs": 360000,
48
+ "defaultsVersion": 1,
49
+ "updatedAt": "2026-09-21T10:00:00.000Z"
50
+ }
@@ -920,34 +920,53 @@ const handlers = {
920
920
  const info = await symInfoRow(symbol);
921
921
  if (!info) return { ok: false, error: 'sembol bulunamadı: ' + symbol + ' (MT5 Market Watch?)' };
922
922
  const range = lotRange(cfg, symbol);
923
- const norm = finrisk.normalizeVolume(info, args.volume, range.maxLot, range.minLot);
924
- if (norm.error) return { ok: false, error: norm.error };
925
- const vol = norm.volume;
926
923
  const ptype = t.type;
927
924
  const pside = t.side;
928
- if (!(Number(args.price) > 0)) {
925
+ const entryPrice = Number(args.price) || 0;
926
+ if (!(entryPrice > 0)) {
929
927
  return { ok: false, error: 'price gerekli — limit/stop bekleyen emirleri için tetik fiyatı ver (market tipleri anlıktır, price istemez)' };
930
928
  }
929
+ /* LOT: riskPct verilirse BEKLEYEN EMİR fiyatı + SL mesafesinden hesaplanır
930
+ (piyasa emriyle aynı risk hattı); volume verilmişse normalize edilir. */
931
+ const explicitRisk = Number(args.riskPct) > 0 ? Number(args.riskPct) : 0;
932
+ let vol = 0;
933
+ let riskInfo = null;
934
+ if (explicitRisk > 0 || (!(Number(args.volume) > 0) && Number(cfg.riskPerTradePct) > 0)) {
935
+ const slRef = Number(args.sl) || 0;
936
+ if (!(slRef > 0)) return { ok: false, error: '%risk ile lot için sl zorunlu — sl ver ya da volume kullan' };
937
+ const riskPct = explicitRisk > 0 ? explicitRisk : Number(cfg.riskPerTradePct);
938
+ const acct = await bcall('account', {}, 8000);
939
+ const balance = Number(acct && acct.account && acct.account.balance) || 0;
940
+ const riskAmount = (balance * riskPct) / 100;
941
+ const rr = finrisk.calcRiskLot(info, entryPrice, slRef, riskAmount, range.maxLot, range.minLot);
942
+ if (rr.error) return { ok: false, error: rr.error };
943
+ vol = rr.volume;
944
+ riskInfo = { riskPct, riskAmount: Math.round(riskAmount * 100) / 100, lossPerLot: rr.lossPerLot, raised: !!rr.raised };
945
+ } else {
946
+ const norm = finrisk.normalizeVolume(info, args.volume, range.maxLot, range.minLot);
947
+ if (norm.error) return { ok: false, error: norm.error };
948
+ vol = norm.volume;
949
+ }
931
950
  /* KODLA DİSİPLİN: bekleyen emir de işlem sayılır */
932
951
  let discErr = null;
933
952
  try { discErr = discipline(pside, symbol, []); } catch {}
934
953
  if (discErr) return { ok: false, error: discErr };
935
954
  /* SHADOW MOD: bekleyen emir de GÖNDERİLMEZ — tez günlüğe düşer */
936
955
  if (cfg.shadowMode) {
937
- noteTrade('shadow', { symbol, type: ptype, side: pside, volume: vol, price: Number(args.price) || 0, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0, timeframe, reason: String(args.reason || '').slice(0, 500) }, ctx);
938
- return { ok: true, shadow: true, note: 'SHADOW MOD: bekleyen emir GÖNDERİLMEDİ, karar günlüğe yazıldı.' };
956
+ noteTrade('shadow', { symbol, type: ptype, side: pside, volume: vol, price: entryPrice, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0, timeframe, reason: String(args.reason || '').slice(0, 500), risk: riskInfo }, ctx);
957
+ return { ok: true, shadow: true, planned: { symbol, type: ptype, volume: vol, price: entryPrice, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 }, risk: riskInfo, note: 'SHADOW MOD: bekleyen emir GÖNDERİLMEDİ, karar günlüğe yazıldı.' };
939
958
  }
940
959
  const data = await bcall('pending', {
941
960
  symbol,
942
961
  type: ptype,
943
962
  volume: vol,
944
- price: Number(args.price) || 0,
963
+ price: entryPrice,
945
964
  sl: Number(args.sl) || 0,
946
965
  tp: Number(args.tp) || 0,
947
966
  comment: orderComment,
948
967
  }, 20000);
949
- noteTrade('pending', { symbol, type: ptype, volume: vol, timeframe, reason: String(args.reason || '').slice(0, 500) }, ctx);
950
- return { ok: true, result: data && data.result, timeframe: timeframe || null };
968
+ noteTrade('pending', { symbol, type: ptype, volume: vol, price: entryPrice, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0, timeframe, reason: String(args.reason || '').slice(0, 500), risk: riskInfo }, ctx);
969
+ return { ok: true, ordered: { symbol, type: ptype, volume: vol, price: entryPrice, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 }, risk: riskInfo, result: data && data.result, timeframe: timeframe || null };
951
970
  },
952
971
  async mt5_cancel(args, ctx) {
953
972
  if (!mt5.running) return notConnected();
package/src/main.js CHANGED
@@ -12724,6 +12724,7 @@ function finTsDecRecord(rec) {
12724
12724
  rsi: Number.isFinite(Number(rec && rec.rsi)) ? Number(rec.rsi) : null,
12725
12725
  atrPct: Number.isFinite(Number(rec && rec.atrPct)) ? Number(rec.atrPct) : null,
12726
12726
  riskPct: Number(rec && rec.riskPct) || 0,
12727
+ orderType: String((rec && rec.orderType) || 'market'),
12727
12728
  closedAt: 0, net: null, mfe: null, mae: null, reason: '',
12728
12729
  };
12729
12730
  d.decisions.push(item);
@@ -13589,8 +13590,10 @@ function finTeamWaitTick(sid) {
13589
13590
  yanıtları) YALNIZ TypeSafe System One'a gider; yanıtlar olasılık olarak
13590
13591
  döner, kararı KOD uygular. Yanıtlar AJAN DM ekip grubuna yazılır — tüm
13591
13592
  ajanlar birbirinin cevabını görür (son yanıtlar sonraki turların state'ine
13592
- de girer) ve emirler YALNIZ market buy/sell olarak açılır (bekleyen emir
13593
- YOK). LLM yalnız finance SOHBET yardımcısında serbesttir. */
13593
+ de girer). Emir tipini de Jev seçer: market / buy-sell limit / buy-sell
13594
+ stop; giriş seviyesi, SL/TP (sıkı-dengeli-geniş ATR profili), kısmi
13595
+ kapatma, SL taşıma, fiyat alarmı ve araç isteği Jev kararıyla KOD tarafından
13596
+ uygulanır. LLM yalnız finance SOHBET yardımcısında serbesttir. */
13594
13597
  const FIN_TS_MARGIN_P = 0.12; /* birinci ile ikinci seçenek arası min fark */
13595
13598
  const FIN_TS_CONFIRM_P = 0.55; /* noul teyit eşiği */
13596
13599
  const FIN_TS_EXIT_P = 0.62; /* pozisyon kapatma eşiği */
@@ -13662,9 +13665,53 @@ function finTsFeedText(limit) {
13662
13665
  return feed.map((x) => `- ${x.who}: ${String(x.text || '').replace(/\s+/g, ' ').slice(0, 220)}`).join('\n');
13663
13666
  }
13664
13667
 
13665
- /* ZAMAN DİLİMİ BOT KARARI: önce öğrenmede en iyi net getiren periyot (>=3
13666
- işlem), sonra ajanın seçimi, sonra rol varsayılanı */
13668
+ /* SAHİP TALİMATI: Trade Ajanı kartına yazılan strateji notu (Jev state'ine girer) */
13669
+ function finTsStrategyText() {
13670
+ try {
13671
+ return String(finCfg().strategy || '').slice(0, 1000);
13672
+ } catch {
13673
+ return '';
13674
+ }
13675
+ }
13676
+
13677
+ /* Talimattan ZAMAN DİLİMİ çıkar: "M15", "1m", "1 dk", "4 saat" → M15/M1/H4 */
13678
+ function finTsStrategyTf() {
13679
+ const s = String(finTsStrategyText() || '');
13680
+ const m = s.match(/\b(M1|M5|M15|M30|H1|H4|D1|W1|MN1)\b/i);
13681
+ if (m) return m[1].toUpperCase();
13682
+ const dk = s.match(/\b(\d{1,2})\s*(?:m|dk|dakika)\b/i);
13683
+ if (dk) return finLearnNormTf('M' + Number(dk[1]));
13684
+ const st = s.match(/\b(\d{1,2})\s*(?:h|saat)\b/i);
13685
+ if (st) return finLearnNormTf('H' + Number(st[1]));
13686
+ return '';
13687
+ }
13688
+
13689
+ /* Talimat "zorunlu giriş" istiyor mu? ("her mumda işlem açmak zorundasın" gibi)
13690
+ Olumsuz yazım ("zorunlu değil") modu AÇMAZ. */
13691
+ function finTsMandatoryEntry() {
13692
+ const s = String(finTsStrategyText() || '').toLowerCase();
13693
+ if (!s) return false;
13694
+ if (/zorunlu değil|mecbur değil|zorunda değil/.test(s)) return false;
13695
+ return /her mum|her barda?|her mumda|işlem açmak zorunda|her zaman işlem|sürekli işlem|durmadan işlem|zorunlu (?:olarak )?(?:işlem|giriş|al|sat)|mecbur/.test(s);
13696
+ }
13697
+
13698
+ /* Ritim: talimatta M1 → işlem ajanı turları en hızlı 60 sn, M5 → 300 sn
13699
+ (rol ajanları analiz ritmini korur; yalnız trader + sembol işçileri hızlanır) */
13700
+ function finTsStrategyPaceSec(agent) {
13701
+ const base = finPaceSec();
13702
+ if (!agent || agent.role) return base;
13703
+ const tf = finTsStrategyTf();
13704
+ if (tf === 'M1') return Math.max(30, Math.min(base, 60));
13705
+ if (tf === 'M5') return Math.max(60, Math.min(base, 300));
13706
+ return base;
13707
+ }
13708
+
13709
+ /* ZAMAN DİLİMİ: önce SAHİBİN TALİMATI (işlem ajanları için), sonra öğrenmede
13710
+ en iyi net getiren periyot (>=3 işlem), sonra ajanın seçimi, sonra rol
13711
+ varsayılanı (rol ajanları kendi analiz periyodunu korur) */
13667
13712
  function finTsPickTf(agent, symbol) {
13713
+ const owned = !(agent && agent.role) ? finTsStrategyTf() : '';
13714
+ if (owned) return owned;
13668
13715
  try {
13669
13716
  const sym = String(symbol || '').toUpperCase();
13670
13717
  const e = finLearnLoad().symbols[sym];
@@ -13862,6 +13909,111 @@ async function finTsManageOpen(sidS, agent, sym, pos, mkt, ans, qi, lines) {
13862
13909
  }
13863
13910
  }
13864
13911
 
13912
+ /* JEV FİYAT ALARMI: Jev'in seçtiği seviyeye TEK SEFERLİK alarm kurar (kod).
13913
+ Aynı sembol/yönde yakın bir alarm zaten varsa tekrar kurmaz (alarm seli yok).
13914
+ Alarm tetiklenince finWakeAgents ilgili ajanı tur beklemeden uyandırır. */
13915
+ async function finTsSetAlarm(sidS, agent, sym, mkt, ans, i, lines) {
13916
+ const c = finTsChoice(ans, 'alarm_' + i);
13917
+ if (!c || String(c.choice) === 'yok') return;
13918
+ if (!(c.p >= FIN_TS_CONFIRM_P && c.p - c.second >= FIN_TS_MARGIN_P)) return;
13919
+ const row = (mkt && mkt.row) || {};
13920
+ const ind = (mkt && mkt.ind) || {};
13921
+ const ref = Number(row.bid) || Number(row.ask) || 0;
13922
+ if (!(ref > 0)) return;
13923
+ const atr = Number(ind.atr14) || 0;
13924
+ const digits = Math.max(0, Math.min(8, Number(row.digits) || 2));
13925
+ const rnd = (v) => Number(Number(v).toFixed(digits));
13926
+ const range = ind.range20 || {};
13927
+ const fallback = atr > 0 ? atr : ref * 0.003;
13928
+ const choice = String(c.choice);
13929
+ let direction = 'above';
13930
+ let price = 0;
13931
+ if (choice === 'ust_kirilim') { direction = 'above'; price = Number(range.high) || (ref + fallback); }
13932
+ else if (choice === 'alt_kirilim') { direction = 'below'; price = Number(range.low) || (ref - fallback); }
13933
+ else if (choice === 'yukari_atr') { direction = 'above'; price = ref + atr * 0.5; }
13934
+ else if (choice === 'asagi_atr') { direction = 'below'; price = ref - atr * 0.5; }
13935
+ else return;
13936
+ /* seviye yanlış tarafta kalırsa (yeni tepe/dip) alarm anında tetiklenmesin */
13937
+ const minAway = Math.max(atr * 0.2, ref * 0.0004);
13938
+ if (direction === 'above' && price <= ref + minAway) price = ref + minAway;
13939
+ if (direction === 'below' && price >= ref - minAway) price = ref - minAway;
13940
+ price = rnd(price);
13941
+ const tol = Math.max(atr * 0.15, price * 0.0005);
13942
+ try {
13943
+ const existing = (financeState.alerts || []).some((a) =>
13944
+ a && String(a.symbol || '').toUpperCase() === sym &&
13945
+ String(a.direction || '') === direction &&
13946
+ Math.abs(Number(a.price) - price) <= tol
13947
+ );
13948
+ if (existing) {
13949
+ lines.push(`- ${sym}: 🔔 alarm zaten var (${direction === 'above' ? '≥' : '≤'} ${price})`);
13950
+ return;
13951
+ }
13952
+ } catch {}
13953
+ let r = null;
13954
+ try {
13955
+ r = await financetools.handlers.mt5_alerts(
13956
+ { action: 'set', symbol: sym, price, direction, mode: 'once', note: `TypeSafe seviye alarmı (p=${c.p.toFixed(2)})` },
13957
+ { sessionId: sidS }
13958
+ );
13959
+ } catch (e) {
13960
+ r = { ok: false, error: String((e && e.message) || e) };
13961
+ }
13962
+ if (r && r.ok) lines.push(`- ${sym}: 🔔 ALARM kuruldu ${direction === 'above' ? '≥' : '≤'} ${price} (tek seferlik)`);
13963
+ else lines.push(`- ${sym}: alarm kurulamadı — ${(r && r.error) || 'hata'}`);
13964
+ }
13965
+
13966
+ /* JEV ARAÇ İSTEĞİ: Jev'in seçtiği ihtiyaç kategorisini KOD somut isteğe çevirir
13967
+ ve TOOL botuna ASENKRON yazar (engine._toolRequest). Ajan BEKLEMEZ; araç
13968
+ hazır olunca rapor AJAN DM/sohbete düşer. Ajan başına saatte 1 istek. */
13969
+ async function finTsToolRequest(sidS, agent, ans, lines) {
13970
+ const c = finTsChoice(ans, 'arac');
13971
+ if (!c || String(c.choice) === 'yok') return;
13972
+ if (!(c.p >= FIN_TS_CONFIRM_P && c.p - c.second >= FIN_TS_MARGIN_P)) return;
13973
+ const now = Date.now();
13974
+ if (agent.tsToolReqAt && now - agent.tsToolReqAt < 60 * 60 * 1000) {
13975
+ lines.push(`- araç isteği (${c.choice}) atlandı — son istek 1 saatten yeni`);
13976
+ return;
13977
+ }
13978
+ const syms = Array.isArray(agent.symbols) && agent.symbols.length ? agent.symbols.join(', ') : '(izleme listesi)';
13979
+ const specs = {
13980
+ haber: {
13981
+ name: 'fin_haber_makro',
13982
+ task:
13983
+ 'fin_haber_makro adında bir tool yaz: verilen sembol listesi için son haber başlıklarını ve bugünün yüksek etkili ekonomik takvim olaylarını toplayıp ' +
13984
+ 'JSON döndürsün: { ok, symbol, news:[{title, source, at, url}], calendar:[{time, currency, event, impact}] }. Girdi: {symbols:[...], hours:24}.',
13985
+ context: `Kullanım: Beast Finance Jev ajanları makro/haber bağlamını karar state'ine koyacak. Örnek semboller: ${syms}. Haber için mevcut runner: src/agent/scripts/news.py (python) ve web_search/websearch.py köprüsü; takvim için investing/forexfactory benzeri ücretsiz kaynak. Çıktı sözleşmesi yukarıdaki JSON olmalı.`,
13986
+ },
13987
+ korelasyon: {
13988
+ name: 'fin_korelasyon',
13989
+ task:
13990
+ 'fin_korelasyon adında bir tool yaz: verilen semboller için son N mum kapanışından korelasyon matrisi + yönlü maruziyet özeti döndürsün: ' +
13991
+ '{ ok, tf, matrix:{A:{B:r}}, exposure:{USD:net,...} }. Girdi: {symbols:[...], timeframe:"H1", count:300}.',
13992
+ context: `Kullanım: aynı yönde birikmiş pozisyon riskini Jev kararlarında görmek için. MT5 köprüsü mt5_rates ile mum verisi çekilebilir (financetools.js desenine bak); saf matematik Node tarafında yapılabilir (getiri serisi → Pearson). Semboller: ${syms}.`,
13993
+ },
13994
+ rapor: {
13995
+ name: 'fin_sembol_rapor',
13996
+ task:
13997
+ 'fin_sembol_rapor adında bir tool yaz: bir sembolün son 30/90 gün kapanan işlemlerini öğrenme deposundan (finance/ogrenme.json) okuyup ' +
13998
+ '{ ok, symbol, tf, trades, wins, net, avgR, bestTf, worstTf, topMistakes:[...] } özeti döndürsün. Girdi: {symbol, days:30}.',
13999
+ context: `Kullanım: Jev tur state'ine kompakt performans özeti koymak. Veri kaynağı: %APPDATA%\\beast\\finance\\ogrenme.json (finLearnApi stats/list ile aynı şema). Semboller: ${syms}.`,
14000
+ },
14001
+ };
14002
+ const spec = specs[String(c.choice)];
14003
+ if (!spec) return;
14004
+ agent.tsToolReqAt = now;
14005
+ try {
14006
+ const r = await engine._toolRequest({ task: spec.task, context: spec.context }, sidS);
14007
+ lines.push(
14008
+ r && r.ok
14009
+ ? `- 🧰 Araç isteği TOOL botuna gönderildi: ${spec.name} (asenkron — hazır olunca rapor düşer)`
14010
+ : `- araç isteği başarısız — ${(r && r.error) || 'hata'}`
14011
+ );
14012
+ } catch (e) {
14013
+ lines.push('- araç isteği hatası: ' + String((e && e.message) || e));
14014
+ }
14015
+ }
14016
+
13865
14017
  function finTsSchedule(sid, agent, sec) {
13866
14018
  if (!agent || !financeState.agents.has(String(sid))) return;
13867
14019
  clearTimeout(agent.timer);
@@ -13925,12 +14077,15 @@ async function finTypeSafeRound(sid, agent) {
13925
14077
  }
13926
14078
  /* VERİ: hesap + pozisyonlar + sembol fiyat/gösterge — hepsi KOD ile toplanır,
13927
14079
  yalnız TypeSafe'e gönderilir; hiçbir LLM çağrısı yapılmaz */
13928
- const [accR, posR] = await Promise.all([
14080
+ const [accR, posR, ordR] = await Promise.all([
13929
14081
  financetools.handlers.mt5_account({}),
13930
14082
  financetools.handlers.mt5_positions({}),
14083
+ financetools.handlers.mt5_orders({}).catch(() => null),
13931
14084
  ]);
13932
14085
  const account = (accR && accR.account) || {};
13933
14086
  const posList = (posR && posR.positions) || [];
14087
+ /* BEKLEYEN EMİRLER: pozisyon slotunu paylaşır (aşırı birikmeyi önler) */
14088
+ const pendCount = (ordR && Array.isArray(ordR.orders) ? ordR.orders.length : 0);
13934
14089
  const market = {};
13935
14090
  for (const sym of symbols) {
13936
14091
  const tf = finTsPickTf(agent, sym);
@@ -13954,6 +14109,11 @@ async function finTypeSafeRound(sid, agent) {
13954
14109
  acik_pozisyon: posList.length,
13955
14110
  },
13956
14111
  limitler: { max_pozisyon: Number(f.maxPositions) || 3, min_lot: Number(f.minLot) || 0.01, max_lot: Number(f.maxLot) || 0.1 },
14112
+ bekleyen_emir: pendCount,
14113
+ /* MEVCUT ALARMLAR: Jev aynı seviyeye tekrar alarm kurmasın (kod yine kapılar) */
14114
+ alarmlar: (financeState.alerts || []).slice(0, 8).map((a) => ({
14115
+ sembol: a.symbol, yon: a.direction, fiyat: a.price, mod: a.once ? 'tek' : 'tekrarlı',
14116
+ })),
13957
14117
  /* AÇIK POZİSYONLAR: R/kâr R'si/en iyi R + koruma bayrakları — Jev'in
13958
14118
  kapatma/kısmi/SL kararları bu bağlamla verilir (finTsPosState) */
13959
14119
  pozisyonlar: posList.map((p) => finTsPosState(p)),
@@ -13962,6 +14122,10 @@ async function finTypeSafeRound(sid, agent) {
13962
14122
  /* KALİBRASYON: öğrenilmiş eşik + risk çarpanı + kazandıran kurallar */
13963
14123
  kalibrasyon: symbols.map((s) => finTsCalState(s, market[s] && market[s].tf)).filter(Boolean).join('\n') || '(kalibrasyon verisi henüz yok)',
13964
14124
  ekip_yanitlari: finTsFeedText(6),
14125
+ /* SAHİBİN TALİMATI (Trade Ajanı kartı): Jev bunu BİRİNCİL kural kabul eder;
14126
+ talimat "zorunlu giriş" içeriyorsa kod eşikleri devre dışı bırakır. */
14127
+ sahip_talimati: finTsStrategyText() || '(yok — temel teknik okuma)',
14128
+ talimat_modu: finTsMandatoryEntry() ? 'ZORUNLU GİRİŞ — bu turda işlem açmak zorunlu (bekle yok); yönü sen seç' : '',
13965
14129
  };
13966
14130
  for (const sym of symbols) {
13967
14131
  const d = market[sym];
@@ -13983,21 +14147,62 @@ async function finTypeSafeRound(sid, agent) {
13983
14147
  if (symbols.includes(s)) posBySym[s] = p;
13984
14148
  }
13985
14149
  const questions = {};
14150
+ const talimatZorunlu = finTsMandatoryEntry();
13986
14151
  symbols.forEach((sym, i) => {
13987
14152
  questions['yon_' + i] = {
13988
14153
  type: 'choice',
13989
- instructions: `${sym} ${market[sym].tf} grafiğinde verilen fiyat, gösterge, pozisyon, öğrenme geçmişi ve ekip yanıtlarına göre kısa vadeli doğru aksiyon nedir? Emin değilsen bekle.`,
13990
- criteria: {
13991
- buy: 'LONG aç — yükseliş teyidi var (yapı/momentum/EMA eğimi destekliyor)',
13992
- sell: 'SHORT aç — düşüş teyidi var',
13993
- bekle: 'İşlem açma — teyit yok, belirsiz ya da riskli',
13994
- },
14154
+ instructions: talimatZorunlu
14155
+ ? `${sym} ${market[sym].tf}: SAHİBİN TALİMATI GEREĞİ bu turda işlem açmak ZORUNDASIN — 'bekle' YOK. Yönü sen seç: gösterge/momentum/yapıya göre en olası yön (buy/sell). Sahip talimatı state'tedir, birincil kuraldır.`
14156
+ : `${sym} ${market[sym].tf} grafiğinde verilen fiyat, gösterge, pozisyon, öğrenme geçmişi, ekip yanıtları ve SAHİP TALİMATINA göre kısa vadeli doğru aksiyon nedir? Emin değilsen bekle.`,
14157
+ criteria: talimatZorunlu
14158
+ ? {
14159
+ buy: 'LONG aç — bu turda giriş zorunlu, yükseliş tarafı daha olası',
14160
+ sell: 'SHORT aç — bu turda giriş zorunlu, düşüş tarafı daha olası',
14161
+ }
14162
+ : {
14163
+ buy: 'LONG aç — yükseliş teyidi var (yapı/momentum/EMA eğimi destekliyor)',
14164
+ sell: 'SHORT aç — düşüş teyidi var',
14165
+ bekle: 'İşlem açma — teyit yok, belirsiz ya da riskli',
14166
+ },
13995
14167
  };
13996
14168
  questions['teyit_' + i] = {
13997
14169
  type: 'noul',
13998
14170
  instructions: `${sym} fiyat yapısı ve momentum, 'yon_' + ${i} kararını gerçekten destekliyor mu?`,
13999
14171
  criteria: { true: 'Net destekliyor', false: 'Zayıf/çelişkili' },
14000
14172
  };
14173
+ /* GİRİŞ TİPİ + RİSK PROFİLİ: seviyeleri KOD hesaplar (ATR/EMA), kararı Jev verir.
14174
+ "Zorunlu giriş" modunda bekleyen emir sorulmaz — giriş garanti olsun diye market. */
14175
+ if (!talimatZorunlu) {
14176
+ questions['emir_' + i] = {
14177
+ type: 'choice',
14178
+ instructions: `${sym} sinyali onaylanırsa giriş tipi ne olsun? Anında mı gir (market), geri çekilme mi bekle (limit), kırılım teyidi mi bekle (stop)? Giriş seviyesini kod ATR/EMA'dan hesaplar.`,
14179
+ criteria: {
14180
+ market: 'Anında piyasadan gir — kaçırma riski yok, fiyat şimdiki',
14181
+ limit: 'Geri çekilmede limit emir — daha iyi fiyat bekle (ATR/EMA seviyesi)',
14182
+ stop: 'Kırılımda stop emir — momentum teyidi bekle (ATR seviyesi)',
14183
+ },
14184
+ };
14185
+ }
14186
+ questions['risk_' + i] = {
14187
+ type: 'choice',
14188
+ instructions: `${sym} için SL/TP profili? Seviyeleri kod ATR'den hesaplar; volatilite ve öğrenme geçmişini dikkate al.`,
14189
+ criteria: {
14190
+ siki: 'Sıkı: SL 1.0×ATR · TP 2.0×ATR — hızlı çık, yüksek isabet',
14191
+ dengeli: 'Dengeli: SL 1.5×ATR · TP 2.5×ATR',
14192
+ genis: 'Geniş: SL 2.0×ATR · TP 3.5×ATR — trendi taşı, gürültüye dayan',
14193
+ },
14194
+ };
14195
+ questions['alarm_' + i] = {
14196
+ type: 'choice',
14197
+ instructions: `${sym} için fiyat alarmı kurulsun mu? Alarm SEVİYESİ gelince tur beklemeden uyanırsın (tek seferlik). Gereksizse yok seç.`,
14198
+ criteria: {
14199
+ yok: 'Alarm gerekmez',
14200
+ ust_kirilim: 'Üst kırılım (son 20 mum tepesi) üzerinde alarm',
14201
+ alt_kirilim: 'Alt kırılım (son 20 mum dibi) altında alarm',
14202
+ yukari_atr: 'Fiyat +0.5×ATR üzerinde alarm',
14203
+ asagi_atr: 'Fiyat -0.5×ATR altında alarm',
14204
+ },
14205
+ };
14001
14206
  if (posBySym[sym]) {
14002
14207
  const pp = posBySym[sym];
14003
14208
  const ps = finTsPosState(pp);
@@ -14032,6 +14237,17 @@ async function finTypeSafeRound(sid, agent) {
14032
14237
  };
14033
14238
  }
14034
14239
  });
14240
+ /* ARAÇ İHTİYACI (tur düzeyi): eksik araç varsa TOOL botuna yazdırılır */
14241
+ questions['arac'] = {
14242
+ type: 'choice',
14243
+ instructions: 'Bu turda eksik bir ARAÇ var mı? Varsa TOOL botuna yazdırılır (asenkron; rapor AJAN DM\'e düşer, ajan beklemez). İhtiyaç yoksa yok seç.',
14244
+ criteria: {
14245
+ yok: 'Araç isteği yok',
14246
+ haber: 'Haber/makro veri aracı (ekonomik takvim + haber akışı)',
14247
+ korelasyon: 'Korelasyon/maruziyet aracı (yönlü risk matrisi)',
14248
+ rapor: 'Sembol performans raporu aracı (öğrenme deposundan özet)',
14249
+ },
14250
+ };
14035
14251
  let ans = null;
14036
14252
  try {
14037
14253
  ans = await finTsAsk(state, questions);
@@ -14050,7 +14266,10 @@ async function finTypeSafeRound(sid, agent) {
14050
14266
  if (!financeState.agents.has(sidS)) return;
14051
14267
  const c = finTsChoice(ans, 'yon_' + i);
14052
14268
  const conf = finTsNoul(ans, 'teyit_' + i);
14269
+ const confVal = conf == null ? 0 : conf;
14053
14270
  const pos = posBySym[sym];
14271
+ /* JEV FİYAT ALARMI: seçilen seviyeye tek seferlik alarm (kurulumu kod yapar) */
14272
+ try { await finTsSetAlarm(sidS, agent, sym, market[sym], ans, i, lines); } catch {}
14054
14273
  /* ÖĞRENİLMİŞ EŞİK + RİSK ÇARPANI: kalibrasyon verisi biriktikçe kod
14055
14274
  eşiği ve riski kendisi ayarlar (sistem öğrendikçe o karar verir) */
14056
14275
  const cal = finTsCalibration(sym, market[sym].tf);
@@ -14074,42 +14293,85 @@ async function finTypeSafeRound(sid, agent) {
14074
14293
  lines.push(`- ${sym}: ${finTsFmtChoice(c)} → BEKLE`);
14075
14294
  continue;
14076
14295
  }
14077
- if (!(c.p >= thAction) || c.p - c.second < FIN_TS_MARGIN_P) {
14078
- lines.push(`- ${sym}: ${finTsFmtChoice(c)} → öğrenilmiş eşik p≥${thAction.toFixed(2)}/fark altı, BEKLE`);
14079
- continue;
14080
- }
14081
- if (conf == null || conf < FIN_TS_CONFIRM_P) {
14082
- lines.push(`- ${sym}: ${finTsFmtChoice(c)} · teyit=${conf == null ? 'yok' : conf.toFixed(2)} < ${FIN_TS_CONFIRM_P} → BEKLE`);
14083
- continue;
14296
+ /* SAHİP TALİMATI "zorunlu giriş" diyorsa eşik/teyit kapıları UYGULANMAZ
14297
+ (talimat birincil kural) — yön Jev'in seçimidir. */
14298
+ if (!talimatZorunlu) {
14299
+ if (!(c.p >= thAction) || c.p - c.second < FIN_TS_MARGIN_P) {
14300
+ lines.push(`- ${sym}: ${finTsFmtChoice(c)} → öğrenilmiş eşik p≥${thAction.toFixed(2)}/fark altı, BEKLE`);
14301
+ continue;
14302
+ }
14303
+ if (conf == null || conf < FIN_TS_CONFIRM_P) {
14304
+ lines.push(`- ${sym}: ${finTsFmtChoice(c)} · teyit=${conf == null ? 'yok' : conf.toFixed(2)} < ${FIN_TS_CONFIRM_P} → BEKLE`);
14305
+ continue;
14306
+ }
14084
14307
  }
14085
- if (opened >= 1 || posList.length >= (Number(f.maxPositions) || 3)) {
14086
- lines.push(`- ${sym}: sinyal güçlü (${c.choice} p=${c.p.toFixed(2)}) ama pozisyon sınırı dolu — bu tur açılmadı`);
14308
+ if (opened >= 1 || posList.length + pendCount >= (Number(f.maxPositions) || 3)) {
14309
+ lines.push(`- ${sym}: sinyal güçlü (${c.choice} p=${c.p.toFixed(2)}) ama pozisyon+bekleyen emir sınırı dolu — bu tur açılmadı`);
14087
14310
  continue;
14088
14311
  }
14089
- /* SL/TP KOD ile: ATR(14) tabanlı — tip ve seviyeler deterministik */
14312
+ /* GİRİŞ TİPİ + RİSK PROFİLİ (JEV kararı) — seviyeleri KOD hesaplar */
14090
14313
  const row = market[sym].row || {};
14091
- const price = c.choice === 'buy' ? Number(row.ask) || Number(row.bid) : Number(row.bid) || Number(row.ask);
14092
- if (!(price > 0)) { lines.push(`- ${sym}: fiyat okunamadı`); continue; }
14314
+ const entryRef = c.choice === 'buy' ? Number(row.ask) || Number(row.bid) : Number(row.bid) || Number(row.ask);
14315
+ if (!(entryRef > 0)) { lines.push(`- ${sym}: fiyat okunamadı`); continue; }
14093
14316
  const atr = Number(market[sym].ind && market[sym].ind.atr14) || 0;
14094
- const slDist = Math.max(atr > 0 ? atr * 1.5 : price * 0.003, price * 0.0004);
14095
- const tpDist = Math.max(atr > 0 ? atr * 2.5 : price * 0.006, slDist * 1.2);
14096
14317
  const digits = Math.max(0, Math.min(8, Number(row.digits) || 2));
14097
14318
  const rnd = (v) => Number(Number(v).toFixed(digits));
14098
- const sl = rnd(c.choice === 'buy' ? price - slDist : price + slDist);
14099
- const tp = rnd(c.choice === 'buy' ? price + tpDist : price - tpDist);
14319
+ const point = Number(row.point) > 0 ? Number(row.point) : Math.pow(10, -digits);
14320
+ const stopsPts = Number(row.trade_stops_level) || 0;
14321
+ const minDist = stopsPts > 0 ? (stopsPts + 1) * point : point;
14322
+ const isBuy = c.choice === 'buy';
14323
+ /* RİSK PROFİLİ: SL/TP çarpanlarını Jev seçer, seviyeleri kod kurar */
14324
+ const profC = finTsChoice(ans, 'risk_' + i);
14325
+ const profKey = ['siki', 'dengeli', 'genis'].includes(String(profC && profC.choice)) ? String(profC.choice) : 'dengeli';
14326
+ const prof = { siki: [1.0, 2.0], dengeli: [1.5, 2.5], genis: [2.0, 3.5] }[profKey];
14327
+ const slDist = Math.max(atr > 0 ? atr * prof[0] : entryRef * 0.003, entryRef * 0.0004);
14328
+ const tpDist = Math.max(atr > 0 ? atr * prof[1] : entryRef * 0.006, slDist * 1.2);
14329
+ /* EMİR TİPİ: market / limit / stop — Jev seçer, giriş seviyesini kod kurar.
14330
+ Zorunlu giriş modunda MARKET sabittir (bekleyen emir açık pozisyon değildir). */
14331
+ const emirC = talimatZorunlu ? null : finTsChoice(ans, 'emir_' + i);
14332
+ const emirKind = talimatZorunlu ? 'market' : (emirC && emirC.p >= FIN_TS_CONFIRM_P && emirC.p - emirC.second >= FIN_TS_MARGIN_P ? String(emirC.choice) : 'market');
14333
+ let orderType = 'market';
14334
+ let entry = entryRef;
14335
+ if (emirKind === 'limit') {
14336
+ const pull = Math.max(atr > 0 ? atr * 0.5 : entryRef * 0.0015, minDist + point);
14337
+ const atrLvl = isBuy ? entryRef - pull : entryRef + pull;
14338
+ const ema = Number(market[sym].ind && market[sym].ind.ema50);
14339
+ const emaOk =
14340
+ isFinite(ema) && ema > 0 &&
14341
+ (isBuy ? ema < entryRef - minDist : ema > entryRef + minDist) &&
14342
+ Math.abs(entryRef - ema) <= Math.max(atr * 2, entryRef * 0.006);
14343
+ entry = rnd(emaOk ? ema : atrLvl);
14344
+ orderType = isBuy ? 'buy_limit' : 'sell_limit';
14345
+ } else if (emirKind === 'stop') {
14346
+ const breakDist = Math.max(atr > 0 ? atr * 0.25 : entryRef * 0.001, minDist + point);
14347
+ entry = rnd(isBuy ? entryRef + breakDist : entryRef - breakDist);
14348
+ orderType = isBuy ? 'buy_stop' : 'sell_stop';
14349
+ }
14350
+ const sl = rnd(isBuy ? entry - slDist : entry + slDist);
14351
+ const tp = rnd(isBuy ? entry + tpDist : entry - tpDist);
14100
14352
  const riskBase = Number(f.riskPerTradePct) > 0 ? Number(f.riskPerTradePct) : 0.5;
14101
14353
  const riskPct = Math.max(0.1, Math.min(2, Math.round(riskBase * cal.riskMult * 100) / 100));
14354
+ const emirLabel =
14355
+ orderType === 'market' ? 'MARKET' :
14356
+ isBuy && orderType === 'buy_limit' ? 'BUY LIMIT' :
14357
+ !isBuy && orderType === 'sell_limit' ? 'SELL LIMIT' :
14358
+ isBuy && orderType === 'buy_stop' ? 'BUY STOP' : 'SELL STOP';
14102
14359
  const r = await financetools.handlers.mt5_trade(
14103
14360
  {
14104
14361
  symbol: sym,
14105
14362
  side: c.choice,
14106
- type: 'market',
14363
+ type: orderType,
14364
+ ...(orderType === 'market' ? {} : { price: entry }),
14107
14365
  sl,
14108
14366
  tp,
14109
14367
  riskPct,
14110
14368
  timeframe: market[sym].tf,
14111
14369
  comment: 'TS',
14112
- reason: `TypeSafe: yön ${c.choice} p=${c.p.toFixed(2)} fark=${(c.p - c.second).toFixed(2)} teyit=${conf.toFixed(2)}`,
14370
+ reason:
14371
+ `TypeSafe: yön ${c.choice} p=${c.p.toFixed(2)} fark=${(c.p - c.second).toFixed(2)} teyit=${confVal.toFixed(2)}` +
14372
+ (orderType !== 'market' ? ` · ${orderType} @${entry}` : '') +
14373
+ ` · ${profKey} profil` +
14374
+ (talimatZorunlu ? ' · SAHİP TALİMATI (zorunlu giriş)' : ''),
14113
14375
  },
14114
14376
  { sessionId: sidS }
14115
14377
  );
@@ -14127,24 +14389,31 @@ async function finTypeSafeRound(sid, agent) {
14127
14389
  action: c.choice,
14128
14390
  p: c.p,
14129
14391
  pSecond: c.second,
14130
- confirm: conf,
14392
+ confirm: confVal,
14131
14393
  hour: new Date().getHours(),
14132
14394
  emaAlign: ind.ema50 != null && ind.ema200 != null ? (ind.ema50 > ind.ema200) === (c.choice === 'buy') : null,
14133
14395
  rsi: ind.rsi14,
14134
- atrPct: atr > 0 && price > 0 ? atr / price : null,
14396
+ atrPct: atr > 0 && entryRef > 0 ? atr / entryRef : null,
14135
14397
  riskPct,
14398
+ orderType,
14136
14399
  });
14137
14400
  } catch {}
14138
14401
  lines.push(
14139
- `- ${sym}: ⚡ MARKET ${c.choice.toUpperCase()} açıldı (lot ${r.opened && r.opened.volume}, SL ${sl}, TP ${tp}, risk %${riskPct}${cal.riskMult !== 1 ? ` ×${cal.riskMult}` : ''}) · p=${c.p.toFixed(2)} teyit=${conf.toFixed(2)} · tf=${market[sym].tf}` +
14402
+ (orderType === 'market'
14403
+ ? `- ${sym}: ⚡ MARKET ${c.choice.toUpperCase()} açıldı (lot ${r.opened && r.opened.volume}, SL ${sl}, TP ${tp}, risk %${riskPct}${cal.riskMult !== 1 ? ` ×${cal.riskMult}` : ''})`
14404
+ : `- ${sym}: ⏳ ${emirLabel} emri kondu @${entry} (${profKey} profil, SL ${sl}, TP ${tp}, risk %${riskPct})`) +
14405
+ ` · p=${c.p.toFixed(2)} teyit=${confVal.toFixed(2)} · tf=${market[sym].tf}` +
14406
+ (talimatZorunlu ? ' · TALİMAT: zorunlu giriş' : '') +
14140
14407
  (thAction !== FIN_TS_DEFAULT_TH.action ? ` · öğrenilmiş eşik p≥${thAction.toFixed(2)}` : '')
14141
14408
  );
14142
14409
  } else if (r && r.ok && r.shadow) {
14143
- lines.push(`- ${sym}: SHADOW mod — emir gönderilmedi (${c.choice} p=${c.p.toFixed(2)})`);
14410
+ lines.push(`- ${sym}: SHADOW mod — emir gönderilmedi (${emirLabel} ${c.choice} p=${c.p.toFixed(2)})`);
14144
14411
  } else {
14145
14412
  lines.push(`- ${sym}: ${c.choice.toUpperCase()} sinyali reddedildi — ${(r && r.error) || 'hata'}`);
14146
14413
  }
14147
14414
  }
14415
+ /* JEV ARAÇ İSTEĞİ (tur düzeyi): eksik araç varsa TOOL botuna asenkron yazdır */
14416
+ try { await finTsToolRequest(sidS, agent, ans, lines); } catch {}
14148
14417
  finTsPost(
14149
14418
  sidS,
14150
14419
  agent,
@@ -14185,6 +14454,17 @@ async function finTypeSafeRound(sid, agent) {
14185
14454
  criteria: { true: 'Risk var — dikkat/engel', false: 'Risk normal' },
14186
14455
  };
14187
14456
  });
14457
+ /* ARAÇ İHTİYACI (tur düzeyi): eksik araç varsa TOOL botuna yazdırılır */
14458
+ questions['arac'] = {
14459
+ type: 'choice',
14460
+ instructions: 'Bu turda rolün için eksik bir ARAÇ var mı? Varsa TOOL botuna yazdırılır (asenkron; rapor AJAN DM\'e düşer, ajan beklemez). İhtiyaç yoksa yok seç.',
14461
+ criteria: {
14462
+ yok: 'Araç isteği yok',
14463
+ haber: 'Haber/makro veri aracı (ekonomik takvim + haber akışı)',
14464
+ korelasyon: 'Korelasyon/maruziyet aracı (yönlü risk matrisi)',
14465
+ rapor: 'Sembol performans raporu aracı (öğrenme deposundan özet)',
14466
+ },
14467
+ };
14188
14468
  let ans = null;
14189
14469
  try {
14190
14470
  ans = await finTsAsk(state, questions);
@@ -14206,12 +14486,15 @@ async function finTypeSafeRound(sid, agent) {
14206
14486
  (rk == null ? '' : ` · risk=${rk.toFixed(2)}${rk >= 0.55 ? ' ⚠' : ''}`)
14207
14487
  );
14208
14488
  });
14489
+ /* JEV ARAÇ İSTEĞİ (rol ajanı): eksik araç varsa TOOL botuna asenkron yazdır */
14490
+ try { await finTsToolRequest(sidS, agent, ans, lines); } catch {}
14209
14491
  finTsPost(sidS, agent, `🧠 TypeSafe analiz (LLM yok — girdi yalnız TypeSafe):\n` + lines.join('\n'), symbols.join(','));
14210
14492
  } catch (e) {
14211
14493
  try { financeLog('[typesafe] tur hatası (' + sidS + '): ' + String((e && e.message) || e)); } catch {}
14212
14494
  } finally {
14213
14495
  agent.tsBusy = false;
14214
- finTsSchedule(sidS, agent, finPaceSec());
14496
+ /* RİTİM: sahip talimatı M1/M5 diyorsa ana trader turları ona göre hızlanır */
14497
+ finTsSchedule(sidS, agent, finTsStrategyPaceSec(agent));
14215
14498
  if (agent.main && financeState.agents.has(sidS)) finPush('trader', { state: 'idle', round: agent.round, mode: 'typesafe' });
14216
14499
  }
14217
14500
  }
@@ -14235,7 +14518,7 @@ function finAgentRound(sid, opts) {
14235
14518
  }
14236
14519
  /* JEV-ONLY (SABİT): finance ajanları LLM HİÇ kullanmaz — tur deterministik
14237
14520
  TypeSafe hattıyla koşar (girdi yalnız TypeSafe'e gider, cevaplar AJAN
14238
- DM'e düşer, emirler yalnız market buy/sell) */
14521
+ DM'e düşer; emir tipi/seviyeler/alarm/araç isteği Jev kararı + kod) */
14239
14522
  if (finTsAgentMode(agent)) {
14240
14523
  finTypeSafeRound(String(sid), agent).catch(() => {});
14241
14524
  return;