beast-agent 2.33.1 → 2.35.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.33.1",
4
+ "version": "2.35.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -25,6 +25,7 @@ const mcp = require('./mcp');
25
25
  const apps = require('./apps');
26
26
  const pdfwrite = require('./pdfwrite');
27
27
  const { estTokens, estMsgTokens } = require('./tokens');
28
+ const squeeze = require('./squeeze');
28
29
  const log = require('./logger');
29
30
  /* opencode mantığı (birebir port): ajanlar, izinler, tool seti, promptlar —
30
31
  Beast Code (bcCode) oturumları bu çekirdek üzerinden koşar */
@@ -767,6 +768,12 @@ class Engine {
767
768
  413/TPM "Requested N" hatası → gerçek token sayısı öğrenilir, tahmin
768
769
  kalibre edilir ve aynı model BİR KEZ daha sıkı sıkıştırmayla denenir. */
769
770
  async _streamWithFallbacks(session, payload, activeTools, signal, onDelta, sel, wasVision) {
771
+ /* SQUEEZE (varsayılan kapalı): istek kopyası sıkıştırılır — oturum kaydı
772
+ değişmez. Provider limiti kontrolünden ÖNCE uygulanır ki limit hesabı
773
+ küçülmüş gövdeyi görsün. */
774
+ if (squeeze.isEnabled()) {
775
+ payload = squeeze.apply(payload, { cacheKey: String(session.id || '') });
776
+ }
770
777
  const cands = this._chatCandidates(sel, wasVision);
771
778
  let lastErr = null;
772
779
  for (let i = 0; i < cands.length; i++) {
@@ -4773,10 +4780,14 @@ const skills = require('./skills');
4773
4780
  ];
4774
4781
  try {
4775
4782
  for (let turn = 0; turn < SUB_MAX_TURNS; turn++) {
4783
+ let sendMsgs = [{ role: 'system', content: system }, ...msgs.slice(1)];
4784
+ if (squeeze.isEnabled()) {
4785
+ sendMsgs = squeeze.apply(sendMsgs, { cacheKey: String(sessionId || 'subagent') });
4786
+ }
4776
4787
  const res = await chatStreamAuto(
4777
4788
  sel,
4778
4789
  {
4779
- messages: [{ role: 'system', content: system }, ...msgs.slice(1)],
4790
+ messages: sendMsgs,
4780
4791
  tools: subTools,
4781
4792
  reasoningEffort: this._thinkEffort(),
4782
4793
  cacheKey: String(sessionId || 'subagent'),
package/src/agent/llm.js CHANGED
@@ -347,18 +347,6 @@ function sessionHeaderId(body) {
347
347
  return _sessionFallback;
348
348
  }
349
349
 
350
- /* Headroom proxy yönlendirme: providerId -> yerel proxy portu haritası.
351
- main (agent/headroom.js) ayarlardan açıldığında kurar; null = düz bağlan. */
352
- let _headroomRoutes = null;
353
- function setHeadroomRoutes(map) {
354
- _headroomRoutes = map && Object.keys(map).length ? map : null;
355
- }
356
- function headroomUrlFor(sel) {
357
- if (!_headroomRoutes || !sel) return '';
358
- const port = Number(_headroomRoutes[String(sel.providerId || '')] || 0);
359
- return port ? 'http://127.0.0.1:' + port + '/v1/chat/completions' : '';
360
- }
361
-
362
350
  async function openChat(sel, body, { stream, signal, drops } = {}) {
363
351
  const d = drops || {};
364
352
  const payload = {
@@ -380,9 +368,7 @@ async function openChat(sel, body, { stream, signal, drops } = {}) {
380
368
  if (body.cacheKey && wantsCacheKey(sel) && !d.noCacheKey) {
381
369
  payload.prompt_cache_key = String(body.cacheKey);
382
370
  }
383
- /* Headroom açıkken istek yerel sıkıştırma proxy'sinden geçer (aynı gövde+başlıklar);
384
- yoksa sağlayıcıya düz bağlan */
385
- const target = headroomUrlFor(sel) || sel.url;
371
+ const target = sel.url;
386
372
  return fetch(target, {
387
373
  method: 'POST',
388
374
  signal,
@@ -641,7 +627,6 @@ module.exports = {
641
627
  capsFor,
642
628
  resetCaps,
643
629
  learnCaps,
644
- setHeadroomRoutes,
645
630
  PARAM_LABELS,
646
631
  isNetworkError,
647
632
  wantsCacheKey,
@@ -0,0 +1,312 @@
1
+ 'use strict';
2
+
3
+ /* Beast SQUEEZE — kendi bağlam (token) sıkıştırıcımız. VARSAYILAN KAPALI.
4
+ Her LLM isteğinden hemen önce mesaj dizisinin KOPYASINA uygulanır; oturum
5
+ kaydı/geçmiş DEĞİŞMEZ. Harici kurulum/proxy yok.
6
+
7
+ Kurallar:
8
+ 1) TEKRAR BLOK: Aynı büyük içerik (araç çıktısı / dosya eki / skill gövdesi)
9
+ aynı bağlamda birden çok kez varsa YALNIZ İLK kopya tam gider; sonrakiler
10
+ kısa işaretçiye iner. İlk kopya bağlamdan düşerse (compaction/bütçe) sonraki
11
+ kopya kendiliğinden yeniden TAM gönderilir — "bağlam koptuysa yeniden" kuralı.
12
+ 2) ESKİ ARAÇ ÇIKTISI: Son N araç sonucu birebir; daha eskiler başlık + baş/son
13
+ önizleme + "gerekirse tekrar çalıştır/oku" notu olarak sıkıştırılır.
14
+ Her dosyanın EN SON read_file sonucu asla sıkıştırılmaz (dosya hafızası).
15
+ 3) SKILL: Aynı skill gövdesi tekrarlıysa eski kopyalar stub'a iner; tek kopya tam.
16
+ 4) DOKUNULMAZLAR: system, son tur birimleri, assistant mesajları, zaten
17
+ sıkıştırılmış içerik (idempotans) ve minBlockChars'tan küçük bloklar. */
18
+
19
+ const crypto = require('crypto');
20
+ const { estMsgTokens } = require('./tokens');
21
+
22
+ const DEFAULTS = {
23
+ minTotalTokens: 1500,
24
+ minBlockChars: 700,
25
+ minUserCompactChars: 12000,
26
+ keepRecentTools: 0,
27
+ keepRecentUnits: 4,
28
+ protectReadsWindow: 8,
29
+ previewHead: 700,
30
+ previewTail: 250,
31
+ };
32
+
33
+ const MARK = '[squeeze:';
34
+
35
+ const state = {
36
+ enabled: false,
37
+ stats: {
38
+ calls: 0,
39
+ changedCalls: 0,
40
+ savedTokens: 0,
41
+ lastBeforeTokens: 0,
42
+ lastAfterTokens: 0,
43
+ lastSavedTokens: 0,
44
+ deduped: 0,
45
+ compactedTools: 0,
46
+ compactedSkills: 0,
47
+ compactedUsers: 0,
48
+ },
49
+ };
50
+
51
+ function setEnabled(on) {
52
+ state.enabled = !!on;
53
+ }
54
+
55
+ function isEnabled() {
56
+ return state.enabled;
57
+ }
58
+
59
+ function resetStats() {
60
+ for (const k of Object.keys(state.stats)) state.stats[k] = 0;
61
+ }
62
+
63
+ function getStats() {
64
+ const s = { ...state.stats };
65
+ s.lastPercent =
66
+ s.lastBeforeTokens > 0 ? Math.round((s.lastSavedTokens / s.lastBeforeTokens) * 100) : 0;
67
+ return s;
68
+ }
69
+
70
+ function _hash(s) {
71
+ try {
72
+ return crypto.createHash('sha1').update(s).digest('hex').slice(0, 16);
73
+ } catch {
74
+ return String(s.length) + ':' + String(s.charCodeAt(0) || 0);
75
+ }
76
+ }
77
+
78
+ function _isSqueezed(content) {
79
+ return typeof content === 'string' && content.includes(MARK);
80
+ }
81
+
82
+ /* assistant(tool_calls) + ardışık tool sonuçlarını BÖLMEYEN birimler (indis) */
83
+ function _indexUnits(messages) {
84
+ const units = [];
85
+ let i = 0;
86
+ while (i < messages.length) {
87
+ const m = messages[i];
88
+ if (m && m.role === 'assistant' && m.tool_calls && m.tool_calls.length) {
89
+ const grp = [i];
90
+ i++;
91
+ while (i < messages.length && messages[i] && messages[i].role === 'tool') grp.push(i++);
92
+ units.push(grp);
93
+ } else {
94
+ units.push([i]);
95
+ i++;
96
+ }
97
+ }
98
+ return units;
99
+ }
100
+
101
+ /* tool_call_id → {name, args} (assistant mesajlarındaki çağrılardan) */
102
+ function _toolCallMap(messages) {
103
+ const map = new Map();
104
+ for (const m of messages) {
105
+ if (!m || m.role !== 'assistant' || !Array.isArray(m.tool_calls)) continue;
106
+ for (const tc of m.tool_calls) {
107
+ if (!tc || !tc.id) continue;
108
+ let args = {};
109
+ try {
110
+ args = JSON.parse((tc.function && tc.function.arguments) || '{}');
111
+ } catch {}
112
+ map.set(String(tc.id), {
113
+ name: String((tc.function && tc.function.name) || ''),
114
+ args,
115
+ });
116
+ }
117
+ }
118
+ return map;
119
+ }
120
+
121
+ function _readTarget(args) {
122
+ if (!args || typeof args !== 'object') return '';
123
+ for (const k of ['file_path', 'path', 'file', 'filename', 'filePath', 'target']) {
124
+ if (args[k]) return String(args[k]);
125
+ }
126
+ return '';
127
+ }
128
+
129
+ function _pointerFor(role, name) {
130
+ if (role === 'tool') {
131
+ return (
132
+ MARK + ' bu araç çıktısı yukarıdaki' + (name ? " '" + name + "'" : '') +
133
+ ' sonucuyla BİREBİR AYNI — tekrar gönderilmedi]'
134
+ );
135
+ }
136
+ return MARK + ' bu içerik yukarıda TAM gönderildi — tekrar gönderilmedi]';
137
+ }
138
+
139
+ function _skillStub(raw) {
140
+ let name = '';
141
+ try {
142
+ const j = JSON.parse(raw);
143
+ name = String(j.name || j.skill || '');
144
+ } catch {}
145
+ return (
146
+ '{"ok":true,"name":"' + name.replace(/"/g, '') + '",' +
147
+ '"content":"' + MARK + ' bu skill gövdesi yukarıda tam yüklendi — tekrar gönderilmedi]"}'
148
+ );
149
+ }
150
+
151
+ function _compactPreview(content, cfg, label, hint) {
152
+ const len = content.length;
153
+ const headLen = Math.min(cfg.previewHead, Math.max(260, Math.floor(len * 0.4)));
154
+ const tailLen = len - headLen > 400 ? Math.min(cfg.previewTail, Math.floor(len * 0.15)) : 0;
155
+ const head = content.slice(0, headLen);
156
+ const tail = tailLen > 0 ? content.slice(-tailLen) : '';
157
+ const note = '\n' + MARK + ' ' + label + ' sıkıştırıldı: ' + len + ' karakter → önizleme. ' + hint + ']\n';
158
+ return tail ? head + note + tail : head + note;
159
+ }
160
+
161
+ /* messages: OpenAI-uyumlu mesaj dizisi. Dönüş: (gerekirse) yeni dizi.
162
+ Kapalıyken aynı referans döner; açıkken de değişiklik yoksa orijinal döner. */
163
+ function apply(messages, opts = {}) {
164
+ if (!state.enabled || !Array.isArray(messages) || messages.length < 2) return messages;
165
+
166
+ const cfg = { ...DEFAULTS, ...(opts.config || {}) };
167
+ const beforeTokens = messages.reduce((a, m) => a + estMsgTokens(m), 0);
168
+ state.stats.calls++;
169
+ state.stats.lastBeforeTokens = beforeTokens;
170
+ state.stats.lastAfterTokens = beforeTokens;
171
+ state.stats.lastSavedTokens = 0;
172
+ if (beforeTokens < cfg.minTotalTokens) return messages;
173
+
174
+ const out = messages.slice();
175
+ let touched = false;
176
+ const replace = (i, msg) => {
177
+ out[i] = msg;
178
+ touched = true;
179
+ };
180
+
181
+ const units = _indexUnits(messages);
182
+ const protectFrom = Math.max(1, units.length - cfg.keepRecentUnits);
183
+ const protectedIdx = new Set([0]);
184
+ for (let u = protectFrom; u < units.length; u++) {
185
+ for (const i of units[u]) protectedIdx.add(i);
186
+ }
187
+ /* son N araç sonucu birebir kalır */
188
+ const toolIdx = [];
189
+ for (let i = 0; i < messages.length; i++) {
190
+ if (messages[i] && messages[i].role === 'tool') toolIdx.push(i);
191
+ }
192
+ if (cfg.keepRecentTools > 0) {
193
+ for (const i of toolIdx.slice(-cfg.keepRecentTools)) protectedIdx.add(i);
194
+ }
195
+
196
+ const meta = _toolCallMap(messages);
197
+ const toolName = (i) => {
198
+ const m = messages[i];
199
+ const fromCall = m && m.tool_call_id ? meta.get(String(m.tool_call_id)) : null;
200
+ return String((m && m.name) || (fromCall && fromCall.name) || '');
201
+ };
202
+
203
+ /* her dosyanın EN SON okuması, yakın penceredeyse korunur */
204
+ const protectReadsFrom =
205
+ toolIdx.length > cfg.protectReadsWindow
206
+ ? toolIdx[toolIdx.length - cfg.protectReadsWindow]
207
+ : (toolIdx[0] ?? Infinity);
208
+ const latestRead = new Map();
209
+ for (const i of toolIdx) {
210
+ const m = messages[i];
211
+ if (!m || String(m.name || '') !== 'read_file') continue;
212
+ const info = m.tool_call_id ? meta.get(String(m.tool_call_id)) : null;
213
+ const target = _readTarget(info && info.args) || '__last__';
214
+ latestRead.set(target, i);
215
+ }
216
+
217
+ let deduped = 0;
218
+ let compactedTools = 0;
219
+ let compactedSkills = 0;
220
+ let compactedUsers = 0;
221
+
222
+ /* 1) SKILL gövde tekrarı: aynı skill'in eski kopyaları stub'a iner */
223
+ const skillSeen = new Set();
224
+ for (let i = 0; i < messages.length; i++) {
225
+ const m = messages[i];
226
+ if (!m || m.role !== 'tool' || toolName(i) !== 'skill') continue;
227
+ if (typeof m.content !== 'string' || _isSqueezed(m.content)) continue;
228
+ let key = '';
229
+ try {
230
+ const j = JSON.parse(m.content);
231
+ key = String(j.name || j.skill || '');
232
+ } catch {}
233
+ if (!key) key = _hash(m.content);
234
+ if (skillSeen.has(key)) {
235
+ replace(i, { ...m, content: _skillStub(m.content) });
236
+ compactedSkills++;
237
+ } else {
238
+ skillSeen.add(key);
239
+ }
240
+ }
241
+
242
+ /* 2) GENEL tekrar bloğu: ilk kopya tam, sonrakiler işaretçi (korumalılar hariç) */
243
+ const seen = new Map();
244
+ for (let i = 0; i < messages.length; i++) {
245
+ const m = messages[i];
246
+ if (!m || m.role === 'system' || m.role === 'assistant') continue;
247
+ if (typeof m.content !== 'string') continue;
248
+ if (m.content.length < cfg.minBlockChars || _isSqueezed(m.content)) continue;
249
+ const role = m.role === 'tool' ? 'tool' : 'user';
250
+ const key = role + ':' + _hash(m.content);
251
+ if (seen.has(key)) {
252
+ /* birebir aynı içerik: en son mesaj (güncel tur) hariç her yerde
253
+ işaretçiye inebilir — içerik zaten aynı bağlamda tam duruyor */
254
+ if (i === messages.length - 1) continue;
255
+ replace(i, { ...out[i], content: _pointerFor(role, toolName(i)) });
256
+ deduped++;
257
+ } else {
258
+ seen.set(key, i);
259
+ }
260
+ }
261
+
262
+ /* 3) ESKİ ARAÇ ÇIKTISI: başlık + baş/son önizleme */
263
+ for (const i of toolIdx) {
264
+ if (protectedIdx.has(i)) continue;
265
+ const m = out[i];
266
+ if (!m || typeof m.content !== 'string' || _isSqueezed(m.content)) continue;
267
+ if (m.content.length < cfg.minBlockChars) continue;
268
+ const name = toolName(i);
269
+ if (name === 'skill') continue;
270
+ if (name === 'read_file') {
271
+ const info = m.tool_call_id ? meta.get(String(m.tool_call_id)) : null;
272
+ const target = _readTarget(info && info.args) || '__last__';
273
+ if (latestRead.get(target) === i && i >= protectReadsFrom) continue;
274
+ }
275
+ const hint =
276
+ name === 'read_file'
277
+ ? 'Tam içerik diskte duruyor; gerekirse read_file ile tekrar oku'
278
+ : 'Gerekirse ' + (name || 'araç') + ' ile tekrar çalıştır';
279
+ replace(i, { ...m, content: _compactPreview(m.content, cfg, "'" + (name || 'araç') + "'", hint) });
280
+ compactedTools++;
281
+ }
282
+
283
+ /* 4) ESKİ BÜYÜK KULLANICI İÇERİĞİ (dosya eki vb.): baş/son önizleme */
284
+ for (let i = 0; i < messages.length; i++) {
285
+ const m = messages[i];
286
+ if (!m || m.role !== 'user' || protectedIdx.has(i) || i === 0) continue;
287
+ if (typeof m.content !== 'string' || _isSqueezed(m.content)) continue;
288
+ if (m.content.length < cfg.minUserCompactChars) continue;
289
+ if (m.content.startsWith('[ÖNCEKİ KONUŞMANIN ÖZETİ')) continue;
290
+ replace(i, {
291
+ ...m,
292
+ content: _compactPreview(m.content, cfg, 'kullanıcı içeriği', 'Tam içerik oturum kaydında duruyor'),
293
+ });
294
+ compactedUsers++;
295
+ }
296
+
297
+ if (!touched) return messages;
298
+
299
+ const afterTokens = out.reduce((a, m) => a + estMsgTokens(m), 0);
300
+ const saved = Math.max(0, beforeTokens - afterTokens);
301
+ state.stats.lastAfterTokens = afterTokens;
302
+ state.stats.lastSavedTokens = saved;
303
+ state.stats.savedTokens += saved;
304
+ state.stats.deduped += deduped;
305
+ state.stats.compactedTools += compactedTools;
306
+ state.stats.compactedSkills += compactedSkills;
307
+ state.stats.compactedUsers += compactedUsers;
308
+ if (saved > 0) state.stats.changedCalls++;
309
+ return out;
310
+ }
311
+
312
+ module.exports = { apply, setEnabled, isEnabled, getStats, resetStats, MARK };
package/src/main.js CHANGED
@@ -28,7 +28,7 @@ const bus = require('./agent/bus');
28
28
  const computeruse = require('./agent/computeruse');
29
29
  const fsguard = require('./agent/fsguard');
30
30
  const log = require('./agent/logger');
31
- const headroom = require('./agent/headroom');
31
+ const squeeze = require('./agent/squeeze');
32
32
  const QRCode = require('qrcode'); /* Expo Go QR (bc-expurl) — whatsapp ile aynı paket */
33
33
 
34
34
  /* Renderer'a sır gönderirken kullanılan maske; kaydederken aynen geri gelirse
@@ -3465,29 +3465,9 @@ function reloadBackend() {
3465
3465
  süreç koşucusu (sandbox:run IPC ile aynı makine). */
3466
3466
  engine.sbRunHook = sbRunStartManaged;
3467
3467
 
3468
- /* HEADROOM (opsiyonel token sıkıştırma proxy'si): engine zincirinden provider
3469
- listesi alınır; durum değişince renderer'a 'headroom-status' düşer */
3470
- headroom.setProviderGetter(() => {
3471
- const seen = new Map();
3472
- for (const c of (engine && engine.cfg && engine.cfg.chain) || []) {
3473
- if (!c || !c.providerId || !c.url || seen.has(c.providerId)) continue;
3474
- seen.set(c.providerId, {
3475
- id: String(c.providerId),
3476
- name: String(c.providerName || c.providerId),
3477
- baseUrl: String(c.url).replace(/\/chat\/completions$/, ''),
3478
- });
3479
- }
3480
- return [...seen.values()];
3481
- });
3482
- headroom.setStatusSink((st) => {
3483
- try {
3484
- if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'headroom-status', status: st });
3485
- } catch {}
3486
- });
3487
- /* ayarlardan açık kaldıysa: uygulama açılışında arka planda başlat */
3488
- if (settings.headroom && settings.headroom.enabled) {
3489
- setTimeout(() => { headroom.setEnabled(true).catch(() => {}); }, 4000);
3490
- }
3468
+ /* SQUEEZE (opsiyonel token sıkıştırma): varsayılan KAPALI; ayar açıksa
3469
+ her LLM isteğinin kopyası gönderimden önce sıkıştırılır. */
3470
+ squeeze.setEnabled(!!(settings.squeeze && settings.squeeze.enabled));
3491
3471
  return engine.publicState();
3492
3472
  }
3493
3473
 
@@ -3718,7 +3698,6 @@ app.whenReady().then(() => {
3718
3698
  flushBrowserStorage(); // x.com/google oturumları (cookies) diske yazılsın
3719
3699
  try { toolsMod.disposeShellSessions(); } catch {} // kalıcı shell oturumlarını kapat
3720
3700
  try { require('./agent/mcp').stopAll(); } catch {} // MCP server süreçlerini kapat
3721
- try { headroom.stopAll(); } catch {} // headroom proxy süreçlerini kapat
3722
3701
  });
3723
3702
 
3724
3703
  if (process.argv.includes('--smoke')) {
@@ -6673,44 +6652,24 @@ ipcMain.handle('install:status', async () => {
6673
6652
  /* 8) Edge TTS (bulut) */
6674
6653
  rows.push({ id: 'edge', name: 'Edge TTS — seslendirme', state: 'cloud', detail: 'bulut — kurulum gerekmez' });
6675
6654
 
6676
- /* 9) Headroom CLI — arka plan paket kurulumu burada görünür;
6677
- aç/kapat Token Sıkıştırma sekmesinde */
6678
- {
6679
- const st = headroom.probe();
6680
- let state = 'optional';
6681
- if (st.installed) state = 'ok';
6682
- else if (st.installing) state = 'loading';
6683
- if (st.failed) state = 'failed';
6684
- rows.push({
6685
- id: 'headroom',
6686
- name: 'Headroom CLI — token sıkıştırma motoru',
6687
- state,
6688
- detail: st.installing
6689
- ? 'arka planda kuruluyor — uv tool install headroom-ai[proxy]'
6690
- : st.failed
6691
- ? (st.error || 'kurulum başarısız')
6692
- : st.installed
6693
- ? 'kurulu — aç/kapat: Token Sıkıştırma sekmesi'
6694
- : 'kurulu değil — Token Sıkıştırma sekmesinden açınca otomatik kurulur',
6695
- ...(st.installing ? pctFields('headroom') : {}),
6696
- });
6697
- }
6698
-
6699
6655
  return rows;
6700
6656
  });
6701
6657
 
6702
6658
  /* cua-driver kurulumunu şimdi başlat (Kurulum sekmesi otomatiği + elle tetikleme) */
6703
6659
  ipcMain.handle('cua:install', () => require('./agent/computeruse').autoInstall());
6704
6660
 
6705
- /* HEADROOM (opsiyonel token sıkıştırma): aç/kapa + durum */
6706
- ipcMain.handle('headroom:toggle', async (_e, on) => {
6707
- settings.headroom = { ...(settings.headroom || {}), enabled: !!on };
6661
+ /* SQUEEZE (kendi token sıkıştırıcımız): aç/kapa + durum + istatistik */
6662
+ ipcMain.handle('squeeze:toggle', (_e, on) => {
6663
+ settings.squeeze = { ...(settings.squeeze || {}), enabled: !!on };
6708
6664
  saveSettings();
6709
- const r = await headroom.setEnabled(!!on).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
6710
- return r;
6665
+ squeeze.setEnabled(!!on);
6666
+ return { ok: true, enabled: !!on };
6667
+ });
6668
+ ipcMain.handle('squeeze:status', () => ({ enabled: squeeze.isEnabled(), stats: squeeze.getStats() }));
6669
+ ipcMain.handle('squeeze:reset', () => {
6670
+ squeeze.resetStats();
6671
+ return { ok: true, stats: squeeze.getStats() };
6711
6672
  });
6712
- ipcMain.handle('headroom:status', () => headroom.probe());
6713
- ipcMain.handle('headroom:stats', () => headroom.stats());
6714
6673
 
6715
6674
  /* ANDROID EMÜLATÖR: kaldırıldı — mobil önizleme tunnel (telefon QR) yoluyla yapılır */
6716
6675
 
@@ -9582,6 +9541,7 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
9582
9541
  financeState.mode = !!(payload && payload.on);
9583
9542
  const sid = String((payload && payload.sessionId) || '');
9584
9543
  let needNew = false;
9544
+ let resumeSid = '';
9585
9545
  if (sid && engine) {
9586
9546
  try {
9587
9547
  let s = engine.cache.get(sid);
@@ -9605,9 +9565,21 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
9605
9565
  }
9606
9566
  } catch {}
9607
9567
  }
9568
+ /* ESKİ SOHBETTEN DEVAM: finance moduna girerken aktif oturum finance değilse
9569
+ (örn. normal sohbet) yeni oturum AÇMAK yerine en son kullanılan finance
9570
+ sohbet oturumu önerilir; hiç yoksa yeni oturum açılır. */
9571
+ if (financeState.mode && needNew && engine) {
9572
+ try {
9573
+ const prev = (engine.listSessions() || []).find((v) => v && v.finance && !v.isBg && v.id);
9574
+ if (prev) {
9575
+ resumeSid = String(prev.id);
9576
+ needNew = false;
9577
+ }
9578
+ } catch {}
9579
+ }
9608
9580
  /* OTOMATİK TRADER KAPALI: finance moduna geçmek trader'ı KENDİLİĞİNDEN
9609
9581
  başlatmaz — kullanıcı TRADE AJANI kartındaki ▶ ile elle başlatır */
9610
- return { ok: true, mode: financeState.mode, needNew };
9582
+ return { ok: true, mode: financeState.mode, needNew, resumeSid };
9611
9583
  });
9612
9584
 
9613
9585
  /* MT5 sembol seçici: terminaldeki tüm semboller (isteğe bağlı *filter*) */
@@ -11070,7 +11042,6 @@ ipcMain.handle('custom:set', (_e, list) => {
11070
11042
  });
11071
11043
  saveSettings();
11072
11044
  engine.setCustomProviders(settings.customProviders);
11073
- headroom.syncProviders(); /* headroom açıksa proxy setini yeni listeye uydur */
11074
11045
  return engine.publicState();
11075
11046
  });
11076
11047
 
package/src/preload.js CHANGED
@@ -260,9 +260,9 @@ contextBridge.exposeInMainWorld('beast', {
260
260
  idePreviewUrl: (url) => ipcRenderer.invoke('ide:previewUrl', url),
261
261
  ideProjInfo: () => ipcRenderer.invoke('ide:projinfo'),
262
262
  ideExpQr: () => ipcRenderer.invoke('ide:expqr'),
263
- headroomToggle: (on) => ipcRenderer.invoke('headroom:toggle', !!on),
264
- headroomStatus: () => ipcRenderer.invoke('headroom:status'),
265
- headroomStats: () => ipcRenderer.invoke('headroom:stats'),
263
+ squeezeToggle: (on) => ipcRenderer.invoke('squeeze:toggle', !!on),
264
+ squeezeStatus: () => ipcRenderer.invoke('squeeze:status'),
265
+ squeezeReset: () => ipcRenderer.invoke('squeeze:reset'),
266
266
  agentsClearAll: () => ipcRenderer.invoke('agents:clearAll'),
267
267
  ideDelete: (rel) => ipcRenderer.invoke('ide:delete', rel),
268
268
  onWaEvent: (cb) => ipcRenderer.on('wa:event', (_e, ev) => cb(ev)),
@@ -66,7 +66,7 @@
66
66
  set_title: 'AYARLAR',
67
67
  tab_lang: 'Dil',
68
68
  lang_sub: 'Arayüz dili — uygulama dili ve sesli komut dili buradan değişir',
69
- tab_headroom: 'Token Sıkıştırma',
69
+ tab_squeeze: 'Squeeze',
70
70
  tab_provider: 'Provider',
71
71
  tab_fallout: 'Fallout',
72
72
  tab_memory: 'Memory',
@@ -787,7 +787,7 @@
787
787
  set_title: 'SETTINGS',
788
788
  tab_lang: 'Language',
789
789
  lang_sub: 'Interface language — app UI and voice command language',
790
- tab_headroom: 'Token Compression',
790
+ tab_squeeze: 'Squeeze',
791
791
  tab_provider: 'Provider',
792
792
  tab_fallout: 'Fallout',
793
793
  tab_memory: 'Memory',
@@ -157,13 +157,13 @@
157
157
  <div class="fin-card-title">HESAP</div>
158
158
  <div class="fin-grid">
159
159
  <div class="fin-kv"><span>Bakiye</span><b id="finBalance">—</b></div>
160
- <div class="fin-kv"><span>Özkaynak</span><b id="finEquity">—</b></div>
160
+ <div class="fin-kv"><span>Varlık</span><b id="finEquityValue">—</b></div>
161
161
  <div class="fin-kv"><span>Kâr/Zarar</span><b id="finProfit">—</b></div>
162
162
  <div class="fin-kv"><span>Serbest Marj</span><b id="finFree">—</b></div>
163
163
  </div>
164
164
  </div>
165
165
  <div class="fin-card fin-card-trader">
166
- <div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span><span class="file-spacer"></span><button id="finSkillsBtn" title="Rol → skill eşleştirmesi — hangi rol hangi skill'i okur">&#9881;</button></div>
166
+ <div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span><span class="file-spacer"></span><button id="finModelRefreshBtn" title="Modelleri yeniden çek — eksik/güncel modelleri tazeler">&#x27F3;</button><button id="finSkillsBtn" title="Rol → skill eşleştirmesi — hangi rol hangi skill'i okur">&#9881;</button></div>
167
167
  <div class="fin-field">
168
168
  <span>Model</span>
169
169
  <select id="finTraderModel" title="Trader ajanının modeli — farklı API/model seçilebilir"></select>
@@ -234,7 +234,7 @@
234
234
  </div>
235
235
  <div class="fin-card" id="finPerfCard" hidden>
236
236
  <div class="fin-card-title">PERFORMANS <span id="finStatsInfo" class="fin-hint"></span><span class="file-spacer"></span><button id="finReportBtn" title="Haftalık rapor üret — istatistik + işlem günlüğü + ajan değerlendirmesi">&#128196; Rapor</button></div>
237
- <div id="finEquity" class="fin-equity"></div>
237
+ <div id="finEquityChart" class="fin-equity"></div>
238
238
  <div class="fin-grid">
239
239
  <div class="fin-kv"><span>Bugün</span><b id="finStatToday">—</b></div>
240
240
  <div class="fin-kv"><span>Hafta</span><b id="finStatWeek">—</b></div>
@@ -511,7 +511,7 @@
511
511
  <button class="tab" data-tab="cron" data-i18n="tab_cron">Cron</button>
512
512
  <button class="tab" data-tab="agenda" data-i18n="tab_agenda">Ajanda</button>
513
513
  <button class="tab" data-tab="usage" data-i18n="tab_usage">Maliyet · Limit</button>
514
- <button class="tab" data-tab="headroom" data-i18n="tab_headroom">Token Sıkıştırma</button>
514
+ <button class="tab" data-tab="squeeze" data-i18n="tab_squeeze">Squeeze</button>
515
515
  <button class="tab" data-tab="logs" data-i18n="tab_logs">Log</button>
516
516
  <button class="tab" data-tab="dash" data-i18n="tab_dash">Dashboard</button>
517
517
  <button class="tab" data-tab="sec" data-i18n="tab_security">Güvenlik</button>
@@ -537,7 +537,7 @@
537
537
  <div id="tab-events" class="pane" hidden></div>
538
538
  <div id="tab-empati" class="pane" hidden></div>
539
539
  <div id="tab-usage" class="pane" hidden></div>
540
- <div id="tab-headroom" class="pane" hidden></div>
540
+ <div id="tab-squeeze" class="pane" hidden></div>
541
541
  <div id="tab-logs" class="pane" hidden></div>
542
542
  <div id="tab-dash" class="pane" hidden></div>
543
543
  <div id="tab-sec" class="pane" hidden></div>
@@ -175,10 +175,11 @@ const els = {
175
175
  finPerfCard: $('#finPerfCard'),
176
176
  finFlowCard: $('#finFlowCard'),
177
177
  finBalance: $('#finBalance'),
178
- finEquity: $('#finEquity'),
178
+ finEquity: $('#finEquityValue'),
179
179
  finProfit: $('#finProfit'),
180
180
  finFree: $('#finFree'),
181
181
  finTraderModel: $('#finTraderModel'),
182
+ finModelRefreshBtn: $('#finModelRefreshBtn'),
182
183
  finInterval: $('#finInterval'),
183
184
  finMaxLot: $('#finMaxLot'),
184
185
  finSymBtn: $('#finSymBtn'),
@@ -227,7 +228,7 @@ const els = {
227
228
  finStatPf: $('#finStatPf'),
228
229
  finStatDd: $('#finStatDd'),
229
230
  finStatTrades: $('#finStatTrades'),
230
- finEquityBox: $('#finEquity'),
231
+ finEquityBox: $('#finEquityChart'),
231
232
  stVideoEl: $('#stVideoEl'),
232
233
  stVideoName: $('#stVideoName'),
233
234
  stVideoOpen: $('#stVideoOpen'),
@@ -1212,7 +1213,6 @@ function addFiles(files, mode) {
1212
1213
  /* ---------------- settings ---------------- */
1213
1214
 
1214
1215
  let setTab = 'lang'; /* ayarlar artık DİL sekmesiyle açılır */
1215
- let hrPaneTimer = null; /* headroom-status → Kurulum sekmesi debounce tazeleme */
1216
1216
 
1217
1217
  /* DİL sekmesi: arayüz dili seçimi (eski sağ-alt dil ikonunun yerine) */
1218
1218
  function renderLangPane() {
@@ -1279,7 +1279,7 @@ async function renderActiveSettingsTab() {
1279
1279
  case 'cron': await openCron(); break;
1280
1280
  case 'agenda': await renderAgendaPane(); break;
1281
1281
  case 'usage': await renderUsagePane(); break;
1282
- case 'headroom': await renderHeadroomPane(); break;
1282
+ case 'squeeze': await renderSqueezePane(); break;
1283
1283
  case 'agents': await refreshAgentsPane(); break;
1284
1284
  case 'logs': await renderLogPane(); break;
1285
1285
  case 'dash': await renderDashboardPane(); break;
@@ -1363,7 +1363,7 @@ function switchTab(name) {
1363
1363
  document.querySelectorAll('#setTabs .tab').forEach((b) =>
1364
1364
  b.classList.toggle('active', b.dataset.tab === name)
1365
1365
  );
1366
- for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'agenda', 'usage', 'headroom', 'logs', 'dash', 'sec', 'update']) {
1366
+ for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'agenda', 'usage', 'squeeze', 'logs', 'dash', 'sec', 'update']) {
1367
1367
  const el = $('#tab-' + p);
1368
1368
  if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
1369
1369
  }
@@ -1373,7 +1373,7 @@ function switchTab(name) {
1373
1373
  if (name === 'email') renderEmailPane();
1374
1374
  if (name === 'usage') renderUsagePane();
1375
1375
  if (name === 'install') renderInstallPane();
1376
- if (name === 'headroom') renderHeadroomPane();
1376
+ if (name === 'squeeze') renderSqueezePane();
1377
1377
  if (name === 'events') renderEventsPane();
1378
1378
  if (name === 'empati') renderEmpatiPane();
1379
1379
  if (name === 'logs') renderLogPane();
@@ -2331,91 +2331,90 @@ function ttsFlushTail() {
2331
2331
  /* ---------------- KURULUM SEKMESİ: bileşenler OTOMATİK kurulur ----------------
2332
2332
  Eksik/kısmen inmiş bileşen sekme açılınca kendiliğinden indirilir; kullanıcı
2333
2333
  hiçbir düğmeye basmaz. Durum canlı güncellenir (4 sn'de bir). */
2334
- /* ---------- TOKEN SIKIŞTIRMA (Headroom) sekmesi ----------
2335
- Opsiyonel yerel sıkıştırma proxy'si: açıkken LLM girdisi %30-70 küçülür.
2336
- Aç/Kapat buradan; CLI kurulumu Kurulum sekmesinde görülür. */
2337
- let hrStatsTimer = null;
2338
-
2339
- async function renderHeadroomPane() {
2340
- const pane = $('#tab-headroom');
2334
+ /* ---------- SQUEEZE (kendi token sıkıştırıcımız) sekmesi ----------
2335
+ Varsayılan KAPALI. Açıkken her LLM isteğinin kopyası gönderimden önce
2336
+ sıkıştırılır: tekrar eden büyük bloklar bağlamda bir kez gider, eski araç
2337
+ çıktıları başlık + önizlemeye iner. Oturum kaydı değişmez. */
2338
+ let sqStatsTimer = null;
2339
+
2340
+ async function renderSqueezePane() {
2341
+ const pane = $('#tab-squeeze');
2341
2342
  if (!pane) return;
2342
- const st = await beast.headroomStatus().catch(() => null);
2343
-
2343
+ const st = await beast.squeezeStatus().catch(() => null);
2344
+ const on = !!(st && st.enabled);
2345
+ const s = (st && st.stats) || {};
2344
2346
  const fmtN = (n) => new Intl.NumberFormat('tr-TR').format(Math.round(Number(n) || 0));
2345
- let stHtml = '';
2346
- if (st) {
2347
- const readyN = (st.proxies || []).filter((p) => p.ready).length;
2348
- let stateTxt;
2349
- if (st.installing) stateTxt = '⏳ arka planda kuruluyor — uv tool install headroom-ai[proxy] (birkaç dakika)';
2350
- else if (st.enabled && readyN) stateTxt = '🟢 açık — proxy: ' + (st.proxies || []).filter((p) => p.ready).map((p) => '127.0.0.1:' + p.port).join(', ');
2351
- else if (st.enabled) stateTxt = '⏳ proxy başlatılıyor… (ilk açılış ~30 sn)';
2352
- else if (st.failed) stateTxt = '⛔ ' + (st.error || 'kurulum başarısız');
2353
- else if (st.installed) stateTxt = '⚪ kapalı — kurulu, aşağıdaki düğmeyle etkinleştir';
2354
- else stateTxt = '⚪ kapalı — açınca uv ile otomatik kurulur (birkaç dakika)';
2355
- stHtml = '<div class="sub" style="margin-top:10px;font-size:12px;text-align:center">' + escapeHtml(stateTxt) + '</div>';
2356
- }
2357
-
2358
- const stats = await beast.headroomStats().catch(() => null);
2347
+
2348
+ const stHtml =
2349
+ '<div class="sub" style="margin-top:10px;font-size:12px;text-align:center">' +
2350
+ (on
2351
+ ? '🟢 açık — her isteğin kopyası gönderimden önce sıkıştırılır; oturum kaydın değişmez'
2352
+ : '⚪ kapalı (varsayılan) — açınca yeni isteklerde uygulanır, geçmişe dokunmaz') +
2353
+ '</div>';
2354
+
2359
2355
  let statsHtml = '';
2360
- if (stats && stats.ok && (stats.input || stats.saved)) {
2356
+ if (s.calls) {
2361
2357
  statsHtml =
2362
2358
  '<div class="divider"></div>' +
2363
- '<h2 style="text-align:center">Tasarruf (proxy açıkken)</h2>' +
2359
+ '<h2 style="text-align:center">Tasarruf</h2>' +
2364
2360
  '<div style="display:flex;gap:24px;flex-wrap:wrap;margin-top:8px;justify-content:center">' +
2365
- '<div><div style="font-size:20px;font-weight:800;color:var(--ok)">%' + Math.round(stats.savingsPercent || 0) + '</div><div class="sub">sıkıştırma oranı</div></div>' +
2366
- '<div><div style="font-size:20px;font-weight:800">' + new Intl.NumberFormat('tr-TR').format(stats.proxyCompressionSaved || 0) + '</div><div class="sub">kaydedilen token</div></div>' +
2367
- '<div><div style="font-size:20px;font-weight:800">' + new Intl.NumberFormat('tr-TR').format(stats.input || 0) + '</div><div class="sub">toplam girdi token</div></div>' +
2368
- '</div>';
2361
+ '<div title="Son istekte tahmini token tasarrufu"><div style="font-size:20px;font-weight:800;color:var(--ok)">%' + (s.lastPercent || 0) + '</div><div class="sub">son istekte sıkıştırma</div></div>' +
2362
+ '<div><div style="font-size:20px;font-weight:800">' + fmtN(s.savedTokens) + '</div><div class="sub">toplam kaydedilen token (tahmini)</div></div>' +
2363
+ '<div><div style="font-size:20px;font-weight:800">' + fmtN(s.changedCalls) + '</div><div class="sub">sıkıştırılan istek</div></div>' +
2364
+ '</div>' +
2365
+ '<div class="sub" style="text-align:center;margin-top:8px">tekrar bloğu: ' + fmtN(s.deduped) +
2366
+ ' · eski araç çıktısı: ' + fmtN(s.compactedTools) +
2367
+ ' · skill: ' + fmtN(s.compactedSkills) +
2368
+ ' · kullanıcı içeriği: ' + fmtN(s.compactedUsers) + '</div>';
2369
2369
  }
2370
2370
 
2371
- /* AÇ/KAPAT düğmesi: ortalanmış + SVG ikonlu (play/pause — uygulama ikon stili) */
2372
- const svgPlay =
2373
- '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="6 3 20 12 6 21 6 3"/></svg>';
2374
- const svgPause =
2375
- '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/></svg>';
2376
2371
  pane.innerHTML =
2377
- '<h2 style="text-align:center">' + _t('tab_headroom') + '</h2>' +
2378
- '<div class="sub" style="text-align:center;max-width:620px;margin:0 auto">Headroom yerel proxy\u2019si — araç çıktıları, loglar ve JSON yükleri modele gitmeden sıkıştırılır; orijinali yerelde saklanır. Token faturasını %30-70 düşürür. Yalnız OpenAI-uyumlu sağlayıcı trafiğini etkiler; kapalıyken hiçbir şey değişmez.</div>' +
2372
+ '<h2 style="text-align:center">' + _t('tab_squeeze') + '</h2>' +
2379
2373
  '<div style="display:flex;flex-direction:column;align-items:center;gap:10px;margin-top:18px">' +
2380
- '<button id="hrToggle" class="btn" style="width:auto;padding:11px 16px;display:inline-flex;align-items:center;gap:8px;justify-content:center" title=""></button>' +
2381
- '<span id="hrToggleMsg" class="sub" style="margin:0;text-align:center;max-width:560px"></span>' +
2374
+ '<button id="sqToggle" class="btn" style="width:auto;padding:11px 16px;display:inline-flex;align-items:center;gap:8px;justify-content:center" title=""></button>' +
2375
+ '<span id="sqToggleMsg" class="sub" style="margin:0;text-align:center;max-width:560px"></span>' +
2382
2376
  '</div>' +
2383
- stHtml + statsHtml;
2377
+ stHtml + statsHtml +
2378
+ (s.calls ? '<div style="text-align:center;margin-top:12px"><button id="sqReset" class="btn" style="width:auto;padding:6px 12px;font-size:12px">İstatistiği sıfırla</button></div>' : '');
2384
2379
 
2385
- const btn = $('#hrToggle');
2380
+ const svgPlay =
2381
+ '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="6 3 20 12 6 21 6 3"/></svg>';
2382
+ const svgPause =
2383
+ '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/></svg>';
2384
+ const btn = $('#sqToggle');
2386
2385
  if (btn) {
2387
- const on = !!(st && st.enabled);
2388
2386
  /* yalnız SVG ikon — anlamı durum satırı + tooltip taşır */
2389
2387
  btn.innerHTML = on ? svgPause : svgPlay;
2390
- btn.title = on ? 'Token sıkıştırmayı kapat' : 'Token sıkıştırmayı aç';
2388
+ btn.title = on ? 'Squeeze\'i kapat' : 'Squeeze\'i aç';
2391
2389
  btn.setAttribute('aria-label', btn.title);
2392
2390
  btn.addEventListener('click', async () => {
2393
- const msg = $('#hrToggleMsg');
2391
+ const msg = $('#sqToggleMsg');
2394
2392
  btn.disabled = true;
2395
2393
  btn.innerHTML = '<span>…</span>';
2396
- const r = await beast.headroomToggle(!on).catch(() => null);
2394
+ const r = await beast.squeezeToggle(!on).catch(() => null);
2397
2395
  if (msg) {
2398
- if (r && r.installing) {
2399
- msg.textContent = 'Headroom arka planda kuruluyor — birkaç dakika sonra hazır olur, bu sekme açık kalabilir.';
2400
- } else if (r && r.error) {
2401
- msg.textContent = r.error;
2402
- } else {
2403
- msg.textContent = !on ? 'Açıldı — proxy hazır olunca trafiği yönlendirir' : 'Kapatıldı — trafik sağlayıcıya düz gidiyor';
2404
- }
2396
+ msg.textContent = r && r.ok
2397
+ ? (!on ? 'Açıldı — sonraki istekler sıkıştırılarak gönderilir' : 'Kapatıldı — istekler olduğu gibi gider')
2398
+ : 'Ayar kaydedilemedi';
2405
2399
  }
2406
- setTimeout(() => renderHeadroomPane(), 2500);
2400
+ setTimeout(() => renderSqueezePane(), 400);
2401
+ });
2402
+ }
2403
+ const rb = $('#sqReset');
2404
+ if (rb) {
2405
+ rb.addEventListener('click', async () => {
2406
+ rb.disabled = true;
2407
+ await beast.squeezeReset().catch(() => null);
2408
+ renderSqueezePane();
2407
2409
  });
2408
2410
  }
2409
2411
 
2410
2412
  /* açıkken istatistikler canlı aksın */
2411
- if (hrStatsTimer) clearInterval(hrStatsTimer);
2412
- hrStatsTimer = setInterval(async () => {
2413
- if (pane.hidden) { clearInterval(hrStatsTimer); return; }
2414
- const s2 = await beast.headroomStatus().catch(() => null);
2415
- const cur = $('#hrToggle');
2416
- if (s2 && cur && cur.disabled) return; /* toggle işlemi sürüyor */
2417
- await renderHeadroomPane();
2418
- }, 6000);
2413
+ if (sqStatsTimer) clearInterval(sqStatsTimer);
2414
+ sqStatsTimer = setInterval(() => {
2415
+ if (pane.hidden) { clearInterval(sqStatsTimer); return; }
2416
+ renderSqueezePane();
2417
+ }, 5000);
2419
2418
  }
2420
2419
 
2421
2420
  async function renderInstallPane() {
@@ -2485,24 +2484,10 @@ async function renderInstallPane() {
2485
2484
  '<div class="sub" style="font-size:11px">' + escapeHtml(r.detail || '') + (r.mb ? ' · ' + r.mb + ' MB' : '') + '</div>' +
2486
2485
  bar(r) +
2487
2486
  '</div>' +
2488
- (r.canToggle
2489
- ? '<button class="btn hr-tgl" data-on="' + (r.toggleOn ? '1' : '0') + '" style="width:auto;padding:2px 12px;flex:none">' + (r.toggleOn ? 'KAPAT' : 'AÇ') + '</button>'
2490
- : '') +
2491
2487
  badge(r.state) +
2492
2488
  pctLabel(r) +
2493
2489
  '</div>'
2494
2490
  )).join('');
2495
- /* Headroom aç/kapa: toggle kurulum + proxy başlatmayı arka planda tetikler */
2496
- el.querySelectorAll('.hr-tgl').forEach((b) => {
2497
- b.addEventListener('click', async () => {
2498
- const on = b.dataset.on !== '1';
2499
- b.disabled = true;
2500
- b.textContent = '…';
2501
- const r = await beast.headroomToggle(on).catch(() => null);
2502
- toast(r && r.error ? r.error : (r && r.installing ? 'Headroom kuruluyor — birkaç dakika sürer' : (on ? 'Headroom açıldı' : 'Headroom kapatıldı')));
2503
- refresh();
2504
- });
2505
- });
2506
2491
  };
2507
2492
 
2508
2493
  const refresh = async () => {
@@ -5744,19 +5729,6 @@ function onEvent(ev) {
5744
5729
  showPermissionCard(req, isBc);
5745
5730
  return;
5746
5731
  }
5747
- /* Headroom durum değişimi → açık olan sekmeyi tazele (Token Sıkıştırma / Kurulum) */
5748
- if (ev.type === 'headroom-status') {
5749
- try {
5750
- const iPane = document.getElementById('tab-install');
5751
- const hPane = document.getElementById('tab-headroom');
5752
- const open = (!iPane || iPane.hidden) && (!hPane || hPane.hidden) ? '' : (!hPane || hPane.hidden ? 'install' : 'headroom');
5753
- if (open) {
5754
- clearTimeout(hrPaneTimer);
5755
- hrPaneTimer = setTimeout(() => { (open === 'headroom' ? renderHeadroomPane() : renderInstallPane()).catch(() => {}); }, 1500);
5756
- }
5757
- } catch {}
5758
- return;
5759
- }
5760
5732
  /* EMPATİ LOOP: proaktif bildirim (masaüstü) + sekme canlı yenileme.
5761
5733
  Toast'ta yalnız haber başlığı — link/markdown kalabalığı chat mesajında. */
5762
5734
  if (ev.type === 'proactive') {
@@ -7178,6 +7150,8 @@ async function init() {
7178
7150
  try {
7179
7151
  state = await beast.refreshModels();
7180
7152
  applyState();
7153
+ /* finance modu açıksa TRADE AJANI model listesini de tazele */
7154
+ if (financeModeOn()) await finFillModels();
7181
7155
  toast(_t('mr_done'));
7182
7156
  } catch {
7183
7157
  toast(_t('ws_fail_toast'));
@@ -9502,6 +9476,8 @@ function finRenderAlerts(list) {
9502
9476
 
9503
9477
  async function finFillModels() {
9504
9478
  if (!els.finTraderModel) return;
9479
+ /* mevcut seçim korunur — yenilemede model kaybolursa "Genel aktif model"e düşer */
9480
+ const cur = els.finTraderModel.value || (finCfgCache && finCfgCache.traderSel) || '';
9505
9481
  let models = [];
9506
9482
  try {
9507
9483
  const st = await beast.getState();
@@ -9511,11 +9487,13 @@ async function finFillModels() {
9511
9487
  const optAuto = document.createElement('option');
9512
9488
  optAuto.value = '';
9513
9489
  optAuto.textContent = 'Genel aktif model';
9490
+ if (!cur) optAuto.selected = true;
9514
9491
  els.finTraderModel.appendChild(optAuto);
9515
9492
  for (const m of models) {
9516
9493
  const o = document.createElement('option');
9517
9494
  o.value = m.sel;
9518
9495
  o.textContent = (m.providerName || m.sel) + ' · ' + m.model;
9496
+ if (m.sel === cur) o.selected = true;
9519
9497
  els.finTraderModel.appendChild(o);
9520
9498
  }
9521
9499
  finModelsFilled = true;
@@ -9594,8 +9572,11 @@ async function setFinanceMode(on) {
9594
9572
  try { localStorage.setItem('beast.financeMode', on ? '1' : '0'); } catch {}
9595
9573
  const fm = await beast.financeMode(!!on, activeId).catch(() => ({}));
9596
9574
  /* finance modu açıldı ama aktif oturum NORMAL (örn. WhatsApp sohbeti) ise
9597
- ASLA o oturumu finance'e çevirme — kendine ÖZEL finance oturumu aç */
9598
- if (on && fm && fm.needNew) {
9575
+ ASLA o oturumu finance'e çevirme — önce EN SON finance sohbetinden devam
9576
+ et (resumeSid); hiç finance sohbeti yoksa kendine ÖZEL yeni oturum aç */
9577
+ if (on && fm && fm.resumeSid) {
9578
+ try { await openSession(fm.resumeSid); } catch {}
9579
+ } else if (on && fm && fm.needNew) {
9599
9580
  try {
9600
9581
  const created = await beast.createSession();
9601
9582
  if (created && created.id) await openSession(created.id);
@@ -9787,6 +9768,23 @@ if (els.finTraderModel) {
9787
9768
  toast('Trade ajanı modeli: ' + (els.finTraderModel.value || 'genel aktif model'));
9788
9769
  });
9789
9770
  }
9771
+ /* TRADE AJANI: modelleri yeniden çek — üstteki ⟳ ile aynı mantık
9772
+ (models:refresh). Eksik/yeni modeller listeye girer, seçim korunur. */
9773
+ if (els.finModelRefreshBtn) {
9774
+ els.finModelRefreshBtn.addEventListener('click', async () => {
9775
+ els.finModelRefreshBtn.classList.add('spin');
9776
+ try {
9777
+ state = await beast.refreshModels();
9778
+ applyState();
9779
+ await finFillModels();
9780
+ toast(_t('mr_done'));
9781
+ } catch {
9782
+ toast(_t('ws_fail_toast'));
9783
+ } finally {
9784
+ els.finModelRefreshBtn.classList.remove('spin');
9785
+ }
9786
+ });
9787
+ }
9790
9788
  if (els.finTraderBtn) {
9791
9789
  els.finTraderBtn.addEventListener('click', async () => {
9792
9790
  if (els.finTraderBtn.classList.contains('stop')) {
@@ -287,9 +287,9 @@ body:not(.ide-mode):not(.studio-mode):not(.sandbox-mode) #watchPane { display: f
287
287
  /* Studio tuşu artık topbar'da (dd-btn dd-icon-btn) — aktif rozet diğer topbar ikonlarıyla aynı */
288
288
  #studioBtn.on { background: var(--accent-dim); color: var(--accent); }
289
289
 
290
- /* model refresh butonu — çekerken döner */
291
- #modelRefreshBtn.spin { opacity: .7; pointer-events: none; }
292
- #modelRefreshBtn.spin { animation: beast-spin 0.9s linear infinite; }
290
+ /* model refresh butonları — çekerken döner */
291
+ #modelRefreshBtn.spin, #finModelRefreshBtn.spin { opacity: .7; pointer-events: none; }
292
+ #modelRefreshBtn.spin, #finModelRefreshBtn.spin { animation: beast-spin 0.9s linear infinite; }
293
293
  @keyframes beast-spin { to { transform: rotate(360deg); } }
294
294
 
295
295
  /* ---------- cron modülü (ayarlar sekmesi) ---------- */
@@ -1,279 +0,0 @@
1
- 'use strict';
2
-
3
- /* ---------- BEAST × HEADROOM (OPSİYONEL token sıkıştırma) ----------
4
- Ayarlardan (Kurulum sekmesi) açılıp kapanır; kapalıyken hiçbir şey yapmaz.
5
- Açıkken: LLM istekleri yerel headroom proxy'sinden geçer — araç çıktıları,
6
- loglar ve JSON yükleri modele gitmeden sıkıştırılır (testte %39-71 tasarruf).
7
- Kurulum: uv tool install "headroom-ai[proxy]" (uv yoksa hata bildirir).
8
- Her provider için AYRI proxy portu (base URL farkları için); llm.js
9
- providerId → port haritasıyla yönlendirir, kapatınca düz bağlanır. */
10
-
11
- const { spawn } = require('child_process');
12
- const fs = require('fs');
13
- const path = require('path');
14
- const os = require('os');
15
- const { emitInstallProgress } = require('./progressbus');
16
-
17
- const PORT_BASE = 18787;
18
- const MAX_PROXIES = 8;
19
-
20
- const state = {
21
- enabled: false,
22
- installing: false,
23
- installFailed: false,
24
- installError: '',
25
- starting: false,
26
- proxies: new Map(), // providerId -> { proc, port, baseUrl, ready }
27
- providerGetter: null, // () => [{ id, name, baseUrl }]
28
- statusSink: null, // (probe) => void — main renderer'a akıtır
29
- };
30
-
31
- function _emit() {
32
- try { if (state.statusSink) state.statusSink(probe()); } catch {}
33
- }
34
-
35
- function headroomExe() {
36
- const local = path.join(os.homedir(), '.local', 'bin', 'headroom.exe');
37
- if (fs.existsSync(local)) return local;
38
- try {
39
- require('child_process').execSync('where headroom', { timeout: 5000, stdio: 'pipe' });
40
- return 'headroom';
41
- } catch {}
42
- return '';
43
- }
44
-
45
- function uvExe() {
46
- const local = path.join(os.homedir(), '.local', 'bin', 'uv.exe');
47
- if (fs.existsSync(local)) return local;
48
- try {
49
- require('child_process').execSync('where uv', { timeout: 5000, stdio: 'pipe' });
50
- return 'uv';
51
- } catch {}
52
- return '';
53
- }
54
-
55
- function probe() {
56
- return {
57
- installed: !!headroomExe(),
58
- installing: state.installing,
59
- failed: state.installFailed,
60
- error: state.installError,
61
- enabled: state.enabled,
62
- starting: state.starting,
63
- proxies: [...state.proxies.values()].map((p) => ({ port: p.port, baseUrl: p.baseUrl, ready: p.ready })),
64
- };
65
- }
66
-
67
- /* ---------- KURULUM (arka plan) ----------
68
- uv tool install headroom-ai[proxy] — kendi izole Python 3.13 ortamını kurar;
69
- sistemde Python gerekmez. uv yoksa pip fallback, o da yoksa elle yönlendirir. */
70
- function autoInstall() {
71
- if (state.installing || headroomExe()) return;
72
- const uv = uvExe();
73
- if (!uv) {
74
- state.installFailed = true;
75
- state.installError = 'uv bulunamadı — kur: winget install astral-sh.uv';
76
- _emit();
77
- return;
78
- }
79
- state.installing = true;
80
- state.installFailed = false;
81
- state.installError = '';
82
- _emit();
83
- emitInstallProgress('headroom', { pct: 4 });
84
- const proc = spawn(uv, ['tool', 'install', '--python', '3.13', 'headroom-ai[proxy]'], { windowsHide: true });
85
- let buf = '';
86
- let stage = 4;
87
- const bump = () => {
88
- stage = Math.min(92, stage + 7);
89
- emitInstallProgress('headroom', { pct: stage });
90
- };
91
- const onData = (c) => {
92
- const s = String(c);
93
- if (/Download|Resolve|Installed|Prepared/i.test(s)) bump();
94
- };
95
- proc.stdout.on('data', onData);
96
- proc.stderr.on('data', onData);
97
- proc.on('close', (code) => {
98
- state.installing = false;
99
- if (code === 0 && headroomExe()) {
100
- emitInstallProgress('headroom', { pct: 100 });
101
- } else {
102
- state.installFailed = true;
103
- state.installError = 'kurulum başarısız — elle: uv tool install "headroom-ai[proxy]"';
104
- }
105
- _emit();
106
- /* kurulum bitti ve açık kaldıysa proxy'leri hemen başlat */
107
- if (state.enabled && headroomExe()) start().catch(() => {});
108
- });
109
- proc.on('error', () => {
110
- state.installing = false;
111
- state.installFailed = true;
112
- state.installError = 'kurulum başlatılamadı (uv)';
113
- _emit();
114
- });
115
- }
116
-
117
- /* ---------- PROXY SÜREÇLERİ ----------
118
- Provider başına bir proxy; cache modunu BOZMAMAK için mode=token,
119
- Beast'in tanımadığı headroom_retrieve aracı enjekte EDİLMESİN diye
120
- no-ccr (geri-getirilemez sıkıştırma, en yüksek tasarruf) ve rate limit
121
- kapalı (Beast'in kendi istek patlamalarına takılmasın). Beacon kapalı. */
122
- function nextPort() {
123
- const used = new Set([...state.proxies.values()].map((p) => p.port));
124
- for (let i = 0; i < MAX_PROXIES; i++) {
125
- const port = PORT_BASE + i;
126
- if (!used.has(port)) return port;
127
- }
128
- return 0;
129
- }
130
-
131
- function spawnFor(provider) {
132
- return new Promise((resolve) => {
133
- const exe = headroomExe();
134
- if (!exe) return resolve({ ok: false, error: 'headroom kurulu değil' });
135
- const port = nextPort();
136
- if (!port) return resolve({ ok: false, error: 'port kalmadı' });
137
- let proc;
138
- try {
139
- proc = spawn(exe, [
140
- 'proxy', '--port', String(port), '--openai-api-url', provider.baseUrl,
141
- '--mode', 'token', '--no-ccr', '--no-rate-limit',
142
- ], {
143
- windowsHide: true,
144
- env: { ...process.env, HEADROOM_BEACON: 'off', DO_NOT_TRACK: '1' },
145
- });
146
- } catch (e) {
147
- return resolve({ ok: false, error: String((e && e.message) || e) });
148
- }
149
- const rec = { proc, port, baseUrl: provider.baseUrl, ready: false };
150
- state.proxies.set(provider.id, rec);
151
- _emit();
152
- proc.on('exit', () => {
153
- if (state.proxies.get(provider.id) === rec) state.proxies.delete(provider.id);
154
- _emit();
155
- });
156
- const t0 = Date.now();
157
- const poll = setInterval(async () => {
158
- let ready = false;
159
- try {
160
- const r = await fetch('http://127.0.0.1:' + port + '/health', { signal: AbortSignal.timeout(2000) });
161
- ready = !!r.ok;
162
- } catch {}
163
- if (ready) {
164
- clearInterval(poll);
165
- rec.ready = true;
166
- _emit();
167
- return resolve({ ok: true, port });
168
- }
169
- if (Date.now() - t0 > 90000) {
170
- clearInterval(poll);
171
- try { rec.proc.kill(); } catch {}
172
- state.proxies.delete(provider.id);
173
- _emit();
174
- resolve({ ok: false, error: 'proxy zaman aşımı (90sn)' });
175
- }
176
- }, 1200);
177
- });
178
- }
179
-
180
- async function start() {
181
- const exe = headroomExe();
182
- if (!exe) {
183
- autoInstall();
184
- return { ok: false, error: 'kuruluyor — birkaç dakika sonra hazır olur' };
185
- }
186
- state.starting = true;
187
- _emit();
188
- try {
189
- const providers = (state.providerGetter ? state.providerGetter() : []).slice(0, MAX_PROXIES);
190
- const routes = {};
191
- /* önce eskileri temizle: listeden çıkan provider'ın proxy'sini kapat */
192
- const keep = new Set(providers.map((p) => p.id));
193
- for (const [id, rec] of [...state.proxies.entries()]) {
194
- if (keep.has(id)) continue;
195
- try { rec.proc.kill(); } catch {}
196
- try { spawn('taskkill', ['/pid', String(rec.proc.pid), '/T', '/F'], { windowsHide: true }); } catch {}
197
- state.proxies.delete(id);
198
- }
199
- for (const p of providers) {
200
- if (!p.baseUrl || state.proxies.has(p.id)) continue;
201
- const r = await spawnFor(p);
202
- if (r.ok) routes[p.id] = r.port;
203
- }
204
- try { require('./llm').setHeadroomRoutes(routes); } catch {}
205
- state.enabled = true;
206
- return { ok: true, routes };
207
- } finally {
208
- state.starting = false;
209
- _emit();
210
- }
211
- }
212
-
213
- async function setEnabled(on) {
214
- if (on) {
215
- state.enabled = true;
216
- if (!headroomExe()) {
217
- autoInstall();
218
- return { ok: false, installing: true, error: 'headroom arka planda kuruluyor — birkaç dakika sonra hazır' };
219
- }
220
- return start();
221
- }
222
- state.enabled = false;
223
- stopAll();
224
- try { require('./llm').setHeadroomRoutes(null); } catch {}
225
- return { ok: true };
226
- }
227
-
228
- /* provider listesi değişti (eklendi/silindi) → proxy setini senkronla */
229
- function syncProviders() {
230
- if (!state.enabled || !headroomExe()) return;
231
- start().catch(() => {});
232
- }
233
-
234
- function stopAll() {
235
- for (const rec of state.proxies.values()) {
236
- try { rec.proc.kill(); } catch {}
237
- try { spawn('taskkill', ['/pid', String(rec.proc.pid), '/T', '/F'], { windowsHide: true }); } catch {}
238
- }
239
- state.proxies.clear();
240
- _emit();
241
- }
242
-
243
- function setProviderGetter(fn) { state.providerGetter = typeof fn === 'function' ? fn : null; }
244
- function setStatusSink(fn) { state.statusSink = typeof fn === 'function' ? fn : null; }
245
-
246
- /* proxy /stats özeti (sağlık duvarı yok — hata.yakalanır) */
247
- async function stats() {
248
- const rec = [...state.proxies.values()].find((p) => p.ready);
249
- if (!rec) return { ok: false, running: false };
250
- try {
251
- const r = await fetch('http://127.0.0.1:' + rec.port + '/stats', { signal: AbortSignal.timeout(4000) });
252
- const j = await r.json();
253
- const s = (j && (j.tokens || j.summary)) || {};
254
- return {
255
- ok: true,
256
- running: true,
257
- port: rec.port,
258
- input: Number(s.input) || 0,
259
- output: Number(s.output) || 0,
260
- saved: Number(s.saved) || 0,
261
- proxyCompressionSaved: Number(s.proxy_compression_saved) || 0,
262
- savingsPercent: Number(s.savings_percent) || 0,
263
- allLayersSaved: Number(s.all_layers_saved) || 0,
264
- };
265
- } catch {
266
- return { ok: false, running: true, port: rec.port };
267
- }
268
- }
269
-
270
- module.exports = {
271
- probe,
272
- autoInstall,
273
- setEnabled,
274
- syncProviders,
275
- stopAll,
276
- setProviderGetter,
277
- setStatusSink,
278
- stats,
279
- };