beast-agent 0.26.0 → 0.26.2

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.
@@ -2828,7 +2828,7 @@ body.browser-open #ghOverlay { right: var(--bw, 480px); }
2828
2828
  width: auto;
2829
2829
  text-overflow: ellipsis;
2830
2830
  }
2831
- /* sol bölme sekmeleri: Trend Depolar ⇄ Tüm Repolar — eşit genişlik, ortalanmış metin */
2831
+ /* sol bölme sekmeleri: Trend Repolar ⇄ Tüm Repolar — eşit genişlik, ortalanmış metin */
2832
2832
  .gh-pane-tabs { display: flex; flex: none; }
2833
2833
  .gh-tab {
2834
2834
  flex: 1 1 0;
@@ -2852,62 +2852,3 @@ body.browser-open #ghOverlay { right: var(--bw, 480px); }
2852
2852
  flex: none;
2853
2853
  text-align: center;
2854
2854
  }
2855
-
2856
- /* proxy ekleme satırı + liste */
2857
- .px-add-row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 6px; }
2858
- .px-add-row select,
2859
- .px-add-row input {
2860
- background: var(--panel);
2861
- border: 1px solid var(--border);
2862
- border-radius: 7px;
2863
- color: var(--text);
2864
- font-size: 12.5px;
2865
- padding: 6px 9px;
2866
- outline: none;
2867
- }
2868
- .px-add-row select { flex: 0 0 auto; }
2869
- .px-add-row input:focus { border-color: var(--accent); }
2870
- #pxHost { flex: 2 1 140px; }
2871
- #pxPort { flex: 0 1 80px; }
2872
- #pxUser, #pxPass { flex: 1 1 110px; }
2873
- .px-row {
2874
- display: flex;
2875
- align-items: center;
2876
- gap: 8px;
2877
- border: 1px solid var(--border);
2878
- background: var(--panel);
2879
- border-radius: 8px;
2880
- padding: 6px 10px;
2881
- margin-bottom: 6px;
2882
- }
2883
- .px-row code {
2884
- font-family: var(--mono, monospace);
2885
- font-size: 12px;
2886
- color: var(--text);
2887
- overflow: hidden;
2888
- text-overflow: ellipsis;
2889
- white-space: nowrap;
2890
- flex: 1;
2891
- }
2892
- .px-row .px-del {
2893
- background: none;
2894
- border: none;
2895
- color: var(--muted);
2896
- cursor: pointer;
2897
- font-size: 13px;
2898
- padding: 2px 6px;
2899
- border-radius: 6px;
2900
- flex: none;
2901
- }
2902
- .px-row .px-del:hover { color: var(--err); background: var(--panel2); }
2903
-
2904
- /* WARP kartı */
2905
- .warp-card {
2906
- border: 1px solid var(--border);
2907
- background: var(--panel);
2908
- border-radius: 10px;
2909
- padding: 12px 14px;
2910
- margin-top: 12px;
2911
- }
2912
- .warp-head { display: flex; justify-content: space-between; align-items: center; font-weight: 800; margin-bottom: 8px; }
2913
- .warp-state { color: var(--muted); font-weight: 400; font-size: 12px; }
@@ -121,18 +121,14 @@ function fakeJob(over) {
121
121
  };
122
122
  }
123
123
 
124
- test('superviseReason: takılan stuck, çalışan uzun iş once wrapup (wrapAt sonrası long), yeni uyarılan null', () => {
124
+ test('superviseReason: takılan stuck, uzun koşan CEO kontrolü (long), yeni uyarılan null', () => {
125
125
  assert.strictEqual(Engine.superviseReason(fakeJob(), Date.now()), 'stuck');
126
126
 
127
+ // süre sınırı KALDIRILDI: aktivite süren uzun iş artık kesilmez → CEO ara kontrolü (long)
127
128
  assert.strictEqual(
128
129
  Engine.superviseReason(fakeJob({ lastActivityAt: new Date(Date.now() - 30 * 1000).toISOString() }), Date.now()),
129
- 'wrapup'
130
- ); // aktivite sürüyor ama 10 dk koştu — önce zarif bitirme uyarısı
131
-
132
- assert.strictEqual(
133
- Engine.superviseReason(fakeJob({ wrapAt: new Date().toISOString(), lastActivityAt: new Date(Date.now() - 30 * 1000).toISOString() }), Date.now()),
134
130
  'long'
135
- ); // wrap-up uyarısı verildi, hâlâ bitmedi → CEO ara kontrolü
131
+ );
136
132
 
137
133
  assert.strictEqual(
138
134
  Engine.superviseReason(fakeJob({ startedAt: new Date(Date.now() - 1 * MIN).toISOString() }), Date.now()),
@@ -182,12 +178,11 @@ test('_supervise önce ajana ÖZ-KURTARMA verir, haklar bitince CEO\'ya uyarır'
182
178
  eng._supervise();
183
179
  assert.strictEqual(job.checks, 1);
184
180
 
185
- /* 2. aşama: öz-kurtarma hakları bitmiş + iş hâlâ koşuyor (wrap-up zaten
186
- verildi, aktif) → CEO uyarısı ('long') */
181
+ /* 2. aşama: öz-kurtarma hakları bitmiş + iş hâlâ koşuyor (aktivite sürüyor,
182
+ süre sınırı yok) → CEO uyarısı ('long') */
187
183
  job.status = 'running';
188
184
  job.endedAt = null;
189
185
  job.fixes = 2;
190
- job.wrapAt = new Date().toISOString();
191
186
  job.lastActivityAt = new Date(Date.now() - 30 * 1000).toISOString();
192
187
  job.lastNudgeAt = new Date(Date.now() - 5 * MIN).toISOString();
193
188
  eng._supervise();
package/tests/llm.test.js CHANGED
@@ -3,7 +3,7 @@
3
3
  require('./setup');
4
4
  const test = require('node:test');
5
5
  const assert = require('node:assert');
6
- const { withRetries, friendlyError } = require('../src/agent/llm');
6
+ const { withRetries, friendlyError, sumUsage, chatStreamAuto } = require('../src/agent/llm');
7
7
 
8
8
  test('withRetries: 503 sonrası başarılı isteği tekrarlar', async () => {
9
9
  let calls = 0;
@@ -54,3 +54,83 @@ test('friendlyError ipuçları içerir', () => {
54
54
  assert.match(m, /kullanılamıyor/);
55
55
  assert.match(m, /model seçici/);
56
56
  });
57
+
58
+ test('sumUsage: sayısal alanları toplar (devam turu faturalaması)', () => {
59
+ const u = sumUsage(
60
+ { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
61
+ { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 }
62
+ );
63
+ assert.deepEqual(u, { prompt_tokens: 18, completion_tokens: 8, total_tokens: 26 });
64
+ assert.equal(sumUsage(null, { total_tokens: 3 }).total_tokens, 3);
65
+ assert.equal(sumUsage({ total_tokens: 7 }, null).total_tokens, 7);
66
+ });
67
+
68
+ /* SSE mock sunucusu: finish_reason=length → otomatik devam turu açılır */
69
+ function sseServer(script) {
70
+ const http = require('node:http');
71
+ const calls = [];
72
+ const srv = http.createServer((req, res) => {
73
+ let body = '';
74
+ req.on('data', (c) => (body += c));
75
+ req.on('end', () => {
76
+ calls.push(JSON.parse(body).messages);
77
+ res.writeHead(200, { 'Content-Type': 'text/event-stream' });
78
+ for (const ev of script(calls.length, calls[calls.length - 1])) res.write(ev);
79
+ res.end();
80
+ });
81
+ });
82
+ return { srv, calls };
83
+ }
84
+
85
+ test('chatStreamAuto: length kesintisinde kaldığı yerden devam eder', async () => {
86
+ const { srv, calls } = sseServer((n) => {
87
+ if (n === 1) {
88
+ return [
89
+ 'data: ' + JSON.stringify({ choices: [{ delta: { content: 'Merhaba dun' } }] }) + '\n\n',
90
+ 'data: ' + JSON.stringify({ choices: [{ delta: { content: 'ya basladi' }, finish_reason: 'length' }] }) + '\n\n',
91
+ 'data: [DONE]\n\n',
92
+ ];
93
+ }
94
+ return [
95
+ 'data: ' + JSON.stringify({ choices: [{ delta: { content: 'yor devam' } }] }) + '\n\n',
96
+ 'data: ' + JSON.stringify({ choices: [{ delta: {}, finish_reason: 'stop' }] }) + '\n\n',
97
+ 'data: [DONE]\n\n',
98
+ ];
99
+ });
100
+ await new Promise((r) => srv.listen(0, '127.0.0.1', r));
101
+ try {
102
+ const port = srv.address().port;
103
+ const sel = { url: `http://127.0.0.1:${port}/v1/chat/completions`, key: 'k', model: 'm' };
104
+ const deltas = [];
105
+ const r = await chatStreamAuto(sel, { messages: [{ role: 'user', content: 'selam' }] }, { onDelta: (d) => deltas.push(d) });
106
+ assert.equal(r.content, 'Merhaba dunya basladiyor devam');
107
+ assert.equal(r.finishReason, 'stop');
108
+ assert.equal(calls.length, 2);
109
+ // 2. turda devam mesajları eklenmiş olmalı (orijinal dizi kopyalanır)
110
+ const m2 = calls[1];
111
+ assert.equal(m2[m2.length - 2].role, 'assistant');
112
+ assert.equal(m2[m2.length - 1].role, 'user');
113
+ assert.match(m2[m2.length - 1].content, /DEVAM/);
114
+ // streaming kesintisiz: 3 delta
115
+ assert.deepEqual(deltas.join(''), r.content);
116
+ } finally {
117
+ srv.close();
118
+ }
119
+ });
120
+
121
+ test('chatStreamAuto: normal (stop) yanıtta hiç devam turu açılmaz', async () => {
122
+ const { srv, calls } = sseServer(() => [
123
+ 'data: ' + JSON.stringify({ choices: [{ delta: { content: 'tam cevap' }, finish_reason: 'stop' }] }) + '\n\n',
124
+ 'data: [DONE]\n\n',
125
+ ]);
126
+ await new Promise((r) => srv.listen(0, '127.0.0.1', r));
127
+ try {
128
+ const port = srv.address().port;
129
+ const sel = { url: `http://127.0.0.1:${port}/v1/chat/completions`, key: 'k', model: 'm' };
130
+ const r = await chatStreamAuto(sel, { messages: [{ role: 'user', content: 'selam' }] });
131
+ assert.equal(r.content, 'tam cevap');
132
+ assert.equal(calls.length, 1);
133
+ } finally {
134
+ srv.close();
135
+ }
136
+ });
@@ -48,8 +48,9 @@ test('paketlenmiş python scriptleri mevcut ve tohumlanır', () => {
48
48
  /* canlı: python hızlı arama yolu (ağ varsa gerçek sonuç, yoksa JS fallback) */
49
49
  test('webSearchFast: python yolu ya da fallback sonuç döner', async function () {
50
50
  const r = await tools.webSearchFast('electron builder portable windows', { maxResults: 5 });
51
- assert.strictEqual(r.ok, true);
52
- if (!r.results.length) return this.skip('motorlar bu turda sonuç döndürmedi (ağ/rate-limit) — kablaj doğru');
51
+ /* rate-limit/CAPTCHA turlarında motorlar ok:false dönebilir — kablaj testi;
52
+ nazik davranınca sonuç şekli doğrulanır, aksi halde skip */
53
+ if (!r || !r.ok || !r.results.length) return this.skip('motorlar bu turda sonuç döndürmedi (ağ/rate-limit) — kablaj doğru');
53
54
  assert.ok(r.results[0].title && r.results[0].url);
54
55
  }, { timeout: 40000 });
55
56
 
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ require('./setup');
4
+ const test = require('node:test');
5
+ const assert = require('node:assert');
6
+
7
+ const research = require('../src/agent/research');
8
+
9
+ test('normKey: scheme/www/slash/hash farklarını yok sayar', () => {
10
+ assert.equal(research.normKey('http://www.Example.com/a/'), research.normKey('https://example.com/a#x'));
11
+ });
12
+
13
+ test('mergeResults: round-robin harman + tekilleştirme', () => {
14
+ const a = [
15
+ { title: 'A1', url: 'https://a.com/1' },
16
+ { title: 'A2', url: 'https://a.com/2' },
17
+ ];
18
+ const b = [
19
+ { title: 'B1', url: 'https://a.com/1/' }, // A1 ile aynı (slash)
20
+ { title: 'B1x', url: 'http://www.b.com/x' },
21
+ ];
22
+ const merged = research.mergeResults([a, b], 10);
23
+ assert.deepEqual(merged.map((r) => r.url), ['https://a.com/1', 'https://a.com/2', 'http://www.b.com/x']);
24
+ assert.equal(merged[2].title, 'B1x'); // ilk gören kazanır, orijinal url korunur
25
+ });
26
+
27
+ test('normalizeRows: {results} veya dizi + alternatif anahtarlar', () => {
28
+ const r1 = research.normalizeRows({ results: [{ name: 'X', href: 'https://x.com', body: 'b' }] });
29
+ assert.equal(r1[0].title, 'X');
30
+ assert.equal(r1[0].url, 'https://x.com');
31
+ const r2 = research.normalizeRows([{ title: 'Y', link: 'https://y.com', description: 'd' }]);
32
+ assert.equal(r2[0].url, 'https://y.com');
33
+ assert.equal(research.normalizeRows(null).length, 0);
34
+ assert.equal(research.normalizeRows([{ title: '', url: 'https://z.com' }]).length, 0); // başlıksız elenir
35
+ });
36
+
37
+ test('deepSearch: paralel sorgular harmanlanır, ilk N sayfa okunur', async () => {
38
+ const searches = {
39
+ 'elma': [{ title: 'A', url: 'https://a.com' }, { title: 'B', url: 'https://b.com' }],
40
+ 'apple': [{ title: 'B2', url: 'https://b.com' }, { title: 'C', url: 'https://c.com' }],
41
+ };
42
+ const readCalls = [];
43
+ const r = await research.deepSearch(
44
+ { queries: ['elma', 'apple'], read_top: 2 },
45
+ {
46
+ search: async (q) => searches[q],
47
+ readPage: async (u) => { readCalls.push(u); return { ok: true, url: u, title: 'T:' + u, content: 'icerik ' + u }; },
48
+ }
49
+ );
50
+ assert.ok(r.ok);
51
+ assert.deepEqual(r.queries, ['elma', 'apple']);
52
+ assert.equal(r.results.length, 3); // b.com duplike düştü
53
+ assert.deepEqual(readCalls, ['https://a.com', 'https://b.com']); // ilk 2 hedef
54
+ assert.equal(r.pages.length, 2);
55
+ assert.ok(r.pages[0].content.includes('https://a.com'));
56
+ assert.match(r.note, /gizli tarayıcı/);
57
+ });
58
+
59
+ test('deepSearch: ikili dosya hedefleri okumadan atlanır', async () => {
60
+ const readCalls = [];
61
+ const r = await research.deepSearch(
62
+ { queries: ['q'], read_top: 3 },
63
+ {
64
+ search: async () => [
65
+ { title: 'PDF', url: 'https://x.com/doc.pdf' },
66
+ { title: 'Sayfa', url: 'https://x.com/page' },
67
+ ],
68
+ readPage: async (u) => { readCalls.push(u); return { ok: true, url: u, content: 'c' }; },
69
+ }
70
+ );
71
+ assert.deepEqual(readCalls, ['https://x.com/page']);
72
+ assert.equal(r.pages.length, 1);
73
+ });
74
+
75
+ test('deepSearch: readPage yoksa yalnız sonuç listesiyle döner', async () => {
76
+ const r = await research.deepSearch(
77
+ { queries: ['q'] },
78
+ { search: async () => [{ title: 'A', url: 'https://a.com' }] }
79
+ );
80
+ assert.ok(r.ok);
81
+ assert.equal(r.results.length, 1);
82
+ assert.equal(r.pages, undefined);
83
+ });
84
+
85
+ test('deepSearch: bir sorgu çökerse diğerleri kurtarır; boşsa ok:false', async () => {
86
+ const r1 = await research.deepSearch(
87
+ { queries: ['patlar', 'iyi'] },
88
+ {
89
+ search: async (q) => { if (q === 'patlar') throw new Error('boom'); return [{ title: 'A', url: 'https://a.com' }]; },
90
+ readPage: async () => ({ ok: false }),
91
+ }
92
+ );
93
+ assert.ok(r1.ok && r1.results.length === 1);
94
+ const r2 = await research.deepSearch(
95
+ { queries: ['yok1', 'yok2'] },
96
+ { search: async () => [], readPage: async () => ({ ok: false }) }
97
+ );
98
+ assert.equal(r2.ok, false);
99
+ assert.match(r2.error, /boş/);
100
+ });
101
+
102
+ test('deepSearch: sorgu yoksa net hata; queries string de kabul edilir', async () => {
103
+ const bad = await research.deepSearch({}, { search: async () => [] });
104
+ assert.equal(bad.ok, false);
105
+ const r = await research.deepSearch(
106
+ { queries: 'tek sorgu' },
107
+ { search: async () => [{ title: 'A', url: 'https://a.com' }] }
108
+ );
109
+ assert.deepEqual(r.queries, ['tek sorgu']);
110
+ });
111
+
112
+ test('SKIP_FILE_RE: pdf/görsel/arşiv yakalanır, sayfa yakalanmaz', () => {
113
+ assert.ok(research.SKIP_FILE_RE.test('https://x.com/a.pdf'));
114
+ assert.ok(research.SKIP_FILE_RE.test('https://x.com/img.png?w=9'));
115
+ assert.ok(!research.SKIP_FILE_RE.test('https://x.com/article'));
116
+ });