beast-agent 1.8.0 → 1.9.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/README.md +2 -0
- package/package.json +1 -1
- package/src/agent/engine.js +12 -3
- package/src/agent/mcp.js +427 -0
- package/src/agent/skills.js +2 -1
- package/src/main.js +37 -0
- package/src/preload.js +4 -0
- package/src/renderer/i18n.js +34 -0
- package/src/renderer/index.html +2 -0
- package/src/renderer/renderer.js +73 -1
- package/src/renderer/style.css +11 -6
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ Beast Agent is a personal AI agent that runs on your machine and connects to **a
|
|
|
24
24
|
- **Slash commands** — `/new`, `/open`, `/change`, `/think`, `/rule`, `/allow`, `/block`, `/backup`, `/approve` and more
|
|
25
25
|
- **Event center** — IMAP IDLE email watching, file-change watching, price feed (Binance), webhook inputs
|
|
26
26
|
- **Cron + watchers** — scheduled tasks, file/web/battery watchers
|
|
27
|
+
- **MCP desteği** — Model Context Protocol server bağla (filesystem, git, fetch, memory + binlerce topluluk server'ı); araçlar `mcp__server__tool` adıyla modele açılır, Ayarlar → MCP'den yönetilir
|
|
28
|
+
- **Skills** — `%APPDATA%\beast\skills\<ad>\SKILL.md` ile ajanına yetenek öğret; Superpowers metodoloji paketi (planlama, TDD, paralel ajan disiplini, doğrulama) builtin gelir
|
|
27
29
|
- **Web search chain** — TinyFish (free, used first if a key is set) → built-in browser (direct Google) → python multi-engine (DDG/Bing/Mojeek) → Exa
|
|
28
30
|
- **Approval gate** — optional confirmation for risky actions (commands / file deletion-modification): default OFF (everything free), when ON the agent asks (`/approve`, `/approve always`, `/deny`)
|
|
29
31
|
- **Provider-based limits** — per-provider max input token limit with context compression
|
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -14,6 +14,7 @@ const agentdefs = require('./agentdefs');
|
|
|
14
14
|
const memory = require('./memory');
|
|
15
15
|
const mem0 = require('./mem0');
|
|
16
16
|
const skills = require('./skills');
|
|
17
|
+
const mcp = require('./mcp');
|
|
17
18
|
const { estTokens, estMsgTokens } = require('./tokens');
|
|
18
19
|
const log = require('./logger');
|
|
19
20
|
|
|
@@ -2828,6 +2829,10 @@ class Engine {
|
|
|
2828
2829
|
}
|
|
2829
2830
|
|
|
2830
2831
|
async _chatTurn(session, signal, onDelta, toolsList = TOOLS) {
|
|
2832
|
+
/* MCP: %APPDATA%\beast\mcp.json'daki etkin serverların araçlarını şema listesine ekle
|
|
2833
|
+
(bağlı değilse lazy bağlanır; kapalıysa liste değişmez; Beast Code paneli hızlı
|
|
2834
|
+
ilk-token sözü için MCP'siz kalır) */
|
|
2835
|
+
if (!session || !session.bcCode) toolsList = await mcp.mergeTools(toolsList);
|
|
2831
2836
|
/* opencode agent.ts port: özel ajan tanımı — prompt/model/araç/steps */
|
|
2832
2837
|
const adef = this._agentDefFor(session, !!session.bgJob);
|
|
2833
2838
|
/* Beast Code: todo_write açıklaması "3+ adım" kısıtı içerir ve model küçük
|
|
@@ -3450,8 +3455,8 @@ class Engine {
|
|
|
3450
3455
|
/* OTOMATİK SKİLL SİSTEMİ: kurulu skill listesi de prompta girer —
|
|
3451
3456
|
ajan "eski skillden daha kolay/better yol buldum" diyebilmesin, KARAR VERİP
|
|
3452
3457
|
skilli GÜNCELLESİN. action: create (yeni) | update (mevcutu iyileştir) | none */
|
|
3453
|
-
|
|
3454
|
-
const existing = skills
|
|
3458
|
+
const skills = require('./skills');
|
|
3459
|
+
const existing = skills
|
|
3455
3460
|
.scan()
|
|
3456
3461
|
.map((s) => `- ${s.name}: ${(s.description || '').slice(0, 100)}`)
|
|
3457
3462
|
.join('\n');
|
|
@@ -3812,7 +3817,7 @@ class Engine {
|
|
|
3812
3817
|
/* onay kapısı: riskli araçta dış onay bekle; reddedilirse araç çalışmaz.
|
|
3813
3818
|
"always" onaylı araçlar doğrudan geçer. */
|
|
3814
3819
|
if (
|
|
3815
|
-
Engine.RISKY_TOOLS.has(name) &&
|
|
3820
|
+
(Engine.RISKY_TOOLS.has(name) || String(name).startsWith('mcp__')) &&
|
|
3816
3821
|
!this.alwaysAllowTools.has(name) &&
|
|
3817
3822
|
this.approvals && typeof this.approvals.request === 'function'
|
|
3818
3823
|
) {
|
|
@@ -3831,6 +3836,10 @@ class Engine {
|
|
|
3831
3836
|
}
|
|
3832
3837
|
/* GERİ ALMA GÜNLÜĞÜ: dosya yazımından ÖNCE eski içerik kayda geçer */
|
|
3833
3838
|
this._journalBefore(sessionId, name, args);
|
|
3839
|
+
/* MCP: dış server araçları (mcp__<server>__<tool>) — tools/call'a köprülenir */
|
|
3840
|
+
if (String(name).startsWith('mcp__')) {
|
|
3841
|
+
return JSON.stringify(await mcp.call(name, args, signal));
|
|
3842
|
+
}
|
|
3834
3843
|
if (name === 'memory_write') {
|
|
3835
3844
|
/* bot oturumu → botun KENDİ hafıza store'una yaz (global Beast hafızasına değil).
|
|
3836
3845
|
mem0 açıkken: hash+semantic dedup'lı store'a gider, MEMORY.md aynası güncellenir. */
|
package/src/agent/mcp.js
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast MCP (Model Context Protocol) istemcisi — el yazımı minimal, sıfır bağımlılık.
|
|
4
|
+
stdio transport: JSON-RPC 2.0, satır-bazlı (newline-delimited).
|
|
5
|
+
|
|
6
|
+
%APPDATA%\beast\mcp.json:
|
|
7
|
+
{
|
|
8
|
+
"servers": {
|
|
9
|
+
"fetch": { "command": "uvx", "args": ["mcp-server-fetch"], "enabled": true },
|
|
10
|
+
"git": { "command": "uvx", "args": ["mcp-server-git", "--repository", "C:/repo"],
|
|
11
|
+
"enabled": true, "tools": ["git_status", "git_log"], "timeoutMs": 60000 }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Tool'lar modele `mcp__<server>__<tool>` adıyla açılır; çağrı tools/call'a gider.
|
|
16
|
+
Server çökerse 3 dk cooldown'a girer, sonra otomatik yeniden denenir. */
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { spawn } = require('child_process');
|
|
21
|
+
const { beastRoot } = require('./memory');
|
|
22
|
+
const log = require('./logger');
|
|
23
|
+
|
|
24
|
+
const INIT_TIMEOUT_MS = 15000;
|
|
25
|
+
const LIST_TIMEOUT_MS = 15000;
|
|
26
|
+
const CALL_TIMEOUT_MS = 120000;
|
|
27
|
+
const COOLDOWN_MS = 3 * 60 * 1000;
|
|
28
|
+
const MAX_TOOLS_PER_SERVER = 40;
|
|
29
|
+
const MAX_TOOLS_TOTAL = 80;
|
|
30
|
+
const MAX_DESC_LEN = 220;
|
|
31
|
+
|
|
32
|
+
/* server adı → { cfg, proc, nextId, pending:Map, tools:[], status, lastError } */
|
|
33
|
+
const conns = new Map();
|
|
34
|
+
/* başarısız server adı → yeniden deneme zamanı */
|
|
35
|
+
const downUntil = new Map();
|
|
36
|
+
|
|
37
|
+
/* ---------- config ---------- */
|
|
38
|
+
|
|
39
|
+
function configPath() {
|
|
40
|
+
return path.join(beastRoot(), 'mcp.json');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let cfgCache = { mtime: -1, cfg: { servers: {} } };
|
|
44
|
+
|
|
45
|
+
function readConfig(force) {
|
|
46
|
+
try {
|
|
47
|
+
const p = configPath();
|
|
48
|
+
const st = fs.statSync(p);
|
|
49
|
+
if (!force && st.mtimeMs === cfgCache.mtime) return cfgCache.cfg;
|
|
50
|
+
const parsed = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
51
|
+
const cfg = { servers: {} };
|
|
52
|
+
if (parsed && typeof parsed.servers === 'object') {
|
|
53
|
+
for (const [name, s] of Object.entries(parsed.servers)) {
|
|
54
|
+
if (!s || typeof s !== 'object' || !s.command) continue;
|
|
55
|
+
cfg.servers[sanitizeName(name)] = {
|
|
56
|
+
command: String(s.command),
|
|
57
|
+
args: Array.isArray(s.args) ? s.args.map(String) : [],
|
|
58
|
+
env: s.env && typeof s.env === 'object' ? Object.fromEntries(Object.entries(s.env).map(([k, v]) => [String(k), String(v)])) : {},
|
|
59
|
+
enabled: s.enabled !== false,
|
|
60
|
+
tools: Array.isArray(s.tools) ? s.tools.map(String) : null,
|
|
61
|
+
timeoutMs: Number(s.timeoutMs) > 1000 ? Math.min(Number(s.timeoutMs), 600000) : CALL_TIMEOUT_MS,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
cfgCache = { mtime: st.mtimeMs, cfg };
|
|
66
|
+
return cfg;
|
|
67
|
+
} catch {
|
|
68
|
+
/* dosya yok/bozuk → boş yapı (mtime -1 kalır; dosya oluşturulunca yeniden okunur) */
|
|
69
|
+
try {
|
|
70
|
+
const st = fs.statSync(configPath());
|
|
71
|
+
cfgCache = { mtime: st.mtimeMs, cfg: { servers: {} } };
|
|
72
|
+
} catch {}
|
|
73
|
+
return { servers: {} };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function saveConfig(cfg) {
|
|
78
|
+
const p = configPath();
|
|
79
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
80
|
+
fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n');
|
|
81
|
+
cfgCache = { mtime: -1, cfg }; /* sonraki okuma diskten gelsin */
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sanitizeName(name) {
|
|
85
|
+
return String(name || '')
|
|
86
|
+
.toLowerCase()
|
|
87
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
88
|
+
.replace(/^-+|-+$/g, '')
|
|
89
|
+
.slice(0, 32) || 'server';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ---------- süreç yaşam döngüsü ---------- */
|
|
93
|
+
|
|
94
|
+
function spawnServer(cfg) {
|
|
95
|
+
/* Windows: npx/uvx gibi .cmd sarmalayıcıları düz spawn ile başlamaz —
|
|
96
|
+
cmd /d /s /c üzerinden, argümanlar elle tırnaklanarak geç. */
|
|
97
|
+
const quote = (t) => (/[\s"]/u.test(t) ? '"' + t.replace(/"/g, '\\"') + '"' : t);
|
|
98
|
+
const line = [cfg.command, ...cfg.args].map(quote).join(' ');
|
|
99
|
+
const child = spawn(line, {
|
|
100
|
+
shell: true,
|
|
101
|
+
windowsHide: true,
|
|
102
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
103
|
+
env: { ...process.env, ...(cfg.env || {}) },
|
|
104
|
+
});
|
|
105
|
+
return child;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function connFor(name) {
|
|
109
|
+
return conns.get(name);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function killConn(name) {
|
|
113
|
+
const c = conns.get(name);
|
|
114
|
+
if (!c) return;
|
|
115
|
+
conns.delete(name);
|
|
116
|
+
try {
|
|
117
|
+
if (c.timer) clearTimeout(c.timer);
|
|
118
|
+
for (const [, p] of c.pending) {
|
|
119
|
+
try { p.reject(new Error('MCP server kapatıldı')); } catch {}
|
|
120
|
+
}
|
|
121
|
+
c.pending.clear();
|
|
122
|
+
try { c.proc.stdin.end(); } catch {}
|
|
123
|
+
try { c.proc.kill(); } catch {}
|
|
124
|
+
} catch {}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function request(conn, method, params, timeoutMs, signal) {
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
const id = conn.nextId++;
|
|
130
|
+
const timer = setTimeout(() => {
|
|
131
|
+
conn.pending.delete(id);
|
|
132
|
+
reject(new Error(`MCP ${method} zaman aşımı (${Math.round(timeoutMs / 1000)}s)`));
|
|
133
|
+
}, timeoutMs);
|
|
134
|
+
const onAbort = () => {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
conn.pending.delete(id);
|
|
137
|
+
reject(new Error('iptal edildi'));
|
|
138
|
+
};
|
|
139
|
+
if (signal) {
|
|
140
|
+
if (signal.aborted) return onAbort();
|
|
141
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
142
|
+
}
|
|
143
|
+
conn.pending.set(id, {
|
|
144
|
+
resolve: (v) => { clearTimeout(timer); if (signal) signal.removeEventListener('abort', onAbort); resolve(v); },
|
|
145
|
+
reject: (e) => { clearTimeout(timer); if (signal) signal.removeEventListener('abort', onAbort); reject(e); },
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
conn.proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
|
|
149
|
+
} catch (e) {
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
conn.pending.delete(id);
|
|
152
|
+
reject(e);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function handleLine(conn, line) {
|
|
158
|
+
if (!line || !line.trim()) return;
|
|
159
|
+
let msg;
|
|
160
|
+
try { msg = JSON.parse(line); } catch { return; }
|
|
161
|
+
if (msg.id !== undefined && msg.id !== null && conn.pending.has(msg.id)) {
|
|
162
|
+
const p = conn.pending.get(msg.id);
|
|
163
|
+
conn.pending.delete(msg.id);
|
|
164
|
+
if (msg.error) p.reject(new Error(String(msg.error.message || JSON.stringify(msg.error)).slice(0, 300)));
|
|
165
|
+
else p.resolve(msg.result);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
/* bildirimler (id'siz) — şimdilik yok sayılır */
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function startServer(name, cfg) {
|
|
172
|
+
killConn(name);
|
|
173
|
+
const conn = {
|
|
174
|
+
cfg,
|
|
175
|
+
proc: null,
|
|
176
|
+
nextId: 1,
|
|
177
|
+
pending: new Map(),
|
|
178
|
+
tools: [],
|
|
179
|
+
status: 'starting',
|
|
180
|
+
lastError: '',
|
|
181
|
+
stdout: '',
|
|
182
|
+
};
|
|
183
|
+
conns.set(name, conn);
|
|
184
|
+
try {
|
|
185
|
+
conn.proc = spawnServer(cfg);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
conn.status = 'down';
|
|
188
|
+
conn.lastError = String((e && e.message) || e).slice(0, 200);
|
|
189
|
+
downUntil.set(name, Date.now() + COOLDOWN_MS);
|
|
190
|
+
return Promise.resolve(false);
|
|
191
|
+
}
|
|
192
|
+
conn.proc.stdout.setEncoding('utf8');
|
|
193
|
+
conn.proc.stdout.on('data', (chunk) => {
|
|
194
|
+
conn.stdout += chunk;
|
|
195
|
+
let idx;
|
|
196
|
+
while ((idx = conn.stdout.indexOf('\n')) >= 0) {
|
|
197
|
+
const line = conn.stdout.slice(0, idx);
|
|
198
|
+
conn.stdout = conn.stdout.slice(idx + 1);
|
|
199
|
+
try { handleLine(conn, line); } catch {}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
conn.proc.stderr.setEncoding('utf8');
|
|
203
|
+
conn.proc.stderr.on('data', () => {}); /* server logları — yok sayılır */
|
|
204
|
+
const fail = (err) => {
|
|
205
|
+
conn.status = 'down';
|
|
206
|
+
conn.lastError = String((err && err.message) || err).slice(0, 200);
|
|
207
|
+
conn.tools = [];
|
|
208
|
+
downUntil.set(name, Date.now() + COOLDOWN_MS);
|
|
209
|
+
for (const [, p] of conn.pending) { try { p.reject(new Error(conn.lastError)); } catch {} }
|
|
210
|
+
conn.pending.clear();
|
|
211
|
+
try { conn.proc.kill(); } catch {}
|
|
212
|
+
};
|
|
213
|
+
conn.proc.on('error', fail);
|
|
214
|
+
conn.proc.on('exit', (code) => {
|
|
215
|
+
if (conn.status === 'up') {
|
|
216
|
+
fail(new Error(`MCP server '${name}' beklenmedik çıkış yaptı (kod ${code})`));
|
|
217
|
+
} else if (conn.status === 'starting') {
|
|
218
|
+
fail(new Error(`MCP server '${name}' başlamadan kapandı (kod ${code})`));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return (async () => {
|
|
222
|
+
try {
|
|
223
|
+
await request(conn, 'initialize', {
|
|
224
|
+
protocolVersion: '2024-11-05',
|
|
225
|
+
capabilities: {},
|
|
226
|
+
clientInfo: { name: 'beast-agent', version: '1.9.0' },
|
|
227
|
+
}, INIT_TIMEOUT_MS);
|
|
228
|
+
try {
|
|
229
|
+
conn.proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }) + '\n');
|
|
230
|
+
} catch {}
|
|
231
|
+
const listed = await request(conn, 'tools/list', {}, LIST_TIMEOUT_MS);
|
|
232
|
+
const tools = Array.isArray(listed && listed.tools) ? listed.tools : [];
|
|
233
|
+
conn.tools = tools.slice(0, MAX_TOOLS_PER_SERVER).map((t) => ({
|
|
234
|
+
name: String(t.name || ''),
|
|
235
|
+
description: String(t.description || '').slice(0, MAX_DESC_LEN),
|
|
236
|
+
inputSchema: t.inputSchema && typeof t.inputSchema === 'object' ? t.inputSchema : { type: 'object', properties: {} },
|
|
237
|
+
}));
|
|
238
|
+
conn.status = 'up';
|
|
239
|
+
downUntil.delete(name);
|
|
240
|
+
log.info(`[mcp] '${name}' bağlı — ${conn.tools.length} araç`);
|
|
241
|
+
return true;
|
|
242
|
+
} catch (e) {
|
|
243
|
+
fail(e);
|
|
244
|
+
log.warn(`[mcp] '${name}' bağlanamadı: ${conn.lastError}`);
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
})();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* Tüm etkin serverlara bağlan (zaten bağlılar hariç; cooldown'dakiler atlanır).
|
|
251
|
+
Toplam `waitMs`'ten uzun beklemez — hangisi hazır değilse bu turda öyle kalır. */
|
|
252
|
+
async function ensureAll(waitMs = 8000) {
|
|
253
|
+
const cfg = readConfig();
|
|
254
|
+
const kicks = [];
|
|
255
|
+
for (const [name, s] of Object.entries(cfg.servers)) {
|
|
256
|
+
if (!s.enabled) continue;
|
|
257
|
+
if (conns.has(name) || (downUntil.get(name) || 0) > Date.now()) continue;
|
|
258
|
+
kicks.push(startServer(name, s));
|
|
259
|
+
}
|
|
260
|
+
if (!kicks.length) return;
|
|
261
|
+
await Promise.race([
|
|
262
|
+
Promise.allSettled(kicks),
|
|
263
|
+
new Promise((r) => setTimeout(r, waitMs)),
|
|
264
|
+
]);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* ---------- şemalar ---------- */
|
|
268
|
+
|
|
269
|
+
function toolFullNames(cfg, serverName, tools) {
|
|
270
|
+
let list = tools;
|
|
271
|
+
if (cfg.tools && cfg.tools.length) {
|
|
272
|
+
const allow = new Set(cfg.tools);
|
|
273
|
+
list = tools.filter((t) => allow.has(t.name));
|
|
274
|
+
}
|
|
275
|
+
return list;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function toolSchemas() {
|
|
279
|
+
const cfg = readConfig();
|
|
280
|
+
const out = [];
|
|
281
|
+
for (const [name, conn] of conns) {
|
|
282
|
+
if (conn.status !== 'up' || !conn.tools.length) continue;
|
|
283
|
+
const s = cfg.servers[name] || {};
|
|
284
|
+
const list = toolFullNames(s, name, conn.tools);
|
|
285
|
+
for (const t of list) {
|
|
286
|
+
out.push({
|
|
287
|
+
type: 'function',
|
|
288
|
+
function: {
|
|
289
|
+
name: `mcp__${name}__${t.name}`,
|
|
290
|
+
description: `[mcp:${name}] ${t.description}`.trim(),
|
|
291
|
+
parameters: t.inputSchema,
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return out.slice(0, MAX_TOOLS_TOTAL);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* engine._chatTurn başında çağrılır: MCP araç şemalarını listeye ekler */
|
|
300
|
+
async function mergeTools(toolsList) {
|
|
301
|
+
try {
|
|
302
|
+
if (!readConfig().servers || !Object.keys(readConfig().servers).length) return toolsList;
|
|
303
|
+
if (toolsList.some((t) => t && t.function && String(t.function.name).startsWith('mcp__'))) return toolsList;
|
|
304
|
+
await ensureAll();
|
|
305
|
+
const extra = toolSchemas();
|
|
306
|
+
return extra.length ? toolsList.concat(extra) : toolsList;
|
|
307
|
+
} catch {
|
|
308
|
+
return toolsList;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* ---------- çağrı ---------- */
|
|
313
|
+
|
|
314
|
+
function parseFullName(fullName) {
|
|
315
|
+
const m = String(fullName || '').match(/^mcp__([a-z0-9_-]{1,32})__([A-Za-z0-9_.-]{1,64})$/);
|
|
316
|
+
return m ? { server: m[1], tool: m[2] } : null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function extractText(result) {
|
|
320
|
+
if (!result) return '';
|
|
321
|
+
const content = Array.isArray(result.content) ? result.content : [];
|
|
322
|
+
const parts = [];
|
|
323
|
+
for (const c of content) {
|
|
324
|
+
if (!c || typeof c !== 'object') continue;
|
|
325
|
+
if (c.type === 'text' && typeof c.text === 'string') parts.push(c.text);
|
|
326
|
+
else if (c.type === 'resource') parts.push(JSON.stringify(c.resource || {}).slice(0, 4000));
|
|
327
|
+
else parts.push(JSON.stringify(c).slice(0, 2000));
|
|
328
|
+
}
|
|
329
|
+
return parts.join('\n').slice(0, 60000);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function call(fullName, args, signal) {
|
|
333
|
+
const parsed = parseFullName(fullName);
|
|
334
|
+
if (!parsed) return { ok: false, error: `geçersiz MCP araç adı: ${fullName}` };
|
|
335
|
+
const { server, tool } = parsed;
|
|
336
|
+
const cfg = readConfig();
|
|
337
|
+
const scfg = cfg.servers[server];
|
|
338
|
+
if (!scfg || !scfg.enabled) return { ok: false, error: `MCP server '${server}' kapalı` };
|
|
339
|
+
let conn = conns.get(server);
|
|
340
|
+
if (!conn || conn.status !== 'up') {
|
|
341
|
+
if ((downUntil.get(server) || 0) > Date.now()) {
|
|
342
|
+
return { ok: false, error: `MCP server '${server}' şu an erişilemiyor (${conn ? conn.lastError : 'cooldown'}) — az sonra yeniden dene` };
|
|
343
|
+
}
|
|
344
|
+
await ensureAll(15000);
|
|
345
|
+
conn = conns.get(server);
|
|
346
|
+
}
|
|
347
|
+
if (!conn || conn.status !== 'up') {
|
|
348
|
+
return { ok: false, error: `MCP server '${server}' bağlı değil` };
|
|
349
|
+
}
|
|
350
|
+
if (scfg.tools && scfg.tools.length && !scfg.tools.includes(tool)) {
|
|
351
|
+
return { ok: false, error: `araç '${tool}' server '${server}' için yetkili listede değil` };
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
const result = await request(conn, 'tools/call', { name: tool, arguments: args || {} }, scfg.timeoutMs || CALL_TIMEOUT_MS, signal);
|
|
355
|
+
const text = extractText(result);
|
|
356
|
+
if (result && result.isError) return { ok: false, error: text.slice(0, 4000) || 'MCP araç hatası' };
|
|
357
|
+
return { ok: true, result: text };
|
|
358
|
+
} catch (e) {
|
|
359
|
+
const msg = String((e && e.message) || e);
|
|
360
|
+
/* ölü süreç → sonraki turda yeniden doğsun */
|
|
361
|
+
if (conn.status !== 'up') killConn(server);
|
|
362
|
+
return { ok: false, error: `MCP çağrısı başarısız: ${msg.slice(0, 300)}` };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* ---------- yönetim (settings UI) ---------- */
|
|
367
|
+
|
|
368
|
+
function status() {
|
|
369
|
+
const cfg = readConfig(true);
|
|
370
|
+
const now = Date.now();
|
|
371
|
+
const servers = [];
|
|
372
|
+
for (const [name, s] of Object.entries(cfg.servers)) {
|
|
373
|
+
const conn = conns.get(name);
|
|
374
|
+
const down = (downUntil.get(name) || 0) > now;
|
|
375
|
+
servers.push({
|
|
376
|
+
name,
|
|
377
|
+
command: s.command,
|
|
378
|
+
args: s.args,
|
|
379
|
+
enabled: !!s.enabled,
|
|
380
|
+
tools: s.tools || null,
|
|
381
|
+
timeoutMs: s.timeoutMs,
|
|
382
|
+
state: !s.enabled ? 'disabled' : conn ? conn.status : down ? 'down' : 'idle',
|
|
383
|
+
toolCount: conn ? conn.tools.length : 0,
|
|
384
|
+
toolNames: conn ? conn.tools.map((t) => t.name) : [],
|
|
385
|
+
lastError: conn ? conn.lastError : '',
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return { path: configPath(), servers };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* Tek serverı yeniden başlat (settings "yenile" düğmesi) */
|
|
392
|
+
async function refresh(name) {
|
|
393
|
+
const cfg = readConfig(true);
|
|
394
|
+
const s = cfg.servers[sanitizeName(name)];
|
|
395
|
+
if (!s || !s.enabled) return false;
|
|
396
|
+
downUntil.delete(name);
|
|
397
|
+
await startServer(sanitizeName(name), s);
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function stopAll() {
|
|
402
|
+
for (const name of Array.from(conns.keys())) killConn(name);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* test/ayalar: tüm bağlantı ve cooldown durumunu sıfırla */
|
|
406
|
+
function _reset() {
|
|
407
|
+
stopAll();
|
|
408
|
+
downUntil.clear();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
process.on('exit', stopAll);
|
|
412
|
+
|
|
413
|
+
module.exports = {
|
|
414
|
+
configPath,
|
|
415
|
+
readConfig,
|
|
416
|
+
saveConfig,
|
|
417
|
+
mergeTools,
|
|
418
|
+
toolSchemas,
|
|
419
|
+
call,
|
|
420
|
+
status,
|
|
421
|
+
refresh,
|
|
422
|
+
stopAll,
|
|
423
|
+
_reset,
|
|
424
|
+
parseFullName,
|
|
425
|
+
extractText,
|
|
426
|
+
sanitizeName,
|
|
427
|
+
};
|
package/src/agent/skills.js
CHANGED
|
@@ -110,6 +110,7 @@ Get-Content "$env:APPDATA\\beast\\sessions\\bg-jobs.json" | ConvertFrom-Json | %
|
|
|
110
110
|
| \`bots.json\` | Bot kayıt defteri — max 5 bot, ilk kayıt admin 'Beast' (silinemez). Bot adı HARFLE başlamak ZORUNDA |
|
|
111
111
|
| \`bots/<id>/\` | Botun izole klasörü: config.json, memory.md, yetkiler.json, logs/ |
|
|
112
112
|
| \`whitelist.json\`, \`wa-auth\\\`, \`wa-chats.json\`, \`wa.log\` | WhatsApp izin listesi, Baileys auth (SİLME), sohbet eşlemesi, log |
|
|
113
|
+
| \`mcp.json\` | MCP server tanımları — dış araçlar (Ayarlar → MCP'den düzenlenir; araçlar modele \`mcp__<server>__<tool>\` adıyla açılır, çağrı onay kapısından geçer, çöken server 3 dk sonra otomatik yeniden denenir) |
|
|
113
114
|
| \`cron.json\`, \`watchers.json\`, \`bus.json\` | Zamanlanmış görevler, web/batarya izleyicileri, olay abonelikleri |
|
|
114
115
|
| \`scripts\\\` | Python scriptleri (websearch.py, news.py) |
|
|
115
116
|
| \`searxng\\settings.yml\` | SearXNG yerel arama motoru ayarları (127.0.0.1:8888 — \`beast searxng\` ile kurulur/başlatılır) |
|
|
@@ -139,7 +140,7 @@ Get-Content "$env:APPDATA\\beast\\sessions\\bg-jobs.json" | ConvertFrom-Json | %
|
|
|
139
140
|
/help · /version · /new · /open <kod> · /sessions · /stop · /start · /restart · /change [n] · /model <isim> · /think 0-5 · /clear · /notes · /rule <metin> · /rules · /notify on|off · /screenshot · /approve [always] · /deny · /update [now] · /usage · /backup · /status · /skills
|
|
140
141
|
|
|
141
142
|
## AYAR SEKMELERİ
|
|
142
|
-
Provider · Fallout · Skills · Paralel Ajanlar · TTS · E-posta · Entegrasyonlar · Web Arama
|
|
143
|
+
Provider · Fallout · Skills · Paralel Ajanlar · TTS · E-posta · Entegrasyonlar · Web Arama · MCP · Olaylar · Cron · Maliyet · Loglar · Panel · Limitler · Güvenlik · Güncelleme
|
|
143
144
|
|
|
144
145
|
## PARALEL AJAN HIZLI BİLGİ
|
|
145
146
|
- CEO modu: konuşan ajan iş YAPMAZ, run_background/run_background_many ile devreder; görev tanımı kendine yeterli olmalı (ajan CEO bağlamını GÖREMEZ).
|
package/src/main.js
CHANGED
|
@@ -151,6 +151,7 @@ function startNpmUpdateWatch() {
|
|
|
151
151
|
setInterval(check, 6 * 60 * 60 * 1000);
|
|
152
152
|
}
|
|
153
153
|
const toolsMod = require('./agent/tools');
|
|
154
|
+
const mcpMod = require('./agent/mcp');
|
|
154
155
|
const { htmlToText, setSearchChain, setTinyfishKey } = toolsMod;
|
|
155
156
|
/* OpenCode köprüsü KALDIRILDI: Beast Code artık tamamen BEAST motoruyla
|
|
156
157
|
çalışır — opencode'in döngü mantığı (compaction, prune, cache disiplini,
|
|
@@ -2972,6 +2973,7 @@ app.whenReady().then(() => {
|
|
|
2972
2973
|
app.isQuitting = true;
|
|
2973
2974
|
flushBrowserStorage(); // x.com/google oturumları (cookies) diske yazılsın
|
|
2974
2975
|
try { toolsMod.disposeShellSessions(); } catch {} // kalıcı shell oturumlarını kapat
|
|
2976
|
+
try { require('./agent/mcp').stopAll(); } catch {} // MCP server süreçlerini kapat
|
|
2975
2977
|
});
|
|
2976
2978
|
|
|
2977
2979
|
if (process.argv.includes('--smoke')) {
|
|
@@ -4977,6 +4979,41 @@ ipcMain.handle('settings:get', () => {
|
|
|
4977
4979
|
return out;
|
|
4978
4980
|
});
|
|
4979
4981
|
|
|
4982
|
+
/* ---------------- MCP IPC ---------------- */
|
|
4983
|
+
|
|
4984
|
+
ipcMain.handle('mcp:status', () => {
|
|
4985
|
+
try { return mcpMod.status(); } catch { return { path: '', servers: [] }; }
|
|
4986
|
+
});
|
|
4987
|
+
|
|
4988
|
+
ipcMain.handle('mcp:config:get', () => {
|
|
4989
|
+
try {
|
|
4990
|
+
const p = mcpMod.configPath();
|
|
4991
|
+
let raw = '';
|
|
4992
|
+
try { raw = require('fs').readFileSync(p, 'utf8'); } catch {}
|
|
4993
|
+
return { path: p, raw };
|
|
4994
|
+
} catch { return { path: '', raw: '' }; }
|
|
4995
|
+
});
|
|
4996
|
+
|
|
4997
|
+
ipcMain.handle('mcp:config:set', (_e, raw) => {
|
|
4998
|
+
try {
|
|
4999
|
+
const text = String(raw || '');
|
|
5000
|
+
if (text.trim()) JSON.parse(text); /* bozuk JSON diske yazılmaz */
|
|
5001
|
+
mcpMod.saveConfig(text.trim() ? JSON.parse(text) : { servers: {} });
|
|
5002
|
+
return { ok: true };
|
|
5003
|
+
} catch (e) {
|
|
5004
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
5005
|
+
}
|
|
5006
|
+
});
|
|
5007
|
+
|
|
5008
|
+
ipcMain.handle('mcp:refresh', async (_e, name) => {
|
|
5009
|
+
try {
|
|
5010
|
+
const ok = await mcpMod.refresh(String(name || ''));
|
|
5011
|
+
return { ok, status: mcpMod.status() };
|
|
5012
|
+
} catch (e) {
|
|
5013
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
5014
|
+
}
|
|
5015
|
+
});
|
|
5016
|
+
|
|
4980
5017
|
/* ---------------- FALLOUT IPC ---------------- */
|
|
4981
5018
|
|
|
4982
5019
|
function defaultFallout() {
|
package/src/preload.js
CHANGED
|
@@ -42,6 +42,10 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
42
42
|
getSettings: () => ipcRenderer.invoke('settings:get'),
|
|
43
43
|
getFallout: () => ipcRenderer.invoke('fallout:get'),
|
|
44
44
|
setFallout: (cfg) => ipcRenderer.invoke('fallout:set', cfg),
|
|
45
|
+
mcpStatus: () => ipcRenderer.invoke('mcp:status'),
|
|
46
|
+
mcpConfigGet: () => ipcRenderer.invoke('mcp:config:get'),
|
|
47
|
+
mcpConfigSet: (raw) => ipcRenderer.invoke('mcp:config:set', raw),
|
|
48
|
+
mcpRefresh: (name) => ipcRenderer.invoke('mcp:refresh', name),
|
|
45
49
|
getLimits: () => ipcRenderer.invoke('limits:get'),
|
|
46
50
|
setLimits: (cfg) => ipcRenderer.invoke('limits:set', cfg),
|
|
47
51
|
secGet: () => ipcRenderer.invoke('sec:get'),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -66,6 +66,23 @@
|
|
|
66
66
|
tab_email: 'E-posta',
|
|
67
67
|
tab_integrations: 'Entegrasyonlar',
|
|
68
68
|
tab_websearch: 'Web Arama',
|
|
69
|
+
tab_mcp: 'MCP',
|
|
70
|
+
mcp_h2: 'MCP Serverlar',
|
|
71
|
+
mcp_sub: 'Model Context Protocol — dış araç serverları (filesystem, git, fetch, memory…). Kaynak: mcp.json (aşağıda düzenlenir). Araçlar modele mcp__server__tool adıyla açılır, riskli sayılır (onay kapısı açıkken onay ister).',
|
|
72
|
+
mcp_state_up: 'BAĞLI',
|
|
73
|
+
mcp_state_starting: 'BAĞLANIYOR',
|
|
74
|
+
mcp_state_down: 'HATA',
|
|
75
|
+
mcp_state_idle: 'BEKLEMEDE',
|
|
76
|
+
mcp_state_disabled: 'KAPALI',
|
|
77
|
+
mcp_refresh: 'Yenile',
|
|
78
|
+
mcp_tool_count: 'araç',
|
|
79
|
+
mcp_json_h2: 'mcp.json',
|
|
80
|
+
mcp_json_sub: 'Server tanımı: {"servers":{"ad":{"command":"uvx","args":["mcp-server-fetch"],"enabled":true,"tools":["izinli_araç"],"timeoutMs":120000}}}',
|
|
81
|
+
mcp_save: 'Kaydet',
|
|
82
|
+
mcp_saved: 'Kaydedildi — serverlar ilk kullanımda bağlanır, çökerse 3 dk sonra yeniden denenir',
|
|
83
|
+
mcp_save_err: 'JSON hatası: ',
|
|
84
|
+
mcp_none: 'Henüz server yok — aşağıdaki örnekten başla',
|
|
85
|
+
mcp_open_folder: 'Klasörü Aç',
|
|
69
86
|
tab_limits: 'Limit Ayarları',
|
|
70
87
|
tab_security: 'Güvenlik',
|
|
71
88
|
tab_update: 'Güncelleme',
|
|
@@ -610,6 +627,23 @@
|
|
|
610
627
|
tab_email: 'E-mail',
|
|
611
628
|
tab_integrations: 'Integrations',
|
|
612
629
|
tab_websearch: 'Web Search',
|
|
630
|
+
tab_mcp: 'MCP',
|
|
631
|
+
mcp_h2: 'MCP Servers',
|
|
632
|
+
mcp_sub: 'Model Context Protocol — external tool servers (filesystem, git, fetch, memory…). Source: mcp.json (editable below). Tools are exposed to the model as mcp__server__tool and count as risky (approval gate applies when enabled).',
|
|
633
|
+
mcp_state_up: 'CONNECTED',
|
|
634
|
+
mcp_state_starting: 'CONNECTING',
|
|
635
|
+
mcp_state_down: 'ERROR',
|
|
636
|
+
mcp_state_idle: 'IDLE',
|
|
637
|
+
mcp_state_disabled: 'OFF',
|
|
638
|
+
mcp_refresh: 'Refresh',
|
|
639
|
+
mcp_tool_count: 'tools',
|
|
640
|
+
mcp_json_h2: 'mcp.json',
|
|
641
|
+
mcp_json_sub: 'Server shape: {"servers":{"name":{"command":"uvx","args":["mcp-server-fetch"],"enabled":true,"tools":["allowed_tool"],"timeoutMs":120000}}}',
|
|
642
|
+
mcp_save: 'Save',
|
|
643
|
+
mcp_saved: 'Saved — servers connect on first use, auto-retry after 3 min on failure',
|
|
644
|
+
mcp_save_err: 'JSON error: ',
|
|
645
|
+
mcp_none: 'No servers yet — start from the example below',
|
|
646
|
+
mcp_open_folder: 'Open Folder',
|
|
613
647
|
tab_limits: 'Limits',
|
|
614
648
|
tab_security: 'Security',
|
|
615
649
|
tab_update: 'Update',
|
package/src/renderer/index.html
CHANGED
|
@@ -196,6 +196,7 @@
|
|
|
196
196
|
<button class="tab" data-tab="email" data-i18n="tab_email">E-posta</button>
|
|
197
197
|
<button class="tab" data-tab="integrations" data-i18n="tab_integrations">Entegrasyonlar</button>
|
|
198
198
|
<button class="tab" data-tab="websearch" data-i18n="tab_websearch">Web Arama</button>
|
|
199
|
+
<button class="tab" data-tab="mcp" data-i18n="tab_mcp">MCP</button>
|
|
199
200
|
<button class="tab" data-tab="events" data-i18n="tab_events">Olay Merkezi</button>
|
|
200
201
|
<button class="tab" data-tab="cron" data-i18n="tab_cron">Cron</button>
|
|
201
202
|
<button class="tab" data-tab="usage" data-i18n="tab_usage">Maliyet · Limit</button>
|
|
@@ -219,6 +220,7 @@
|
|
|
219
220
|
<div id="tab-email" class="pane" hidden></div>
|
|
220
221
|
<div id="tab-integrations" class="pane" hidden></div>
|
|
221
222
|
<div id="tab-websearch" class="pane" hidden></div>
|
|
223
|
+
<div id="tab-mcp" class="pane" hidden></div>
|
|
222
224
|
<div id="tab-events" class="pane" hidden></div>
|
|
223
225
|
<div id="tab-usage" class="pane" hidden></div>
|
|
224
226
|
<div id="tab-logs" class="pane" hidden></div>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -1013,6 +1013,7 @@ async function renderActiveSettingsTab() {
|
|
|
1013
1013
|
case 'email': await renderEmailPane(); break;
|
|
1014
1014
|
case 'integrations': await renderIntegrationsPane(); break;
|
|
1015
1015
|
case 'websearch': await renderWebSearchPane(); break;
|
|
1016
|
+
case 'mcp': await renderMcpPane(); break;
|
|
1016
1017
|
case 'events': await renderEventsPane(); break;
|
|
1017
1018
|
case 'cron': await openCron(); break;
|
|
1018
1019
|
case 'usage': await renderUsagePane(); break;
|
|
@@ -1024,12 +1025,82 @@ async function renderActiveSettingsTab() {
|
|
|
1024
1025
|
}
|
|
1025
1026
|
}
|
|
1026
1027
|
|
|
1028
|
+
async function renderMcpPane() {
|
|
1029
|
+
const pane = $('#tab-mcp');
|
|
1030
|
+
if (!pane) return;
|
|
1031
|
+
const st = await beast.mcpStatus().catch(() => ({ path: '', servers: [] }));
|
|
1032
|
+
const cfg = await beast.mcpConfigGet().catch(() => ({ path: '', raw: '' }));
|
|
1033
|
+
const stateT = (s) => _t('mcp_state_' + s) || String(s || '').toUpperCase();
|
|
1034
|
+
let rows = '';
|
|
1035
|
+
if (!(st.servers || []).length) {
|
|
1036
|
+
rows = '<div class="sub">' + _t('mcp_none') + '</div>';
|
|
1037
|
+
}
|
|
1038
|
+
for (const s of st.servers || []) {
|
|
1039
|
+
rows +=
|
|
1040
|
+
'<div style="display:flex;align-items:center;gap:8px;padding:8px;border:1px solid var(--border);border-radius:8px;margin-bottom:6px;flex-wrap:wrap">' +
|
|
1041
|
+
'<span style="font-weight:600">' + escapeHtml(s.name) + '</span>' +
|
|
1042
|
+
'<span class="sub" style="margin:0;max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escapeHtml(s.command + ' ' + (s.args || []).join(' ')) + '</span>' +
|
|
1043
|
+
'<span style="color:var(--muted);font-size:12px">' + stateT(s.state) + (s.enabled && s.toolCount ? ' · ' + s.toolCount + ' ' + _t('mcp_tool_count') : '') + '</span>' +
|
|
1044
|
+
(s.lastError && s.state === 'down' ? '<span class="sub" style="color:#e06c75;margin:0;max-width:30%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escapeHtml(s.lastError.slice(0, 90)) + '</span>' : '') +
|
|
1045
|
+
'<span style="flex:1"></span>' +
|
|
1046
|
+
'<button class="btn mcp-tgl" data-n="' + escapeHtml(s.name) + '" style="width:auto;padding:2px 10px">' + (s.enabled ? '⏸' : '▶') + '</button>' +
|
|
1047
|
+
'<button class="btn ghost mcp-ref" data-n="' + escapeHtml(s.name) + '" style="width:auto;padding:2px 10px">' + _t('mcp_refresh') + '</button>' +
|
|
1048
|
+
'</div>';
|
|
1049
|
+
}
|
|
1050
|
+
pane.innerHTML =
|
|
1051
|
+
'<h2>' + _t('mcp_h2') + '</h2>' +
|
|
1052
|
+
'<div class="sub">' + _t('mcp_sub') + '</div>' +
|
|
1053
|
+
'<div id="mcpList" style="margin-top:10px">' + rows + '</div>' +
|
|
1054
|
+
'<div class="divider"></div>' +
|
|
1055
|
+
'<h2>' + _t('mcp_json_h2') + '</h2>' +
|
|
1056
|
+
'<div class="sub">' + _t('mcp_json_sub') + '</div>' +
|
|
1057
|
+
'<textarea id="mcpJson" class="inp" rows="10" spellcheck="false" style="margin-top:8px;font-family:monospace;font-size:12px"></textarea>' +
|
|
1058
|
+
'<div style="display:flex;gap:8px;margin-top:8px">' +
|
|
1059
|
+
'<button id="mcpSave" class="btn">' + _t('mcp_save') + '</button>' +
|
|
1060
|
+
'<span id="mcpMsg" class="sub" style="margin:0;align-self:center"></span></div>';
|
|
1061
|
+
const box = $('#mcpJson');
|
|
1062
|
+
box.value = cfg.raw || JSON.stringify({ servers: {} }, null, 2);
|
|
1063
|
+
$('#mcpSave').addEventListener('click', async () => {
|
|
1064
|
+
const msg = $('#mcpMsg');
|
|
1065
|
+
const r = await beast.mcpConfigSet(box.value).catch(() => ({ ok: false, error: 'IPC' }));
|
|
1066
|
+
if (r && r.ok) {
|
|
1067
|
+
msg.textContent = _t('mcp_saved');
|
|
1068
|
+
msg.style.color = '';
|
|
1069
|
+
renderMcpPane();
|
|
1070
|
+
} else {
|
|
1071
|
+
msg.textContent = _t('mcp_save_err') + ((r && r.error) || '?');
|
|
1072
|
+
msg.style.color = '#e06c75';
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
pane.querySelectorAll('.mcp-ref').forEach((b) =>
|
|
1076
|
+
b.addEventListener('click', async () => {
|
|
1077
|
+
b.disabled = true;
|
|
1078
|
+
await beast.mcpRefresh(b.dataset.n).catch(() => {});
|
|
1079
|
+
renderMcpPane();
|
|
1080
|
+
})
|
|
1081
|
+
);
|
|
1082
|
+
pane.querySelectorAll('.mcp-tgl').forEach((b) =>
|
|
1083
|
+
b.addEventListener('click', async () => {
|
|
1084
|
+
b.disabled = true;
|
|
1085
|
+
try {
|
|
1086
|
+
const obj = JSON.parse(box.value || '{}');
|
|
1087
|
+
const name = b.dataset.n;
|
|
1088
|
+
if (obj.servers && obj.servers[name]) {
|
|
1089
|
+
obj.servers[name].enabled = obj.servers[name].enabled === false;
|
|
1090
|
+
await beast.mcpConfigSet(JSON.stringify(obj));
|
|
1091
|
+
}
|
|
1092
|
+
} catch {}
|
|
1093
|
+
renderMcpPane();
|
|
1094
|
+
})
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1027
1098
|
function switchTab(name) {
|
|
1028
1099
|
setTab = name;
|
|
1029
1100
|
document.querySelectorAll('#setTabs .tab').forEach((b) =>
|
|
1030
1101
|
b.classList.toggle('active', b.dataset.tab === name)
|
|
1031
1102
|
);
|
|
1032
|
-
for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'email', 'integrations', 'websearch', 'events', 'cron', 'usage', 'logs', 'dash', 'sec', 'update']) {
|
|
1103
|
+
for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'email', 'integrations', 'websearch', 'mcp', 'events', 'cron', 'usage', 'logs', 'dash', 'sec', 'update']) {
|
|
1033
1104
|
const el = $('#tab-' + p);
|
|
1034
1105
|
if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
|
|
1035
1106
|
}
|
|
@@ -1043,6 +1114,7 @@ function switchTab(name) {
|
|
|
1043
1114
|
if (name === 'update') renderUpdatePane(true);
|
|
1044
1115
|
if (name === 'agents') refreshAgentsPane();
|
|
1045
1116
|
if (name === 'websearch') renderWebSearchPane();
|
|
1117
|
+
if (name === 'mcp') renderMcpPane();
|
|
1046
1118
|
/* Fallout: her açılışta güncel provider zincirini çek */
|
|
1047
1119
|
if (name === 'fallout') refreshFalloutPane();
|
|
1048
1120
|
}
|
package/src/renderer/style.css
CHANGED
|
@@ -392,7 +392,7 @@ body.term-open #topbar { width: auto; }
|
|
|
392
392
|
display: flex;
|
|
393
393
|
align-items: center;
|
|
394
394
|
gap: 8px;
|
|
395
|
-
padding: 0 10px;
|
|
395
|
+
padding: 0 148px 0 10px; /* sağ: sabit pencere butonları (win-controls) alanı */
|
|
396
396
|
border-bottom: 1px solid var(--border);
|
|
397
397
|
}
|
|
398
398
|
#termTitle { font-size: 11px; font-weight: 800; letter-spacing: 1px; color: var(--accent); flex: none; }
|
|
@@ -503,13 +503,18 @@ body.term-open #settingsOverlay { right: var(--tw, 520px); }
|
|
|
503
503
|
.drag-spacer { flex: 1; height: 100%; -webkit-app-region: drag; }
|
|
504
504
|
|
|
505
505
|
/* ---------- custom pencere butonları (native overlay yerine) ----------
|
|
506
|
-
|
|
507
|
-
|
|
506
|
+
PENCERENİN sağ üst köşesine SABİT: tarayıcı/terminal/paralel ajan konsolu
|
|
507
|
+
hangi panel açılırsa açılsın butonlar hep en sağda kalır (panellerin
|
|
508
|
+
başlık satırları bu alanı boş bırakır). Zemin yok — altındaki yüzeyde
|
|
509
|
+
yüzür; hover'da accent-dim, kapat kırmızı. */
|
|
508
510
|
.win-controls {
|
|
511
|
+
position: fixed;
|
|
512
|
+
top: 0;
|
|
513
|
+
right: 0;
|
|
514
|
+
z-index: 100;
|
|
509
515
|
display: flex;
|
|
510
516
|
align-items: center;
|
|
511
|
-
height:
|
|
512
|
-
margin-left: -4px;
|
|
517
|
+
height: 46px;
|
|
513
518
|
-webkit-app-region: no-drag;
|
|
514
519
|
}
|
|
515
520
|
.win-controls button {
|
|
@@ -1805,7 +1810,7 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
|
|
|
1805
1810
|
display: flex;
|
|
1806
1811
|
align-items: center;
|
|
1807
1812
|
gap: 8px;
|
|
1808
|
-
padding: 12px
|
|
1813
|
+
padding: 12px 148px 10px 16px; /* sağ: sabit pencere butonları (win-controls) alanı */
|
|
1809
1814
|
font-weight: 800;
|
|
1810
1815
|
letter-spacing: 1px;
|
|
1811
1816
|
font-size: 13px;
|