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 +1 -1
- package/src/agent/engine.js +12 -1
- package/src/agent/llm.js +1 -16
- package/src/agent/squeeze.js +312 -0
- package/src/main.js +28 -57
- package/src/preload.js +3 -3
- package/src/renderer/i18n.js +2 -2
- package/src/renderer/index.html +5 -5
- package/src/renderer/renderer.js +93 -95
- package/src/renderer/style.css +3 -3
- package/src/agent/headroom.js +0 -279
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
|
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
|
-
/*
|
|
3469
|
-
|
|
3470
|
-
|
|
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
|
-
/*
|
|
6706
|
-
ipcMain.handle('
|
|
6707
|
-
settings.
|
|
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
|
-
|
|
6710
|
-
return
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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)),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
790
|
+
tab_squeeze: 'Squeeze',
|
|
791
791
|
tab_provider: 'Provider',
|
|
792
792
|
tab_fallout: 'Fallout',
|
|
793
793
|
tab_memory: 'Memory',
|
package/src/renderer/index.html
CHANGED
|
@@ -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
|
|
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">⚙</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">⟳</button><button id="finSkillsBtn" title="Rol → skill eşleştirmesi — hangi rol hangi skill'i okur">⚙</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">📄 Rapor</button></div>
|
|
237
|
-
<div id="
|
|
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="
|
|
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-
|
|
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>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -175,10 +175,11 @@ const els = {
|
|
|
175
175
|
finPerfCard: $('#finPerfCard'),
|
|
176
176
|
finFlowCard: $('#finFlowCard'),
|
|
177
177
|
finBalance: $('#finBalance'),
|
|
178
|
-
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: $('#
|
|
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 '
|
|
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', '
|
|
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 === '
|
|
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
|
-
/* ----------
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
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.
|
|
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
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
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 (
|
|
2356
|
+
if (s.calls) {
|
|
2361
2357
|
statsHtml =
|
|
2362
2358
|
'<div class="divider"></div>' +
|
|
2363
|
-
'<h2 style="text-align:center">Tasarruf
|
|
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)">%' +
|
|
2366
|
-
'<div><div style="font-size:20px;font-weight:800">' +
|
|
2367
|
-
'<div><div style="font-size:20px;font-weight:800">' +
|
|
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('
|
|
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="
|
|
2381
|
-
'<span id="
|
|
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
|
|
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 ? '
|
|
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 = $('#
|
|
2391
|
+
const msg = $('#sqToggleMsg');
|
|
2394
2392
|
btn.disabled = true;
|
|
2395
2393
|
btn.innerHTML = '<span>…</span>';
|
|
2396
|
-
const r = await beast.
|
|
2394
|
+
const r = await beast.squeezeToggle(!on).catch(() => null);
|
|
2397
2395
|
if (msg) {
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
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(() =>
|
|
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 (
|
|
2412
|
-
|
|
2413
|
-
if (pane.hidden) { clearInterval(
|
|
2414
|
-
|
|
2415
|
-
|
|
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 —
|
|
9598
|
-
|
|
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')) {
|
package/src/renderer/style.css
CHANGED
|
@@ -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
|
|
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) ---------- */
|
package/src/agent/headroom.js
DELETED
|
@@ -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
|
-
};
|