beast-agent 2.22.2 → 2.23.1

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.22.2",
4
+ "version": "2.23.1",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -364,9 +364,17 @@ class Engine {
364
364
  (AJAN DM paneli buradan beslenir; restart sonrası da durur) */
365
365
  this._agentDms = [];
366
366
  this._agentDmsFile = path.join(this.sessionsDir, 'agent-dms.json');
367
+ /* AJAN DM GRUPLARI: ajanların agent_dm ile kurduğu grup sohbetleri
368
+ (id → {id,title,members,titles,createdAt,closed,closedAt}) */
369
+ this._agentGroups = new Map();
367
370
  try {
368
371
  const raw = JSON.parse(fs.readFileSync(this._agentDmsFile, 'utf8'));
369
372
  this._agentDms = Array.isArray(raw.dms) ? raw.dms : [];
373
+ if (raw.groups && typeof raw.groups === 'object') {
374
+ for (const g of Object.values(raw.groups)) {
375
+ if (g && g.id && g.title) this._agentGroups.set(String(g.id), g);
376
+ }
377
+ }
370
378
  } catch {
371
379
  this._agentDms = [];
372
380
  }
@@ -1774,7 +1782,7 @@ class Engine {
1774
1782
  } catch {
1775
1783
  return null;
1776
1784
  }
1777
- }
1785
+ }
1778
1786
 
1779
1787
  /* Beast Studio (video paneli): video YAPMA ve DÜZENLEME ajanı. Workspace'teki
1780
1788
  her şey video proje malzemesi olarak görülür (ham görüntü, ses, görsel,
@@ -1874,7 +1882,7 @@ class Engine {
1874
1882
  modeBlock2 + '\n' +
1875
1883
  'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_trade (piyasa emri), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (bekleyen emir), mt5_cancel (emir iptal).\n' +
1876
1884
  'VERİ AKIŞI (her değerlendirmede): mt5_account + mt5_positions + mt5_market çağrılarını AYNI turda PARALEL ver; gerekiyorsa mt5_history ile son işlemleri gör.\n' +
1877
- 'PARALEL + KOORDİNASYON: uzun araştırma/işleri run_background ile paralel finance işçisine devret (parent finance olduğu için işçi mt5 okuma araçlarını görür); koşan ajanlarla konuşmak için agent_dm (to: ajan başlığındaki anahtar kelime, örn "GOLD").\n' +
1885
+ 'PARALEL + KOORDİNASYON: uzun araştırma/işleri run_background ile paralel finance işçisine devret (parent finance olduğu için işçi mt5 okuma araçlarını görür); koşan ajanlarla konuşmak için agent_dm (to: ajan başlığındaki anahtar kelime, örn "GOLD"; ortak karar için group: "İSİM" ile grup sohbeti kur — mesaj tüm üyelere düşer). Görevin bitince DM/grup sohbetleri otomatik KAPANIR (geçmiş panelde kalır).\n' +
1878
1886
  (symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
1879
1887
  (lim.maxLot ? `LİMİTLER: max lot ${lim.maxLot}` + (lim.maxPositions ? ` · max eşzamanlı pozisyon ${lim.maxPositions}` : '') + ' — bunları aşıp araç kullanma; sistem zaten reddeder.\n' : '') +
1880
1888
  'RİSK DİSİPLİNİ:\n' +
@@ -2710,7 +2718,13 @@ class Engine {
2710
2718
  }
2711
2719
  this.emit({ type: 'message', sessionId: s.id, message: msg });
2712
2720
  this.emit({ type: 'sessions' });
2713
- this._run(s).catch(() => {});
2721
+ /* sessiz catch YOK: tur kurulumu patlarsa hata UI'ya düşsün —
2722
+ "ajan cvp vermiyor, hata görünmüyor" bu yüzdendi */
2723
+ this._run(s).catch((e) => {
2724
+ try {
2725
+ this.emit({ type: 'error', sessionId: s.id, error: String((e && e.message) || e) });
2726
+ } catch {}
2727
+ });
2714
2728
  return true;
2715
2729
  }
2716
2730
 
@@ -3066,6 +3080,19 @@ class Engine {
3066
3080
 
3067
3081
  /* #17 iş kayıtlarını diske yaz — uygulama kapansa da geçmiş dursun.
3068
3082
  Oturum dosyası silinmiş işler (elle silinmiş) geri yazılmaz. */
3083
+ /* ATOMİK YAZIM: önce .tmp sonra rename — iki instance çakışsın ya da
3084
+ proses yazım ortasında ölse bile dosya ASLA 0 bayt bozulmaz
3085
+ (bg-jobs.json 0 bayt olunca tüm ajanlar kayboluyordu) */
3086
+ _atomicWrite(file, data) {
3087
+ try {
3088
+ const tmp = file + '.tmp';
3089
+ fs.writeFileSync(tmp, data);
3090
+ fs.renameSync(tmp, file);
3091
+ } catch {
3092
+ try { fs.writeFileSync(file, data); } catch {}
3093
+ }
3094
+ }
3095
+
3069
3096
  _saveBgJobs() {
3070
3097
  try {
3071
3098
  const jobs = [];
@@ -3075,7 +3102,7 @@ class Engine {
3075
3102
  delete c._persisted;
3076
3103
  jobs.push(c);
3077
3104
  }
3078
- fs.writeFileSync(this._bgJobsFile, JSON.stringify({ v: 1, jobs }) + '\n');
3105
+ this._atomicWrite(this._bgJobsFile, JSON.stringify({ v: 1, jobs }) + '\n');
3079
3106
  } catch {}
3080
3107
  }
3081
3108
 
@@ -3181,6 +3208,8 @@ class Engine {
3181
3208
  }
3182
3209
  /* #17 öz-kurtarma hakları tükendi → owner'a anlık altyapı uyarısı */
3183
3210
  this._notifyOwnerTaskFailed(job, msg);
3211
+ /* AJAN DM: işi kesin bitti — ajanın DM/grup sohbetleri kapanır */
3212
+ try { this._agentDmClose(sid); } catch {}
3184
3213
  if (grouped) {
3185
3214
  /* #18 hatalı üye de gruba yazılır — birleşik raporda görünecek */
3186
3215
  this._bgGroupRecord(job.title, 'error', 'HATA: ' + msg.slice(0, 300));
@@ -3204,6 +3233,11 @@ class Engine {
3204
3233
  this._pendingReports.push({ parentId: job.parentId, text });
3205
3234
  this.flushPendingReports(job.parentId);
3206
3235
  }
3236
+ /* AJAN DM: işi bitti (kullanıcı iptali dahil) — sohbetler kapanır */
3237
+ try { this._agentDmClose(sid); } catch {}
3238
+ } else if (status === 'done') {
3239
+ /* AJAN DM: görev tamamlandı — ajanın DM/grup sohbetleri kapanır */
3240
+ try { this._agentDmClose(sid); } catch {}
3207
3241
  }
3208
3242
  }
3209
3243
 
@@ -3464,7 +3498,7 @@ class Engine {
3464
3498
  `- Bağımsız araç çağrılarını aynı turda PARALEL ver.\n` +
3465
3499
  `- PDF gerekirse pip\u2019ten paket kurma (Türkçe bozar); Node\u2019un kurulu \`pdf-lib\`+fontkit\u2019iyle .js script yazıp \`node\` ile çalıştır. md→pdf çevirme: belge çıktısını doğrudan PDF olarak üret.\n` +
3466
3500
  `- ARAŞTIRMA SINIRI: 3-5 kaynak yeter; süre hedefi ~3 dakika. 2-3 denemede bulunamayan bilgiyi BIRAK — bulabildiğin kısmi sonucu raporla ve neyi bulamadığını yaz. Kapalı/gizli içerik peşinde koşma.\n` +
3467
- `AJAN KOORDİNASYONU: diğer koşan ajanlarla (paralel işler, finance ajanları) konuşman gerekiyorsa agent_dm aracını kullan — to: hedefin başlığındaki anahtar kelime (örn "GOLD", "Trader"), message: 1-3 cümlelik net mesaj. Senden istenen görevde başka bir ajanın zaten yaptığın işe ihtiyacı varsa DM ile haber ver.\n` +
3501
+ `AJAN KOORDİNASYONU: diğer koşan ajanlarla (paralel işler, finance ajanları) konuşman gerekiyorsa agent_dm aracını kullan — to: hedefin başlığındaki anahtar kelime (örn "GOLD", "Trader"), message: 1-3 cümlelik net mesaj. Senden istenen görevde başka bir ajanın zaten yaptığın işe ihtiyacı varsa DM ile haber ver. Birden fazla ajanla ORTAK KARAR almanız gerekiyorsa group: "İSİM" vererek grup sohbeti kur — mesajın tüm üyelere düşer, cevaplar aynı gruba gelir. Görevin bitince DM/grup sohbetleri otomatik KAPANIR (geçmiş panelde kalır).\n` +
3468
3502
  FORMAT_RULES + '\n' +
3469
3503
  `SON ÇIKTI: 3-5 satırlık net sonuç raporu, madde madde. Soru sorma, sohbet etme.`
3470
3504
  );
@@ -3551,7 +3585,7 @@ class Engine {
3551
3585
  ? this.buildStudioSystem(session)
3552
3586
  : bcAgent
3553
3587
  ? this.buildOcSystem(session, bcAgent)
3554
- : await this.buildSystem(promptText, session);
3588
+ : await this.buildSystem(promptText, session);
3555
3589
  // Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
3556
3590
  // Çoklu izin (ör. web+read) seçiliyse kümeler BİRLEŞİR — hepsinin araçları açık olur.
3557
3591
  // BC'de opencode adları Beast adına çevrilerek denetlenir (read → read_file).
@@ -4061,7 +4095,7 @@ class Engine {
4061
4095
  }
4062
4096
  }
4063
4097
  } catch {}
4064
- }
4098
+ }
4065
4099
  // Ekran görüntüsü gibi araçlar görseli sonraki tura enjekte eder
4066
4100
  let injectedImage = null;
4067
4101
  /* opencode ctx.metadata portu: edit/write sonucundaki diffView, LLM
@@ -5377,18 +5411,19 @@ const AGENT_DM_DEF = {
5377
5411
  function: {
5378
5412
  name: 'agent_dm',
5379
5413
  description:
5380
- 'Send a short DM to another running agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). `topic` = short subject label — replies to the same topic stay in the SAME conversation thread, so ALWAYS reuse the topic you were DMed with when replying.',
5414
+ 'Send a short DM to another running agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work, and MAKE JOINT DECISIONS. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). `topic` = short subject label — replies to the same topic stay in the SAME conversation thread, so ALWAYS reuse the topic you were DMed with when replying. `group` = optional group-chat name (e.g. "GOLD EKIP"): creates or reuses a group conversation, adds the target agent as a member, and your message is delivered to EVERY member — use groups when a decision needs multiple agents.',
5381
5415
  parameters: {
5382
5416
  type: 'object',
5383
5417
  properties: {
5384
5418
  to: { type: 'string', description: 'Target agent session id or a title keyword' },
5385
5419
  topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
5386
5420
  message: { type: 'string', description: 'Short message (1-3 sentences)' },
5421
+ group: { type: 'string', description: 'Optional group-chat name — send to ALL members of that group (creates it on first use, adds the target agent)' },
5387
5422
  },
5388
- required: ['to', 'message'],
5423
+ required: ['message'],
5389
5424
  },
5390
5425
  },
5391
- };
5426
+ };
5392
5427
 
5393
5428
  const TOOLS = [
5394
5429
  ...tools.definitions,
@@ -6180,89 +6215,146 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6180
6215
  const to = String((args && args.to) || '').trim();
6181
6216
  const text = String((args && args.message) || '').trim().slice(0, 1200);
6182
6217
  if (!text) return { ok: false, error: 'mesaj boş' };
6218
+ const groupName = String((args && args.group) || '').replace(/\s+/g, ' ').trim().slice(0, 40);
6183
6219
  const jobs = this._bgJobs || new Map();
6184
- let target = jobs.has(to) ? to : '';
6185
- if (!target) {
6186
- const q = to.toLowerCase();
6187
- for (const [sid, j] of jobs) {
6188
- if (sid === String(fromSid)) continue;
6189
- if (j && j.title && String(j.title).toLowerCase().includes(q)) {
6190
- target = sid;
6191
- break;
6220
+ const fromJob = jobs.get(String(fromSid));
6221
+ /* gönderici koşan ajan değilse (düz chat oturumu) cache'ten oturum bilgisi */
6222
+ const fromSess = fromJob ? null : this.cache.get(String(fromSid)) || null;
6223
+ const fromTitle =
6224
+ fromJob && fromJob.title
6225
+ ? String(fromJob.title)
6226
+ : fromSess
6227
+ ? 'Beast · ' + String(fromSess.bgTitle || fromSess.title || fromSess.code || '')
6228
+ : 'Ajan';
6229
+ /* hedef çöz: oturum id YA DA başlık anahtar kelimesi (koşan ajanlar +
6230
+ ANA OTURUM FALLBACK: chat oturumları this.cache'te yaşar — finance
6231
+ trader → ana Beast DM zinciri böyle kapanır). */
6232
+ let target = '';
6233
+ if (to) {
6234
+ target = jobs.has(to) ? to : '';
6235
+ if (!target) {
6236
+ const q = to.toLowerCase();
6237
+ for (const [sid, j] of jobs) {
6238
+ if (sid === String(fromSid)) continue;
6239
+ if (j && j.title && String(j.title).toLowerCase().includes(q)) {
6240
+ target = sid;
6241
+ break;
6242
+ }
6192
6243
  }
6193
6244
  }
6194
- }
6195
- /* ANA OTURUM FALLBACK: chat oturumları this.cache'te yaşar (_bgJobs dışında).
6196
- Finance trader → ana Beast DM zinciri böyle kapanır: ajan sahibine
6197
- (ana sohbete) DM ile rapor verebilir. Eşleşme: bgTitle / title / kod. */
6198
- if (!target) {
6199
- const q = to.toLowerCase();
6200
- for (const [sid, s] of this.cache) {
6201
- if (sid === String(fromSid)) continue;
6202
- if (!s || s.bgJob || s.isBotDm) continue;
6203
- if (s.botId && s.botId !== 'beast') continue;
6204
- const hay = [s.bgTitle, s.title, s.code].map((x) => String(x || '').toLowerCase());
6205
- if (hay.some((x) => x && x.includes(q))) {
6206
- target = sid;
6207
- break;
6245
+ if (!target) {
6246
+ const q = to.toLowerCase();
6247
+ for (const [sid, s] of this.cache) {
6248
+ if (sid === String(fromSid)) continue;
6249
+ if (!s || s.bgJob || s.isBotDm) continue;
6250
+ if (s.botId && s.botId !== 'beast') continue;
6251
+ /* tam başlık da eşleşir: ajan DM'den gelen "Beast · KOD" başlığıyla
6252
+ cevap verir — fromTitle formatı birebir tanınmalı */
6253
+ const hay = [s.bgTitle, s.title, s.code, s.code ? 'beast · ' + s.code : ''].map((x) =>
6254
+ String(x || '').toLowerCase()
6255
+ );
6256
+ if (hay.some((x) => x && x.includes(q))) {
6257
+ target = sid;
6258
+ break;
6259
+ }
6208
6260
  }
6209
6261
  }
6210
- }
6211
- if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
6212
- const fromJob = jobs.get(String(fromSid));
6213
- const toJob = jobs.get(target);
6214
- const targetIsAgent = jobs.has(target);
6215
- const mainChatSess = targetIsAgent ? null : this.cache.get(String(target)) || null;
6216
- const targetIsMainChat = !!(
6217
- mainChatSess &&
6218
- !mainChatSess.bgJob &&
6219
- !mainChatSess.isBotDm &&
6220
- (!mainChatSess.botId || mainChatSess.botId === 'beast')
6221
- );
6222
- if (!targetIsAgent && !targetIsMainChat) {
6223
- const titles = [...jobs.values()]
6224
- .filter((j) => j && j.status === 'running' && j.title)
6225
- .map((j) => j.title)
6226
- .slice(0, 8);
6262
+ if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
6263
+ if (!target) {
6264
+ const titles = [...jobs.values()]
6265
+ .filter((j) => j && j.status === 'running' && j.title)
6266
+ .map((j) => j.title)
6267
+ .slice(0, 8);
6268
+ return {
6269
+ ok: false,
6270
+ error: 'hedef ajan bulunamadı: "' + to + '"' + (titles.length ? ' — koşan ajanlar: ' + titles.join(', ') : ''),
6271
+ };
6272
+ }
6273
+ } else if (!groupName) {
6274
+ return { ok: false, error: 'to (hedef ajan) ya da group (grup adı) gerekli' };
6275
+ }
6276
+ const toJob = target ? jobs.get(target) : null;
6277
+ const mainChatSess = target && !toJob ? this.cache.get(String(target)) || null : null;
6278
+ const toTitle = target
6279
+ ? toJob
6280
+ ? String((toJob && toJob.title) || 'Ajan')
6281
+ : String((mainChatSess && (mainChatSess.bgTitle || mainChatSess.title)) || 'Beast')
6282
+ : '';
6283
+ /* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
6284
+ const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
6285
+ const at = new Date().toISOString();
6286
+
6287
+ /* GRUP SOHBETİ: kur/yeniden kullan, hedefi üye yap, mesaj HER ÜYEYE düşer.
6288
+ Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır. */
6289
+ if (groupName) {
6290
+ /* gid Türkçe-kararsız normalize: ALTIN/Altın/altın aynı grup */
6291
+ const gid =
6292
+ 'grp:' +
6293
+ groupName
6294
+ .toLowerCase()
6295
+ .replace(/\u0307/g, '')
6296
+ .replace(/ı/g, 'i')
6297
+ .replace(/ğ/g, 'g')
6298
+ .replace(/ü/g, 'u')
6299
+ .replace(/ş/g, 's')
6300
+ .replace(/ö/g, 'o')
6301
+ .replace(/ç/g, 'c');
6302
+ let g = this._agentGroups.get(gid);
6303
+ if (!g) {
6304
+ if (!target) return { ok: false, error: 'yeni grup için to ile en az bir ajan belirt' };
6305
+ g = { id: gid, title: groupName, members: [], titles: {}, createdAt: at, closed: false };
6306
+ this._agentGroups.set(gid, g);
6307
+ }
6308
+ g.titles = g.titles || {};
6309
+ const addMember = (sid, title) => {
6310
+ sid = String(sid);
6311
+ if (!g.members.includes(sid)) g.members.push(sid);
6312
+ if (title) g.titles[sid] = title;
6313
+ };
6314
+ addMember(fromSid, fromTitle);
6315
+ if (target) addMember(target, toTitle);
6316
+ g.closed = false; /* yeni mesaj → sohbet yeniden aktif */
6317
+ delete g.closedAt;
6318
+ const dm = {
6319
+ at,
6320
+ from: String(fromSid),
6321
+ fromTitle,
6322
+ to: target || '',
6323
+ toTitle: target ? toTitle : 'grup',
6324
+ group: gid,
6325
+ groupTitle: g.title,
6326
+ topic,
6327
+ text,
6328
+ };
6329
+ this._pushAgentDm(dm);
6330
+ for (const m of g.members) {
6331
+ if (m === String(fromSid) || !jobs.has(m)) continue;
6332
+ emitSafe(this, m, { type: 'agent-dm', ...dm });
6333
+ (this._pendingReports = this._pendingReports || []).push({
6334
+ parentId: m,
6335
+ text:
6336
+ `[AJAN DM (grup: "${g.title}") — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6337
+ `(Cevabını agent_dm aracıyla ver — group: "${g.title}", to: "${dm.fromTitle}", topic: "${topic}")`,
6338
+ });
6339
+ this.flushPendingReports(m);
6340
+ }
6341
+ this._persistAgentDms();
6227
6342
  return {
6228
- ok: false,
6229
- error: 'hedef ajan bulunamadı: "' + to + '"' + (titles.length ? ' — koşan ajanlar: ' + titles.join(', ') : ''),
6343
+ ok: true,
6344
+ group: g.title,
6345
+ members: g.members.map((m) => g.titles[m] || m),
6346
+ ...(target ? { to: target, toTitle } : {}),
6347
+ topic,
6230
6348
  };
6231
6349
  }
6232
- /* TÜM ajanlar birbiriyle iletişim kurabilir — sınırsız DM (yalnız kendine
6233
- DM atanamaz). Finance ajanları da dahil herkes herkesle konuşur. */
6234
- /* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
6235
- const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
6236
- const fromTitle =
6237
- fromJob && fromJob.title
6238
- ? String(fromJob.title)
6239
- : fromSess
6240
- ? 'Beast · ' + String(fromSess.code || '')
6241
- : 'Ajan';
6242
- const toTitle = targetIsAgent
6243
- ? String((toJob && toJob.title) || 'Ajan')
6244
- : String((mainChatSess && (mainChatSess.bgTitle || mainChatSess.title)) || 'Beast');
6245
- const dm = {
6246
- at: new Date().toISOString(),
6247
- from: String(fromSid),
6248
- fromTitle,
6249
- to: String(target),
6250
- toTitle,
6251
- topic,
6252
- text,
6253
- };
6254
- this._agentDms = this._agentDms || [];
6255
- this._agentDms.push(dm);
6256
- if (this._agentDms.length > 400) this._agentDms.splice(0, this._agentDms.length - 400);
6257
- try {
6258
- fs.writeFileSync(this._agentDmsFile, JSON.stringify({ dms: this._agentDms }, null, 2));
6259
- } catch {}
6350
+
6351
+ /* BİREYSEL DM — DÜZ CHAT OTURUMUNA (sahibin sohbet geçmişi) ENJEKTE
6352
+ ETMEYİZ: DM trafik yalnız AJAN DM konsolunda görünür. */
6353
+ const dm = { at, from: String(fromSid), fromTitle, to: String(target), toTitle, topic, text };
6354
+ this._pushAgentDm(dm);
6355
+ this._persistAgentDms();
6260
6356
  emitSafe(this, target, { type: 'agent-dm', ...dm });
6261
- /* teslim: hedef AJANSA (arka plan işi / finance ajanı) mesaj olarak düşer —
6262
- meşgulse pending kuyruğunda bekler, done olunca görünür.
6263
- DÜZ CHAT OTURUMUNA (sahibin sohbet geçmişi) ENJEKTE ETMEYİZ — DM trafik
6264
- yalnız AJAN DM konsolunda görünür, senin sohbetlerin karışmaz. */
6265
- if (targetIsAgent) {
6357
+ if (jobs.has(target)) {
6266
6358
  (this._pendingReports = this._pendingReports || []).push({
6267
6359
  parentId: target,
6268
6360
  text:
@@ -6277,12 +6369,60 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6277
6369
  }
6278
6370
  };
6279
6371
 
6372
+ Engine.prototype._pushAgentDm = function (dm) {
6373
+ this._agentDms = this._agentDms || [];
6374
+ this._agentDms.push(dm);
6375
+ if (this._agentDms.length > 600) this._agentDms.splice(0, this._agentDms.length - 600);
6376
+ };
6377
+
6378
+ Engine.prototype._persistAgentDms = function () {
6379
+ try {
6380
+ const groups = {};
6381
+ for (const [, g] of this._agentGroups || new Map()) groups[g.id] = g;
6382
+ this._atomicWrite(this._agentDmsFile, JSON.stringify({ dms: this._agentDms, groups }, null, 2));
6383
+ } catch {}
6384
+ };
6385
+
6386
+ /* AJAN İŞİ BİTİNCE: o ajanın bireysel DM'leri kapanır; üyesi olduğu
6387
+ gruplarda DİĞER koşan ajan kalmadıysa grup sohbeti de kapanır.
6388
+ Geçmiş panelde GÖRÜNMEYE DEVAM EDER (silinmez, "Geçmiş" bölümüne düşer). */
6389
+ Engine.prototype._agentDmClose = function (sid) {
6390
+ sid = String(sid);
6391
+ const now = new Date().toISOString();
6392
+ let changed = false;
6393
+ for (const dm of this._agentDms || []) {
6394
+ if (dm.closed || dm.group) continue;
6395
+ if (String(dm.from) === sid || String(dm.to) === sid) {
6396
+ dm.closed = true;
6397
+ dm.closedAt = now;
6398
+ changed = true;
6399
+ }
6400
+ }
6401
+ const jobs = this._bgJobs || new Map();
6402
+ for (const [, g] of this._agentGroups || new Map()) {
6403
+ if (g.closed || !(g.members || []).includes(sid)) continue;
6404
+ const anyRunning = g.members.some((m) => {
6405
+ const j = jobs.get(m);
6406
+ return j && j.status === 'running';
6407
+ });
6408
+ if (!anyRunning) {
6409
+ g.closed = true;
6410
+ g.closedAt = now;
6411
+ changed = true;
6412
+ }
6413
+ }
6414
+ if (changed) this._persistAgentDms();
6415
+ };
6416
+
6280
6417
  Engine.prototype.agentDmsList = function () {
6281
- return [...(this._agentDms || [])].slice(-200);
6418
+ const groups = [];
6419
+ for (const [, g] of this._agentGroups || new Map()) groups.push(g);
6420
+ return { dms: [...(this._agentDms || [])].slice(-600), groups };
6282
6421
  };
6283
6422
 
6284
6423
  Engine.prototype.agentDmsClear = function () {
6285
6424
  this._agentDms = [];
6425
+ this._agentGroups = new Map();
6286
6426
  try { fs.rmSync(this._agentDmsFile, { force: true }); } catch {}
6287
6427
  return { ok: true };
6288
6428
  };
package/src/main.js CHANGED
@@ -8764,6 +8764,15 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
8764
8764
  }
8765
8765
  } catch {}
8766
8766
  }
8767
+ if (financeState.mode) {
8768
+ /* OTOMATİK TRADER: finance modu açılır açılmaz trader botu devreye girer
8769
+ (best-effort — model/köprü yoksa sessizce atlanır, log'a düşer) */
8770
+ financeTraderStart()
8771
+ .then((r) => {
8772
+ if (!r || !r.ok) financeLog('[trader] otomatik başlatılamadı: ' + ((r && r.error) || 'bilinmeyen'));
8773
+ })
8774
+ .catch(() => {});
8775
+ }
8767
8776
  return { ok: true, mode: financeState.mode, needNew };
8768
8777
  });
8769
8778
 
@@ -8814,7 +8823,9 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
8814
8823
  return { ok: true, cfg: f };
8815
8824
  });
8816
8825
 
8817
- ipcMain.handle('finance:trader:start', async () => {
8826
+ /* Trader botu başlat: ANA trader varsa sürdür, yoksa yeni sürekli ajan aç.
8827
+ finance:trader:start'tan VE finance modu AÇILDIĞINDAN otomatik çağrılır. */
8828
+ async function financeTraderStart() {
8818
8829
  if (!engine) return { ok: false, error: 'ajan hazır değil' };
8819
8830
  const f = finCfg();
8820
8831
  if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
@@ -8828,6 +8839,11 @@ ipcMain.handle('finance:trader:start', async () => {
8828
8839
  break;
8829
8840
  }
8830
8841
  }
8842
+ /* zaten koşuyorsa yeniden brief düşürme — sessizce onayla */
8843
+ if (mainSid && financeState.traderOn) {
8844
+ const mj = engine._bgJobs && engine._bgJobs.get(mainSid);
8845
+ if (mj && mj.status === 'running') return { ok: true, sid: mainSid, already: true };
8846
+ }
8831
8847
  if (!mainSid) {
8832
8848
  const created = finAgentCreate([], true);
8833
8849
  mainSid = String(created.s.id);
@@ -8845,7 +8861,9 @@ ipcMain.handle('finance:trader:start', async () => {
8845
8861
  financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
8846
8862
  finPush('trader', { state: 'running', round: mainAgent.round });
8847
8863
  return { ok: true, sid: mainSid };
8848
- });
8864
+ }
8865
+
8866
+ ipcMain.handle('finance:trader:start', () => financeTraderStart());
8849
8867
 
8850
8868
  ipcMain.handle('finance:trader:stop', async () => {
8851
8869
  financeState.traderOn = false;
@@ -5031,18 +5031,35 @@ async function loadRailChat(id) {
5031
5031
  açılır — gelen/giden hepsi kronolojik. Thread = ajan çifti + KONU. */
5032
5032
 
5033
5033
  const dmState = {
5034
- dms: [], // { at, from, fromTitle, to, toTitle, topic, text }
5034
+ dms: [], // { at, from, fromTitle, to, toTitle, topic, text, group?, closed? }
5035
+ groups: [], // { id, title, members, titles, createdAt, closed, closedAt }
5035
5036
  modalThread: null, // modalde açık oturum key'i (null = oturum listesi)
5036
5037
  };
5037
5038
 
5039
+ function dmGroupOf(id) {
5040
+ return (dmState.groups || []).find((g) => String(g.id) === String(id)) || null;
5041
+ }
5042
+
5038
5043
  function dmThreadOf(dm) {
5044
+ if (dm.group) return 'G|' + String(dm.group);
5039
5045
  const a = String(dm.from || '');
5040
5046
  const b = String(dm.to || '');
5041
5047
  const pair = a < b ? a + '|' + b : b + '|' + a;
5042
5048
  return pair + '|' + String(dm.topic || '(genel)').toLowerCase();
5043
5049
  }
5044
5050
 
5051
+ function dmThreadClosed(msgs) {
5052
+ const last = msgs[msgs.length - 1];
5053
+ if (!last) return false;
5054
+ if (last.group) {
5055
+ const g = dmGroupOf(last.group);
5056
+ return !!(g && g.closed);
5057
+ }
5058
+ return !!last.closed;
5059
+ }
5060
+
5045
5061
  function dmThreadTitle(dm) {
5062
+ if (dm.group) return '👥 ' + String(dm.groupTitle || 'Grup');
5046
5063
  const a = String(dm.fromTitle || 'Ajan');
5047
5064
  const b = String(dm.toTitle || 'Ajan');
5048
5065
  const pair = a < b ? a + ' ↔ ' + b : b + ' ↔ ' + a;
@@ -5069,14 +5086,61 @@ function dmTime(iso) {
5069
5086
  }
5070
5087
 
5071
5088
  function dmMsgHtml(m) {
5089
+ const who = m.group
5090
+ ? escapeHtml(String(m.fromTitle || 'Ajan'))
5091
+ : `${escapeHtml(String(m.fromTitle || 'Ajan'))} → ${escapeHtml(String(m.toTitle || 'Ajan'))}`;
5072
5092
  return (
5073
- `<div class="dmt-msg"><span class="dm-who">${escapeHtml(String(m.fromTitle || 'Ajan'))} → ${escapeHtml(String(m.toTitle || 'Ajan'))}</span>` +
5093
+ `<div class="dmt-msg"><span class="dm-who">${who}</span>` +
5074
5094
  `${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
5075
5095
  );
5076
5096
  }
5077
5097
 
5098
+ /* bir sohbet kartı (rail + modal ortak): AKTİF/GEÇMİŞ ayrımı için closed
5099
+ bayrağı kartta işaretlenir */
5100
+ function dmThreadCard(key, msgs, onClick) {
5101
+ const last = msgs[msgs.length - 1];
5102
+ const closed = dmThreadClosed(msgs);
5103
+ const el = document.createElement('div');
5104
+ el.className = 'dmt' + (closed ? ' dmt-closed' : '');
5105
+ let membersLine = '';
5106
+ if (last.group) {
5107
+ const g = dmGroupOf(last.group);
5108
+ if (g && Array.isArray(g.members) && g.members.length) {
5109
+ const names = g.members.map((m) => (g.titles && g.titles[m]) || m).slice(0, 4);
5110
+ membersLine = `<div class="dmt-members">👥 ${escapeHtml(names.join(', '))}</div>`;
5111
+ }
5112
+ }
5113
+ el.innerHTML =
5114
+ '<div class="dmt-head">' +
5115
+ `<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
5116
+ `<span class="dmt-count">${closed ? 'kapandı' : msgs.length}</span>` +
5117
+ '</div>' +
5118
+ membersLine +
5119
+ `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>`;
5120
+ el.addEventListener('click', onClick);
5121
+ return el;
5122
+ }
5123
+
5078
5124
  /* --- sağ sütun: OTURUM LİSTESİ (rail gibi) --- */
5079
5125
 
5126
+ /* AKTİF/GEÇMİŞ ayrılmış sohbet listesi (rail + modal ortak) */
5127
+ function dmAppendThreadSections(list, onClickThread) {
5128
+ const all = dmThreads();
5129
+ const act = all.filter(([, msgs]) => !dmThreadClosed(msgs));
5130
+ const past = all.filter(([, msgs]) => dmThreadClosed(msgs));
5131
+ const sec = (label, items) => {
5132
+ if (!items.length) return;
5133
+ const h = document.createElement('div');
5134
+ h.className = 'dm-sec';
5135
+ h.textContent = label;
5136
+ list.appendChild(h);
5137
+ for (const [key, msgs] of items) list.appendChild(dmThreadCard(key, msgs, () => onClickThread(key)));
5138
+ };
5139
+ sec('● AKTİF', act);
5140
+ sec('— GEÇMİŞ', past);
5141
+ return all.length;
5142
+ }
5143
+
5080
5144
  function renderDmRail() {
5081
5145
  const list = els.dmRailList;
5082
5146
  if (!list) return;
@@ -5085,25 +5149,17 @@ function renderDmRail() {
5085
5149
  return;
5086
5150
  }
5087
5151
  list.innerHTML = '';
5088
- for (const [key, msgs] of dmThreads()) {
5089
- const last = msgs[msgs.length - 1];
5090
- const el = document.createElement('div');
5091
- el.className = 'dmt';
5092
- el.innerHTML =
5093
- '<div class="dmt-head">' +
5094
- `<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
5095
- `<span class="dmt-count">${msgs.length}</span>` +
5096
- '</div>' +
5097
- `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>`;
5098
- el.addEventListener('click', () => openDmModal(key)); // sohbet yalnız modalde
5099
- list.appendChild(el);
5100
- }
5152
+ dmAppendThreadSections(list, (key) => openDmModal(key)); // sohbet yalnız modalde
5101
5153
  }
5102
5154
 
5103
5155
  async function refreshDmRail() {
5104
5156
  try {
5105
- const dms = await beast.agentDmsList();
5106
- if (Array.isArray(dms)) dmState.dms = dms;
5157
+ const res = await beast.agentDmsList();
5158
+ if (Array.isArray(res)) dmState.dms = res;
5159
+ else {
5160
+ dmState.dms = (res && res.dms) || [];
5161
+ dmState.groups = (res && res.groups) || [];
5162
+ }
5107
5163
  } catch {}
5108
5164
  renderDmRail();
5109
5165
  if (els.dmOverlay && !els.dmOverlay.hidden) renderDmModal();
@@ -5142,26 +5198,14 @@ function renderDmModal() {
5142
5198
  if (box) box.scrollTop = box.scrollHeight;
5143
5199
  return;
5144
5200
  }
5145
- /* OTURUM LİSTESİ */
5146
- if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — OTURUMLAR';
5201
+ /* OTURUM LİSTESİ — AKTİF + GEÇMİŞ bölümleri */
5202
+ if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — AKTİF & GEÇMİŞ';
5147
5203
  if (els.dmBackBtn) els.dmBackBtn.hidden = true;
5148
5204
  list.innerHTML = '';
5149
- for (const [key, msgs] of dmThreads()) {
5150
- const last = msgs[msgs.length - 1];
5151
- const el = document.createElement('div');
5152
- el.className = 'dmt';
5153
- el.innerHTML =
5154
- '<div class="dmt-head">' +
5155
- `<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
5156
- `<span class="dmt-count">${msgs.length}</span>` +
5157
- '</div>' +
5158
- `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 120))} · ${dmTime(last.at)}</div>`;
5159
- el.addEventListener('click', () => {
5160
- dmState.modalThread = key;
5161
- renderDmModal();
5162
- });
5163
- list.appendChild(el);
5164
- }
5205
+ dmAppendThreadSections(list, (key) => {
5206
+ dmState.modalThread = key;
5207
+ renderDmModal();
5208
+ });
5165
5209
  }
5166
5210
 
5167
5211
  function openDmModal(threadKey) {
@@ -2157,6 +2157,18 @@ body.rail-hidden #dmRailHead { padding-right: 148px; }
2157
2157
  font-size: 11px; margin-top: 4px;
2158
2158
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
2159
2159
  }
2160
+ /* AJAN DM: AKTİF/GEÇMİŞ bölüm başlıkları + kapalı sohbet görünümü */
2161
+ .dm-sec {
2162
+ font-size: 10.5px; font-weight: 800; letter-spacing: 0.08em;
2163
+ color: var(--muted);
2164
+ padding: 8px 2px 4px;
2165
+ }
2166
+ .dmt.dmt-closed { opacity: 0.62; }
2167
+ .dmt.dmt-closed .dmt-count { background: var(--border); color: var(--muted); }
2168
+ .dmt-members {
2169
+ font-size: 10.5px; color: var(--muted); margin-top: 3px;
2170
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
2171
+ }
2160
2172
  .dmt-msgs {
2161
2173
  display: none;
2162
2174
  margin-top: 6px;