beast-agent 2.34.0 → 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 +14 -56
- package/src/preload.js +3 -3
- package/src/renderer/i18n.js +2 -2
- package/src/renderer/index.html +2 -2
- package/src/renderer/renderer.js +62 -91
- 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
|
|
|
@@ -11083,7 +11042,6 @@ ipcMain.handle('custom:set', (_e, list) => {
|
|
|
11083
11042
|
});
|
|
11084
11043
|
saveSettings();
|
|
11085
11044
|
engine.setCustomProviders(settings.customProviders);
|
|
11086
|
-
headroom.syncProviders(); /* headroom açıksa proxy setini yeni listeye uydur */
|
|
11087
11045
|
return engine.publicState();
|
|
11088
11046
|
});
|
|
11089
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
|
@@ -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
|
@@ -1213,7 +1213,6 @@ function addFiles(files, mode) {
|
|
|
1213
1213
|
/* ---------------- settings ---------------- */
|
|
1214
1214
|
|
|
1215
1215
|
let setTab = 'lang'; /* ayarlar artık DİL sekmesiyle açılır */
|
|
1216
|
-
let hrPaneTimer = null; /* headroom-status → Kurulum sekmesi debounce tazeleme */
|
|
1217
1216
|
|
|
1218
1217
|
/* DİL sekmesi: arayüz dili seçimi (eski sağ-alt dil ikonunun yerine) */
|
|
1219
1218
|
function renderLangPane() {
|
|
@@ -1280,7 +1279,7 @@ async function renderActiveSettingsTab() {
|
|
|
1280
1279
|
case 'cron': await openCron(); break;
|
|
1281
1280
|
case 'agenda': await renderAgendaPane(); break;
|
|
1282
1281
|
case 'usage': await renderUsagePane(); break;
|
|
1283
|
-
case '
|
|
1282
|
+
case 'squeeze': await renderSqueezePane(); break;
|
|
1284
1283
|
case 'agents': await refreshAgentsPane(); break;
|
|
1285
1284
|
case 'logs': await renderLogPane(); break;
|
|
1286
1285
|
case 'dash': await renderDashboardPane(); break;
|
|
@@ -1364,7 +1363,7 @@ function switchTab(name) {
|
|
|
1364
1363
|
document.querySelectorAll('#setTabs .tab').forEach((b) =>
|
|
1365
1364
|
b.classList.toggle('active', b.dataset.tab === name)
|
|
1366
1365
|
);
|
|
1367
|
-
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']) {
|
|
1368
1367
|
const el = $('#tab-' + p);
|
|
1369
1368
|
if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
|
|
1370
1369
|
}
|
|
@@ -1374,7 +1373,7 @@ function switchTab(name) {
|
|
|
1374
1373
|
if (name === 'email') renderEmailPane();
|
|
1375
1374
|
if (name === 'usage') renderUsagePane();
|
|
1376
1375
|
if (name === 'install') renderInstallPane();
|
|
1377
|
-
if (name === '
|
|
1376
|
+
if (name === 'squeeze') renderSqueezePane();
|
|
1378
1377
|
if (name === 'events') renderEventsPane();
|
|
1379
1378
|
if (name === 'empati') renderEmpatiPane();
|
|
1380
1379
|
if (name === 'logs') renderLogPane();
|
|
@@ -2332,91 +2331,90 @@ function ttsFlushTail() {
|
|
|
2332
2331
|
/* ---------------- KURULUM SEKMESİ: bileşenler OTOMATİK kurulur ----------------
|
|
2333
2332
|
Eksik/kısmen inmiş bileşen sekme açılınca kendiliğinden indirilir; kullanıcı
|
|
2334
2333
|
hiçbir düğmeye basmaz. Durum canlı güncellenir (4 sn'de bir). */
|
|
2335
|
-
/* ----------
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
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');
|
|
2342
2342
|
if (!pane) return;
|
|
2343
|
-
const st = await beast.
|
|
2344
|
-
|
|
2343
|
+
const st = await beast.squeezeStatus().catch(() => null);
|
|
2344
|
+
const on = !!(st && st.enabled);
|
|
2345
|
+
const s = (st && st.stats) || {};
|
|
2345
2346
|
const fmtN = (n) => new Intl.NumberFormat('tr-TR').format(Math.round(Number(n) || 0));
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
else if (st.installed) stateTxt = '⚪ kapalı — kurulu, aşağıdaki düğmeyle etkinleştir';
|
|
2355
|
-
else stateTxt = '⚪ kapalı — açınca uv ile otomatik kurulur (birkaç dakika)';
|
|
2356
|
-
stHtml = '<div class="sub" style="margin-top:10px;font-size:12px;text-align:center">' + escapeHtml(stateTxt) + '</div>';
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
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
|
+
|
|
2360
2355
|
let statsHtml = '';
|
|
2361
|
-
if (
|
|
2356
|
+
if (s.calls) {
|
|
2362
2357
|
statsHtml =
|
|
2363
2358
|
'<div class="divider"></div>' +
|
|
2364
|
-
'<h2 style="text-align:center">Tasarruf
|
|
2359
|
+
'<h2 style="text-align:center">Tasarruf</h2>' +
|
|
2365
2360
|
'<div style="display:flex;gap:24px;flex-wrap:wrap;margin-top:8px;justify-content:center">' +
|
|
2366
|
-
'<div><div style="font-size:20px;font-weight:800;color:var(--ok)">%' +
|
|
2367
|
-
'<div><div style="font-size:20px;font-weight:800">' +
|
|
2368
|
-
'<div><div style="font-size:20px;font-weight:800">' +
|
|
2369
|
-
'</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>';
|
|
2370
2369
|
}
|
|
2371
2370
|
|
|
2372
|
-
/* AÇ/KAPAT düğmesi: ortalanmış + SVG ikonlu (play/pause — uygulama ikon stili) */
|
|
2373
|
-
const svgPlay =
|
|
2374
|
-
'<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>';
|
|
2375
|
-
const svgPause =
|
|
2376
|
-
'<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>';
|
|
2377
2371
|
pane.innerHTML =
|
|
2378
|
-
'<h2 style="text-align:center">' + _t('
|
|
2379
|
-
'<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>' +
|
|
2380
2373
|
'<div style="display:flex;flex-direction:column;align-items:center;gap:10px;margin-top:18px">' +
|
|
2381
|
-
'<button id="
|
|
2382
|
-
'<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>' +
|
|
2383
2376
|
'</div>' +
|
|
2384
|
-
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>' : '');
|
|
2385
2379
|
|
|
2386
|
-
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');
|
|
2387
2385
|
if (btn) {
|
|
2388
|
-
const on = !!(st && st.enabled);
|
|
2389
2386
|
/* yalnız SVG ikon — anlamı durum satırı + tooltip taşır */
|
|
2390
2387
|
btn.innerHTML = on ? svgPause : svgPlay;
|
|
2391
|
-
btn.title = on ? '
|
|
2388
|
+
btn.title = on ? 'Squeeze\'i kapat' : 'Squeeze\'i aç';
|
|
2392
2389
|
btn.setAttribute('aria-label', btn.title);
|
|
2393
2390
|
btn.addEventListener('click', async () => {
|
|
2394
|
-
const msg = $('#
|
|
2391
|
+
const msg = $('#sqToggleMsg');
|
|
2395
2392
|
btn.disabled = true;
|
|
2396
2393
|
btn.innerHTML = '<span>…</span>';
|
|
2397
|
-
const r = await beast.
|
|
2394
|
+
const r = await beast.squeezeToggle(!on).catch(() => null);
|
|
2398
2395
|
if (msg) {
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
msg.textContent = r.error;
|
|
2403
|
-
} else {
|
|
2404
|
-
msg.textContent = !on ? 'Açıldı — proxy hazır olunca trafiği yönlendirir' : 'Kapatıldı — trafik sağlayıcıya düz gidiyor';
|
|
2405
|
-
}
|
|
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';
|
|
2406
2399
|
}
|
|
2407
|
-
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();
|
|
2408
2409
|
});
|
|
2409
2410
|
}
|
|
2410
2411
|
|
|
2411
2412
|
/* açıkken istatistikler canlı aksın */
|
|
2412
|
-
if (
|
|
2413
|
-
|
|
2414
|
-
if (pane.hidden) { clearInterval(
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
if (s2 && cur && cur.disabled) return; /* toggle işlemi sürüyor */
|
|
2418
|
-
await renderHeadroomPane();
|
|
2419
|
-
}, 6000);
|
|
2413
|
+
if (sqStatsTimer) clearInterval(sqStatsTimer);
|
|
2414
|
+
sqStatsTimer = setInterval(() => {
|
|
2415
|
+
if (pane.hidden) { clearInterval(sqStatsTimer); return; }
|
|
2416
|
+
renderSqueezePane();
|
|
2417
|
+
}, 5000);
|
|
2420
2418
|
}
|
|
2421
2419
|
|
|
2422
2420
|
async function renderInstallPane() {
|
|
@@ -2486,24 +2484,10 @@ async function renderInstallPane() {
|
|
|
2486
2484
|
'<div class="sub" style="font-size:11px">' + escapeHtml(r.detail || '') + (r.mb ? ' · ' + r.mb + ' MB' : '') + '</div>' +
|
|
2487
2485
|
bar(r) +
|
|
2488
2486
|
'</div>' +
|
|
2489
|
-
(r.canToggle
|
|
2490
|
-
? '<button class="btn hr-tgl" data-on="' + (r.toggleOn ? '1' : '0') + '" style="width:auto;padding:2px 12px;flex:none">' + (r.toggleOn ? 'KAPAT' : 'AÇ') + '</button>'
|
|
2491
|
-
: '') +
|
|
2492
2487
|
badge(r.state) +
|
|
2493
2488
|
pctLabel(r) +
|
|
2494
2489
|
'</div>'
|
|
2495
2490
|
)).join('');
|
|
2496
|
-
/* Headroom aç/kapa: toggle kurulum + proxy başlatmayı arka planda tetikler */
|
|
2497
|
-
el.querySelectorAll('.hr-tgl').forEach((b) => {
|
|
2498
|
-
b.addEventListener('click', async () => {
|
|
2499
|
-
const on = b.dataset.on !== '1';
|
|
2500
|
-
b.disabled = true;
|
|
2501
|
-
b.textContent = '…';
|
|
2502
|
-
const r = await beast.headroomToggle(on).catch(() => null);
|
|
2503
|
-
toast(r && r.error ? r.error : (r && r.installing ? 'Headroom kuruluyor — birkaç dakika sürer' : (on ? 'Headroom açıldı' : 'Headroom kapatıldı')));
|
|
2504
|
-
refresh();
|
|
2505
|
-
});
|
|
2506
|
-
});
|
|
2507
2491
|
};
|
|
2508
2492
|
|
|
2509
2493
|
const refresh = async () => {
|
|
@@ -5745,19 +5729,6 @@ function onEvent(ev) {
|
|
|
5745
5729
|
showPermissionCard(req, isBc);
|
|
5746
5730
|
return;
|
|
5747
5731
|
}
|
|
5748
|
-
/* Headroom durum değişimi → açık olan sekmeyi tazele (Token Sıkıştırma / Kurulum) */
|
|
5749
|
-
if (ev.type === 'headroom-status') {
|
|
5750
|
-
try {
|
|
5751
|
-
const iPane = document.getElementById('tab-install');
|
|
5752
|
-
const hPane = document.getElementById('tab-headroom');
|
|
5753
|
-
const open = (!iPane || iPane.hidden) && (!hPane || hPane.hidden) ? '' : (!hPane || hPane.hidden ? 'install' : 'headroom');
|
|
5754
|
-
if (open) {
|
|
5755
|
-
clearTimeout(hrPaneTimer);
|
|
5756
|
-
hrPaneTimer = setTimeout(() => { (open === 'headroom' ? renderHeadroomPane() : renderInstallPane()).catch(() => {}); }, 1500);
|
|
5757
|
-
}
|
|
5758
|
-
} catch {}
|
|
5759
|
-
return;
|
|
5760
|
-
}
|
|
5761
5732
|
/* EMPATİ LOOP: proaktif bildirim (masaüstü) + sekme canlı yenileme.
|
|
5762
5733
|
Toast'ta yalnız haber başlığı — link/markdown kalabalığı chat mesajında. */
|
|
5763
5734
|
if (ev.type === 'proactive') {
|
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
|
-
};
|