beast-agent 2.6.2 → 2.6.3

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.
@@ -0,0 +1,235 @@
1
+ 'use strict';
2
+
3
+ /* Excel araç katmanı: xlsx_read / xlsx_write / xlsx_edit.
4
+ Motor: src/agent/xlsx.js (sıfır bağımlılık — zip + minimal OOXML).
5
+ edit akışı tüm çalışma kitabını okuyup değiştirerek yeniden YAZAR;
6
+ formüller önbellek değerlerine dönüşür (araç açıklamasında belirtilir). */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const engine = require('./xlsx');
11
+
12
+ const MAX_OUT_ROWS = 500;
13
+ const MAX_OUT_CHARS = 120000;
14
+
15
+ function asRows(v) {
16
+ if (!Array.isArray(v)) return null;
17
+ return v;
18
+ }
19
+
20
+ /* ---------- xlsx_read ---------- */
21
+ async function xlsxRead(args, ctx) {
22
+ const p = path.resolve(String((ctx && ctx.cwd) || '.'), String(args.path || ''));
23
+ if (!fs.existsSync(p)) return { ok: false, error: 'dosya bulunamadı: ' + p };
24
+ if (!/\.xlsx$/i.test(p)) return { ok: false, error: 'yalnızca .xlsx okunur — .xls (eski biçim) değil' };
25
+ let book;
26
+ try {
27
+ book = engine.read(p);
28
+ } catch (e) {
29
+ return { ok: false, error: String((e && e.message) || e) };
30
+ }
31
+ const sheetArg = args.sheet == null ? null : String(args.sheet);
32
+ let sheet = sheetArg == null ? book[0] : book.find((s) => s.name.toLowerCase() === sheetArg.toLowerCase());
33
+ if (!sheet && sheetArg != null && /^\d+$/.test(sheetArg)) sheet = book[Number(sheetArg) - 1];
34
+ if (!sheet) return { ok: false, error: 'sayfa yok: ' + sheetArg, sheets: book.map((s) => s.name) };
35
+
36
+ let rows = sheet.rows;
37
+ const total = rows.length;
38
+ const offset = Math.max(1, Math.floor(Number(args.offset) || 1));
39
+
40
+ /* header_row: true → 1. satır anahtar; satırlar objeye çevrilir.
41
+ offset sayfa satırına karşılık gelir (1 = başlık satırı dahil başlangıç). */
42
+ if (args.header_row !== false && total > 0) {
43
+ const headRaw = sheet.rows[0] || [];
44
+ const head = headRaw.map((h, i) => (String(h == null ? '' : h).trim() || 'kolon' + (i + 1)));
45
+ const start = Math.max(2, offset); /* veri 2. satırdan başlar */
46
+ const body = sheet.rows.slice(start - 1, start - 1 + Math.min(MAX_OUT_ROWS, Math.max(1, Math.floor(Number(args.limit) || MAX_OUT_ROWS))));
47
+ const outRows = body.map((r) => {
48
+ const o = {};
49
+ head.forEach((h, i) => {
50
+ o[h] = r[i] === undefined ? '' : r[i];
51
+ });
52
+ return o;
53
+ });
54
+ const endRow = start - 1 + body.length;
55
+ return {
56
+ ok: true,
57
+ sheet: sheet.name,
58
+ totalRows: total,
59
+ offset: start,
60
+ headers: head,
61
+ rows: outRows,
62
+ ...(endRow < total ? { note: `satır ${endRow + 1}-${total} için offset=${endRow + 1} ile devam et` } : {}),
63
+ };
64
+ }
65
+
66
+ const limit = Math.min(MAX_OUT_ROWS, Math.max(1, Math.floor(Number(args.limit) || MAX_OUT_ROWS)));
67
+ rows = rows.slice(offset - 1, offset - 1 + limit);
68
+ const text = JSON.stringify(rows);
69
+ return {
70
+ ok: true,
71
+ sheet: sheet.name,
72
+ totalRows: total,
73
+ offset,
74
+ rows,
75
+ ...(total > offset - 1 + rows.length ? { note: `satır ${offset + rows.length}+ için offset ile devam et` } : {}),
76
+ ...(text.length > MAX_OUT_CHARS ? { warning: 'çıktı büyük — limit/offset ile parçala' } : {}),
77
+ };
78
+ }
79
+
80
+ /* ---------- xlsx_write ---------- */
81
+ async function xlsxWrite(args, ctx) {
82
+ const p = path.resolve(String((ctx && ctx.cwd) || '.'), String(args.path || ''));
83
+ if (!/\.xlsx$/i.test(p)) return { ok: false, error: 'dosya adı .xlsx ile bitmeli' };
84
+ const sheets = Array.isArray(args.sheets) ? args.sheets : null;
85
+ if (!sheets || !sheets.length) return { ok: false, error: 'sheets gerekli: [{ name, rows }] — rows dizi-dizisi ya da obje-dizisi olabilir' };
86
+ let buf;
87
+ try {
88
+ buf = engine.write(sheets);
89
+ } catch (e) {
90
+ return { ok: false, error: 'xlsx üretilemedi: ' + String((e && e.message) || e) };
91
+ }
92
+ fs.mkdirSync(path.dirname(p), { recursive: true });
93
+ if (fs.existsSync(p) && args.overwrite === false) return { ok: false, error: 'dosya zaten var ve overwrite:false' };
94
+ fs.writeFileSync(p, buf);
95
+ return {
96
+ ok: true,
97
+ path: p,
98
+ bytes: buf.length,
99
+ sheets: sheets.map((s, i) => ({ name: (s && s.name) || 'Sheet' + (i + 1), rows: Array.isArray(s && s.rows) ? s.rows.length : 0 })),
100
+ };
101
+ }
102
+
103
+ /* ---------- xlsx_edit ---------- */
104
+ async function xlsxEdit(args, ctx) {
105
+ const p = path.resolve(String((ctx && ctx.cwd) || '.'), String(args.path || ''));
106
+ if (!fs.existsSync(p)) return { ok: false, error: 'dosya bulunamadı: ' + p };
107
+ if (!/\.xlsx$/i.test(p)) return { ok: false, error: 'yalnızca .xlsx düzenlenir' };
108
+ const hasUpdates = Array.isArray(args.updates) && args.updates.length > 0;
109
+ const hasAppend = Array.isArray(args.append_rows) && args.append_rows.length > 0;
110
+ if (!hasUpdates && !hasAppend) return { ok: false, error: 'updates ([{cell, value}]) ya da append_rows ([[...],...]) gerekli' };
111
+
112
+ let book;
113
+ try {
114
+ book = engine.read(p);
115
+ } catch (e) {
116
+ return { ok: false, error: String((e && e.message) || e) };
117
+ }
118
+ const sheetArg = args.sheet == null ? null : String(args.sheet);
119
+ let sheet = sheetArg == null ? book[0] : book.find((s) => s.name.toLowerCase() === sheetArg.toLowerCase());
120
+ if (!sheet && sheetArg != null && /^\d+$/.test(sheetArg)) sheet = book[Number(sheetArg) - 1];
121
+ if (!sheet) return { ok: false, error: 'sayfa yok: ' + sheetArg, sheets: book.map((s) => s.name) };
122
+
123
+ const rows = sheet.rows;
124
+ let changed = 0;
125
+
126
+ if (hasUpdates) {
127
+ for (const u of args.updates.slice(0, 2000)) {
128
+ let r = null;
129
+ if (u && typeof u.cell === 'string') r = engine.refToCell(u.cell);
130
+ if (!r && u && Number.isFinite(u.row) && Number.isFinite(u.col)) r = { row: Math.floor(u.row), col: Math.floor(u.col) };
131
+ if (!r || r.row < 1 || r.col < 1) return { ok: false, error: 'geçersiz hücre: ' + JSON.stringify(u).slice(0, 60) };
132
+ while (rows.length < r.row) rows.push([]);
133
+ const row = rows[r.row - 1];
134
+ while (row.length < r.col) row.push('');
135
+ row[r.col - 1] = u.value === undefined ? '' : u.value;
136
+ changed++;
137
+ }
138
+ }
139
+ if (hasAppend) {
140
+ const head = (rows[0] || []).map((h) => String(h == null ? '' : h).trim());
141
+ for (const r of args.append_rows.slice(0, 2000)) {
142
+ if (r && typeof r === 'object' && !Array.isArray(r) && !(r instanceof Date)) {
143
+ /* obje satırı: ilk satırdaki başlıklara göre sütunlanır */
144
+ rows.push(head.map((h) => (h && r[h] !== undefined ? r[h] : '')));
145
+ } else {
146
+ rows.push(Array.isArray(r) ? r : [r]);
147
+ }
148
+ changed++;
149
+ }
150
+ }
151
+
152
+ const buf = engine.write(book.map((s) => ({ name: s.name, rows: s.rows })));
153
+ fs.writeFileSync(p, buf);
154
+ return { ok: true, path: p, sheet: sheet.name, changed, totalRows: rows.length };
155
+ }
156
+
157
+ const definitions = [
158
+ {
159
+ type: 'function',
160
+ function: {
161
+ name: 'xlsx_read',
162
+ description:
163
+ 'Read an .xlsx workbook: returns sheet rows as JSON. Default: first sheet, first row treated as headers (rows become objects). Use `sheet` (name or 1-based index) for other sheets, `header_row: false` for raw arrays, `offset`/`limit` to paginate big sheets. Dates come back as ISO strings.',
164
+ parameters: {
165
+ type: 'object',
166
+ properties: {
167
+ path: { type: 'string', description: 'Path to the .xlsx file' },
168
+ sheet: { type: 'string', description: 'Sheet name or 1-based index (default: first sheet)' },
169
+ header_row: { type: 'boolean', description: 'Treat first row as headers (default true)' },
170
+ offset: { type: 'number', description: '1-based start row (default 1)' },
171
+ limit: { type: 'number', description: 'Max rows returned (default 500)' },
172
+ },
173
+ required: ['path'],
174
+ },
175
+ },
176
+ },
177
+ {
178
+ type: 'function',
179
+ function: {
180
+ name: 'xlsx_write',
181
+ description:
182
+ 'Create or overwrite an .xlsx file. sheets: [{ name, rows }] where rows is an array of arrays (mixed primitives; Date → date cell) OR an array of objects (keys become the header row). Returns file path and size.',
183
+ parameters: {
184
+ type: 'object',
185
+ properties: {
186
+ path: { type: 'string', description: 'Target .xlsx path' },
187
+ sheets: {
188
+ type: 'array',
189
+ description: 'Sheets to write',
190
+ items: {
191
+ type: 'object',
192
+ properties: {
193
+ name: { type: 'string' },
194
+ rows: { type: 'array', description: 'Array of arrays or array of objects' },
195
+ },
196
+ required: ['rows'],
197
+ },
198
+ },
199
+ },
200
+ required: ['path', 'sheets'],
201
+ },
202
+ },
203
+ },
204
+ {
205
+ type: 'function',
206
+ function: {
207
+ name: 'xlsx_edit',
208
+ description:
209
+ 'Edit an existing .xlsx in place: update cells (updates: [{cell: "B2", value}]) and/or append rows (append_rows: [[...], ...]) to a sheet, rewriting the file with all sheets intact. NOTE: formulas are replaced by their cached values (recalculate by opening in Excel).',
210
+ parameters: {
211
+ type: 'object',
212
+ properties: {
213
+ path: { type: 'string', description: 'Path to the .xlsx file' },
214
+ sheet: { type: 'string', description: 'Sheet name or 1-based index (default: first sheet)' },
215
+ updates: {
216
+ type: 'array',
217
+ description: 'Cell updates',
218
+ items: {
219
+ type: 'object',
220
+ properties: {
221
+ cell: { type: 'string', description: 'Cell ref like "B2"' },
222
+ value: { description: 'New value (string/number/boolean)' },
223
+ },
224
+ required: ['cell'],
225
+ },
226
+ },
227
+ append_rows: { type: 'array', description: 'Rows to append at the end' },
228
+ },
229
+ required: ['path'],
230
+ },
231
+ },
232
+ },
233
+ ];
234
+
235
+ module.exports = { definitions, handlers: { xlsx_read: xlsxRead, xlsx_write: xlsxWrite, xlsx_edit: xlsxEdit } };
package/src/main.js CHANGED
@@ -2053,7 +2053,8 @@ function botToolSet(cfg) {
2053
2053
  }
2054
2054
  if (s.email) { set.add('email_list'); set.add('email_read'); set.add('email_send'); }
2055
2055
  if (s.run_command) {
2056
- for (const t of ['run_command', 'python_run', 'read_file', 'write_file', 'list_dir', 'computer_look', 'computer_act']) set.add(t);
2056
+ for (const t of ['run_command', 'python_run', 'read_file', 'write_file', 'list_dir', 'computer_look', 'computer_act',
2057
+ 'git_commit', 'git_diff_review', 'git_pr_create', 'repo_map', 'repo_symbols', 'xlsx_read', 'xlsx_write', 'xlsx_edit']) set.add(t);
2057
2058
  }
2058
2059
  if (s.memory) { set.add('memory_write'); set.add('user_write'); set.add('memory_search'); set.add('memory_hygiene'); }
2059
2060
  if (s.kb) { set.add('kb_search'); set.add('kb_add'); }
@@ -6381,9 +6382,9 @@ function empatiLlmCompose(prompt) {
6381
6382
  KENDİSİNİN attığını bilir — aynı oturum, kesintisiz bağlam. */
6382
6383
  const PROACTIVE_MARK = '🫡 *Beast proaktif:*';
6383
6384
 
6384
- /* bildirimin ALTINA YAZILAN KAYNAK: haberin gerçek linki varsa o, yoksa kaynak adı.
6385
- Kullanıcı "bu proje neymiş?" diye sorduğunda linkten bulabilsin.
6386
- TEK SATIR kuralı: satır sonu/boşluklar temizlenir, uzunsa sona "..." gelir. */
6385
+ /* bildirimin ALTINA YAZILAN KAYNAK desktop chat + Discord için MARKDOWN
6386
+ başlık-linki: kullanıcı ham URL görmez, haber başlığına tıklayınca açılır.
6387
+ URL yoksa kaynak adı düz yazılır. TEK SATIR kuralı korunur. */
6387
6388
  function empatiSourceLine(ev) {
6388
6389
  if (!ev) return '';
6389
6390
  const oneLine = (s) =>
@@ -6392,7 +6393,29 @@ function empatiSourceLine(ev) {
6392
6393
  const cap = (s, max) =>
6393
6394
  oneLine(s).length > max ? oneLine(s).slice(0, max - ELLIPSIS.length) + ELLIPSIS : oneLine(s);
6394
6395
  const url = oneLine(ev.url);
6395
- if (/^https?:\/\//i.test(url)) return '🔗 ' + cap(url, 100);
6396
+ if (/^https?:\/\//i.test(url)) {
6397
+ /* markdown linki bozmasın: başlıktaki köşeli parantezler silinir, URL'deki
6398
+ ')' kaçırılır (mdInline linki ilk ')'da keser) */
6399
+ const title =
6400
+ oneLine(ev.title || ev.source || '').replace(/[[\]]/g, ' ').replace(/\s{2,}/g, ' ').trim() || 'kaynak';
6401
+ return '🔗 [' + cap(title, 80) + '](' + url.replace(/\)/g, '%29') + ')';
6402
+ }
6403
+ const src = oneLine(ev.source);
6404
+ return src ? '🔗 kaynak: ' + cap(src, 80) : '';
6405
+ }
6406
+
6407
+ /* WhatsApp/Telegram düz metin sürümü: bu kanallar [başlık](url) render etmez —
6408
+ link TIKLANABİLMEK İÇİN ham URL olmalı. Kısaltılan URL kırık link olur;
6409
+ o yüzden burada link tam uzunlukta tek satır gider. */
6410
+ function empatiSourceLinePlain(ev) {
6411
+ if (!ev) return '';
6412
+ const oneLine = (s) =>
6413
+ String(s || '').replace(/\s*\n+\s*/g, ' ').replace(/\s{2,}/g, ' ').trim();
6414
+ const url = oneLine(ev.url);
6415
+ if (/^https?:\/\//i.test(url)) return '🔗 ' + url;
6416
+ const ELLIPSIS = '...';
6417
+ const cap = (s, max) =>
6418
+ oneLine(s).length > max ? oneLine(s).slice(0, max - ELLIPSIS.length) + ELLIPSIS : oneLine(s);
6396
6419
  const src = oneLine(ev.source);
6397
6420
  return src ? '🔗 kaynak: ' + cap(src, 80) : '';
6398
6421
  }
@@ -6502,8 +6525,12 @@ function empatiDesktopSid() {
6502
6525
  Hiçbir entegrasyon yazılamazsa masaüstü chat UI (toast) kalır. */
6503
6526
  function empatiNotify(text, ev) {
6504
6527
  const cfg = empatiCfg();
6528
+ /* desktop + Discord: başlık-linki (markdown); WA/TG: ham URL (tıklanabilir
6529
+ olması için şart) — kanallar kendi biçimini alır */
6505
6530
  const src = empatiSourceLine(ev);
6531
+ const srcPlain = empatiSourceLinePlain(ev);
6506
6532
  const out = PROACTIVE_MARK + '\n' + text + (src ? '\n' + src : '');
6533
+ const outPlain = PROACTIVE_MARK + '\n' + text + (srcPlain ? '\n' + srcPlain : '');
6507
6534
  const inject = empatiInjectText(ev, out);
6508
6535
  const senders = [];
6509
6536
  const tryWa = () => {
@@ -6512,7 +6539,7 @@ function empatiNotify(text, ev) {
6512
6539
  if (own && wa && wa.connected) {
6513
6540
  const jid = own + '@s.whatsapp.net';
6514
6541
  senders.push(() =>
6515
- Promise.resolve(sendWaSafe(jid, out))
6542
+ Promise.resolve(sendWaSafe(jid, outPlain))
6516
6543
  .then(() => empatiInjectToSession(ensureWaSession(jid), inject))
6517
6544
  .catch(() => {})
6518
6545
  );
@@ -6524,7 +6551,7 @@ function empatiNotify(text, ev) {
6524
6551
  if (tg && tg.connected) {
6525
6552
  for (const id of tgOwnerIds()) {
6526
6553
  senders.push(() =>
6527
- Promise.resolve(sendTgSafe(id, out))
6554
+ Promise.resolve(sendTgSafe(id, outPlain))
6528
6555
  .then(() => empatiInjectToSession(ensureTgSession(String(id)), inject))
6529
6556
  .catch(() => {})
6530
6557
  );
@@ -667,6 +667,12 @@ function argSummary(name, args) {
667
667
  if (name === 'glob') return String(args.pattern || '');
668
668
  if (name === 'list_dir') return String(args.path || '.');
669
669
  if (name === 'memory_write' || name === 'user_write') return String(args.text || '').slice(0, 80);
670
+ if (name === 'git_commit') return String(args.message || '').slice(0, 100);
671
+ if (name === 'git_diff_review') return args.staged ? '--staged' : String(args.ref || 'working tree');
672
+ if (name === 'git_pr_create') return String(args.title || '').slice(0, 100);
673
+ if (name === 'repo_map') return String(args.path || '.');
674
+ if (name === 'repo_symbols') return String(args.query || '').slice(0, 60) || '.';
675
+ if (name === 'xlsx_read' || name === 'xlsx_write' || name === 'xlsx_edit') return String(args.path || '');
670
676
  return JSON.stringify(args).slice(0, 100);
671
677
  } catch {
672
678
  return '';
@@ -4893,9 +4899,10 @@ function onEvent(ev) {
4893
4899
  return;
4894
4900
  }
4895
4901
  if (ev.type === 'install-progress') { updateInstallPct(ev); return; }
4896
- /* EMPATİ LOOP: proaktif bildirim (masaüstü) + sekme canlı yenileme */
4902
+ /* EMPATİ LOOP: proaktif bildirim (masaüstü) + sekme canlı yenileme.
4903
+ Toast'ta yalnız haber başlığı — link/markdown kalabalığı chat mesajında. */
4897
4904
  if (ev.type === 'proactive') {
4898
- toast('🫡 ' + (ev.text || ev.title || ''));
4905
+ toast('🫡 ' + (ev.title || ev.text || ''));
4899
4906
  return;
4900
4907
  }
4901
4908
  if (ev.type === 'empati') {
@@ -5445,7 +5452,7 @@ function applyState() {
5445
5452
  els.thinkBtnLabel.innerHTML =
5446
5453
  '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 2A2.5 2.5 0 0 0 7 4.5v.55A3.5 3.5 0 0 0 4.5 8.5c0 .74.23 1.43.62 2A3.5 3.5 0 0 0 4 13.5 3.5 3.5 0 0 0 7 16.95v.55A2.5 2.5 0 0 0 9.5 20a2.5 2.5 0 0 0 2.5-2.5v-13A2.5 2.5 0 0 0 9.5 2z"/><path d="M14.5 2A2.5 2.5 0 0 1 17 4.5v.55a3.5 3.5 0 0 1 2.5 3.45c0 .74-.23 1.43-.62 2a3.5 3.5 0 0 1 1.12 3 3.5 3.5 0 0 1-3 3.45v.55A2.5 2.5 0 0 1 14.5 20 2.5 2.5 0 0 1 12 17.5v-13A2.5 2.5 0 0 1 14.5 2z"/></svg>';
5447
5454
  renderModelMenu();
5448
- }
5455
+ }
5449
5456
 
5450
5457
  /* düşünme (reasoning) seviyesi picker — gerçek API değerleri */
5451
5458
  const THINK_UI_LEVELS = [