beast-agent 2.21.0 → 2.22.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.21.0",
4
+ "version": "2.22.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -40,6 +40,22 @@ function invalidate() {
40
40
  cache = null;
41
41
  }
42
42
 
43
+ /* klasör damgası: ajan/kullanıcı klasöre elle müdahale edince (write_file
44
+ vb.) cache BAYATLAR — damga değişince otomatik yeniden taranır */
45
+ let lastStamp = 0;
46
+ function folderStamp() {
47
+ try { return fs.statSync(dir()).mtimeMs; } catch { return 0; }
48
+ }
49
+
50
+ function ensureFresh() {
51
+ const s = folderStamp();
52
+ if (dirty || !cache || s !== lastStamp) {
53
+ cache = scan();
54
+ dirty = false;
55
+ lastStamp = s;
56
+ }
57
+ }
58
+
43
59
  function readTool(d) {
44
60
  try {
45
61
  const json = JSON.parse(fs.readFileSync(path.join(d, 'tool.json'), 'utf8'));
@@ -81,10 +97,7 @@ function list() {
81
97
 
82
98
  /* engine _chatTurn'e OpenAI function formatında şemalar */
83
99
  function definitions() {
84
- if (dirty || !cache) {
85
- cache = scan();
86
- dirty = false;
87
- }
100
+ ensureFresh();
88
101
  return cache.map((t) => ({
89
102
  type: 'function',
90
103
  function: {
@@ -97,10 +110,7 @@ function definitions() {
97
110
  }
98
111
 
99
112
  function names() {
100
- if (dirty || !cache) {
101
- cache = scan();
102
- dirty = false;
103
- }
113
+ ensureFresh();
104
114
  return cache.map((t) => 'tool__' + t.id);
105
115
  }
106
116
 
@@ -2190,7 +2190,52 @@ class Engine {
2190
2190
  flat[k] = rest;
2191
2191
  }
2192
2192
  }
2193
- return [{ role: 'system', content: system }, ...head, ...flat];
2193
+ /* emniyet: assistant(tool_calls) ↔ tool sonucu ÇİFTLERİ hizala —
2194
+ yetim tool mesajı / cevapsız tool_call sağlayıcıda
2195
+ "Messages with role 'tool' must be a response to a preceding message
2196
+ with 'tool_calls'" (HTTP 400) verir (tur ortası enjeksiyon/kırpma
2197
+ çifti böldüğünde). */
2198
+ return Engine._alignToolPairs([{ role: 'system', content: system }, ...head, ...flat]);
2199
+ }
2200
+
2201
+ /* assistant(tool_calls) + hemen ardından gelen tool sonuçlarını hizalar:
2202
+ - cevabı GELMEYEN tool_call, assistant'tan DÜŞER (sonuç yok)
2203
+ - karşılıksız tool sonucu (yetim) DÜŞER
2204
+ - hiç sonucu olmayan assistant, düz metin asistana döner
2205
+ - tool_calls'sız mesajların ardından gelen tool'lar da yetimdir → düşer */
2206
+ static _alignToolPairs(msgs) {
2207
+ const out = [];
2208
+ for (let i = 0; i < msgs.length; i++) {
2209
+ const m = msgs[i];
2210
+ if (m && m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) {
2211
+ const results = [];
2212
+ let j = i + 1;
2213
+ while (j < msgs.length && msgs[j] && msgs[j].role === 'tool') {
2214
+ results.push(msgs[j]);
2215
+ j++;
2216
+ }
2217
+ const answered = new Set(results.map((r) => String(r.tool_call_id || '')));
2218
+ const kept = m.tool_calls.filter((tc) => answered.has(String(tc.id || '')));
2219
+ if (kept.length === m.tool_calls.length) {
2220
+ out.push(m);
2221
+ } else if (kept.length) {
2222
+ out.push({ ...m, tool_calls: kept });
2223
+ } else {
2224
+ const plain = { ...m };
2225
+ delete plain.tool_calls;
2226
+ out.push(plain);
2227
+ }
2228
+ const keepIds = new Set(kept.map((tc) => String(tc.id || '')));
2229
+ for (const r of results) {
2230
+ if (keepIds.has(String(r.tool_call_id || ''))) out.push(r);
2231
+ }
2232
+ i = j - 1;
2233
+ continue;
2234
+ }
2235
+ if (m && m.role === 'tool') continue; // yetim araç sonucu
2236
+ out.push(m);
2237
+ }
2238
+ return out;
2194
2239
  }
2195
2240
 
2196
2241
  _lastUserText(session) {
@@ -6152,6 +6197,21 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6152
6197
  if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
6153
6198
  const fromJob = jobs.get(String(fromSid));
6154
6199
  const toJob = jobs.get(target);
6200
+ /* FİNANS SINIRI: Beast Finance oturumu agent_dm kullansa bile finance
6201
+ dünyasının DIŞINA ÇIKMAZ — yalnız finance ajanlarına DM atabilir
6202
+ (trader ↔ GOLD işçisi ↔ finance işçileri). Finance dışı hedef reddedilir. */
6203
+ const fromSess = this.cache.get(String(fromSid)) || null;
6204
+ if (fromSess && fromSess.finance) {
6205
+ const targetSess = this.cache.get(String(target)) || null;
6206
+ if (!targetSess || !targetSess.finance) {
6207
+ return {
6208
+ ok: false,
6209
+ error:
6210
+ 'Beast Finance ajanı yalnız finance dünyasındaki ajanlara DM atabilir — hedef finance dışı: ' +
6211
+ String((toJob && toJob.title) || to),
6212
+ };
6213
+ }
6214
+ }
6155
6215
  /* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
6156
6216
  const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
6157
6217
  const dm = {
package/src/main.js CHANGED
@@ -5063,22 +5063,19 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
5063
5063
  }
5064
5064
  }
5065
5065
  } catch {}
5066
- /* BEAST FINANCE: finance modu açıkken bu sohbet finance listesine girer ve
5067
- mt5_* araçlarını görür (botlar hariç); mod kapalıysa etiket kaldırılır */
5066
+ /* BEAST FINANCE: finance modunda yazılan sohbetler KALICI finance etiketi
5067
+ taşır — beast finance'ta kayıtlı kalır; normal modda yazmak etiketi
5068
+ KALDIRMAZ (oturum finance dışına çıkamaz) */
5068
5069
  try {
5069
5070
  if (sessionId) {
5070
5071
  const fsid = String(sessionId);
5071
5072
  let fsess = engine.cache.get(fsid);
5072
5073
  if (!fsess) { try { fsess = engine._load(fsid); } catch {} }
5073
- if (fsess && !fsess.botId && !fsess.bgJob) {
5074
- if (financeState.mode) {
5075
- if (!fsess.finance) engine.markFinance(fsid, false);
5076
- finApplyTraderFields(fsess);
5077
- fsess.financeTrader = false; /* chat copilot'ı — trader değil */
5078
- engine.cache.set(fsid, fsess);
5079
- } else if (fsess.finance) {
5080
- engine.unmarkFinance(fsid); /* normal modda yazınca normal sohbete döner */
5081
- }
5074
+ if (fsess && !fsess.botId && !fsess.bgJob && financeState.mode && !fsess.finance) {
5075
+ engine.markFinance(fsid, false);
5076
+ finApplyTraderFields(fsess);
5077
+ fsess.financeTrader = false; /* chat copilot'ı — trader değil */
5078
+ engine.cache.set(fsid, fsess);
5082
5079
  }
5083
5080
  }
5084
5081
  } catch {}
@@ -8369,6 +8366,22 @@ try {
8369
8366
  });
8370
8367
  } catch {}
8371
8368
 
8369
+ /* TOOLS klasörü izleyici: ajan write_file ile tool yazdığında (ya da elle)
8370
+ konsol OTOMATİK güncellenir — restart gerekmez */
8371
+ try {
8372
+ fs.mkdirSync(customtools.dir(), { recursive: true });
8373
+ let toolWatchTimer = null;
8374
+ fs.watch(customtools.dir(), { recursive: true }, () => {
8375
+ clearTimeout(toolWatchTimer);
8376
+ toolWatchTimer = setTimeout(() => {
8377
+ customtools.invalidate();
8378
+ try {
8379
+ if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'tools-changed' });
8380
+ } catch {}
8381
+ }, 400);
8382
+ });
8383
+ } catch {}
8384
+
8372
8385
  function financeEnsureBridge() {
8373
8386
  const f = finCfg();
8374
8387
  const candidates = [String(f.pythonPath || '').trim(), 'python', 'py -3'].filter(Boolean);
@@ -8744,10 +8757,9 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
8744
8757
  (eski davranış WhatsApp oturumunu da finance listesine karıştırıyordu) */
8745
8758
  needNew = true;
8746
8759
  }
8747
- } else {
8748
- /* mod kapandı: aktif finance oturumu normal sohbete döner */
8749
- if (s.finance) engine.unmarkFinance(sid);
8750
8760
  }
8761
+ /* mod kapandığında finance oturumları etiketlerini KORUR —
8762
+ beast finance'ta kayıtlı kalırlar (kaybolmazlar) */
8751
8763
  engine.cache.set(sid, s);
8752
8764
  }
8753
8765
  } catch {}
@@ -378,6 +378,7 @@
378
378
  <div id="dmDialogHead">
379
379
  <span id="dmDialogTitle">AJAN DM — OTURUMLAR</span>
380
380
  <span class="bot-bar-btns">
381
+ <button id="dmBackBtn" title="Oturum listesine dön" hidden>&#x25C0;&#xFE0E; Oturumlar</button>
381
382
  <button id="dmColumnBtn" title="Yan sütunda canlı akış olarak da göster">&#x25A4;&#xFE0E;</button>
382
383
  <button id="dmModalClear" title="Tüm DM geçmişini sil"><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 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
383
384
  <button id="dmModalClose" title="Kapat">&#x2715;&#xFE0E;</button>
@@ -50,6 +50,7 @@ const els = {
50
50
  dmModalClose: $('#dmModalClose'),
51
51
  dmModalClear: $('#dmModalClear'),
52
52
  dmColumnBtn: $('#dmColumnBtn'),
53
+ dmBackBtn: $('#dmBackBtn'),
53
54
  watchBtn: $('#watchBtn'),
54
55
  watchPaneList: $('#watchPaneList'),
55
56
  watchPaneOpen: $('#watchPaneOpen'),
@@ -5025,13 +5026,13 @@ async function loadRailChat(id) {
5025
5026
  }
5026
5027
 
5027
5028
  /* ---------------- AJAN DM (ajanlar arası konuşma) ----------------
5028
- İki görünüm: ✉ modal (oturum listesi) + yan sütun (canlı akış).
5029
- Thread anahtarı = ajan çifti + KONU — aynı konudaki mesajlar tek
5030
- oturumda toplanır, gelen/giden hepsi kronolojik görünür. */
5029
+ Paralel Ajanlar gibi: ✉ sağdaki SÜTUNU açar/kapatır (oturum listesi,
5030
+ canlı akış). Sütunda bir oturuma tıklayınca SOHBET yalnızca MODAL'da
5031
+ açılır — gelen/giden hepsi kronolojik. Thread = ajan çifti + KONU. */
5031
5032
 
5032
5033
  const dmState = {
5033
5034
  dms: [], // { at, from, fromTitle, to, toTitle, topic, text }
5034
- open: new Set(), // açık thread key'leri (modal + sütun ortak)
5035
+ modalThread: null, // modalde açık oturum key'i (null = oturum listesi)
5035
5036
  };
5036
5037
 
5037
5038
  function dmThreadOf(dm) {
@@ -5074,35 +5075,27 @@ function dmMsgHtml(m) {
5074
5075
  );
5075
5076
  }
5076
5077
 
5077
- /* --- yan sütun --- */
5078
+ /* --- sağ sütun: OTURUM LİSTESİ (rail gibi) --- */
5078
5079
 
5079
5080
  function renderDmRail() {
5080
5081
  const list = els.dmRailList;
5081
5082
  if (!list) return;
5082
5083
  if (!dmState.dms.length) {
5083
- list.innerHTML = '<div class="dm-empty">Ajanlar arasında DM yok.<br>Ajanlar agent_dm aracıyla birbirine mesaj attıkça burada görünür.</div>';
5084
+ list.innerHTML = '<div class="dm-empty">Ajanlar arasında DM yok.<br>Ajanlar agent_dm aracıyla birbirine mesaj attıkça oturumlar burada birikir.</div>';
5084
5085
  return;
5085
5086
  }
5086
5087
  list.innerHTML = '';
5087
5088
  for (const [key, msgs] of dmThreads()) {
5088
- const open = dmState.open.has(key);
5089
5089
  const last = msgs[msgs.length - 1];
5090
5090
  const el = document.createElement('div');
5091
- el.className = 'dmt' + (open ? ' open' : '');
5091
+ el.className = 'dmt';
5092
5092
  el.innerHTML =
5093
5093
  '<div class="dmt-head">' +
5094
5094
  `<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
5095
5095
  `<span class="dmt-count">${msgs.length}</span>` +
5096
5096
  '</div>' +
5097
- `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>` +
5098
- '<div class="dmt-msgs">' + msgs.map(dmMsgHtml).join('') + '</div>';
5099
- el.addEventListener('click', () => {
5100
- if (dmState.open.has(key)) dmState.open.delete(key);
5101
- else dmState.open.add(key);
5102
- renderDmRail();
5103
- const again = els.dmRailList.querySelector('.dmt.open .dmt-msgs');
5104
- if (again) again.scrollTop = again.scrollHeight;
5105
- });
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
5106
5099
  list.appendChild(el);
5107
5100
  }
5108
5101
  }
@@ -5113,6 +5106,7 @@ async function refreshDmRail() {
5113
5106
  if (Array.isArray(dms)) dmState.dms = dms;
5114
5107
  } catch {}
5115
5108
  renderDmRail();
5109
+ if (els.dmOverlay && !els.dmOverlay.hidden) renderDmModal();
5116
5110
  }
5117
5111
 
5118
5112
  function toggleDmRail(hide) {
@@ -5123,45 +5117,58 @@ function toggleDmRail(hide) {
5123
5117
  if (!hide) refreshDmRail();
5124
5118
  }
5125
5119
 
5126
- /* --- modal (✉ butonu) --- */
5120
+ /* --- modal: SOHBET (yalnız burada tam döküm) --- */
5127
5121
 
5128
5122
  function renderDmModal() {
5129
5123
  const list = els.dmDialogList;
5130
5124
  if (!list) return;
5131
5125
  if (!dmState.dms.length) {
5132
- list.innerHTML = '<div class="dm-empty">Henüz ajan DM\u2019i yok.<br>Ajanlar agent_dm aracıyla birbirine mesaj attıkça oturumlar burada birikir.</div>';
5126
+ list.innerHTML = '<div class="dm-empty">Henüz ajan DM\u2019i yok.<br>Ajanlar agent_dm aracıyla birbirine mesaj attıkça oturumlar birikir.</div>';
5127
+ if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — OTURUMLAR';
5128
+ if (els.dmBackBtn) els.dmBackBtn.hidden = true;
5129
+ return;
5130
+ }
5131
+ if (dmState.modalThread) {
5132
+ /* TEK OTURUM: tam sohbet dökümü */
5133
+ const msgs = (dmThreads().find(([k]) => k === dmState.modalThread) || [null, []])[1];
5134
+ if (!msgs.length) {
5135
+ dmState.modalThread = null;
5136
+ return renderDmModal();
5137
+ }
5138
+ if (els.dmDialogTitle) els.dmDialogTitle.textContent = dmThreadTitle(msgs[msgs.length - 1]);
5139
+ if (els.dmBackBtn) els.dmBackBtn.hidden = false;
5140
+ list.innerHTML = '<div class="dm-thread-msgs">' + msgs.map(dmMsgHtml).join('') + '</div>';
5141
+ const box = list.querySelector('.dm-thread-msgs');
5142
+ if (box) box.scrollTop = box.scrollHeight;
5133
5143
  return;
5134
5144
  }
5145
+ /* OTURUM LİSTESİ */
5146
+ if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — OTURUMLAR';
5147
+ if (els.dmBackBtn) els.dmBackBtn.hidden = true;
5135
5148
  list.innerHTML = '';
5136
5149
  for (const [key, msgs] of dmThreads()) {
5137
- const open = dmState.open.has(key);
5138
5150
  const last = msgs[msgs.length - 1];
5139
5151
  const el = document.createElement('div');
5140
- el.className = 'dmt' + (open ? ' open' : '');
5152
+ el.className = 'dmt';
5141
5153
  el.innerHTML =
5142
5154
  '<div class="dmt-head">' +
5143
5155
  `<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
5144
5156
  `<span class="dmt-count">${msgs.length}</span>` +
5145
5157
  '</div>' +
5146
- `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 120))} · ${dmTime(last.at)}</div>` +
5147
- '<div class="dmt-msgs">' + msgs.map(dmMsgHtml).join('') + '</div>';
5158
+ `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 120))} · ${dmTime(last.at)}</div>`;
5148
5159
  el.addEventListener('click', () => {
5149
- if (dmState.open.has(key)) dmState.open.delete(key);
5150
- else dmState.open.add(key);
5160
+ dmState.modalThread = key;
5151
5161
  renderDmModal();
5152
- const again = els.dmDialogList.querySelector('.dmt.open .dmt-msgs');
5153
- if (again) again.scrollTop = again.scrollHeight;
5154
5162
  });
5155
5163
  list.appendChild(el);
5156
5164
  }
5157
5165
  }
5158
5166
 
5159
- async function openDmModal() {
5167
+ function openDmModal(threadKey) {
5160
5168
  if (!els.dmOverlay) return;
5169
+ dmState.modalThread = threadKey || null;
5161
5170
  els.dmOverlay.hidden = false;
5162
- await refreshDmRail(); // kayıtları tazele
5163
- renderDmModal();
5164
- if (els.dmBtn) els.dmBtn.classList.add('on');
5171
+ refreshDmRail().then(() => renderDmModal());
5165
5172
  }
5166
5173
 
5167
5174
  function closeDmModal() {
@@ -5173,14 +5180,21 @@ async function dmClearAll() {
5173
5180
  if (!(await uiConfirm('TÜM ajan DM geçmişi silinsin mi?', 'Sil'))) return;
5174
5181
  try { await beast.agentDmsClear(); } catch {}
5175
5182
  dmState.dms = [];
5183
+ dmState.modalThread = null;
5176
5184
  renderDmRail();
5177
5185
  renderDmModal();
5178
5186
  toast('Ajan DM geçmişi silindi');
5179
5187
  }
5180
5188
 
5181
- if (els.dmBtn) els.dmBtn.addEventListener('click', openDmModal);
5189
+ if (els.dmBtn) els.dmBtn.addEventListener('click', () => toggleDmRail($('#dmRail').classList.contains('dm-hidden')));
5182
5190
  if (els.dmModalClose) els.dmModalClose.addEventListener('click', closeDmModal);
5183
5191
  if (els.dmModalClear) els.dmModalClear.addEventListener('click', dmClearAll);
5192
+ if (els.dmBackBtn) {
5193
+ els.dmBackBtn.addEventListener('click', () => {
5194
+ dmState.modalThread = null; // oturum listesine dön
5195
+ renderDmModal();
5196
+ });
5197
+ }
5184
5198
  if (els.dmOverlay) {
5185
5199
  els.dmOverlay.addEventListener('click', (e) => {
5186
5200
  if (e.target === els.dmOverlay) closeDmModal();
@@ -5193,7 +5207,7 @@ if (els.dmClear) els.dmClear.addEventListener('click', dmClearAll);
5193
5207
  if (els.dmColumnBtn) {
5194
5208
  els.dmColumnBtn.addEventListener('click', () => {
5195
5209
  closeDmModal();
5196
- toggleDmRail(false); // yan sütunda canlı akış
5210
+ toggleDmRail(false); // sütunda canlı akış
5197
5211
  });
5198
5212
  }
5199
5213
 
@@ -5557,6 +5571,11 @@ function onEvent(ev) {
5557
5571
  scheduleToolRender();
5558
5572
  return;
5559
5573
  }
5574
+ /* TOOLS klasörü değişti (ajan yeni tool yazdı / elle edit) — konsolu tazele */
5575
+ if (ev.type === 'tools-changed') {
5576
+ scheduleToolRender();
5577
+ return;
5578
+ }
5560
5579
  /* OFFLINE MESAJ KUYRUĞU: bağlantı + kuyruk olayları (sessionId filtresinden önce) */
5561
5580
  if (ev.type === 'net' || ev.type === 'netQueue') { onNetEvent(ev); return; }
5562
5581
  /* Sandbox klonlama progress'i (sessionId'siz global olay) */
@@ -2240,6 +2240,14 @@ body.rail-hidden #dmRailHead { padding-right: 148px; }
2240
2240
  flex-direction: column;
2241
2241
  align-items: stretch;
2242
2242
  }
2243
+ /* tek oturum sohbeti: kaydırılabilir blok */
2244
+ .dm-thread-msgs {
2245
+ display: flex;
2246
+ flex-direction: column;
2247
+ gap: 5px;
2248
+ max-height: 56vh;
2249
+ overflow-y: auto;
2250
+ }
2243
2251
  .dm-hint {
2244
2252
  flex: none;
2245
2253
  font-size: 10px;