beast-agent 2.13.0 → 2.14.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/android.js +265 -0
- package/src/agent/engine.js +69 -0
- package/src/agent/llm.js +3 -1
- package/src/main.js +84 -0
- package/src/preload.js +3 -0
- package/src/renderer/i18n.js +3 -1
- package/src/renderer/index.html +9 -1
- package/src/renderer/renderer.js +114 -11
- package/src/renderer/style.css +65 -0
package/package.json
CHANGED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* ---------- ANDROID EMÜLATÖR (İSTEĞE BAĞLI — Beast Code mobil önizleme) ----------
|
|
4
|
+
Ayarlardan (Kurulum) görünür; KUR denince arka planda HAFİF set kurar:
|
|
5
|
+
JDK 17 (winget, java yoksa) + Android cmdline-tools (~150MB) +
|
|
6
|
+
platform-tools/emulator + 1 sistem imajı + 1 AVD ("Beast").
|
|
7
|
+
SDK'yı standarda yerleştirir: %LOCALAPPDATA%\Android\Sdk (Android Studio ile
|
|
8
|
+
paylaşımlı konum — kullanıcıda zaten varsa KURULUM YAPMAZ, onu kullanır).
|
|
9
|
+
Beast Code preview akışı: AVD varsa ajan `npx expo start --android` ile
|
|
10
|
+
uygulamayı emülatörde açar (expo AVD'yi adb üzerinden kendisi bulur). */
|
|
11
|
+
|
|
12
|
+
const { spawn, execSync } = require('child_process');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const { emitInstallProgress } = require('./progressbus');
|
|
17
|
+
|
|
18
|
+
const SDK_DIR = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'Android', 'Sdk');
|
|
19
|
+
const CMDTOOLS_URL = 'https://dl.google.com/android/repository/commandlinetools-win-11076708_latest.zip';
|
|
20
|
+
const SYS_IMAGE = 'system-images;android-34;google_apis;x86_64';
|
|
21
|
+
const AVD_NAME = 'Beast';
|
|
22
|
+
|
|
23
|
+
const state = {
|
|
24
|
+
installing: false,
|
|
25
|
+
installFailed: false,
|
|
26
|
+
installError: '',
|
|
27
|
+
stage: '',
|
|
28
|
+
booting: false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function sdkCandidates() {
|
|
32
|
+
const list = [];
|
|
33
|
+
for (const v of [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT]) {
|
|
34
|
+
if (v && fs.existsSync(v)) list.push(path.resolve(String(v)));
|
|
35
|
+
}
|
|
36
|
+
list.push(SDK_DIR);
|
|
37
|
+
return list.filter((p) => fs.existsSync(p));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findSdk(rel, name) {
|
|
41
|
+
for (const sdk of sdkCandidates()) {
|
|
42
|
+
const p = path.join(sdk, ...rel.split('/'), name);
|
|
43
|
+
if (fs.existsSync(p)) return p;
|
|
44
|
+
}
|
|
45
|
+
/* PATH'te ara (kullanıcı elle kurduysa) */
|
|
46
|
+
try {
|
|
47
|
+
const out = String(execSync(`where ${name}`, { timeout: 5000, stdio: 'pipe', encoding: 'utf8', windowsHide: true }));
|
|
48
|
+
const first = out.split(/\r?\n/).find(Boolean);
|
|
49
|
+
if (first) return String(first).trim();
|
|
50
|
+
} catch {}
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function findAdb() { return findSdk('platform-tools', 'adb.exe'); }
|
|
55
|
+
|
|
56
|
+
function findEmulator() { return findSdk('emulator', 'emulator.exe'); }
|
|
57
|
+
|
|
58
|
+
function javaOk() {
|
|
59
|
+
try {
|
|
60
|
+
const out = String(execSync('java -version 2>&1', { timeout: 6000, stdio: 'pipe', encoding: 'utf8', windowsHide: true }));
|
|
61
|
+
const m = /version "?(\d+)/.exec(out);
|
|
62
|
+
return m ? Number(m[1]) >= 17 : false;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function listAvds() {
|
|
69
|
+
const emu = findEmulator();
|
|
70
|
+
if (!emu) return [];
|
|
71
|
+
try {
|
|
72
|
+
const out = String(execSync(`"${emu}" -list-avds`, { timeout: 10000, stdio: 'pipe', encoding: 'utf8', windowsHide: true }));
|
|
73
|
+
return out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
74
|
+
} catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function probe() {
|
|
80
|
+
const adb = findAdb();
|
|
81
|
+
const emu = findEmulator();
|
|
82
|
+
return {
|
|
83
|
+
sdk: !!(adb && emu),
|
|
84
|
+
sdkPath: sdkCandidates()[0] || '',
|
|
85
|
+
adb,
|
|
86
|
+
emulator: emu,
|
|
87
|
+
installing: state.installing,
|
|
88
|
+
failed: state.installFailed,
|
|
89
|
+
error: state.installError,
|
|
90
|
+
booting: state.booting,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ---------- ARKA PLAN KURULUM ----------
|
|
95
|
+
Aşamalar (progress bus 'android'): 1 java → 2 cmdline-tools → 3 sdk paketleri
|
|
96
|
+
(en ağır: ~1.4GB) → 4 AVD. Yarıda kalırsa idempotent: yeniden KUR kaldığından değil
|
|
97
|
+
baştan ama varolan parçaları atlar (licenses/paket kontrolü). */
|
|
98
|
+
function autoInstall() {
|
|
99
|
+
if (state.installing) return { ok: true, installing: true };
|
|
100
|
+
if (probe().adb && probe().emu) return { ok: true, already: true };
|
|
101
|
+
state.installing = true;
|
|
102
|
+
state.installFailed = false;
|
|
103
|
+
state.installError = '';
|
|
104
|
+
runInstall().catch((e) => {
|
|
105
|
+
state.installing = false;
|
|
106
|
+
state.installFailed = true;
|
|
107
|
+
state.installError = String((e && e.message) || e).slice(0, 160);
|
|
108
|
+
});
|
|
109
|
+
return { ok: true, installing: true };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* .bat çağırıcı: Node güvenlik yaması (CVE-2024-27980) .bat/.cmd'yi shell'siz
|
|
113
|
+
spawn etmeyi EINVAL'le reddeder — cmd.exe /c üzerinden güvenli çağrı */
|
|
114
|
+
function spawnBat(bat, args, opts = {}) {
|
|
115
|
+
const cmdline = ['"' + bat + '"']
|
|
116
|
+
.concat(args.map((a) => (/[\s;&|^<>(),!%]/.test(a) ? '"' + a + '"' : a)))
|
|
117
|
+
.join(' ');
|
|
118
|
+
return spawn('cmd.exe', ['/d', '/s', '/c', cmdline], { windowsHide: true, ...opts.spawnOpts });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* .bat çağırıcı (GÜVENLİ PATİKA): Node .bat'i shell'siz spawn etmeyi EINVAL'le
|
|
122
|
+
reddeder; cmd.exe'ye args dizisiyle geçmek de tırnak/redirect kaçışında kırılır.
|
|
123
|
+
ÇÖZÜM: geçici wrapper .bat YAZ (redirect + tırnaklar cmd'nin içine kalır),
|
|
124
|
+
wrapper'ı tek tırnaklı /c ile çalıştır. */
|
|
125
|
+
let _batSeq = 0;
|
|
126
|
+
function runBat(bat, args, opts = {}) {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const inner = ['"' + bat + '"']
|
|
129
|
+
.concat(args.map((a) => (/[\s;&|^<>(),!%]/.test(a) ? '"' + a + '"' : a)))
|
|
130
|
+
.join(' ') + (opts.redirect ? ' < "' + opts.redirect + '"' : '');
|
|
131
|
+
const wrapper = path.join(os.tmpdir(), 'beast-sdk-' + Date.now() + '-' + (++_batSeq) + '.bat');
|
|
132
|
+
fs.writeFileSync(wrapper, '@echo off\r\n' + inner + '\r\nexit /b %ERRORLEVEL%\r\n', 'utf8');
|
|
133
|
+
/* wrapper'a tüm tırnak/redirect işi bırakılır — .bat spawn için Node shell:true ister */
|
|
134
|
+
const proc = spawn(wrapper, [], { windowsHide: true, shell: true, ...opts.spawnOpts });
|
|
135
|
+
let out = '';
|
|
136
|
+
proc.stdout.on('data', (c) => { out += String(c); opts.onData && opts.onData(String(c)); });
|
|
137
|
+
proc.stderr.on('data', (c) => { out += String(c); });
|
|
138
|
+
proc.on('close', (code) => {
|
|
139
|
+
try { fs.unlinkSync(wrapper); } catch {}
|
|
140
|
+
(code === 0 ? resolve : reject)(code === 0 ? out : new Error(opts.tag + ' exit ' + code + (out ? ' :: ' + out.slice(-300) : '')));
|
|
141
|
+
});
|
|
142
|
+
proc.on('error', (e) => {
|
|
143
|
+
try { fs.unlinkSync(wrapper); } catch {}
|
|
144
|
+
reject(e);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function runInstall() {
|
|
150
|
+
const stage = (pct) => { try { emitInstallProgress('android', { pct }); } catch {} };
|
|
151
|
+
|
|
152
|
+
/* 1) JDK 17 (sdkmanager şartı) — java yoksa winget ile */
|
|
153
|
+
stage(3);
|
|
154
|
+
if (!javaOk()) {
|
|
155
|
+
state.stage = 'JDK 17 kuruluyor (winget)';
|
|
156
|
+
await new Promise((resolve, reject) => {
|
|
157
|
+
const proc = spawn('winget', ['install', '--id', 'Microsoft.OpenJDK.17', '--accept-source-agreements', '--accept-package-agreements', '--silent'], { windowsHide: true, shell: true });
|
|
158
|
+
proc.on('close', (code) => (code === 0 || javaOk() ? resolve() : reject(new Error('JDK kurulumu başarısız (winget)'))));
|
|
159
|
+
proc.on('error', () => reject(new Error('winget bulunamadı — JDK 17 elle kur')));
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
stage(15);
|
|
163
|
+
|
|
164
|
+
/* 2) cmdline-tools indir + aç */
|
|
165
|
+
const toolsBin = path.join(SDK_DIR, 'cmdline-tools', 'latest', 'bin', 'sdkmanager.bat');
|
|
166
|
+
if (!fs.existsSync(toolsBin)) {
|
|
167
|
+
state.stage = 'cmdline-tools indiriliyor (~150MB)';
|
|
168
|
+
const zipPath = path.join(os.tmpdir(), 'commandlinetools.zip');
|
|
169
|
+
const res = await fetch(CMDTOOLS_URL);
|
|
170
|
+
if (!res.ok) throw new Error('cmdline-tools indirilemedi: HTTP ' + res.status);
|
|
171
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
172
|
+
fs.mkdirSync(SDK_DIR, { recursive: true });
|
|
173
|
+
fs.writeFileSync(zipPath, buf);
|
|
174
|
+
stage(35);
|
|
175
|
+
execSync(
|
|
176
|
+
`powershell -NoProfile -Command "Expand-Archive -LiteralPath '${zipPath}' -DestinationPath '${SDK_DIR}' -Force"`,
|
|
177
|
+
{ timeout: 300000, windowsHide: true, stdio: 'pipe' }
|
|
178
|
+
);
|
|
179
|
+
/* zip içi: cmdline-tools/ → cmdline-tools/latest/ olmalı */
|
|
180
|
+
const inner = path.join(SDK_DIR, 'cmdline-tools');
|
|
181
|
+
if (fs.existsSync(inner) && !fs.existsSync(toolsBin)) {
|
|
182
|
+
fs.renameSync(inner, inner + '_tmp');
|
|
183
|
+
fs.mkdirSync(path.join(SDK_DIR, 'cmdline-tools'), { recursive: true });
|
|
184
|
+
fs.renameSync(inner + '_tmp', path.join(SDK_DIR, 'cmdline-tools', 'latest'));
|
|
185
|
+
}
|
|
186
|
+
try { fs.unlinkSync(zipPath); } catch {}
|
|
187
|
+
}
|
|
188
|
+
stage(45);
|
|
189
|
+
|
|
190
|
+
/* 3) lisanslar + paketler (platform-tools + emulator + sistem imajı ~1.3GB)
|
|
191
|
+
Cevaplar stdin spam'iyle DEĞİL dosya redirect'iyle verilir — sdkmanager
|
|
192
|
+
interaktif stdin'i cmd borularında düzgün okumuyor */
|
|
193
|
+
state.stage = 'SDK paketleri iniyor (platform-tools · emulator · sistem imajı)';
|
|
194
|
+
const sysEnv = { ...process.env };
|
|
195
|
+
const yesFile = path.join(os.tmpdir(), 'beast-sdk-yes.txt');
|
|
196
|
+
fs.writeFileSync(yesFile, 'y\n'.repeat(80), 'utf8');
|
|
197
|
+
await runBat(toolsBin, ['--licenses'], {
|
|
198
|
+
tag: 'lisans adımı',
|
|
199
|
+
spawnOpts: { env: sysEnv },
|
|
200
|
+
redirect: yesFile,
|
|
201
|
+
});
|
|
202
|
+
stage(50);
|
|
203
|
+
{
|
|
204
|
+
const pkgs = ['platform-tools', 'emulator', SYS_IMAGE];
|
|
205
|
+
/* GERÇEK PROGRESS: sdkmanager çıktısı "[==== ] 17% Downloading x" biçiminde
|
|
206
|
+
GLOBAL yüzde basar (monotonik artar) — %50-95 aralığına doğrudan eşle */
|
|
207
|
+
let last = 50;
|
|
208
|
+
const lastPercent = /(\d{1,3})%[^%]*$/;
|
|
209
|
+
/* sdkmanager ağ ortasında patlarsa: yarım .temp'i temizle + 3 kere yeniden
|
|
210
|
+
dene (indirilenler korunur — kaldığı yeri hızla atlar) */
|
|
211
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
212
|
+
try {
|
|
213
|
+
try { fs.rmSync(path.join(SDK_DIR, '.temp'), { recursive: true, force: true }); } catch {}
|
|
214
|
+
await runBat(toolsBin, pkgs, {
|
|
215
|
+
tag: 'sdkmanager',
|
|
216
|
+
spawnOpts: { env: sysEnv },
|
|
217
|
+
redirect: yesFile,
|
|
218
|
+
onData: (s) => {
|
|
219
|
+
const m = lastPercent.exec(s);
|
|
220
|
+
if (m) {
|
|
221
|
+
const sdkPct = Math.max(0, Math.min(100, Number(m[1])));
|
|
222
|
+
const pct = 50 + sdkPct * 0.45; /* %0→50, %100→95 */
|
|
223
|
+
if (pct > last + 0.4) { last = pct; stage(pct); }
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
break;
|
|
228
|
+
} catch (e) {
|
|
229
|
+
if (attempt < 3 && !findAdb()) continue;
|
|
230
|
+
throw e;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* 4) AVD yarat */
|
|
236
|
+
stage(96);
|
|
237
|
+
state.stage = 'AVD (emülatör cihazı) oluşturuluyor';
|
|
238
|
+
if (!(await listAvds()).includes(AVD_NAME)) {
|
|
239
|
+
const avdm = path.join(SDK_DIR, 'cmdline-tools', 'latest', 'bin', 'avdmanager.bat');
|
|
240
|
+
const noFile = path.join(os.tmpdir(), 'beast-sdk-no.txt');
|
|
241
|
+
fs.writeFileSync(noFile, 'no\n', 'utf8');
|
|
242
|
+
await runBat(avdm, ['create', 'avd', '-n', AVD_NAME, '-k', SYS_IMAGE, '-d', 'pixel'], {
|
|
243
|
+
tag: 'avdmanager',
|
|
244
|
+
spawnOpts: { env: sysEnv },
|
|
245
|
+
redirect: noFile,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
emitInstallProgress('android', { pct: 100 });
|
|
249
|
+
state.installing = false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* ---------- EMÜLATÖR BAŞLATMA (uzun ömürlü — kendi penceresi açılır) ---------- */
|
|
253
|
+
async function startAvd(name) {
|
|
254
|
+
const emu = findEmulator();
|
|
255
|
+
if (!emu) return { ok: false, error: 'emulator.exe yok — önce Kurulum sekmesinden kur' };
|
|
256
|
+
const target = name || (await listAvds())[0];
|
|
257
|
+
if (!target) return { ok: false, error: 'AVD yok — önce kur' };
|
|
258
|
+
state.booting = true;
|
|
259
|
+
const proc = spawn(emu, ['-avd', target, '-no-snapshot'], { windowsHide: false, detached: false });
|
|
260
|
+
proc.on('exit', () => { state.booting = false; });
|
|
261
|
+
/* boot tamamlanmasını bekleme — arka planda; expo/adb kendisi bekler */
|
|
262
|
+
return { ok: true, avd: target, note: 'emülatör başlatıldı — ilk açılış 30-90 sn sürer' };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = { probe, autoInstall, listAvds, startAvd };
|
package/src/agent/engine.js
CHANGED
|
@@ -1064,6 +1064,39 @@ class Engine {
|
|
|
1064
1064
|
return removed;
|
|
1065
1065
|
}
|
|
1066
1066
|
|
|
1067
|
+
/* Düşünme geri-besleme kurtarması: asistan mesajlarındaki reasoning_content'i
|
|
1068
|
+
bellekten + jsonl'den temizler. "must be passed back" hatasını veren sağlayıcı
|
|
1069
|
+
için tek kurtarma yolu — döndürür: temizlenen mesaj sayısı */
|
|
1070
|
+
_stripAssistantReasoning(session) {
|
|
1071
|
+
let dropped = 0;
|
|
1072
|
+
for (const m of session.messages) {
|
|
1073
|
+
if (m && m.role === 'assistant' && (m.reasoning_content || m.reasoning)) {
|
|
1074
|
+
delete m.reasoning_content;
|
|
1075
|
+
delete m.reasoning;
|
|
1076
|
+
dropped++;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
if (!dropped) return 0;
|
|
1080
|
+
try {
|
|
1081
|
+
const file = this._file(session.id);
|
|
1082
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
1083
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1084
|
+
if (!lines[i].trim()) continue;
|
|
1085
|
+
let r;
|
|
1086
|
+
try { r = JSON.parse(lines[i]); } catch { continue; }
|
|
1087
|
+
if (r.t === 'msg' && r.role === 'assistant' && (r.reasoning_content || r.reasoning)) {
|
|
1088
|
+
delete r.reasoning_content;
|
|
1089
|
+
delete r.reasoning;
|
|
1090
|
+
lines[i] = JSON.stringify(r);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const tmp = file + '.tmp';
|
|
1094
|
+
fs.writeFileSync(tmp, lines.join('\n'));
|
|
1095
|
+
fs.renameSync(tmp, file);
|
|
1096
|
+
} catch {}
|
|
1097
|
+
return dropped;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1067
1100
|
_load(id) {
|
|
1068
1101
|
if (this.cache.has(id)) return this.cache.get(id);
|
|
1069
1102
|
const file = this._file(id);
|
|
@@ -3240,6 +3273,37 @@ class Engine {
|
|
|
3240
3273
|
arındırıp turu BİR KEZ görüntüsüz dener. */
|
|
3241
3274
|
const msgStr = String((e && e.message) || '');
|
|
3242
3275
|
const aborted = signal && signal.aborted;
|
|
3276
|
+
/* DÜŞÜNME GERİ-BESLEME KURTARMASI: "reasoning_content ... must be passed
|
|
3277
|
+
back" hatası eski oturumlarda (reasoning saklanmadan önce) olur —
|
|
3278
|
+
geçmişi reasoning'siz arındırıp turu BİR KEZ yeniden dene */
|
|
3279
|
+
if (!aborted && /reasoning_content/i.test(msgStr)) {
|
|
3280
|
+
const dropped = this._stripAssistantReasoning(session);
|
|
3281
|
+
if (dropped) {
|
|
3282
|
+
emitSafe(this, session.id, {
|
|
3283
|
+
type: 'status',
|
|
3284
|
+
status: `🧠 düşük uyumlu sağlayıcı: geçmişteki düşünme içerikleri temizlendi (${dropped} mesaj) — tur yeniden deneniyor`,
|
|
3285
|
+
});
|
|
3286
|
+
payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
|
|
3287
|
+
res = await this._streamWithFallbacks(
|
|
3288
|
+
session,
|
|
3289
|
+
payload,
|
|
3290
|
+
activeTools,
|
|
3291
|
+
signal,
|
|
3292
|
+
onDelta,
|
|
3293
|
+
sel,
|
|
3294
|
+
role === 'vision'
|
|
3295
|
+
);
|
|
3296
|
+
const actualR = res.usage && res.usage.prompt_tokens;
|
|
3297
|
+
if (actualR > 0) {
|
|
3298
|
+
const predicted = this._payloadTokens(payload);
|
|
3299
|
+
if (predicted > 50) {
|
|
3300
|
+
const r = clamp(actualR / predicted, 0.75, 3);
|
|
3301
|
+
this.tokRatio = clamp(this.tokRatio * 0.7 + r * 0.3, 0.75, 3);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
return res;
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3243
3307
|
const imgish =
|
|
3244
3308
|
!aborted &&
|
|
3245
3309
|
/image|vision|multimodal|no endpoints/i.test(msgStr);
|
|
@@ -3395,6 +3459,10 @@ class Engine {
|
|
|
3395
3459
|
|
|
3396
3460
|
const assistant = { role: 'assistant', content: res.content || '' };
|
|
3397
3461
|
if (res.toolCalls && res.toolCalls.length) assistant.tool_calls = res.toolCalls;
|
|
3462
|
+
/* DÜŞÜNME MODU GERİ-BESLEMESİ: Console Go gibi sağlayıcılar thinking
|
|
3463
|
+
modunda reasoning_content'in GEÇMİŞTE geri gönderilmesini şart koşar
|
|
3464
|
+
("must be passed back to the API") — saklamazsak sonraki tur 400'le ölür */
|
|
3465
|
+
if (res.reasoning) assistant.reasoning_content = res.reasoning;
|
|
3398
3466
|
session.messages.push(assistant);
|
|
3399
3467
|
try {
|
|
3400
3468
|
this._append(session, assistant);
|
|
@@ -4078,6 +4146,7 @@ const skills = require('./skills');
|
|
|
4078
4146
|
);
|
|
4079
4147
|
const assistant = { role: 'assistant', content: res.content || '' };
|
|
4080
4148
|
if (res.toolCalls && res.toolCalls.length) assistant.tool_calls = res.toolCalls;
|
|
4149
|
+
if (res.reasoning) assistant.reasoning_content = res.reasoning; /* thinking geri-besleme */
|
|
4081
4150
|
msgs.push(assistant);
|
|
4082
4151
|
if (!res.toolCalls || !res.toolCalls.length) return res.content || '(alt-agent boş döndü)';
|
|
4083
4152
|
for (const tc of res.toolCalls) {
|
package/src/agent/llm.js
CHANGED
|
@@ -573,7 +573,9 @@ async function chatStreamAuto(sel, body, opts = {}) {
|
|
|
573
573
|
let last = first;
|
|
574
574
|
for (let i = 0; i < 2; i++) {
|
|
575
575
|
const msgs2 = body.messages.slice();
|
|
576
|
-
|
|
576
|
+
const asst = { role: 'assistant', content: full };
|
|
577
|
+
if (first.reasoning) asst.reasoning_content = first.reasoning; /* thinking geri-besleme */
|
|
578
|
+
msgs2.push(asst);
|
|
577
579
|
msgs2.push({ role: 'user', content: CONTINUE_PROMPT });
|
|
578
580
|
let cont;
|
|
579
581
|
try {
|
package/src/main.js
CHANGED
|
@@ -26,6 +26,7 @@ const watchers = require('./agent/watchers');
|
|
|
26
26
|
const usageMod = require('./agent/usage');
|
|
27
27
|
const bus = require('./agent/bus');
|
|
28
28
|
const computeruse = require('./agent/computeruse');
|
|
29
|
+
const android = require('./agent/android');
|
|
29
30
|
const log = require('./agent/logger');
|
|
30
31
|
const headroom = require('./agent/headroom');
|
|
31
32
|
const QRCode = require('qrcode'); /* Expo Go QR (bc-expurl) — whatsapp ile aynı paket */
|
|
@@ -3109,7 +3110,14 @@ function reloadBackend() {
|
|
|
3109
3110
|
/* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
|
|
3110
3111
|
preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
|
|
3111
3112
|
if (ev.type === 'bc-preview' && /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?/i.test(String(ev.url || ''))) {
|
|
3113
|
+
/* klasör değiştiyse eski sunucu bayat: KAPAT, en yeniye geç */
|
|
3114
|
+
try {
|
|
3115
|
+
if (bcLastServerUrl && bcLastServerRoot && path.relative(bcLastServerRoot, ideRoot()) !== '') {
|
|
3116
|
+
bcKillServer(bcLastServerUrl);
|
|
3117
|
+
}
|
|
3118
|
+
} catch {}
|
|
3112
3119
|
bcLastServerUrl = String(ev.url);
|
|
3120
|
+
try { bcLastServerRoot = ideRoot(); } catch { bcLastServerRoot = ''; }
|
|
3113
3121
|
}
|
|
3114
3122
|
/* Expo Go QR: ajanın dev sunucusu exp:// adresi yazdıysa QR üretip panele bas */
|
|
3115
3123
|
if (ev.type === 'bc-expurl') {
|
|
@@ -6302,6 +6310,29 @@ ipcMain.handle('install:status', async () => {
|
|
|
6302
6310
|
});
|
|
6303
6311
|
}
|
|
6304
6312
|
|
|
6313
|
+
/* 10) Android Emülatörü — opsiyonel; varsa Beast Code mobil uygulamaları emülatörde açar */
|
|
6314
|
+
{
|
|
6315
|
+
const st = android.probe();
|
|
6316
|
+
let state = 'optional';
|
|
6317
|
+
if (st.sdk) state = 'ok';
|
|
6318
|
+
else if (st.installing) state = 'loading';
|
|
6319
|
+
if (st.failed) state = 'failed';
|
|
6320
|
+
rows.push({
|
|
6321
|
+
id: 'android',
|
|
6322
|
+
name: 'Android Emülatörü — mobil uygulamalar emülatörde çalışır',
|
|
6323
|
+
state,
|
|
6324
|
+
canInstall: true,
|
|
6325
|
+
detail: st.installing
|
|
6326
|
+
? (st.stage || 'arka planda kuruluyor — JDK + SDK + sistem imajı (~1.5GB, internete göre 5-20 dk)')
|
|
6327
|
+
: st.failed
|
|
6328
|
+
? (st.error || 'kurulum başarısız')
|
|
6329
|
+
: st.sdk
|
|
6330
|
+
? 'hazır — SDK: ' + st.sdkPath
|
|
6331
|
+
: 'kurulu değil — KUR ile arka planda kurulur (istekge bağlı)',
|
|
6332
|
+
...(st.installing ? pctFields('android') : {}),
|
|
6333
|
+
});
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6305
6336
|
return rows;
|
|
6306
6337
|
});
|
|
6307
6338
|
|
|
@@ -6318,6 +6349,11 @@ ipcMain.handle('headroom:toggle', async (_e, on) => {
|
|
|
6318
6349
|
ipcMain.handle('headroom:status', () => headroom.probe());
|
|
6319
6350
|
ipcMain.handle('headroom:stats', () => headroom.stats());
|
|
6320
6351
|
|
|
6352
|
+
/* ANDROID EMÜLATÖR (opsiyonel): durum + arka plan kurulum + AVD başlatma */
|
|
6353
|
+
ipcMain.handle('android:status', async () => ({ ok: true, ...(await android.probe()), avds: await android.listAvds() }));
|
|
6354
|
+
ipcMain.handle('android:install', () => android.autoInstall());
|
|
6355
|
+
ipcMain.handle('android:startAvd', async (_e, name) => android.startAvd(name));
|
|
6356
|
+
|
|
6321
6357
|
/* embedding modelini şimdi indir (mem0 arama yolu ısıtılır) */
|
|
6322
6358
|
ipcMain.handle('embed:prefetch', () => {
|
|
6323
6359
|
try {
|
|
@@ -8127,6 +8163,14 @@ ipcMain.handle('ide:setroot', async () => {
|
|
|
8127
8163
|
properties: ['openDirectory'],
|
|
8128
8164
|
});
|
|
8129
8165
|
if (r.canceled || !r.filePaths || !r.filePaths[0]) return { ok: false, canceled: true };
|
|
8166
|
+
/* klasör değişti: eski klasörün dev server'ı bayat — kill + unut */
|
|
8167
|
+
try {
|
|
8168
|
+
if (bcLastServerUrl) {
|
|
8169
|
+
bcKillServer(bcLastServerUrl);
|
|
8170
|
+
bcLastServerUrl = '';
|
|
8171
|
+
bcLastServerRoot = '';
|
|
8172
|
+
}
|
|
8173
|
+
} catch {}
|
|
8130
8174
|
settings.ideRoot = r.filePaths[0];
|
|
8131
8175
|
saveSettings();
|
|
8132
8176
|
ideWatchStart(); // yeni kökte izleme yeniden kurulur
|
|
@@ -8750,6 +8794,39 @@ ipcMain.handle('sandbox:openurl', async (_e, url) => {
|
|
|
8750
8794
|
ve "clean URL" yolları file://'da çalışmaz. Her preview http://127.0.0.1 üzerinden
|
|
8751
8795
|
servis edilir; ajan kendi dev sunucusunu başlattıysa o adres önceliklidir. */
|
|
8752
8796
|
let bcLastServerUrl = ''; /* ajanın başlattığı dev server adresi (bc-preview'dan yakalanır) */
|
|
8797
|
+
let bcLastServerRoot = ''; /* adresin yakalandığı workspace kökü — klasör değişince bayat sayılır */
|
|
8798
|
+
|
|
8799
|
+
/* dev server'ı port'tan bulup süreç AĞACINI kapat (best-effort).
|
|
8800
|
+
Ajan arka planda başlattığı sunucular izlenmez — port üzerinden bulunur. */
|
|
8801
|
+
function bcKillServer(url) {
|
|
8802
|
+
try {
|
|
8803
|
+
const m = /:(\d{2,5})(?:\/|$)/.exec(String(url || ''));
|
|
8804
|
+
if (!m) return false;
|
|
8805
|
+
const port = m[1];
|
|
8806
|
+
const { execSync } = require('child_process');
|
|
8807
|
+
const out = String(execSync(
|
|
8808
|
+
'netstat -ano | findstr ":' + port + '" | findstr LISTENING',
|
|
8809
|
+
{ timeout: 6000, stdio: 'pipe', encoding: 'utf8', windowsHide: true }
|
|
8810
|
+
));
|
|
8811
|
+
const pids = new Set();
|
|
8812
|
+
for (const line of out.split(/\r?\n/)) {
|
|
8813
|
+
const p = /\s(\d+)\s*$/.exec(String(line).trim());
|
|
8814
|
+
if (p) pids.add(p[1]);
|
|
8815
|
+
}
|
|
8816
|
+
let killed = 0;
|
|
8817
|
+
for (const pid of pids) {
|
|
8818
|
+
if (Number(pid) === process.pid) continue; /* kendimize dokunma */
|
|
8819
|
+
try {
|
|
8820
|
+
spawn('taskkill', ['/pid', String(pid), '/T', '/F'], { windowsHide: true });
|
|
8821
|
+
killed++;
|
|
8822
|
+
} catch {}
|
|
8823
|
+
}
|
|
8824
|
+
try { log.info('bc', 'bayat dev server kapatıldı (port ' + port + ', ' + killed + ' süreç)'); } catch {}
|
|
8825
|
+
return killed > 0;
|
|
8826
|
+
} catch {
|
|
8827
|
+
return false; /* port boş / netstat hata — yok say */
|
|
8828
|
+
}
|
|
8829
|
+
}
|
|
8753
8830
|
const bcStatic = { server: null, root: '', base: '' };
|
|
8754
8831
|
/* MOBİL PROJE önizleme: npm run web (expo start --web) süreci + canlı adres */
|
|
8755
8832
|
const bcMobile = { proc: null, url: '', root: '' };
|
|
@@ -9026,6 +9103,13 @@ ipcMain.handle('ide:preview', async () => {
|
|
|
9026
9103
|
if (!entry) return { ok: false, error: 'workspace kökünde index.html yok — önce agent\'a siteyi yazdır' };
|
|
9027
9104
|
/* forceVisible: preview'a basınca tarayıcı ikonuna basmaya gerek kalmasın */
|
|
9028
9105
|
setBrowserOpen(true, true);
|
|
9106
|
+
/* KLASÖR UYUŞMAZLIĞI: kayıtlı dev server ESKİ klasörün artıklarıysa
|
|
9107
|
+
kill et ve unut — yeni klasör kendi sunucusuyla başlasın */
|
|
9108
|
+
if (bcLastServerUrl && bcLastServerRoot && path.relative(bcLastServerRoot, root) !== '') {
|
|
9109
|
+
bcKillServer(bcLastServerUrl);
|
|
9110
|
+
bcLastServerUrl = '';
|
|
9111
|
+
bcLastServerRoot = '';
|
|
9112
|
+
}
|
|
9029
9113
|
let url = '';
|
|
9030
9114
|
if (bcLastServerUrl) {
|
|
9031
9115
|
url = bcLastServerUrl; /* ajanın kendi dev sunucusu öncelikli */
|
package/src/preload.js
CHANGED
|
@@ -227,6 +227,9 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
227
227
|
headroomToggle: (on) => ipcRenderer.invoke('headroom:toggle', !!on),
|
|
228
228
|
headroomStatus: () => ipcRenderer.invoke('headroom:status'),
|
|
229
229
|
headroomStats: () => ipcRenderer.invoke('headroom:stats'),
|
|
230
|
+
androidStatus: () => ipcRenderer.invoke('android:status'),
|
|
231
|
+
androidInstall: () => ipcRenderer.invoke('android:install'),
|
|
232
|
+
androidStartAvd: (name) => ipcRenderer.invoke('android:startAvd', name),
|
|
230
233
|
agentsClearAll: () => ipcRenderer.invoke('agents:clearAll'),
|
|
231
234
|
ideDelete: (rel) => ipcRenderer.invoke('ide:delete', rel),
|
|
232
235
|
onWaEvent: (cb) => ipcRenderer.on('wa:event', (_e, ev) => cb(ev)),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -540,6 +540,8 @@
|
|
|
540
540
|
bot_plugin_sub: 'Aktif plugin\u2019leri seç (değişiklik loglanır).',
|
|
541
541
|
bot_watcher_sub: 'Bu botun oturumlarına bağlı izleyiciler.',
|
|
542
542
|
bot_no_watchers: 'Bu bota ait izleyici yok.',
|
|
543
|
+
w_pane: 'İZLEYİCİLER',
|
|
544
|
+
w_empty: 'Kayıtlı izleyici yok.',
|
|
543
545
|
bot_stats_sub: 'Bu botun kullanım özeti.',
|
|
544
546
|
bot_stat_numbers: 'Numara',
|
|
545
547
|
bot_stat_sessions: 'Sohbet',
|
|
@@ -1035,6 +1037,7 @@
|
|
|
1035
1037
|
cr_ran: 'Started',
|
|
1036
1038
|
cr_toggle_pause: 'Pause',
|
|
1037
1039
|
w_empty: 'No saved watchers — add from the form above.',
|
|
1040
|
+
w_pane: 'WATCHERS',
|
|
1038
1041
|
wa_vazgec: 'Cancel',
|
|
1039
1042
|
wa_kaydet: 'save',
|
|
1040
1043
|
wa_updated: 'Updated',
|
|
@@ -1065,7 +1068,6 @@
|
|
|
1065
1068
|
cr_modal_empty: 'Kayıtlı cron görev yok — Ayarlar → Cron sekmesinden ekle.',
|
|
1066
1069
|
cr_ran: 'Çalıştırıldı',
|
|
1067
1070
|
cr_toggle_pause: 'Duraklat',
|
|
1068
|
-
w_empty: 'Kayıtlı izleyici yok — yukarıdaki formla ekle.',
|
|
1069
1071
|
wa_vazgec: 'Vazgeç',
|
|
1070
1072
|
wa_kaydet: 'kaydet',
|
|
1071
1073
|
wa_updated: 'Güncellendi',
|
package/src/renderer/index.html
CHANGED
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
<div id="brand"><span class="brand-beast">BEAST</span><span class="brand-sub">Agent</span></div>
|
|
14
14
|
<button id="newChat" title="Yeni sohbet" data-i18n="newChat">+ Yeni Sohbet</button>
|
|
15
15
|
<div id="sessList"></div>
|
|
16
|
+
<div id="watchPane">
|
|
17
|
+
<div id="watchPaneHead">
|
|
18
|
+
<span id="watchPaneTitle" data-i18n="w_pane">İZLEYİCİLER</span>
|
|
19
|
+
<span class="file-spacer"></span>
|
|
20
|
+
<button id="watchPaneOpen" title="İzleyicileri yönet">+</button>
|
|
21
|
+
</div>
|
|
22
|
+
<div id="watchPaneList"></div>
|
|
23
|
+
</div>
|
|
16
24
|
<div id="botBar">
|
|
17
25
|
<div id="botBarHead">
|
|
18
26
|
<span id="botBarTitle" data-i18n="bot_head">BOTLAR</span>
|
|
@@ -177,7 +185,7 @@
|
|
|
177
185
|
</div>
|
|
178
186
|
<img id="bcQrImg" alt="Expo QR" draggable="false" />
|
|
179
187
|
<div id="bcQrUrl" title=""></div>
|
|
180
|
-
<div class="bq-hint">Telefonda <b>Expo Go</b>
|
|
188
|
+
<div class="bq-hint">Telefonda <b>Expo Go</b> ile okut — tünel modunda her ağdan çalışır</div>
|
|
181
189
|
</div>
|
|
182
190
|
<div id="bcInputRow">
|
|
183
191
|
<button id="bcAttach" title="Dosya / resim ekle" data-i18n-title="tipAttach">+</button>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -43,6 +43,8 @@ const els = {
|
|
|
43
43
|
railBtn: $('#railBtn'),
|
|
44
44
|
railClear: $('#railClear'),
|
|
45
45
|
watchBtn: $('#watchBtn'),
|
|
46
|
+
watchPaneList: $('#watchPaneList'),
|
|
47
|
+
watchPaneOpen: $('#watchPaneOpen'),
|
|
46
48
|
cronBtn: $('#cronBtn'),
|
|
47
49
|
watchOverlay: $('#watchOverlay'),
|
|
48
50
|
cronOverlay: $('#cronOverlay'),
|
|
@@ -955,8 +957,19 @@ let dragSid = null;
|
|
|
955
957
|
async function refreshSessions() {
|
|
956
958
|
await renderSessions(await beast.listSessions());
|
|
957
959
|
renderBotCards(); // bot kartlarındaki numara/sayı etiketleri de tazelensin
|
|
960
|
+
renderWatchersPane().catch(() => {}); // kenar izleyici listesi de tazelensin
|
|
958
961
|
}
|
|
959
962
|
|
|
963
|
+
/* İzleyici kenar pane canlı tazeleme: agent modunda 8 sn'de bir sessiz çekim;
|
|
964
|
+
başka moddaysa zamanlayıcı pas geçer (IPC maliyeti yok) */
|
|
965
|
+
setInterval(() => {
|
|
966
|
+
try {
|
|
967
|
+
if (document.body.classList.contains('ide-mode') || document.body.classList.contains('studio-mode') || document.body.classList.contains('sandbox-mode')) return;
|
|
968
|
+
if (document.hidden) return;
|
|
969
|
+
renderWatchersPane().catch(() => {});
|
|
970
|
+
} catch {}
|
|
971
|
+
}, 8000);
|
|
972
|
+
|
|
960
973
|
async function openSession(id) {
|
|
961
974
|
activeId = id;
|
|
962
975
|
streamEl = null;
|
|
@@ -2366,6 +2379,9 @@ async function renderInstallPane() {
|
|
|
2366
2379
|
(r.canToggle
|
|
2367
2380
|
? '<button class="btn hr-tgl" data-on="' + (r.toggleOn ? '1' : '0') + '" style="width:auto;padding:2px 12px;flex:none">' + (r.toggleOn ? 'KAPAT' : 'AÇ') + '</button>'
|
|
2368
2381
|
: '') +
|
|
2382
|
+
(r.canInstall
|
|
2383
|
+
? '<button class="btn inst-kur" style="width:auto;padding:2px 12px;flex:none">KUR</button>'
|
|
2384
|
+
: '') +
|
|
2369
2385
|
badge(r.state) +
|
|
2370
2386
|
pctLabel(r) +
|
|
2371
2387
|
'</div>'
|
|
@@ -2381,7 +2397,17 @@ async function renderInstallPane() {
|
|
|
2381
2397
|
refresh();
|
|
2382
2398
|
});
|
|
2383
2399
|
});
|
|
2384
|
-
|
|
2400
|
+
/* Android emülatörü: KUR → arka plan SDK kurulumu (JDK + cmdline-tools + imaj) */
|
|
2401
|
+
el.querySelectorAll('.inst-kur').forEach((b) => {
|
|
2402
|
+
b.addEventListener('click', async () => {
|
|
2403
|
+
b.disabled = true;
|
|
2404
|
+
b.textContent = '…';
|
|
2405
|
+
await beast.androidInstall().catch(() => {});
|
|
2406
|
+
toast('Android SDK arka planda kuruluyor — ~1.5GB, internete göre 5-20 dk sürer');
|
|
2407
|
+
refresh();
|
|
2408
|
+
});
|
|
2409
|
+
});
|
|
2410
|
+
};
|
|
2385
2411
|
|
|
2386
2412
|
const refresh = async () => {
|
|
2387
2413
|
if (pane.hidden) return;
|
|
@@ -5495,10 +5521,68 @@ function fmtWhen(iso) {
|
|
|
5495
5521
|
return d.toLocaleDateString([], { day: '2-digit', month: '2-digit' }) + ' ' + t;
|
|
5496
5522
|
}
|
|
5497
5523
|
|
|
5498
|
-
|
|
5499
|
-
|
|
5524
|
+
/* ---------- İZLEYİCİLER kenar pane (sohbet geçmişinin alt yarısı) ----------
|
|
5525
|
+
Kompakt liste: durum noktası + ad + bir sonraki tetik; satır → izleyici
|
|
5526
|
+
modalı; ▶/× mini kontroller. Yalnız agent modunda görünür (CSS). */
|
|
5527
|
+
let lastWatchPaneKey = '';
|
|
5528
|
+
async function renderWatchersPane(force) {
|
|
5529
|
+
const list = els.watchPaneList;
|
|
5530
|
+
if (!list) return;
|
|
5531
|
+
if (document.body.classList.contains('ide-mode') || document.body.classList.contains('studio-mode') || document.body.classList.contains('sandbox-mode')) return;
|
|
5500
5532
|
let rows = [];
|
|
5501
5533
|
try { rows = (await beast.watchersList()) || []; } catch {}
|
|
5534
|
+
const key = JSON.stringify(rows);
|
|
5535
|
+
if (!force && key === lastWatchPaneKey) return;
|
|
5536
|
+
lastWatchPaneKey = key;
|
|
5537
|
+
list.innerHTML = '';
|
|
5538
|
+
if (!rows.length) {
|
|
5539
|
+
list.innerHTML = '<div class="wp-empty">' + _t('w_empty') + '</div>';
|
|
5540
|
+
return;
|
|
5541
|
+
}
|
|
5542
|
+
for (const w of rows) {
|
|
5543
|
+
const row = document.createElement('div');
|
|
5544
|
+
row.className = 'wp-row' + (w.enabled ? '' : ' off');
|
|
5545
|
+
row.title = (w.kind === 'battery' ? 'pil yüzdesi' : w.kind === 'logs' ? 'log izleyici' : (w.path || w.re || w.url)) + ' · ' + opText(w) + ' · her ' + fmtEvery(w);
|
|
5546
|
+
row.innerHTML =
|
|
5547
|
+
`<span class="wp-dot"></span>` +
|
|
5548
|
+
`<div class="wp-main">` +
|
|
5549
|
+
`<div class="wp-name">${escapeHtml(w.name)}</div>` +
|
|
5550
|
+
`<div class="wp-meta">${opText(w)} · her ${escapeHtml(String(fmtEvery(w)))} · ${escapeHtml(fmtWhen(w.lastCheckAt))}` +
|
|
5551
|
+
(w.lastValue !== null && w.lastValue !== undefined ? ` · <b>${escapeHtml(String(w.lastValue).slice(0, 14))}</b>` : '') +
|
|
5552
|
+
`</div></div>`;
|
|
5553
|
+
const btns = document.createElement('div');
|
|
5554
|
+
btns.className = 'wp-btns';
|
|
5555
|
+
const t = document.createElement('button');
|
|
5556
|
+
t.className = 'wp-btn';
|
|
5557
|
+
t.textContent = w.enabled ? '❚❚' : '▶';
|
|
5558
|
+
t.title = w.enabled ? _t('cr_toggle_pause') : _t('cr_start');
|
|
5559
|
+
t.addEventListener('click', async (e) => {
|
|
5560
|
+
e.stopPropagation();
|
|
5561
|
+
await beast.watchersToggle(w.id);
|
|
5562
|
+
renderWatchersPane(true);
|
|
5563
|
+
renderWatchersModal(true);
|
|
5564
|
+
});
|
|
5565
|
+
const d = document.createElement('button');
|
|
5566
|
+
d.className = 'wp-btn del';
|
|
5567
|
+
d.textContent = '×';
|
|
5568
|
+
d.title = _t('cr_del');
|
|
5569
|
+
d.addEventListener('click', async (e) => {
|
|
5570
|
+
e.stopPropagation();
|
|
5571
|
+
await beast.watchersRemove(w.id);
|
|
5572
|
+
lastWatchPaneKey = '';
|
|
5573
|
+
renderWatchersPane(true);
|
|
5574
|
+
renderWatchersModal(true);
|
|
5575
|
+
});
|
|
5576
|
+
btns.append(t, d);
|
|
5577
|
+
row.appendChild(btns);
|
|
5578
|
+
row.addEventListener('click', () => openWatchModal());
|
|
5579
|
+
list.appendChild(row);
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
|
|
5583
|
+
let lastWatchKey = '';
|
|
5584
|
+
async function renderWatchersModal(force) {
|
|
5585
|
+
let rows = []; try { rows = (await beast.watchersList()) || []; } catch {}
|
|
5502
5586
|
/* canlı yenilemede içerik değişmediyse DOM'a dokunma — buton tıklamaları
|
|
5503
5587
|
yeniden kurulurken yutulmasın */
|
|
5504
5588
|
const key = JSON.stringify(rows);
|
|
@@ -5623,6 +5707,7 @@ function closeCronModal() {
|
|
|
5623
5707
|
async function openWatchModal() {
|
|
5624
5708
|
toggleMini(els.watchOverlay, false);
|
|
5625
5709
|
await renderWatchersModal(true); /* her basışta TAZE çekim */
|
|
5710
|
+
lastWatchPaneKey = ''; /* pane de bir sonraki tazelemede yeniden kurulur */
|
|
5626
5711
|
startMiniRefresh();
|
|
5627
5712
|
}
|
|
5628
5713
|
|
|
@@ -6337,6 +6422,7 @@ async function init() {
|
|
|
6337
6422
|
|
|
6338
6423
|
/* cron modülü — olay bağlama */
|
|
6339
6424
|
if (els.watchBtn) els.watchBtn.addEventListener('click', openWatchModal);
|
|
6425
|
+
if (els.watchPaneOpen) els.watchPaneOpen.addEventListener('click', openWatchModal);
|
|
6340
6426
|
if (els.cronBtn) els.cronBtn.addEventListener('click', openCronModal);
|
|
6341
6427
|
if (els.watchClose) els.watchClose.addEventListener('click', closeWatchModal);
|
|
6342
6428
|
if (els.cronClose) els.cronClose.addEventListener('click', closeCronModal);
|
|
@@ -8070,14 +8156,31 @@ $('#filePreview').addEventListener('click', async () => {
|
|
|
8070
8156
|
toast((r && r.error) || 'preview açılamadı');
|
|
8071
8157
|
return;
|
|
8072
8158
|
}
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8159
|
+
/* ANDROID EMÜLATÖR: kuruluysa Expo uygulamaları emülatörde açılır —
|
|
8160
|
+
AVD arka planda başlatılır (kendi penceresi açılır), ajan expo'yu kaldırır */
|
|
8161
|
+
let ask = '';
|
|
8162
|
+
if (kind === 'expo' || kind === 'react-native') {
|
|
8163
|
+
const dev = await beast.androidStatus().catch(() => null);
|
|
8164
|
+
const hasEmu = dev && dev.ok && dev.sdk && Array.isArray(dev.avds) && dev.avds.length;
|
|
8165
|
+
if (hasEmu) {
|
|
8166
|
+
beast.androidStartAvd(dev.avds[0]).catch(() => {});
|
|
8167
|
+
toast('Android emülatörü başlatılıyor — ilk açılış 30-90 sn');
|
|
8168
|
+
ask =
|
|
8169
|
+
kind === 'expo'
|
|
8170
|
+
? 'Preview aç — bu klasör bir Expo/React Native projesi (app.json var). Android emülatörü başlatıldı; arka planda `npx expo start --android` çalıştır — uygulama emülatörde açılır (expo adb ile cihazı kendisi bulur). Çalışan http://localhost:PORT adresini raporda yaz.'
|
|
8171
|
+
: 'Preview aç — bu klasör bir React Native projesi. Android emülatörü hazır. Metro\u2019yu arka planda başlat (scripts.start varsa `npm start`) ve uygulamayı emülatöre kur: `npx react-native run-android`. Sunucu adresini raporda yaz.';
|
|
8172
|
+
}
|
|
8173
|
+
}
|
|
8174
|
+
if (!ask) {
|
|
8175
|
+
ask =
|
|
8176
|
+
kind === 'expo'
|
|
8177
|
+
? 'Preview aç — bu klasör bir Expo/React Native projesi (app.json var). Arka planda `npx expo start --tunnel` çalıştır (tunnel: telefon her ağdan bağlanabilir; web önizleme için `--web` de ekle). Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
|
|
8178
|
+
: kind === 'react-native'
|
|
8179
|
+
? 'Preview aç — bu klasör bir React Native projesi. Metro sunucusunu arka planda başlat: package.json scripts.start varsa `npm start`, yoksa `npx react-native start`. Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
|
|
8180
|
+
: kind === 'web'
|
|
8181
|
+
? 'Preview aç — bu proje dev-server ister. Doğru komutu package.json scripts\'ından seç (dev/start/serve — `npm run dev` vb.) ve arka planda başlat. Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
|
|
8182
|
+
: 'Preview aç — bu projenin nasıl çalıştığını package.json / konfigürasyon dosyalarından anla, doğru dev komutunu arka planda başlat ve çalışan http://localhost:PORT adresini raporda yaz.';
|
|
8183
|
+
}
|
|
8081
8184
|
if (els.bcInput) {
|
|
8082
8185
|
els.bcInput.value = ask;
|
|
8083
8186
|
bcInputResize();
|
package/src/renderer/style.css
CHANGED
|
@@ -112,6 +112,71 @@ body {
|
|
|
112
112
|
|
|
113
113
|
#sessList { flex: 1; overflow-y: auto; padding: 4px 8px; }
|
|
114
114
|
|
|
115
|
+
/* ---------- İZLEYİCİLER paneli (sol kenar çubuğu — sohbet geçmişinin alt yarısı) ----------
|
|
116
|
+
Yalnız agent (chat) modunda görünür; IDE/Studio/Sandbox'ta sessList ile birlikte gizli. */
|
|
117
|
+
#watchPane {
|
|
118
|
+
display: none;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
height: 36%;
|
|
121
|
+
min-height: 120px;
|
|
122
|
+
border-top: 1px solid var(--border);
|
|
123
|
+
background: var(--panel);
|
|
124
|
+
}
|
|
125
|
+
body:not(.ide-mode):not(.studio-mode):not(.sandbox-mode) #watchPane { display: flex; }
|
|
126
|
+
#watchPaneHead {
|
|
127
|
+
flex: none;
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 6px;
|
|
131
|
+
padding: 7px 10px;
|
|
132
|
+
border-bottom: 1px solid var(--border);
|
|
133
|
+
}
|
|
134
|
+
#watchPaneTitle { font-size: 10.5px; letter-spacing: 2px; color: var(--muted); font-weight: 800; }
|
|
135
|
+
#watchPaneHead button {
|
|
136
|
+
margin-left: auto;
|
|
137
|
+
background: none;
|
|
138
|
+
border: 1px solid var(--border);
|
|
139
|
+
border-radius: 7px;
|
|
140
|
+
color: var(--muted);
|
|
141
|
+
cursor: pointer;
|
|
142
|
+
font-size: 12px;
|
|
143
|
+
line-height: 1.2;
|
|
144
|
+
padding: 1px 7px;
|
|
145
|
+
}
|
|
146
|
+
#watchPaneHead button:hover { color: var(--text); background: var(--panel2); }
|
|
147
|
+
#watchPaneList { flex: 1; overflow-y: auto; padding: 4px; }
|
|
148
|
+
.wp-row {
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
gap: 7px;
|
|
152
|
+
padding: 4px 6px;
|
|
153
|
+
border-radius: 6px;
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
font-size: 12px;
|
|
156
|
+
white-space: nowrap;
|
|
157
|
+
overflow: hidden;
|
|
158
|
+
}
|
|
159
|
+
.wp-row:hover { background: var(--panel2); }
|
|
160
|
+
.wp-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
|
|
161
|
+
.wp-row.off .wp-dot { background: var(--muted); opacity: 0.5; }
|
|
162
|
+
.wp-main { flex: 1; min-width: 0; overflow: hidden; }
|
|
163
|
+
.wp-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; font-size: 12px; }
|
|
164
|
+
.wp-meta { color: var(--muted); font-size: 10px; overflow: hidden; text-overflow: ellipsis; }
|
|
165
|
+
.wp-btns { display: flex; gap: 4px; flex: none; }
|
|
166
|
+
.wp-btn {
|
|
167
|
+
background: none;
|
|
168
|
+
border: 1px solid var(--border);
|
|
169
|
+
border-radius: 6px;
|
|
170
|
+
color: var(--muted);
|
|
171
|
+
cursor: pointer;
|
|
172
|
+
font-size: 10px;
|
|
173
|
+
padding: 1px 5px;
|
|
174
|
+
line-height: 1.3;
|
|
175
|
+
}
|
|
176
|
+
.wp-btn:hover { color: var(--text); background: var(--panel2); }
|
|
177
|
+
.wp-btn.del:hover { color: var(--err); border-color: var(--err); }
|
|
178
|
+
.wp-empty { color: var(--muted); font-size: 11.5px; padding: 10px; }
|
|
179
|
+
|
|
115
180
|
.sess {
|
|
116
181
|
display: flex;
|
|
117
182
|
align-items: center;
|