beast-agent 0.23.7 → 0.23.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "0.23.7",
4
+ "version": "0.23.9",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -220,7 +220,10 @@ class Engine {
220
220
  this.tokRatio = 1; // gerçek prompt_tokens ile kalibre edilir
221
221
  this._codeIndex = new Map(); // kısa oturum kodu -> session id
222
222
  /* yansıma: oturumda 5+ yeni araç çağrısında skill taslağı denenir */
223
- this.reflection = { enabled: opts.reflection !== false, minTools: 5 };
223
+ this.reflection = { enabled: opts.reflection !== false, minTools: 3 };
224
+ /* OTOMATİK SKİLL SİSTEMİ: öğrenilen prosedür taslak onayı beklemeden
225
+ kurulu skill olur; mevcut skillin daha iyisi bulunursa üzerine günceller */
226
+ this.autoSkills = opts.autoSkills !== false;
224
227
  this.historyTokenBudget = Number(opts.historyTokenBudget) || HISTORY_TOKEN_BUDGET;
225
228
  this.browser = opts.browser || null; // dahili tarayıcı kancaları
226
229
  this.fileSend = opts.fileSend || null; // #26 dosya gönderim köprüsü (chat/WA)
@@ -1014,11 +1017,27 @@ class Engine {
1014
1017
  const firstUser = s.messages.find((m) => m.role === 'user');
1015
1018
  const isBg = !!s.bgTitle;
1016
1019
  const job = isBg ? this._bgJobs.get(String(s.id)) : null;
1020
+ /* Başlık: ilk user mesajının METNİ. Ekli mesajlarda content dizidir —
1021
+ String() doğrudan "[object Object]" üretir; text parçaları birleştirilir.
1022
+ WhatsApp/Telegram transport etiketi ([WhatsApp — gönderen: ...]) başlıkta
1023
+ gürültüdür — kırpılır, gerçek konuşma metni kalsın. */
1024
+ let title = '';
1025
+ if (!isBg && firstUser) {
1026
+ let t = Array.isArray(firstUser.content)
1027
+ ? firstUser.content.filter((p) => p && p.type === 'text').map((p) => String(p.text || '')).join(' ')
1028
+ : String(firstUser.content || '');
1029
+ t = t
1030
+ .replace(/^\s*\[(WhatsApp|Telegram)[^\]]*\]\s*/i, '')
1031
+ .replace(/\[görsel gösterilemedi[^\]]*\]/gi, '')
1032
+ .replace(/\[(resim eki alındı|belge alındı[^\]]*|sesli mesaj[^\]]*)\]/gi, '')
1033
+ .trim();
1034
+ title = (t.split('\n')[0] || '').replace(/\s+/g, ' ').trim().slice(0, 48);
1035
+ }
1017
1036
  return {
1018
1037
  id: s.id,
1019
1038
  code: s.code || '',
1020
1039
  botId: s.botId || null,
1021
- title: (isBg ? s.bgTitle : (firstUser ? String(firstUser.content).slice(0, 48) : '')) || 'Yeni Sohbet',
1040
+ title: (isBg ? s.bgTitle : title) || 'Yeni Sohbet',
1022
1041
  createdAt: s.createdAt,
1023
1042
  updatedAt: s.updatedAt,
1024
1043
  count: s.messages.length,
@@ -2463,12 +2482,25 @@ class Engine {
2463
2482
  );
2464
2483
  if (transcript.length < 400) return null; // çok kısa deneyim değmez
2465
2484
 
2485
+ /* OTOMATİK SKİLL SİSTEMİ: kurulu skill listesi de prompta girer —
2486
+ ajan "eski skillden daha kolay/better yol buldum" diyebilmesin, KARAR VERİP
2487
+ skilli GÜNCELLESİN. action: create (yeni) | update (mevcutu iyileştir) | none */
2488
+ const skills = require('./skills');
2489
+ const existing = skills
2490
+ .scan()
2491
+ .map((s) => `- ${s.name}: ${(s.description || '').slice(0, 100)}`)
2492
+ .join('\n');
2493
+
2466
2494
  const prompt =
2467
- 'Aşağıdaki agent oturumunda TEKRARLANABİLİR, yeniden kullanılabilir bir prosedür/bilgi ' +
2468
- 'birikti mi? Kullanıcıya özel geçici detaylar sayılmaz. Karar: evet ise SKILL.md taslağı üret.\n\n' +
2495
+ 'Aşağıdaki agent oturumunda TEKRARLANABİLİR, yeniden kullanılabilir bir prosedür/bilgi birikti mi?\n' +
2496
+ 'Bir kurulu skill, bu oturumda öğrenilen DAHA KOLAY/DAHA İYİ yöntemle güncellenmeyi hak ediyor mu?\n' +
2497
+ 'Kullanıcıya özel geçici detaylar (tarih, şehir, numara gibi) skill\u2019e YAZILMAZ — genel yöntem yazılır.\n\n' +
2498
+ '# KURULU SKİLLER\n' + (existing || '(yok)') + '\n\n' +
2469
2499
  'SADECE şu JSON formatında cevap ver, başka metin yok:\n' +
2470
- '{"create": true|false, "name": "kisa-kebab-ad", "description": "tek cümle ne işe yarar", ' +
2471
- '"body": "# Başlık\\n\\nmaddeler halinde adım adım prosedür"}\n\n' +
2500
+ '{"action": "none" | "create" | "update", "name": "kisa-kebab-ad", "description": "tek cümle ne işe yarar", "body": "# Başlık\\n\\nmaddeler halinde adım adım prosedür"}\n\n' +
2501
+ 'action=create → yeni skill (body: baştan sona tam prosedür)\n' +
2502
+ 'action=update → "name" MEVCUT skillin adı; body o skillin GÜNCELLENMİŞ TAM HALI (eskiden iyi olan adımları koru, yeni kolaylığı işle)\n' +
2503
+ 'action=none → değerlenecek bir şey yok\n\n' +
2472
2504
  '# OTURUM ÖZETİ\n' + transcript.slice(0, 6000);
2473
2505
 
2474
2506
  const res = await chatOnce(
@@ -2477,8 +2509,30 @@ class Engine {
2477
2509
  {}
2478
2510
  );
2479
2511
  const draft = parseReflectionJson(res.content || '');
2480
- if (!draft || !draft.create || !draft.name || !draft.body) return null;
2481
- const skills = require('./skills');
2512
+ if (!draft || !draft.name || !draft.body) return null;
2513
+
2514
+ const action = String(draft.action || (draft.create ? 'create' : 'none')).toLowerCase();
2515
+ if (action !== 'create' && action !== 'update') return null;
2516
+
2517
+ if (action === 'update') {
2518
+ /* mevcut skillin GÜNCELLENMİŞ hali — doğrudan üzerine (eski hali .bak) */
2519
+ const r = skills.upsertSkill({
2520
+ name: String(draft.name),
2521
+ description: String(draft.description || ''),
2522
+ body: String(draft.body),
2523
+ });
2524
+ return r.ok ? skills.slugify(draft.name) + ' (güncellendi)' : null;
2525
+ }
2526
+
2527
+ /* create: otomatik skill modu AÇIKSA taslak beklemeden direkt kur */
2528
+ if (this.autoSkills) {
2529
+ const r = skills.upsertSkill({
2530
+ name: String(draft.name),
2531
+ description: String(draft.description || ''),
2532
+ body: String(draft.body),
2533
+ });
2534
+ return r.ok ? skills.slugify(draft.name) : null;
2535
+ }
2482
2536
  const r = skills.addDraft({
2483
2537
  name: String(draft.name),
2484
2538
  description: String(draft.description || ''),
@@ -487,6 +487,33 @@ function dropDraft(id) {
487
487
  }
488
488
  }
489
489
 
490
+ /* OTOMATIK SKILL SİSTEMİ: yansımadan doğan prosedürü DOĞRUDAN kurulu skill olarak
491
+ yazar (taslak onayı beklemeden). Aynı isimde skill varsa GÜNCELLENİR:
492
+ eski içerik .bak'a alınır, created korunur, updated damgası vurulur. */
493
+ function upsertSkill({ name, description, body }) {
494
+ try {
495
+ const nm = String(name || '').trim() || 'yetenek';
496
+ const folder = slugify(nm);
497
+ const d = path.join(dir(), folder);
498
+ const file = path.join(d, 'SKILL.md');
499
+ fs.mkdirSync(d, { recursive: true });
500
+ let fm = `---\nname: ${nm}\ndescription: ${String(description || '').trim().slice(0, 160)}\n`;
501
+ let updated = false;
502
+ if (fs.existsSync(file)) {
503
+ const old = fs.readFileSync(file, 'utf8');
504
+ const oldFm = parseFrontmatter(old);
505
+ fm += `created: ${oldFm.created || oldFm.generatedAt || new Date().toISOString()}\n`;
506
+ fs.writeFileSync(file + '.bak', old);
507
+ updated = true;
508
+ }
509
+ fm += `updated: ${new Date().toISOString()}\n---\n\n`;
510
+ fs.writeFileSync(file, fm + String(body || '').trim() + '\n');
511
+ return { ok: true, folder, file, updated };
512
+ } catch (e) {
513
+ return { ok: false, error: String((e && e.message) || e) };
514
+ }
515
+ }
516
+
490
517
  /* Kurulu bir skill'e madde ekler (#3 kural hattı): "## Kurallar" altına yazılır. */
491
518
  function appendRuleToSkill(nameOrFolder, ruleText) {
492
519
  try {
@@ -520,4 +547,5 @@ module.exports = {
520
547
  acceptDraft,
521
548
  dropDraft,
522
549
  appendRuleToSkill,
550
+ upsertSkill,
523
551
  };
package/src/main.js CHANGED
@@ -2071,6 +2071,9 @@ function reloadBackend() {
2071
2071
  thinkLevel: settings.thinkLevel || 0,
2072
2072
  fallout: settings.fallout || null,
2073
2073
  limits: settings.limits || null,
2074
+ /* OTOMATİK SKİLL SİSTEMİ: ayarlardan kapatılmadıysa öğrenilen prosedürler
2075
+ otomatik skill olur, mevcutların daha iyisi bulunursa güncellenir */
2076
+ autoSkills: settings.autoSkills !== false,
2074
2077
  approvals: settings.security && settings.security.approvals ? approvalsBridge : null,
2075
2078
  alwaysAllowTools: (settings.security && settings.security.alwaysAllow) || [],
2076
2079
  crashFile: FALLOUT_CRASH_FILE,
@@ -3828,6 +3831,16 @@ ipcMain.handle('memory:save', (_e, { file, content }) => memory.save(file, conte
3828
3831
 
3829
3832
  ipcMain.handle('skills:list', () => skillsMod.scan());
3830
3833
 
3834
+ /* OTOMATİK SKİLL SİSTEMİ: açıkken öğrenilen prosedürler direkt kurulur,
3835
+ mevcut skillin daha iyisi bulunursa güncellenir */
3836
+ ipcMain.handle('skills:auto:get', () => settings.autoSkills !== false);
3837
+ ipcMain.handle('skills:auto:set', (_e, v) => {
3838
+ settings.autoSkills = !!v;
3839
+ saveSettings();
3840
+ if (engine) engine.autoSkills = !!v;
3841
+ return settings.autoSkills;
3842
+ });
3843
+
3831
3844
  /* taslak skill'ler (#2 yansıma ürünleri) */
3832
3845
  ipcMain.handle('skills:drafts:list', () => skillsMod.listDrafts());
3833
3846
  ipcMain.handle('skills:drafts:accept', (_e, id) => {
package/src/preload.js CHANGED
@@ -25,6 +25,8 @@ contextBridge.exposeInMainWorld('beast', {
25
25
  clearNotes: (id) => ipcRenderer.invoke('notes:clear', id),
26
26
  saveMemory: (file, content) => ipcRenderer.invoke('memory:save', { file, content }),
27
27
  listSkills: () => ipcRenderer.invoke('skills:list'),
28
+ skillsGetAuto: () => ipcRenderer.invoke('skills:auto:get'),
29
+ skillsSetAuto: (v) => ipcRenderer.invoke('skills:auto:set', v),
28
30
  openSkillsFolder: () => ipcRenderer.invoke('skills:openFolder'),
29
31
  draftsList: () => ipcRenderer.invoke('skills:drafts:list'),
30
32
  draftAccept: (id) => ipcRenderer.invoke('skills:drafts:accept', id),
@@ -164,6 +164,7 @@
164
164
  mem_saved: 'Memory kaydedildi — SOUL.md dahil',
165
165
  sk_h2: 'Skills',
166
166
  sk_sub: 'SKILL.md indeksi — tam metin model tarafından okunur',
167
+ sk_auto: 'Otomatik skill: ajan öğrendiği yeni yöntemleri taslak onayı beklemeden kurar; bir skillin daha kolay/daha iyi yolunu bulursa mevcut skilli kendisi günceller',
167
168
  sk_rules_h2: 'Kalıcı Kurallar',
168
169
  sk_rules_sub: 'Agent\u2019ın her sohbette uyduğu talimatlar — WA\u2019dan /rule ile de eklenir',
169
170
  sk_no_rule: 'Henüz kural yok.',
@@ -678,6 +679,7 @@
678
679
  mem_saved: 'Memory saved — including SOUL.md',
679
680
  sk_h2: 'Skills',
680
681
  sk_sub: 'SKILL.md index — full text is read by the model',
682
+ sk_auto: 'Auto skills: the agent installs learned procedures without draft approval and updates existing skills when it finds an easier or better way',
681
683
  sk_rules_h2: 'Persistent Rules',
682
684
  sk_rules_sub: 'Instructions the agent follows in every chat — also addable via /rule on WA',
683
685
  sk_no_rule: 'No rules yet.',
@@ -458,9 +458,13 @@ function finishToolCard(callId, ok, result) {
458
458
 
459
459
  async function renderSessions(list) {
460
460
  let waSet = new Set();
461
+ let tgSet = new Set();
461
462
  try {
462
463
  waSet = new Set(await beast.waListSessions());
463
464
  } catch {}
465
+ try {
466
+ tgSet = new Set(await beast.tgListSessions());
467
+ } catch {}
464
468
  els.sessList.innerHTML = '';
465
469
  /* aktif botun oturumları — botlar arası geçişte liste de o bota göre değişir */
466
470
  for (const s of list) {
@@ -469,6 +473,7 @@ async function renderSessions(list) {
469
473
  row.className = 'sess' + (s.id === activeId ? ' active' : '');
470
474
  row.innerHTML =
471
475
  (waSet.has(s.id) ? '<span class="sess-wa" title="WhatsApp">W</span>' : '') +
476
+ (tgSet.has(s.id) ? '<span class="sess-tg" title="Telegram">T</span>' : '') +
472
477
  `<span class="sess-title">${escapeHtml(s.title || 'Yeni Sohbet')}</span>` +
473
478
  `<span class="sess-code" title="Oturum kodu">${escapeHtml(s.code || '')}</span>` +
474
479
  `<button class="sess-del" title="Sil">×</button>`;
@@ -1250,6 +1255,16 @@ async function renderFalloutPane() {
1250
1255
  async function renderSkillsPane() {
1251
1256
  const pane = $('#tab-skills');
1252
1257
  pane.innerHTML = '<h2>' + _t('sk_h2') + '</h2><div class="sub">' + _t('sk_sub') + '</div>';
1258
+ /* OTOMATİK SKİLL SİSTEMİ: öğrenilen prosedürler otomatik kurulur/güncellenir */
1259
+ const auto = await beast.skillsGetAuto().catch(() => true);
1260
+ pane.insertAdjacentHTML(
1261
+ 'afterbegin',
1262
+ `<div class="fo-toggles" style="margin-bottom:12px"><label class="lock-row"><input type="checkbox" id="autoSkillsOn" ${auto ? 'checked' : ''}/><span>${_t('sk_auto')}</span></label></div>`
1263
+ );
1264
+ pane.querySelector('#autoSkillsOn').addEventListener('change', async (e) => {
1265
+ await beast.skillsSetAuto(e.target.checked);
1266
+ toast((e.target.checked ? 'Otomatik skill: AÇIK' : 'Otomatik skill: KAPALI'));
1267
+ });
1253
1268
  const list = await beast.listSkills();
1254
1269
  for (const s of list) {
1255
1270
  const row = document.createElement('div');
@@ -1619,7 +1634,9 @@ async function renderDashboardPane() {
1619
1634
  let sessions = [];
1620
1635
  try { sessions = (await beast.listSessions()) || []; } catch {}
1621
1636
  let waSet = new Set();
1637
+ let tgSet = new Set();
1622
1638
  try { waSet = new Set(await beast.waListSessions()); } catch {}
1639
+ try { tgSet = new Set(await beast.tgListSessions()); } catch {}
1623
1640
 
1624
1641
  const totalMsgs = sessions.reduce((a, s) => a + (Number(s.count) || 0), 0);
1625
1642
  const today = new Date().toDateString();
@@ -1655,6 +1672,7 @@ async function renderDashboardPane() {
1655
1672
  row.className = 'usage-row' + (s.id === activeId ? ' dash-active' : '');
1656
1673
  row.innerHTML =
1657
1674
  (waSet.has(s.id) ? '<span class="sess-wa" title="WhatsApp">W</span>' : '') +
1675
+ (tgSet.has(s.id) ? '<span class="sess-tg" title="Telegram">T</span>' : '') +
1658
1676
  `<span class="sess-code" title="Oturum kodu">${escapeHtml(s.code || '')}</span>` +
1659
1677
  `<span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">` +
1660
1678
  `${escapeHtml(s.title || 'Yeni Sohbet')}</span>` +
@@ -2630,7 +2648,9 @@ async function renderBotChats(b) {
2630
2648
  const box = $('#botChatMsgs');
2631
2649
  box.innerHTML = '';
2632
2650
  let waSet = new Set();
2651
+ let tgSet = new Set();
2633
2652
  try { waSet = new Set(await beast.waListSessions()); } catch {}
2653
+ try { tgSet = new Set(await beast.tgListSessions()); } catch {}
2634
2654
  const sessions = [];
2635
2655
  try {
2636
2656
  for (const s of await beast.listSessions()) {
@@ -2648,7 +2668,11 @@ async function renderBotChats(b) {
2648
2668
  const row = document.createElement('div');
2649
2669
  row.className = 'bot-chat-row';
2650
2670
  row.innerHTML =
2651
- (waSet.has(s.id) ? '<span class="sess-wa" title="WhatsApp">W</span>' : '<span class="sess-wa" style="opacity:.35">D</span>') +
2671
+ (waSet.has(s.id)
2672
+ ? '<span class="sess-wa" title="WhatsApp">W</span>'
2673
+ : tgSet.has(s.id)
2674
+ ? '<span class="sess-tg" title="Telegram">T</span>'
2675
+ : '<span class="sess-wa" style="opacity:.35">D</span>') +
2652
2676
  `<span class="bt">${escapeHtml(s.title || 'Yeni Sohbet')}</span>` +
2653
2677
  `<span class="sess-code">${escapeHtml(s.code || '')}</span>`;
2654
2678
  row.addEventListener('click', async () => {
@@ -130,8 +130,19 @@ body {
130
130
  display: flex;
131
131
  align-items: center;
132
132
  justify-content: center;
133
- flex-shrink: 0;
134
133
  }
134
+ .sess-tg {
135
+ width: 15px;
136
+ height: 15px;
137
+ border-radius: 4px;
138
+ background: #229ed9;
139
+ color: #fff;
140
+ font-size: 10px;
141
+ font-weight: 800;
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ }
135
146
  .sess-title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px; }
136
147
  .sess-code {
137
148
  color: var(--muted);
@@ -1525,7 +1536,7 @@ body.browser-open #toast { left: calc((100vw - var(--bw, 480px)) / 2); }
1525
1536
 
1526
1537
  #toast {
1527
1538
  position: fixed;
1528
- bottom: 28px;
1539
+ bottom: 84px; /* chat input alanının hemen üstünde */
1529
1540
  left: 50%;
1530
1541
  transform: translateX(-50%) translateY(16px);
1531
1542
  opacity: 0;