beast-agent 2.7.2 → 2.8.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 +33 -1
- package/src/agent/pdfwrite.js +528 -0
- package/src/agent/skills.js +18 -5
- package/src/main.js +56 -11
- package/src/renderer/renderer.js +13 -0
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -18,6 +18,7 @@ const nightref = require('./nightref');
|
|
|
18
18
|
const skills = require('./skills');
|
|
19
19
|
const mcp = require('./mcp');
|
|
20
20
|
const apps = require('./apps');
|
|
21
|
+
const pdfwrite = require('./pdfwrite');
|
|
21
22
|
const { estTokens, estMsgTokens } = require('./tokens');
|
|
22
23
|
const log = require('./logger');
|
|
23
24
|
|
|
@@ -1661,7 +1662,7 @@ class Engine {
|
|
|
1661
1662
|
'CONUŞMA ODAĞI SENDE KALSIN: kullanıcı seninle konuşurken iş çıkmışsa — uzun da olsa UFACIK da (tek komutluk dizin listesi, tek dosya okuma, tek arama…) — run_background ile PARALEL ajana devret; ana sohbet hiçbir işi beklemez; bittiğinde özet otomatik düşer.',
|
|
1662
1663
|
'Python işleri için python_run kullan: küçük betikler inline code ile; tekrarlayan işler %APPDATA%\\beast\\scripts klasöründeki dosyalarla (ör. news.py = RSS haber toplayıcı: args ["--limit","8","--json"]). Python kurulu olmasa bile ilk çağrıda taşınabilir gömülü runtime otomatik iner.',
|
|
1663
1664
|
'PYTHON DURUMU: makinede sistem Python\'u görünmese bile ŞAŞIRMA ve "python yok" DEME — python_run aracı kendi taşınabilir runtime\'ını (%APPDATA%\\beast\\py\\python.exe) otomatik indirir/kullanır ve bu klasör run_command PATH\'inde önceliklidir; yani run_command içinde de `python` çalışır. Ham Google/Bing scrape yerine önce web_search aracını kullan (SearXNG + stealth TLS + TinyFish + Python çoklu-motor destekli), script gerektiğinde python_run yaz.',
|
|
1664
|
-
'PDF ÇIKTI KURALI: PDF üretirken pip\u2019ten pdf paketi (fpdf, fpdf2, markdown-pdf, weasyprint, reportlab vb.) KURMA/KULLANMA — bunlar Türkçe karakterleri bozar.
|
|
1665
|
+
'PDF ÇIKTI KURALI: PDF üretirken pip\u2019ten pdf paketi (fpdf, fpdf2, markdown-pdf, weasyprint, reportlab vb.) KURMA/KULLANMA — bunlar Türkçe karakterleri bozar. İLK TERCİH pdf_write aracı: tek çağrıda başlık+markdown-lite içeriği düzenli, Türkçe-güvenli PDF\u2019e çevirir (Windows fontu otomatik gömülür; ğ ş ı İ bozulmaz) — script YAZMA, pip KULLANMA. Sıradışı ihtiyaç (form doldurma, PDF birleştirme, sayfa işleme) için doğru kit Node\u2019ta ZATEN kurulu: `pdf-lib` + `@pdf-lib/fontkit` ve `pdfkit` — o zaman write_file ile .js script yazıp run_command ile `node script.js` çalıştır (python_run İLE DEĞİL). md→pdf çevirici YOKTUR ve kurulmaz: kullanıcıya rapor/özet/belge çıktısı vereceksen .md dosyası gönderme — pdf_write ile DOĞRUDAN PDF üret ve send_file ile gönder. Ayrıntılı örnekler: pdf skill\u2019i (SKILL.md).',
|
|
1665
1666
|
'Eski bir hafıza kaydına ihtiyacın olursa memory_search ile ara; kalıcı bilgi/birikim için kb_search kullan, yeni bilgi öğrenirsen kb_add ile kaynak belirt.',
|
|
1666
1667
|
'Bir oturumda 3+ kez memory_write yaptıysan iş bitince memory_hygiene çağır (duplike/eskime temizliği).',
|
|
1667
1668
|
'BEAST KAYNAK KORUMASI: Beast\u2019in kendi kurulum/kaynak kod klasörü KİLİTLİDİR — oraya dosya yazamaz, silamaz, komut/betikle değiştiremezsin; okumak serbest. Kullanıcı kodu ancak kendi eliyle dışarıdan değiştirir; böyle bir istek gelirse yapamayacağını söyle ve kullanıcının elle yapması için yol göster.',
|
|
@@ -2350,6 +2351,9 @@ class Engine {
|
|
|
2350
2351
|
) {
|
|
2351
2352
|
lastMsg.content = (lastMsg.content + '\n\n' + body).slice(0, USER_MAX);
|
|
2352
2353
|
this._rewriteLastMsg(s.id, lastMsg.content, null, 'assistant');
|
|
2354
|
+
/* UI'ya bildir: birleştirme yeni bubble basmaz — son asistan balonu
|
|
2355
|
+
güncellenir (yoksa proaktif not ekranda hiç görünmezdi) */
|
|
2356
|
+
this.emit({ type: 'message-merge', sessionId: s.id, message: lastMsg });
|
|
2353
2357
|
this.emit({ type: 'sessions' });
|
|
2354
2358
|
return true;
|
|
2355
2359
|
}
|
|
@@ -4206,6 +4210,16 @@ const skills = require('./skills');
|
|
|
4206
4210
|
const r = await this.fileSend(sessionId, p, String((args && args.caption) || ''));
|
|
4207
4211
|
return JSON.stringify(r);
|
|
4208
4212
|
}
|
|
4213
|
+
if (name === 'pdf_write') {
|
|
4214
|
+
const r = await pdfwrite.write({
|
|
4215
|
+
outPath: String((args && args.path) || ''),
|
|
4216
|
+
title: String((args && args.title) || ''),
|
|
4217
|
+
subtitle: String((args && args.subtitle) || ''),
|
|
4218
|
+
content: String((args && args.content) || ''),
|
|
4219
|
+
workspace: this.workspace,
|
|
4220
|
+
});
|
|
4221
|
+
return JSON.stringify(r);
|
|
4222
|
+
}
|
|
4209
4223
|
if (name === 'run_background') {
|
|
4210
4224
|
if (args.agent && !agentdefs.get(String(args.agent))) {
|
|
4211
4225
|
return JSON.stringify({
|
|
@@ -4902,6 +4916,24 @@ const TOOLS = [
|
|
|
4902
4916
|
},
|
|
4903
4917
|
},
|
|
4904
4918
|
},
|
|
4919
|
+
{
|
|
4920
|
+
type: 'function',
|
|
4921
|
+
function: {
|
|
4922
|
+
name: 'pdf_write',
|
|
4923
|
+
description:
|
|
4924
|
+
'Create a polished, Turkish-safe PDF document in ONE call — NO script writing, NO pip packages (Turkish characters ğ ş ı İ Ğ Ş İ Ö Ü render perfectly; a Windows font is embedded automatically). content = markdown-lite: "# ## ###" headings, plain paragraphs, "- " bullets, "1. " numbered items, **bold**, `mono`, "> " quote, "```" code blocks, "| col | col |" table rows. Then call send_file with the returned path to deliver it. Use this for reports/summaries/notes/documents instead of writing a script file.',
|
|
4925
|
+
parameters: {
|
|
4926
|
+
type: 'object',
|
|
4927
|
+
properties: {
|
|
4928
|
+
path: { type: 'string', description: 'Output file path (absolute or workspace-relative; .pdf is added if missing)' },
|
|
4929
|
+
title: { type: 'string', description: 'Document title shown at the top (optional but recommended)' },
|
|
4930
|
+
subtitle: { type: 'string', description: 'Small line under the title (date, author, subject…)' },
|
|
4931
|
+
content: { type: 'string', description: 'Markdown-lite body: headings, paragraphs, lists, **bold**, tables, code blocks' },
|
|
4932
|
+
},
|
|
4933
|
+
required: ['path', 'content'],
|
|
4934
|
+
},
|
|
4935
|
+
},
|
|
4936
|
+
},
|
|
4905
4937
|
{
|
|
4906
4938
|
type: 'function',
|
|
4907
4939
|
function: {
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast PDF ÜRETİCİ — tek çağrıda kusursuz, Türkçe-güvenli PDF.
|
|
4
|
+
pdf-lib + @pdf-lib/fontkit ile Windows TTF gömer (ğ ş ı İ Ğ Ş İ Ö Ü garantili;
|
|
5
|
+
Helvetica gibi standard fontlar bunları BASAMAZ). Markdown-lite ayrıştırır:
|
|
6
|
+
# ## ### başlıklar, paragraf, -/* bullet, 1. numaralı liste, **bold**,
|
|
7
|
+
`kod`, | tablo satırları |, > alıntı, ``` kod bloğu. Otomatik satır
|
|
8
|
+
kaydırma + sayfa geçişi + sayfa numarası + başlık bloğu.
|
|
9
|
+
Ajanın elle .js script yazıp font yolu/uğraşmasına GEREK YOK — pdf_write aracı bunu çağırır. */
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
/* Türkçe glifleri TAM kapsayan Windows sistem fontları — sırayla denenir.
|
|
15
|
+
[normal, bold, mono] */
|
|
16
|
+
const FONT_SETS = [
|
|
17
|
+
['C:/Windows/Fonts/segoeui.ttf', 'C:/Windows/Fonts/segoeuib.ttf', 'C:/Windows/Fonts/consola.ttf'],
|
|
18
|
+
['C:/Windows/Fonts/arial.ttf', 'C:/Windows/Fonts/arialbd.ttf', 'C:/Windows/Fonts/consola.ttf'],
|
|
19
|
+
['C:/Windows/Fonts/calibri.ttf', 'C:/Windows/Fonts/calibrib.ttf', 'C:/Windows/Fonts/consola.ttf'],
|
|
20
|
+
['C:/Windows/Fonts/tahoma.ttf', 'C:/Windows/Fonts/tahomabd.ttf', 'C:/Windows/Fonts/consola.ttf'],
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function firstFont() {
|
|
24
|
+
for (const set of FONT_SETS) {
|
|
25
|
+
if (set.slice(0, 2).every((f) => fs.existsSync(f))) return set;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function hasFonts() {
|
|
31
|
+
return !!firstFont();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const CONTENT_CAP = 300000;
|
|
35
|
+
|
|
36
|
+
/* ---------- markdown-lite → bloklar ---------- */
|
|
37
|
+
|
|
38
|
+
function parseBlocks(md) {
|
|
39
|
+
const lines = String(md || '').replace(/\r\n?/g, '\n').split('\n');
|
|
40
|
+
const blocks = [];
|
|
41
|
+
let i = 0;
|
|
42
|
+
while (i < lines.length) {
|
|
43
|
+
const raw = lines[i];
|
|
44
|
+
const line = raw.trim();
|
|
45
|
+
if (!line) {
|
|
46
|
+
i++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (/^```/.test(line)) {
|
|
50
|
+
const buf = [];
|
|
51
|
+
i++;
|
|
52
|
+
while (i < lines.length && !/^```/.test(lines[i].trim())) {
|
|
53
|
+
buf.push(lines[i].replace(/\t/g, ' '));
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
i++; // kapanış fence (ya da EOF)
|
|
57
|
+
blocks.push({ type: 'code', lines: buf });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
let m;
|
|
61
|
+
if ((m = line.match(/^(#{1,3})\s+(.*)$/))) {
|
|
62
|
+
blocks.push({ type: 'h', level: m[1].length, text: m[2].replace(/\s*#+\s*$/, '') });
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(line)) {
|
|
67
|
+
blocks.push({ type: 'hr' });
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if ((m = line.match(/^>\s?(.*)$/))) {
|
|
72
|
+
blocks.push({ type: 'quote', text: m[1] });
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if ((m = line.match(/^[-*•]\s+(.*)$/))) {
|
|
77
|
+
blocks.push({ type: 'li', marker: '\u2022', text: m[1] });
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if ((m = line.match(/^(\d{1,3})[.)]\s+(.*)$/))) {
|
|
82
|
+
blocks.push({ type: 'li', marker: m[1] + '.', text: m[2] });
|
|
83
|
+
i++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (line.startsWith('|')) {
|
|
87
|
+
const rows = [];
|
|
88
|
+
while (i < lines.length && lines[i].trim().startsWith('|')) {
|
|
89
|
+
const cells = lines[i]
|
|
90
|
+
.trim()
|
|
91
|
+
.replace(/^\|/, '')
|
|
92
|
+
.replace(/\|$/, '')
|
|
93
|
+
.split('|')
|
|
94
|
+
.map((c) => c.trim());
|
|
95
|
+
if (!cells.every((c) => /^:?-{2,}:?$/.test(c) || c === '')) rows.push(cells);
|
|
96
|
+
i++;
|
|
97
|
+
}
|
|
98
|
+
if (rows.length) blocks.push({ type: 'table', rows });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
/* paragraf: özel blok başlatmayan ardışık satırlar birleşir */
|
|
102
|
+
const para = [line];
|
|
103
|
+
i++;
|
|
104
|
+
while (i < lines.length) {
|
|
105
|
+
const l2 = lines[i].trim();
|
|
106
|
+
if (
|
|
107
|
+
!l2 ||
|
|
108
|
+
/^(#{1,3}\s|[-*•]\s|\d{1,3}[.)]\s|>|```|\|)/.test(l2) ||
|
|
109
|
+
/^(-{3,}|\*{3,}|_{3,})$/.test(l2)
|
|
110
|
+
)
|
|
111
|
+
break;
|
|
112
|
+
para.push(l2);
|
|
113
|
+
i++;
|
|
114
|
+
}
|
|
115
|
+
blocks.push({ type: 'p', text: para.join(' ') });
|
|
116
|
+
}
|
|
117
|
+
return blocks;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ---------- inline: **bold** ve `mono` → span dizisi ---------- */
|
|
121
|
+
|
|
122
|
+
function toSpans(text) {
|
|
123
|
+
const spans = [];
|
|
124
|
+
const re = /(\*\*([^*]+)\*\*)|(`([^`]+)`)/g;
|
|
125
|
+
let last = 0;
|
|
126
|
+
let m;
|
|
127
|
+
while ((m = re.exec(text))) {
|
|
128
|
+
if (m.index > last) spans.push({ t: text.slice(last, m.index), b: false });
|
|
129
|
+
if (m[1]) spans.push({ t: m[2], b: true });
|
|
130
|
+
else spans.push({ t: m[4], b: false, mono: true });
|
|
131
|
+
last = re.lastIndex;
|
|
132
|
+
}
|
|
133
|
+
if (last < text.length) spans.push({ t: text.slice(last), b: false });
|
|
134
|
+
return spans.length ? spans : [{ t: '', b: false }];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ---------- satır kaydırma: span'ler → [token] satırları ---------- */
|
|
138
|
+
|
|
139
|
+
function wrapSpans(spans, fontFor, size, maxW) {
|
|
140
|
+
const tokens = [];
|
|
141
|
+
for (const s of spans) {
|
|
142
|
+
const parts = s.t.split(/(\s+)/);
|
|
143
|
+
for (const w of parts) {
|
|
144
|
+
if (!w) continue;
|
|
145
|
+
tokens.push({ w, b: !!s.b, mono: !!s.mono, space: /^\s+$/.test(w) });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const widthOf = (tk) => {
|
|
149
|
+
try {
|
|
150
|
+
return fontFor(tk).widthOfTextAtSize(tk.w, size);
|
|
151
|
+
} catch {
|
|
152
|
+
return tk.w.length * size * 0.6;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const lines = [];
|
|
156
|
+
let cur = [];
|
|
157
|
+
let curW = 0;
|
|
158
|
+
const flush = () => {
|
|
159
|
+
while (cur.length && cur[cur.length - 1].space) cur.pop();
|
|
160
|
+
if (cur.length) lines.push(cur);
|
|
161
|
+
cur = [];
|
|
162
|
+
curW = 0;
|
|
163
|
+
};
|
|
164
|
+
for (const tk of tokens) {
|
|
165
|
+
if (tk.space) {
|
|
166
|
+
if (cur.length) {
|
|
167
|
+
cur.push(tk);
|
|
168
|
+
curW += widthOf(tk);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
let w = widthOf(tk);
|
|
173
|
+
/* tek kelime satırdan genişse karakter karakter böl (uzun URL/link emniyeti) */
|
|
174
|
+
if (w > maxW) {
|
|
175
|
+
flush();
|
|
176
|
+
let chunk = '';
|
|
177
|
+
let chunkW = 0;
|
|
178
|
+
for (const ch of tk.w) {
|
|
179
|
+
const cw = (() => {
|
|
180
|
+
try {
|
|
181
|
+
return fontFor(tk).widthOfTextAtSize(ch, size);
|
|
182
|
+
} catch {
|
|
183
|
+
return size * 0.6;
|
|
184
|
+
}
|
|
185
|
+
})();
|
|
186
|
+
if (chunk && chunkW + cw > maxW) {
|
|
187
|
+
lines.push([{ w: chunk, b: tk.b, mono: tk.mono, space: false, hard: true }]);
|
|
188
|
+
chunk = ch;
|
|
189
|
+
chunkW = cw;
|
|
190
|
+
} else {
|
|
191
|
+
chunk += ch;
|
|
192
|
+
chunkW += cw;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (chunk) {
|
|
196
|
+
cur = [{ w: chunk, b: tk.b, mono: tk.mono, space: false }];
|
|
197
|
+
curW = chunkW;
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (curW + w > maxW) flush();
|
|
202
|
+
cur.push(tk);
|
|
203
|
+
curW += w;
|
|
204
|
+
}
|
|
205
|
+
flush();
|
|
206
|
+
return lines;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* drawTextSpan'lı satır: token dizisini çizer, son x döner */
|
|
210
|
+
function drawTokenLine(page, line, x, y, fontFor, size, color) {
|
|
211
|
+
let cx = x;
|
|
212
|
+
for (const tk of line) {
|
|
213
|
+
if (tk.space) {
|
|
214
|
+
try {
|
|
215
|
+
cx += fontFor(tk).widthOfTextAtSize(tk.w, size);
|
|
216
|
+
} catch {
|
|
217
|
+
cx += tk.w.length * size * 0.6;
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
page.drawText(tk.w, { x: cx, y, size, font: fontFor(tk), color });
|
|
223
|
+
cx += fontFor(tk).widthOfTextAtSize(tk.w, size);
|
|
224
|
+
} catch {
|
|
225
|
+
cx += tk.w.length * size * 0.6;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return cx;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function lineCount(lines) {
|
|
232
|
+
return Math.max(1, lines.length);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ---------- ana üretici ---------- */
|
|
236
|
+
|
|
237
|
+
async function writePdf({ outPath, title, subtitle, content, workspace }) {
|
|
238
|
+
try {
|
|
239
|
+
const raw = String((content == null ? '' : content) || '');
|
|
240
|
+
if (!raw.trim()) return { ok: false, error: 'content gerekli' };
|
|
241
|
+
const outRaw = String(outPath || '').trim();
|
|
242
|
+
if (!outRaw) return { ok: false, error: 'path gerekli' };
|
|
243
|
+
let abs = path.isAbsolute(outRaw) ? outRaw : path.join(String(workspace || process.cwd()), outRaw);
|
|
244
|
+
if (!/\.pdf$/i.test(abs)) abs += '.pdf';
|
|
245
|
+
|
|
246
|
+
const fonts = firstFont();
|
|
247
|
+
if (!fonts) {
|
|
248
|
+
return {
|
|
249
|
+
ok: false,
|
|
250
|
+
error:
|
|
251
|
+
'Türkçe destekli Windows TTF bulunamadı (Segoe UI/Arial/Calibri/Tahoma) — pdf skill\u2019indeki elle şablonu kullan',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const { PDFDocument, rgb } = require('pdf-lib');
|
|
256
|
+
const fontkit = require('@pdf-lib/fontkit');
|
|
257
|
+
const doc = await PDFDocument.create();
|
|
258
|
+
doc.registerFontkit(fontkit);
|
|
259
|
+
const [fReg, fBold, fMono] = fonts;
|
|
260
|
+
const reg = await doc.embedFont(fs.readFileSync(fReg), { subset: true });
|
|
261
|
+
let bold = reg;
|
|
262
|
+
try {
|
|
263
|
+
if (fs.existsSync(fBold)) bold = await doc.embedFont(fs.readFileSync(fBold), { subset: true });
|
|
264
|
+
} catch {}
|
|
265
|
+
let mono = reg;
|
|
266
|
+
try {
|
|
267
|
+
if (fMono && fs.existsSync(fMono)) mono = await doc.embedFont(fs.readFileSync(fMono), { subset: true });
|
|
268
|
+
} catch {}
|
|
269
|
+
|
|
270
|
+
const PAGE_W = 595.28;
|
|
271
|
+
const PAGE_H = 841.89;
|
|
272
|
+
const MX = 56;
|
|
273
|
+
const MT = 64;
|
|
274
|
+
const MB = 76;
|
|
275
|
+
const availW = PAGE_W - 2 * MX;
|
|
276
|
+
const textC = rgb(0.13, 0.13, 0.16);
|
|
277
|
+
const mutedC = rgb(0.42, 0.45, 0.5);
|
|
278
|
+
const accentC = rgb(0.15, 0.35, 0.6);
|
|
279
|
+
const codeBgC = rgb(0.95, 0.96, 0.97);
|
|
280
|
+
const rowBgC = rgb(0.96, 0.96, 0.98);
|
|
281
|
+
|
|
282
|
+
let page = doc.addPage([PAGE_W, PAGE_H]);
|
|
283
|
+
let y = PAGE_H - MT;
|
|
284
|
+
|
|
285
|
+
const newPage = () => {
|
|
286
|
+
page = doc.addPage([PAGE_W, PAGE_H]);
|
|
287
|
+
y = PAGE_H - MT;
|
|
288
|
+
};
|
|
289
|
+
const ensure = (h) => {
|
|
290
|
+
if (y - h < MB) newPage();
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const fontFor = (tk) => (tk.mono ? mono : tk.b ? bold : reg);
|
|
294
|
+
const H_SIZES = { 1: 20, 2: 15.5, 3: 13 };
|
|
295
|
+
|
|
296
|
+
/* başlık bloğu */
|
|
297
|
+
const titleText = String(title || '').trim();
|
|
298
|
+
if (titleText) {
|
|
299
|
+
const tSpans = [{ t: titleText, b: true }];
|
|
300
|
+
const tLines = wrapSpans(tSpans, fontFor, 22, availW);
|
|
301
|
+
for (const ln of tLines) {
|
|
302
|
+
ensure(30);
|
|
303
|
+
drawTokenLine(page, ln, MX, y - 22, fontFor, 22, textC);
|
|
304
|
+
y -= 30;
|
|
305
|
+
}
|
|
306
|
+
const sub = String(subtitle || '').trim();
|
|
307
|
+
if (sub) {
|
|
308
|
+
const sLines = wrapSpans(toSpans(sub), fontFor, 10.5, availW);
|
|
309
|
+
for (const ln of sLines) {
|
|
310
|
+
ensure(16);
|
|
311
|
+
drawTokenLine(page, ln, MX, y - 10, fontFor, 10.5, mutedC);
|
|
312
|
+
y -= 16;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
y -= 6;
|
|
316
|
+
page.drawLine({
|
|
317
|
+
start: { x: MX, y },
|
|
318
|
+
end: { x: PAGE_W - MX, y },
|
|
319
|
+
thickness: 1,
|
|
320
|
+
color: accentC,
|
|
321
|
+
});
|
|
322
|
+
y -= 22;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const blocks = parseBlocks(raw.slice(0, CONTENT_CAP));
|
|
326
|
+
|
|
327
|
+
/* title verildiyse içeriğin başındaki AYNI H1'i at (çift başlık olmasın) */
|
|
328
|
+
if (titleText && blocks.length && blocks[0].type === 'h' && blocks[0].level === 1) {
|
|
329
|
+
const norm = (s) =>
|
|
330
|
+
String(s || '')
|
|
331
|
+
.toLocaleLowerCase('tr')
|
|
332
|
+
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
333
|
+
.trim();
|
|
334
|
+
if (norm(blocks[0].text) === norm(titleText)) blocks.shift();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const BODY = 10.5;
|
|
338
|
+
const LINE = BODY * 1.42;
|
|
339
|
+
|
|
340
|
+
for (const b of blocks) {
|
|
341
|
+
if (b.type === 'h') {
|
|
342
|
+
const size = H_SIZES[b.level] || 13;
|
|
343
|
+
const above = b.level === 1 ? 16 : b.level === 2 ? 13 : 10;
|
|
344
|
+
const below = b.level === 1 ? 10 : 6;
|
|
345
|
+
y -= above;
|
|
346
|
+
ensure(size * 1.4 + below);
|
|
347
|
+
const lines = wrapSpans(toSpans(b.text), fontFor, size, availW);
|
|
348
|
+
for (const ln of lines) {
|
|
349
|
+
ensure(size * 1.45);
|
|
350
|
+
drawTokenLine(page, ln, MX, y - size, fontFor, size, textC);
|
|
351
|
+
y -= size * 1.45;
|
|
352
|
+
}
|
|
353
|
+
if (b.level === 1) {
|
|
354
|
+
y -= 4;
|
|
355
|
+
page.drawLine({
|
|
356
|
+
start: { x: MX, y },
|
|
357
|
+
end: { x: MX + 42, y },
|
|
358
|
+
thickness: 1.4,
|
|
359
|
+
color: accentC,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
y -= below;
|
|
363
|
+
} else if (b.type === 'p') {
|
|
364
|
+
const lines = wrapSpans(toSpans(b.text), fontFor, BODY, availW);
|
|
365
|
+
for (const ln of lines) {
|
|
366
|
+
ensure(LINE);
|
|
367
|
+
drawTokenLine(page, ln, MX, y - BODY, fontFor, BODY, textC);
|
|
368
|
+
y -= LINE;
|
|
369
|
+
}
|
|
370
|
+
y -= LINE * 0.55;
|
|
371
|
+
} else if (b.type === 'li') {
|
|
372
|
+
const lines = wrapSpans(toSpans(b.text), fontFor, BODY, availW - 24);
|
|
373
|
+
const mW = (() => {
|
|
374
|
+
try {
|
|
375
|
+
return bold.widthOfTextAtSize(b.marker, BODY);
|
|
376
|
+
} catch {
|
|
377
|
+
return 12;
|
|
378
|
+
}
|
|
379
|
+
})();
|
|
380
|
+
lines.forEach((ln, idx) => {
|
|
381
|
+
ensure(LINE);
|
|
382
|
+
if (idx === 0) {
|
|
383
|
+
try {
|
|
384
|
+
page.drawText(b.marker, { x: MX + 2, y: y - BODY, size: BODY, font: bold, color: accentC });
|
|
385
|
+
} catch {}
|
|
386
|
+
}
|
|
387
|
+
drawTokenLine(page, ln, MX + 24, y - BODY, fontFor, BODY, textC);
|
|
388
|
+
y -= LINE;
|
|
389
|
+
});
|
|
390
|
+
y -= LINE * 0.28;
|
|
391
|
+
} else if (b.type === 'quote') {
|
|
392
|
+
const lines = wrapSpans(toSpans(b.text), fontFor, BODY, availW - 18);
|
|
393
|
+
y -= 3;
|
|
394
|
+
ensure(LINE * lineCount(lines) + 6);
|
|
395
|
+
const top = y + LINE * 0.2;
|
|
396
|
+
page.drawLine({
|
|
397
|
+
start: { x: MX + 4, y: top - LINE * lineCount(lines) },
|
|
398
|
+
end: { x: MX + 4, y: top },
|
|
399
|
+
thickness: 2,
|
|
400
|
+
color: accentC,
|
|
401
|
+
});
|
|
402
|
+
for (const ln of lines) {
|
|
403
|
+
drawTokenLine(page, ln, MX + 18, y - BODY, fontFor, BODY, mutedC);
|
|
404
|
+
y -= LINE;
|
|
405
|
+
}
|
|
406
|
+
y -= LINE * 0.55;
|
|
407
|
+
} else if (b.type === 'code') {
|
|
408
|
+
const CS = 9.5;
|
|
409
|
+
const CL = CS * 1.4;
|
|
410
|
+
/* kod satırları: mono genişliğine göre SERT böl (wrapSpans değil —
|
|
411
|
+
boşluk korunsun, sekme 4 boşluk) */
|
|
412
|
+
b._wrapped = b.lines
|
|
413
|
+
.map((l) => {
|
|
414
|
+
const out = [];
|
|
415
|
+
let cur = l;
|
|
416
|
+
while (cur.length) {
|
|
417
|
+
let cut = cur.length;
|
|
418
|
+
try {
|
|
419
|
+
while (cut > 0 && mono.widthOfTextAtSize(cur.slice(0, cut), CS) > availW - 24) cut = Math.floor(cut * 0.92);
|
|
420
|
+
} catch {
|
|
421
|
+
cut = Math.min(cur.length, 90);
|
|
422
|
+
}
|
|
423
|
+
if (cut <= 0) cut = 1;
|
|
424
|
+
out.push(cur.slice(0, cut));
|
|
425
|
+
cur = cur.slice(cut);
|
|
426
|
+
}
|
|
427
|
+
return out.length ? out : [''];
|
|
428
|
+
})
|
|
429
|
+
.flat();
|
|
430
|
+
const nLines = Math.max(1, b._wrapped.length);
|
|
431
|
+
ensure(CL * nLines + 14);
|
|
432
|
+
const top = y + 8;
|
|
433
|
+
const boxH = CL * nLines + 10;
|
|
434
|
+
page.drawRectangle({
|
|
435
|
+
x: MX - 4,
|
|
436
|
+
y: top - boxH,
|
|
437
|
+
width: availW + 8,
|
|
438
|
+
height: boxH,
|
|
439
|
+
color: codeBgC,
|
|
440
|
+
});
|
|
441
|
+
for (const cl of b._wrapped) {
|
|
442
|
+
try {
|
|
443
|
+
page.drawText(cl, { x: MX + 6, y: y - CS, size: CS, font: mono, color: textC });
|
|
444
|
+
} catch {}
|
|
445
|
+
y -= CL;
|
|
446
|
+
}
|
|
447
|
+
y -= 10;
|
|
448
|
+
} else if (b.type === 'hr') {
|
|
449
|
+
ensure(18);
|
|
450
|
+
y -= 8;
|
|
451
|
+
page.drawLine({
|
|
452
|
+
start: { x: MX, y },
|
|
453
|
+
end: { x: PAGE_W - MX, y },
|
|
454
|
+
thickness: 0.7,
|
|
455
|
+
color: rgb(0.85, 0.86, 0.9),
|
|
456
|
+
});
|
|
457
|
+
y -= 14;
|
|
458
|
+
} else if (b.type === 'table') {
|
|
459
|
+
const TS = 9.5;
|
|
460
|
+
const TL = TS * 1.38;
|
|
461
|
+
const rows = b.rows;
|
|
462
|
+
const nCol = Math.max(...rows.map((r) => r.length));
|
|
463
|
+
if (nCol <= 0 || nCol > 12) continue;
|
|
464
|
+
const gap = 6;
|
|
465
|
+
const colW = (availW - gap * (nCol - 1)) / nCol;
|
|
466
|
+
const cellLines = (text) => wrapSpans(toSpans(String(text || '')), fontFor, TS, colW - 8);
|
|
467
|
+
const rendered = rows.map((r) => {
|
|
468
|
+
const cells = [];
|
|
469
|
+
for (let c = 0; c < nCol; c++) cells.push(cellLines(r[c] || ''));
|
|
470
|
+
return cells;
|
|
471
|
+
});
|
|
472
|
+
for (let r = 0; r < rendered.length; r++) {
|
|
473
|
+
const isHead = r === 0;
|
|
474
|
+
const nLines = Math.max(...rendered[r].map((cl) => cl.length), 1);
|
|
475
|
+
const rowH = TL * nLines + 8;
|
|
476
|
+
ensure(rowH + 2);
|
|
477
|
+
if (isHead) {
|
|
478
|
+
page.drawRectangle({
|
|
479
|
+
x: MX - 4,
|
|
480
|
+
y: y - rowH + 4,
|
|
481
|
+
width: availW + 8,
|
|
482
|
+
height: rowH,
|
|
483
|
+
color: rowBgC,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
for (let c = 0; c < nCol; c++) {
|
|
487
|
+
let cy = y;
|
|
488
|
+
for (const ln of rendered[r][c]) {
|
|
489
|
+
const spans = isHead ? ln.map((t) => ({ ...t, b: true })) : ln;
|
|
490
|
+
drawTokenLine(page, spans, MX + c * (colW + gap) + 4, cy - TS, fontFor, TS, textC);
|
|
491
|
+
cy -= TL;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
y -= rowH;
|
|
495
|
+
}
|
|
496
|
+
y -= 10;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/* sayfa numaraları: toplam sayı üretilince ikinci geçişte basılır */
|
|
501
|
+
const docTitle = String(title || '').slice(0, 60);
|
|
502
|
+
const pages = doc.getPages();
|
|
503
|
+
pages.forEach((pg, idx) => {
|
|
504
|
+
const label = `Sayfa ${idx + 1} / ${pages.length}`;
|
|
505
|
+
try {
|
|
506
|
+
const w = reg.widthOfTextAtSize(label, 8);
|
|
507
|
+
pg.drawText(label, { x: PAGE_W - MX - w, y: 30, size: 8, font: reg, color: mutedC });
|
|
508
|
+
if (docTitle) pg.drawText(docTitle, { x: MX, y: 30, size: 8, font: reg, color: mutedC });
|
|
509
|
+
} catch {}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const bytes = await doc.save();
|
|
513
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
514
|
+
fs.writeFileSync(abs, Buffer.from(bytes));
|
|
515
|
+
return {
|
|
516
|
+
ok: true,
|
|
517
|
+
path: abs,
|
|
518
|
+
name: path.basename(abs),
|
|
519
|
+
pages: pages.length,
|
|
520
|
+
bytes: bytes.length,
|
|
521
|
+
note: 'PDF üretildi (Türkçe font gömülü) — kullanıcıya ulaşmak için send_file çağır',
|
|
522
|
+
};
|
|
523
|
+
} catch (e) {
|
|
524
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
module.exports = { writePdf, write: writePdf, parseBlocks, toSpans, firstFont, hasFonts };
|
package/src/agent/skills.js
CHANGED
|
@@ -299,25 +299,38 @@ Zorunlu kurallar:
|
|
|
299
299
|
force: true,
|
|
300
300
|
body: `---
|
|
301
301
|
name: pdf
|
|
302
|
-
description: PDF işleme rehberi — metin çıkarma (pdf-parse),
|
|
303
|
-
version: 1.
|
|
302
|
+
description: PDF işleme rehberi — RAPOR/BELGE ÜRETİMİ pdf_write aracıyla TEK ÇAĞRIDA (Türkçe garantili); metin çıkarma (pdf-parse), PDF değiştirme/birleştirme (pdf-lib), sayfa→görsel (pdf-to-img), taranmış PDF için OCR (tesseract.js). Kullanıcı bir .pdf dosyasından bahsederse YA DA PDF çıktısı istenirse ÖNCE bu skill'i oku.
|
|
303
|
+
version: 1.2.0
|
|
304
304
|
---
|
|
305
305
|
|
|
306
306
|
# PDF İşleme Rehberi
|
|
307
307
|
|
|
308
|
-
## EN
|
|
308
|
+
## EN HIZLI YOL — pdf_write aracı (RAPOR/ÖZET/NOT/BELGE İSTEĞİNDE ÖNCE BU)
|
|
309
|
+
|
|
310
|
+
Elle script YAZMA. Ajanın pdf_write aracı tek çağrıda markdown-lite içeriği düzenli, Türkçe-güvenli PDF'e çevirir (Windows fontu otomatik gömülür — ğ ş ı İ Ğ Ş Ö Ü bozulmaz; başlık, madde işareti, tablo, sayfa numarası otomatik):
|
|
311
|
+
|
|
312
|
+
\`\`\`
|
|
313
|
+
pdf_write {"path": "rapor.pdf", "title": "2026 Özet Raporu", "subtitle": "07.09.2026", "content": "# Başlık\\n## Alt Başlık\\n- madde 1\\n- **önemli** madde\\n1. sıralı adım\\n| Kolon | Değer |\\n|---|---|\\n| Gelir | 12.500 TL |\\n> alıntı satırı"}
|
|
314
|
+
\`\`\`
|
|
315
|
+
|
|
316
|
+
Desteklenen markdown-lite: "# ## ###" başlıklar · düz paragraf · "- " bullet · "1. " numaralı · **kalın** · kod (geri tırnak içi) · "> " alıntı · kod bloğu (üç ters tırnakla açılıp kapanır) · "| kolon |" tablo satırları.
|
|
317
|
+
|
|
318
|
+
Sonra çıktıyı kullanıcıya ulaştırmak için send_file çağır. Script yazma, pip kurma — GEREK YOK.
|
|
319
|
+
|
|
320
|
+
## EN KRİTİK KURALLAR (elle script gerektiğinde bile geçerli)
|
|
309
321
|
|
|
310
322
|
1. Türkçe karakterli PDF için pip\u2019ten paket KURMA/KULLANMA (fpdf, fpdf2, markdown-pdf, weasyprint, reportlab vb.) — bunlar ğ ş ı İ karakterlerini bozar. Doğru kit Node\u2019ta ZATEN kurulu: \`pdf-lib\` + \`@pdf-lib/fontkit\` ve \`pdfkit\`. Script\u2019i write_file ile .js yaz, run_command ile \`node script.js\` çalıştır (python_run İLE DEĞİL).
|
|
311
|
-
2. md→pdf çevirici YOKTUR ve kurulmaz — kullanıcıya rapor/özet/belge çıktısı vereceksen .md gönderme;
|
|
323
|
+
2. md→pdf çevirici YOKTUR ve kurulmaz — kullanıcıya rapor/özet/belge çıktısı vereceksen .md gönderme; pdf_write (ya da elle pdf-lib script) ile DOĞRUDAN PDF üret ve send_file ile gönder.
|
|
312
324
|
3. \`pdf-parse\` ile \`pdf-to-img\` aynı process\u2019e yüklenmez (aşağıdaki çakışma kuralı).
|
|
313
325
|
|
|
314
326
|
Kurulu paketler ve rolleri:
|
|
315
327
|
|
|
316
328
|
| İstek | Paket |
|
|
317
329
|
|---|---|
|
|
330
|
+
| Rapor/özet/belge üretimi | **pdf_write aracı (tek çağrı)** |
|
|
318
331
|
| Metin çıkar / oku | \`pdf-parse\` |
|
|
319
332
|
| Yeni PDF üret / mevcut PDF'i değiştir | \`pdf-lib\` (+ \`@pdf-lib/fontkit\`) |
|
|
320
|
-
| Düzenli belge üretimi (
|
|
333
|
+
| Düzenli belge üretimi (elle) | \`pdfkit\` |
|
|
321
334
|
| Sayfa→PNG/JPG (taranmış/görsel PDF) | \`pdf-to-img\` |
|
|
322
335
|
| Taranmış PDF'den metin (OCR) | \`pdf-to-img\` + \`tesseract.js\` |
|
|
323
336
|
|
package/src/main.js
CHANGED
|
@@ -2054,8 +2054,8 @@ function fmtPerm(p) {
|
|
|
2054
2054
|
function botToolSet(cfg) {
|
|
2055
2055
|
const s = cfg && cfg.skills ? cfg.skills : {};
|
|
2056
2056
|
const set = new Set([
|
|
2057
|
-
/* çekirdek: her botta konuşma + plan + hatırlatıcı */
|
|
2058
|
-
'todo_write', 'set_reminder', 'send_file',
|
|
2057
|
+
/* çekirdek: her botta konuşma + plan + hatırlatıcı + PDF üretimi */
|
|
2058
|
+
'todo_write', 'set_reminder', 'send_file', 'pdf_write',
|
|
2059
2059
|
'tasks_list', 'task_status', 'task_cancel',
|
|
2060
2060
|
'run_background', 'run_background_many', 'delegate_task',
|
|
2061
2061
|
'event_list', 'event_subscribe', 'event_unsubscribe',
|
|
@@ -3000,6 +3000,7 @@ function reloadBackend() {
|
|
|
3000
3000
|
emit: (ev) => {
|
|
3001
3001
|
if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
|
|
3002
3002
|
try { empatiRememberFromEvent(ev); } catch {} /* empati hafızası: sohbet → kayıt + ilgi öğrenme */
|
|
3003
|
+
empatiFlushInjectsOnDone(ev); /* tur ortasında kuyruğa düşen proaktif bildirimleri iş bitince geçmişe bas */
|
|
3003
3004
|
flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
|
|
3004
3005
|
bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
|
|
3005
3006
|
stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
|
|
@@ -6472,11 +6473,51 @@ function empatiSourceLineWa(ev) {
|
|
|
6472
6473
|
return title ? '🔗 ' + cap(title, 80) : '';
|
|
6473
6474
|
}
|
|
6474
6475
|
|
|
6476
|
+
/* Enjeksiyon kuyruğu: oturum TUR ORTASINDAYKEN (ajan bir işle uğraşırken)
|
|
6477
|
+
gelen proaktif bildirimi sessizce DÜŞÜRME — eskisi tam olarak buydu:
|
|
6478
|
+
"bazen ajan konudan haber olmuyor". Metin kuyrukta bekler, oturumun
|
|
6479
|
+
işi bitince ('done'/'error') geçmişe asistan mesajı olarak yazılır;
|
|
6480
|
+
model bir sonraki turda "ben bu bildirimi attım" der, kullanıcı sorusuna
|
|
6481
|
+
hazırdır. */
|
|
6482
|
+
const empatiInjectQueue = new Map(); // sid -> [text]
|
|
6483
|
+
const EMPATI_INJECT_QUEUE_CAP = 20;
|
|
6484
|
+
|
|
6475
6485
|
function empatiInjectToSession(sid, out) {
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6486
|
+
const target = String(sid || '');
|
|
6487
|
+
const body = String(out || '');
|
|
6488
|
+
if (!target || !body.trim()) return;
|
|
6489
|
+
let busy = false;
|
|
6490
|
+
try { busy = engine.isBusy(target); } catch {}
|
|
6491
|
+
if (busy) {
|
|
6492
|
+
let q = empatiInjectQueue.get(target);
|
|
6493
|
+
if (!q) {
|
|
6494
|
+
q = [];
|
|
6495
|
+
empatiInjectQueue.set(target, q);
|
|
6496
|
+
}
|
|
6497
|
+
if (q.length < EMPATI_INJECT_QUEUE_CAP) {
|
|
6498
|
+
q.push(body);
|
|
6499
|
+
empatiLog(`bildirim enjeksiyonu kuyrukta (ajan tur sürüyor) sid=${target} bekleyen=${q.length}`);
|
|
6500
|
+
}
|
|
6501
|
+
return;
|
|
6502
|
+
}
|
|
6503
|
+
try { engine.injectAssistant(target, body); } catch {}
|
|
6504
|
+
}
|
|
6505
|
+
|
|
6506
|
+
/* iş bitince (done/error) biriken proaktif enjeksiyonları geçmişe bas.
|
|
6507
|
+
'done' eventi isBusy temizlenmeden ÖNCE gelir (engine.js: ctrl delete
|
|
6508
|
+
finally bloğunda) — flushDesktopOnDone gibi macrotask'a ertele. */
|
|
6509
|
+
function empatiFlushInjectsOnDone(ev) {
|
|
6510
|
+
if (!ev || !ev.sessionId || (ev.type !== 'done' && ev.type !== 'error')) return;
|
|
6511
|
+
const q = empatiInjectQueue.get(String(ev.sessionId));
|
|
6512
|
+
if (!q || !q.length) return;
|
|
6513
|
+
empatiInjectQueue.delete(String(ev.sessionId));
|
|
6514
|
+
setTimeout(() => {
|
|
6515
|
+
/* empatiInjectToSession üzerinden: arada yeni tur başladıysa yeniden kuyruğa düşer */
|
|
6516
|
+
for (const text of q) {
|
|
6517
|
+
try { empatiInjectToSession(String(ev.sessionId), text); } catch {}
|
|
6518
|
+
}
|
|
6519
|
+
empatiLog(`kuyrukta bekleyen ${q.length} proaktif bildirim geçmişe işlendi sid=${ev.sessionId}`);
|
|
6520
|
+
}, 250);
|
|
6480
6521
|
}
|
|
6481
6522
|
|
|
6482
6523
|
/* kanala giden metin + Beast'in İÇ bağlamı ayrıdır: kullanıcı linki silse bile
|
|
@@ -6635,12 +6676,16 @@ function empatiNotify(text, ev) {
|
|
|
6635
6676
|
for (const fn of senders) {
|
|
6636
6677
|
try { fn(); sent++; } catch {}
|
|
6637
6678
|
}
|
|
6638
|
-
/*
|
|
6639
|
-
|
|
6679
|
+
/* MASAÜSTÜ CHAT UI: entegrasyon kullanılsa da kullanılmasa da proaktif mesaj
|
|
6680
|
+
sohbette MESAJ olarak görünür (asistan balonu) + toast çıkar. Ajan da
|
|
6681
|
+
desktop oturumu geçmişinde attığı bildirimi GÖRÜR — hangi kanala gittiğine
|
|
6682
|
+
bakmaksızın konudan haberdar olur. Kanal oturumlarına ise BAĞLAM'lı `inject`
|
|
6683
|
+
gider (kanal kullanıcısı ham BAĞLAM bloğunu görmez, ajan görür). */
|
|
6640
6684
|
try {
|
|
6641
|
-
if (
|
|
6642
|
-
|
|
6643
|
-
|
|
6685
|
+
if (win && !win.isDestroyed()) {
|
|
6686
|
+
const dsid = empatiDesktopSid();
|
|
6687
|
+
empatiInjectToSession(dsid, out);
|
|
6688
|
+
win.webContents.send('agent:event', { type: 'proactive', sessionId: dsid, id: ev.id, level: ev.level, title: ev.title, text: out });
|
|
6644
6689
|
}
|
|
6645
6690
|
} catch {}
|
|
6646
6691
|
}
|
package/src/renderer/renderer.js
CHANGED
|
@@ -5019,6 +5019,19 @@ function onEvent(ev) {
|
|
|
5019
5019
|
if (!hasTools) closeChatToolGroup();
|
|
5020
5020
|
}
|
|
5021
5021
|
break;
|
|
5022
|
+
case 'message-merge':
|
|
5023
|
+
/* proaktif not son asistan mesajına BİRLEŞTİRİLDİ (injectAssistant) —
|
|
5024
|
+
yeni bubble basma; son asistan balonunun içeriğini güncelle */
|
|
5025
|
+
{
|
|
5026
|
+
const bubbles = els.msgs.querySelectorAll('.msg-assistant .md');
|
|
5027
|
+
const el = bubbles[bubbles.length - 1];
|
|
5028
|
+
const txt = String((ev.message && ev.message.content) || '');
|
|
5029
|
+
if (el && txt) {
|
|
5030
|
+
el.innerHTML = md(txt);
|
|
5031
|
+
scrollDown();
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
break;
|
|
5022
5035
|
case 'token':
|
|
5023
5036
|
ensureStreamBubble();
|
|
5024
5037
|
streamRaw += ev.delta;
|