beast-agent 2.44.14 → 2.46.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.
@@ -72,6 +72,19 @@ function boundToolOutput(text) {
72
72
  başına sıraya girer (opencode ile aynı: oturum başına sıralı yürütme). */
73
73
  const SHELL_QUEUE_CAP = 6; /* en fazla bu kadar ayrı workspace oturumu */
74
74
  const SHELL_IDLE_MS = 10 * 60 * 1000; /* 10 dk boşta kalan oturum kapatılır */
75
+ /* TAKILMA SAVUNMASI:
76
+ - PROMPT_STALL_MS: komut bu süre HİÇ çıktı üretmediyse ve çıktı kuyruğu
77
+ etkileşimli prompt'a benziyorsa (y/n, "Ok to proceed?", parola...) komut
78
+ beklemeden kesilir — 120 sn timeout'u beklemek "takılma" gibi görünür.
79
+ - LONG_RUNNING_MS: dev server / uzun süreç kalıbı taşıyan komut bu süre
80
+ içinde bitmezse kesilir; ajan turu kilitlenmez. */
81
+ const PROMPT_STALL_MS = 3500;
82
+ const PROMPT_WATCH_MS = 1200;
83
+ const LONG_RUNNING_MS = 10000;
84
+ const PROMPT_STALL_RE =
85
+ /(?:\(y\/n\)|\[y\/n\]|\[Y\/n\]|\[y\/N\]|\(yes\/no\)|ok to proceed\?|do you want to (?:proceed|continue|allow)|are you sure|press any key|press enter|enter (?:the )?(?:password|passphrase|username|otp|verification code)|password:\s*$|passphrase:\s*$|username for [^\n]*:\s*$|(?:^|\n)\s*>>?\s*$)/i;
86
+ const LONG_RUNNING_RE =
87
+ /\b(?:(?:npm|yarn|pnpm|bun)\s+(?:run\s+)?(?:dev|start|serve)\b|(?:npm|yarn|pnpm|bun)\s+run\s+\S*(?:dev|watch|serve)\S*|\bvite\b(?!\s+build)|next\s+dev|nuxt\s+dev|ng\s+serve|nodemon|live-server|http-server|(?:python|py|python3)\s+-m\s+http\.server|uvicorn|gunicorn|flask\s+run|streamlit\s+run|manage\.py\s+runserver|hugo\s+server|jekyll\s+serve|(?:cargo|dotnet)\s+watch|docker[ -]compose\s+up\b(?!.*\s-d\b))/i;
75
88
  const _shSessions = new Map(); // cwd → session
76
89
 
77
90
  /* boşta reaper: unref'li zamanlayıcı — event loop'u tek başına tutmaz */
@@ -86,6 +99,142 @@ const _shellReaper = setInterval(() => {
86
99
  }, 60000);
87
100
  if (_shellReaper.unref) _shellReaper.unref();
88
101
 
102
+ /* prompt watchdog: çıktı akmıyorsa ve son satırlar girdi/onay prompt'una
103
+ benziyorsa komutu (ve yalnız onu) kes; sıradaki komutlar taze oturumda sürer */
104
+ const _shellPromptWatch = setInterval(() => {
105
+ const now = Date.now();
106
+ for (const sess of _shSessions.values()) {
107
+ if (!sess.busy || sess.dead || !sess.queue.length) continue;
108
+ const w = sess.queue[0];
109
+ if (!w || !w.dispatched) continue;
110
+ if (now - (sess.lastDataAt || sess.dispatchAt || 0) < PROMPT_STALL_MS) continue;
111
+ const tail = sess.buf.slice(-300);
112
+ if (!tail.trim() || !PROMPT_STALL_RE.test(tail)) continue;
113
+ _shellAbortCurrent(sess, 'komut girdi/onay bekliyor görünüyor (etkileşimli prompt)');
114
+ }
115
+ }, PROMPT_WATCH_MS);
116
+ if (_shellPromptWatch.unref) _shellPromptWatch.unref();
117
+
118
+ /* süren komutu kes, kuyruktakileri TAZE oturumda sürdür: tur kilitlenmez,
119
+ paralel çağrılar kaybolmaz. Diske/oturuma yazılan değişkenler sıfırlanır. */
120
+ function _shellAbortCurrent(sess, reason) {
121
+ const w = sess.queue.shift();
122
+ if (!w) return;
123
+ clearTimeout(w.timer);
124
+ clearTimeout(w.longTimer);
125
+ if (w.onAbort && w.signal) {
126
+ try { w.signal.removeEventListener('abort', w.onAbort); } catch {}
127
+ }
128
+ const out = sess.buf.slice(-1500);
129
+ sess.buf = '';
130
+ sess.err = '';
131
+ const pending = sess.queue.splice(0);
132
+ _shellDispose(sess);
133
+ if (_shSessions.get(sess.cwd) === sess) _shSessions.delete(sess.cwd);
134
+ try {
135
+ w.resolve({
136
+ ok: false,
137
+ code: null,
138
+ output:
139
+ (out.trim() ? out.trim() + '\n' : '') +
140
+ '[beast] ' + reason +
141
+ ' — komut durduruldu. Etkileşimsiz yeniden dene (ör. -y / -m / --yes / --accept-*) ya da kalıcı girdi gerektiren programı ajan terminalinde çalıştırma.',
142
+ });
143
+ } catch {}
144
+ if (!pending.length) return;
145
+ let fresh = null;
146
+ try {
147
+ fresh = _spawnShell(sess.cwd);
148
+ _shSessions.set(sess.cwd, fresh);
149
+ } catch {}
150
+ if (!fresh) {
151
+ for (const p of pending) {
152
+ clearTimeout(p.timer);
153
+ clearTimeout(p.longTimer);
154
+ p.resolve({ ok: false, code: null, output: '[beast] kabuk oturumu yenilendi — komut çalıştırılamadı' });
155
+ }
156
+ return;
157
+ }
158
+ fresh.busy = true;
159
+ fresh.queue = pending;
160
+ _shellDispatch(fresh);
161
+ }
162
+
163
+ /* etkileşimli/REPL komutlar kalıcı kabuğun stdin işaret protokolünü bozar
164
+ (Read-Host işaret satırını yer) ya da 120 sn boyunca asılı kalır. Bilinen
165
+ kalıplar daha spawn edilmeden net hata + düzeltme önerisiyle geri çevrilir.
166
+ null dönerse komut güvenli sayılır. */
167
+ function interactiveCommandReason(command) {
168
+ const raw = String(command || '').trim();
169
+ if (!raw) return null;
170
+ const chain = raw.split(/[;&|]+/)[0].trim().replace(/^&\s*/, '');
171
+ /* ilk token: tırnaklı yol da olabilir (ör. "C:\Program Files\Git\python.exe") */
172
+ const qm = /^(["'])(.+?)\1/.exec(chain);
173
+ const firstTok = qm ? qm[2] : (chain.split(/\s+/)[0] || '');
174
+ const rest = (qm ? chain.slice(qm[0].length) : chain.slice(firstTok.length)).trim();
175
+ const base = path.basename(firstTok.replace(/\.(exe|cmd|bat)$/i, '')).toLowerCase();
176
+ const bare = !rest;
177
+ /* çıplak REPL / iç içe kabuk: ajan oraya ASLA yazamaz */
178
+ if (bare && /^(python[23]?|py|node|deno|bun|cmd|powershell|pwsh|bash|sh|wsl|mysql|psql|sqlite3|mongosh|redis-cli|irb|ruby|php|lua|r)$/.test(base)) {
179
+ return (
180
+ '`' + firstTok + '` çıplak bir REPL/kabuktur — etkileşimli girdi bekler ve ajan turunu kilitler. ' +
181
+ 'Betiği dosyaya yazıp çalıştır (ör. `python script.py`, `node script.js`) ya da tek satır komut kullan (ör. `python -c "..."`).'
182
+ );
183
+ }
184
+ /* editörler: pencere açar ve kapanana kadar bekler */
185
+ if (/^(vi|vim|nvim|nano|emacs|notepad|code)$/.test(base)) {
186
+ return (
187
+ '`' + base + '` etkileşimli editör — ajan terminalinde asılı kalır. Dosyayı write_file/edit_file araçlarıyla düzenle.'
188
+ );
189
+ }
190
+ /* pager: dosya argümanı yoksa stdin bekler */
191
+ if (/^(less|more)$/.test(base) && !/[<|]|\S+\.\S{1,6}\b/.test(rest)) {
192
+ return (
193
+ '`' + base + '` girdi dosyası olmadan stdin bekler. Dosyayı doğrudan belirt (ör. `more dosya.txt`) ya da read_file aracını kullan.'
194
+ );
195
+ }
196
+ /* stdin filtreleri: dosya/pipe yoksa pipe'tan okumaya çalışır ve asılır */
197
+ if (/^(findstr|sort|clip|tee)$/.test(base) && !/[<|]|\S+\.\S{1,6}\b/.test(rest)) {
198
+ return (
199
+ '`' + base + '` girdi belirtilmeden stdin bekler. Girdiyi dosya/pipe ile ver ya da grep/read_file araçlarını kullan.'
200
+ );
201
+ }
202
+ /* PowerShell onay/girdi cmdlet'leri */
203
+ if (/\b(Read-Host|Get-Credential|Out-GridView|Wait-Event|Wait-Input)\b/i.test(raw)) {
204
+ return (
205
+ '`Read-Host`/`Get-Credential` benzeri etkileşimli cmdlet ajan terminalinde çalışmaz — girdiyi parametre olarak ver.'
206
+ );
207
+ }
208
+ if (/(?:^|[;&|]\s*|\/c\s+)pause(?:\s|$)/i.test(raw)) {
209
+ return '`pause` girdi bekler — ajan terminalinde çalışmaz; beklemeyi kaldır.';
210
+ }
211
+ /* git: editör açan / interaktif alt komutlar */
212
+ if (/\bgit\s+commit\b/i.test(raw) && !/(\s-[a-zA-Z]*m\b|\s--message\b|\s-F\b|\s--file\b|--no-edit\b|--fixup\b|--squash\b)/i.test(raw)) {
213
+ return '`git commit` mesaj bayrağı olmadan editör açar — `git commit -m "mesaj"` kullan (gerekiyorsa `-F dosya`).';
214
+ }
215
+ if (/\bgit\s+merge\b/i.test(raw) && !/(\s-[a-zA-Z]*m\b|\s--message\b|--no-edit\b|--abort\b|--continue\b|--quit\b)/i.test(raw)) {
216
+ return '`git merge` düzenleyici açabilir — `git merge --no-edit` ya da `-m "mesaj"` kullan.';
217
+ }
218
+ if (/\bgit\s+(?:rebase\s+(?:-i|--interactive)|add\s+(?:-i|-p|--interactive|--patch))\b/i.test(raw)) {
219
+ return '`git rebase -i` / `git add -p` interaktiftir — ajan terminalinde çalışmaz; otomatik/`-m` formunu kullan.';
220
+ }
221
+ /* kurulum sihirbazları: soru sorar */
222
+ if (/\bnpm\s+init\b/i.test(raw) && !/(\s-y\b|\s--yes\b)/i.test(raw)) {
223
+ return '`npm init` sihirbazı soru sorar — `npm init -y` kullan ya da package.json\'u write_file ile yaz.';
224
+ }
225
+ if (/\b(?:choco|chocolatey)\s+install\b/i.test(raw) && !/(\s-y\b|\s--yes\b)/i.test(raw)) {
226
+ return '`choco install` onay sorar — `-y` ekle.';
227
+ }
228
+ /* çıplak `npx` paket yoksa "Ok to proceed? (y)" ile asılır: --yes öner */
229
+ if (bare && base === 'npx') {
230
+ return '`npx` paket kurulum onayı isteyebilir — `npx --yes <paket>` kullan.';
231
+ }
232
+ if (base === 'ssh' && !/BatchMode/i.test(raw)) {
233
+ return '`ssh` parola sorabilir ve asılır — `ssh -o BatchMode=yes` kullan ya da anahtar tabanlı oturum kur.';
234
+ }
235
+ return null;
236
+ }
237
+
89
238
  function _shellDispose(sess) {
90
239
  try {
91
240
  if (sess.proc && sess.proc.pid) {
@@ -104,7 +253,7 @@ function disposeShellSessions() {
104
253
  function _spawnShell(cwd) {
105
254
  const proc = spawn(
106
255
  'powershell.exe',
107
- ['-NoProfile', '-NoExit', '-Command', '-'],
256
+ ['-NoProfile', '-NonInteractive', '-NoExit', '-ExecutionPolicy', 'Bypass', '-Command', '-'],
108
257
  { cwd: cwd || process.cwd(), windowsHide: true, env: envWithPathPrefix() }
109
258
  );
110
259
  const sess = {
@@ -114,9 +263,23 @@ function _spawnShell(cwd) {
114
263
  err: '',
115
264
  seq: 0,
116
265
  busy: false,
117
- queue: [], // {command, resolve, timer, signal, onAbort}
266
+ queue: [], // {command, resolve, timer, longTimer, signal, onAbort}
118
267
  dead: false,
268
+ lastDataAt: 0,
269
+ dispatchAt: 0,
119
270
  };
271
+ /* TAKILMA SAVUNMASI: prompter/prefs kapatılır — Read-Host, -Confirm gibi
272
+ girdi bekleyen cmdlet'ler asılmak yerine ANINDA hata verir.
273
+ Çıkış UTF-8: Türkçe çıktı (Write-Output, dosya adları) bozulmaz.
274
+ Giriş kodlaması ÖNEMSİZ: komutlar base64 (ASCII) ile gönderilir. */
275
+ try {
276
+ proc.stdin.write(
277
+ "$ConfirmPreference='None'\n" +
278
+ "$ProgressPreference='SilentlyContinue'\n" +
279
+ "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8\n" +
280
+ "$OutputEncoding=[System.Text.Encoding]::UTF8\n"
281
+ );
282
+ } catch {}
120
283
  /* Node 22: child + stdio stream'leri event loop'a bağlanmaz — test/CLI'da
121
284
  bekleyen oturum sürecin çıkmasını ENGELLEMEZ; uygulama kapanışında
122
285
  disposeShellSessions() yine de temiz kapatır */
@@ -130,10 +293,12 @@ function _spawnShell(cwd) {
130
293
  proc.stdout.setEncoding('utf8');
131
294
  proc.stderr.setEncoding('utf8');
132
295
  proc.stdout.on('data', (d) => {
296
+ sess.lastDataAt = Date.now();
133
297
  sess.buf += d;
134
298
  _shellPump(sess);
135
299
  });
136
300
  proc.stderr.on('data', (d) => {
301
+ sess.lastDataAt = Date.now();
137
302
  sess.err += d;
138
303
  if (sess.err.length > MAX_CMD_OUTPUT * 4) sess.err = sess.err.slice(-MAX_CMD_OUTPUT * 2);
139
304
  });
@@ -143,6 +308,7 @@ function _spawnShell(cwd) {
143
308
  while (sess.queue.length) {
144
309
  const w = sess.queue.shift();
145
310
  clearTimeout(w.timer);
311
+ clearTimeout(w.longTimer);
146
312
  w.resolve({ ok: false, code: null, output: sess.buf.slice(-2000) + '\n[beast] shell oturumu kapandı' });
147
313
  }
148
314
  sess.busy = false;
@@ -168,6 +334,10 @@ function _shellPump(sess) {
168
334
  sess.queue.shift();
169
335
  sess.busy = sess.queue.length > 0;
170
336
  clearTimeout(w.timer);
337
+ clearTimeout(w.longTimer);
338
+ if (w.onAbort && w.signal) {
339
+ try { w.signal.removeEventListener('abort', w.onAbort); } catch {}
340
+ }
171
341
  const codeM = /_(\-?\d+)\s*$/.exec(markerLine);
172
342
  const exitCode = codeM ? Number(codeM[1]) : null;
173
343
  if (out.length > MAX_CMD_OUTPUT * 4) out = out.slice(-MAX_CMD_OUTPUT * 2);
@@ -193,23 +363,58 @@ function _shellDispatch(sess) {
193
363
  w.n = ++sess.seq;
194
364
  const cmd = w.command;
195
365
  const marker = `${SHELL_MARKER_PREFIX}${w.n}_$LASTEXITCODE`;
196
- /* $LASTEXITCODE sıfırlanır: yalnız cmdlet koşan komutlar da ok:true dönsün;
197
- native exe hata verirse gerçek kod işaret satırına yazılır */
198
- sess.proc.stdin.write(`$global:LASTEXITCODE = 0\n${cmd}${cmd.endsWith('\n') ? '' : '\n'}Write-Output "${marker}"\n`);
366
+ /* KOMUT base64 ile taşınır: PowerShell'ın stdin okuyucusu oturum açılışında
367
+ sabit (eski) codepage ile kurulur; UTF-8 komut metni Türkçe karakterlerde
368
+ bozulurdu. base64 ASCII'dir → kod sayfası ne olursa olsun metin birebir
369
+ çözülür. Tüm satır TEK satır olarak yazılır: PowerShell tüm ifadeyi
370
+ okuyup öyle çalıştırır — stdin okuyan bir program protokolü yiyemez. */
371
+ const b64 = Buffer.from(String(cmd), 'utf8').toString('base64');
372
+ sess.dispatchAt = Date.now();
373
+ sess.lastDataAt = sess.dispatchAt;
374
+ try {
375
+ sess.proc.stdin.write(
376
+ `$__beastB64='${b64}'; $global:LASTEXITCODE=0; ` +
377
+ `Invoke-Expression ([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($__beastB64))); ` +
378
+ `Write-Output "${marker}"\n`
379
+ );
380
+ } catch {
381
+ sess.dead = true;
382
+ return;
383
+ }
199
384
  const finishTimeout = () => {
200
385
  _shellDispose(sess);
201
386
  if (_shSessions.get(sess.cwd) === sess) _shSessions.delete(sess.cwd);
202
387
  sess.busy = false;
388
+ const secs = Math.round((w.timeoutMs || 0) / 1000);
203
389
  while (sess.queue.length) {
204
390
  const x = sess.queue.shift();
205
391
  clearTimeout(x.timer);
206
- x.resolve({ ok: false, code: null, output: '[beast] shell komutu zaman aşımı — oturum tazelendi' });
392
+ clearTimeout(x.longTimer);
393
+ x.resolve({
394
+ ok: false,
395
+ code: null,
396
+ output:
397
+ '[beast] shell komutu zaman aşımı (' + secs + ' sn) — komut girdi bekliyor olabilir ' +
398
+ 'ya da çok uzun sürdü. Etkileşimsiz bayraklarla dene (-y / -m / --yes); uzun süreçleri ' +
399
+ 'arka planda başlat (panel_run ya da Start-Process + çıktıyı dosyaya yönlendir). Oturum tazelendi.',
400
+ });
207
401
  }
208
402
  };
209
403
  w.timer = setTimeout(finishTimeout, w.timeoutMs);
404
+ /* UZUN SÜRELİ SÜREÇ: dev server/serving kalıbı turu kilitlemesin — makul
405
+ süre içinde bitmezse erken kes, ajana nasıl başlatacağını söyle. */
406
+ if (LONG_RUNNING_RE.test(cmd)) {
407
+ w.longTimer = setTimeout(() => {
408
+ _shellAbortCurrent(
409
+ sess,
410
+ 'uzun süreli süreç (dev server/serving) ' + Math.round(LONG_RUNNING_MS / 1000) + ' sn içinde bitmedi — turu kilitlememek için durduruldu'
411
+ );
412
+ }, LONG_RUNNING_MS);
413
+ }
210
414
  if (w.signal) {
211
415
  if (w.signal.aborted) return finishTimeout();
212
- w.signal.addEventListener('abort', finishTimeout, { once: true });
416
+ w.onAbort = finishTimeout;
417
+ w.signal.addEventListener('abort', w.onAbort, { once: true });
213
418
  }
214
419
  }
215
420
 
@@ -269,6 +474,8 @@ function runBashCommand(command, cwd, timeoutMs = 90000, signal) {
269
474
  child = spawn(gitbash(), ['-lc', command], {
270
475
  cwd: cwd || process.cwd(),
271
476
  windowsHide: true,
477
+ /* stdin KAPALI: etkileşimli programlar girdi bekleyip asılmaz, hemen çıkar */
478
+ stdio: ['ignore', 'pipe', 'pipe'],
272
479
  env: envWithPathPrefix(),
273
480
  });
274
481
  } catch {
@@ -277,7 +484,9 @@ function runBashCommand(command, cwd, timeoutMs = 90000, signal) {
277
484
  }
278
485
  const timer = setTimeout(() => {
279
486
  try { spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true }); } catch {}
280
- err += '\n[beast] command timed out';
487
+ err +=
488
+ '\n[beast] komut zaman aşımı (' + Math.round(timeoutMs / 1000) + ' sn) — girdi bekliyor olabilir ' +
489
+ 'ya da çok uzun sürdü; etkileşimsiz bayraklarla dene, uzun süreçleri arka planda başlat.';
281
490
  finish(null, true);
282
491
  }, timeoutMs);
283
492
  if (signal) {
@@ -329,16 +538,23 @@ function runCommand(command, cwd, timeoutMs = 90000, signal) {
329
538
  const child = spawn(
330
539
  'powershell.exe',
331
540
  ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', command],
332
- { cwd: cwd || process.cwd(), windowsHide: true, env: envWithPathPrefix() }
541
+ {
542
+ cwd: cwd || process.cwd(),
543
+ windowsHide: true,
544
+ /* stdin KAPALI: etkileşimli programlar girdi bekleyip asılmaz, hemen çıkar */
545
+ stdio: ['ignore', 'pipe', 'pipe'],
546
+ env: envWithPathPrefix(),
547
+ }
333
548
  );
334
549
 
335
550
  const timer = setTimeout(() => {
336
551
  try {
337
552
  spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true });
338
553
  } catch {}
554
+ err +=
555
+ '\n[beast] komut zaman aşımı (' + Math.round(timeoutMs / 1000) + ' sn) — girdi bekliyor olabilir ' +
556
+ 'ya da çok uzun sürdü; etkileşimsiz bayraklarla dene, uzun süreçleri arka planda başlat.';
339
557
  finish(null, true);
340
- // append notice
341
- err += '\n[beast] command timed out';
342
558
  }, timeoutMs);
343
559
 
344
560
  if (signal) {
@@ -1587,7 +1803,9 @@ const definitions = [
1587
1803
  function: {
1588
1804
  name: 'run_command',
1589
1805
  description:
1590
- 'Executes a given PowerShell command on the user\'s Windows machine in the workspace directory and returns combined stdout/stderr. Use this tool for terminal operations like builds, git, npm installs, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead: read_file, write_file, edit_file, grep, glob.',
1806
+ 'Executes a given PowerShell command on the user\'s Windows machine in the workspace directory and returns combined stdout/stderr. Use this tool for terminal operations like builds, git, npm installs, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead: read_file, write_file, edit_file, grep, glob.\n' +
1807
+ 'NON-INTERACTIVE: Komutlar girdi alamaz — REPL/editör/pager başlatma (`node`, `python`, `vi`, `notepad`), girdi bekleyen komut çalıştırma (`git commit` mesajsız, `npm init -y`siz, `ssh` BatchMode olmadan) — bunlar otomatik reddedilir/kilitlenmeden kesilir. Etkileşimsiz bayrak kullan (-y, -m, --yes, --accept-*), betikleri dosyaya yazıp çalıştır.\n' +
1808
+ 'UZUN SÜRELİ SÜREÇ: dev server/serving komutlarını (npm run dev/start, vite, uvicorn, flask run...) bu araçla BAŞLATMA — 10 sn içinde kesilir; panel_run (ÇALIŞTIR paneli) ya da arka plan (Start-Process + çıktıyı log dosyasına yönlendir) kullan.',
1591
1809
  parameters: {
1592
1810
  type: 'object',
1593
1811
  properties: {
@@ -1884,11 +2102,18 @@ async function exec(name, args, ctx) {
1884
2102
  return JSON.stringify({ ok: false, error: `Invalid timeout value: ${tRaw}. Timeout must be a positive number.` });
1885
2103
  }
1886
2104
  const t = Number.isFinite(tRaw) && tRaw > 0 ? tRaw : 120000;
2105
+ const cmdStr = String(args.command || '');
2106
+ /* TAKILMA SAVUNMASI: etkileşimli komutlar stdin beklerken turu kilitler
2107
+ (Read-Host işaret satırını yer, REPL/editör asılı kalır) — başlamadan reddet */
2108
+ const interact = interactiveCommandReason(cmdStr);
2109
+ if (interact) {
2110
+ return JSON.stringify({ ok: false, code: null, output: '[beast] ' + interact });
2111
+ }
1887
2112
  const shell = String(args.shell || 'powershell').toLowerCase();
1888
2113
  const r =
1889
2114
  shell === 'bash' || shell === 'sh'
1890
- ? await runBashCommand(String(args.command || ''), cwd, t, ctx.signal)
1891
- : await runShellCommand(String(args.command || ''), cwd, t, ctx.signal);
2115
+ ? await runBashCommand(cmdStr, cwd, t, ctx.signal)
2116
+ : await runShellCommand(cmdStr, cwd, t, ctx.signal);
1892
2117
  const b = boundToolOutput((r && r.output) || '');
1893
2118
  return JSON.stringify({
1894
2119
  ok: !!(r && r.ok),
@@ -2360,6 +2585,11 @@ function banBrowser(minutes = 10) {
2360
2585
  _browserBanUntil = Date.now() + minutes * 60 * 1000;
2361
2586
  }
2362
2587
 
2588
+ /* test/teşhis: ban bitimine kalan süre */
2589
+ function browserBanRemainingMs() {
2590
+ return Math.max(0, _browserBanUntil - Date.now());
2591
+ }
2592
+
2363
2593
  /* Zinciri sırayla koştur: her motor boş/hata dönerse sıradakine geç.
2364
2594
  browser motoru yalnız engine'den gelir (hook); araç-bağımsız çağrılarda atlanır. */
2365
2595
  async function searchChainWeb(query, maxResults, { signal, browser } = {}) {
@@ -2384,7 +2614,10 @@ async function searchChainWeb(query, maxResults, { signal, browser } = {}) {
2384
2614
  if (typeof browser !== 'function' || banned) continue;
2385
2615
  out = await browser();
2386
2616
  if (!out || !out.ok || !(out.results || []).length) {
2387
- banBrowser(); /* tarayıcı sorunlu — 10 dk atla, alternatifler devrede */
2617
+ /* GERÇEK engel (CAPTCHA / olağandışı trafik) → 10 dk; geçici hata
2618
+ (ağ, zaman aşımı, boş yanıt) → yalnızca 1 dk bekleme — eskiden
2619
+ her geçici hatada tarayıcı 10 dk boyunca devre dışı kalıyordu. */
2620
+ banBrowser(out && out.blocked ? 10 : 1);
2388
2621
  out = null;
2389
2622
  }
2390
2623
  } else if (row.id === 'stealth') {
@@ -2404,7 +2637,7 @@ async function searchChainWeb(query, maxResults, { signal, browser } = {}) {
2404
2637
  if (out && out.ok && !(out.results || []).length) out = null; /* boş → sıradaki motor */
2405
2638
  }
2406
2639
  if (out && out.ok && banned && (out.results || []).length) {
2407
- out.note = 'dahili tarayıcı 10 dk askıda (CAPTCHA/trafik) — alternatif motor kullanıldı';
2640
+ out.note = 'dahili tarayıcı kısa süre askıda (CAPTCHA/trafik ya da geçici hata) — alternatif motor kullanıldı';
2408
2641
  }
2409
2642
  return out || { ok: false, error: 'web arama başarısız — tüm motorlar boş döndü' };
2410
2643
  }
@@ -2474,6 +2707,7 @@ module.exports = {
2474
2707
  runCommand,
2475
2708
  runShellCommand,
2476
2709
  runBashCommand,
2710
+ interactiveCommandReason,
2477
2711
  gitbash,
2478
2712
  disposeShellSessions,
2479
2713
  boundToolOutput,
@@ -2498,6 +2732,7 @@ module.exports = {
2498
2732
  searchChainWeb,
2499
2733
  browserBanned,
2500
2734
  banBrowser,
2735
+ browserBanRemainingMs,
2501
2736
  setTinyfishKey,
2502
2737
  tinyfishSearch,
2503
2738
  stealthSearch,
@@ -49,6 +49,8 @@ const TOOL_LABELS = {
49
49
  browser_screenshot: () => 'Ekran görüntüsü',
50
50
  browser_snapshot: () => 'Sayfa yapısı',
51
51
  browser_read_text: () => 'Sayfa metni',
52
+ browser_wait: (a) =>
53
+ `Bekliyor${a.text ? ': "' + String(a.text).slice(0, 40) + '"' : a.selector ? ': ' + String(a.selector).slice(0, 40) : a.ms ? ' (' + a.ms + ' ms)' : ''}`,
52
54
  email_list: () => 'E-posta listesi',
53
55
  email_read: (a) => `E-posta oku: ${a.uid ?? ''}`,
54
56
  email_send: () => 'E-posta gönder',