@omerrgocmen/crewctl 1.1.1 → 1.3.1

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.
@@ -29,10 +29,29 @@ const CODEX_MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
29
29
  let codexModelRefreshDue = false;
30
30
  let codexModelCache = { checkedAt: 0, models: [] };
31
31
 
32
+ // OpenCode model listesini config'e yaz: prob taze (cache-disi) liste dondurdugunde saklariz
33
+ // ki bir sonraki acilista `opencode models` gecici olarak yavas/basarisiz olsa bile dropdown
34
+ // bos kalmasin — son bilinen liste fallback olur. codexModelCache ile ayni desen.
35
+ function persistOpenCodeModels(cfg, statuses) {
36
+ const oc = (statuses || []).find((c) => c.id === "opencode");
37
+ const fresh = oc && oc.installed && !oc.modelsFromCache && Array.isArray(oc.models) ? oc.models : [];
38
+ if (!fresh.length) return false;
39
+ if (JSON.stringify((cfg.openCodeModelCache || {}).models || []) === JSON.stringify(fresh)) return false;
40
+ cfg.openCodeModelCache = { checkedAt: new Date().toISOString(), models: fresh };
41
+ return true;
42
+ }
43
+
32
44
  store.ensureDirs();
33
45
  // Acilis maliyetini dusurmek icin onceki kesfin sonucu kullanilir; ayrintili kurallar
34
46
  // cli-registry.discoverInstalled icinde. Onbellek gecersiz/eksikse tam yoklama yapilir.
35
- let cliStatus = cliRegistry.discoverInstalled({ cache: store.loadConfig().cliDiscoveryCache });
47
+ // OpenCode modelleri persist EDILMEDIGI icin son bilinen listeyi fallback olarak veririz.
48
+ // cfg ayrica model onerisinin kullanicinin modelPreferences desenlerine gore uretilmesini saglar.
49
+ const bootCfg = store.loadConfig();
50
+ let cliStatus = cliRegistry.discoverInstalled({
51
+ cache: bootCfg.cliDiscoveryCache,
52
+ openCodeModels: bootCfg.openCodeModelCache?.models,
53
+ cfg: bootCfg,
54
+ });
36
55
  {
37
56
  const cfg = store.loadConfig();
38
57
  const nextDiscoveryCache = cliRegistry.discoveryCacheFrom(cliStatus);
@@ -58,7 +77,8 @@ let cliStatus = cliRegistry.discoverInstalled({ cache: store.loadConfig().cliDis
58
77
  }
59
78
  let changed = cliRegistry.addMissingAgents(cfg, cliStatus);
60
79
  if (cliRegistry.ensureValidOperator(cfg, cliStatus)) changed = true;
61
- if (changed || staleHealthCleared || discoveryCacheChanged || JSON.stringify(savedModels) !== JSON.stringify(cfg.codexModelCache)) store.saveConfig(cfg);
80
+ const openCodeModelsChanged = persistOpenCodeModels(cfg, cliStatus);
81
+ if (changed || staleHealthCleared || discoveryCacheChanged || openCodeModelsChanged || JSON.stringify(savedModels) !== JSON.stringify(cfg.codexModelCache)) store.saveConfig(cfg);
62
82
  }
63
83
 
64
84
  // ---- SSE istemcileri ----
@@ -220,7 +240,6 @@ async function refreshCliHealth(force = false) {
220
240
  }
221
241
  }
222
242
 
223
- // ---- yardimcilar ----
224
243
  function send(res, code, obj) {
225
244
  res.writeHead(code, { "Content-Type": "application/json; charset=utf-8" });
226
245
  res.end(JSON.stringify(obj));
@@ -360,7 +379,6 @@ async function browseDir(p, warning = "") {
360
379
  const server = http.createServer(async (req, res) => {
361
380
  const { pathname } = url.parse(req.url, true);
362
381
 
363
- // SSE
364
382
  if (pathname === "/api/events") {
365
383
  res.writeHead(200, {
366
384
  "Content-Type": "text/event-stream",
@@ -374,7 +392,6 @@ const server = http.createServer(async (req, res) => {
374
392
  return;
375
393
  }
376
394
 
377
- // API
378
395
  if (pathname.startsWith("/api/")) {
379
396
  try {
380
397
  if (pathname === "/api/state" && req.method === "GET") {
@@ -403,7 +420,7 @@ const server = http.createServer(async (req, res) => {
403
420
  return send(res, 200, await browseDir(url.parse(req.url, true).query.path));
404
421
  }
405
422
  if (pathname === "/api/tasks" && req.method === "POST") {
406
- const { prompt, targetDir, operatorCli, executionMode } = await readBody(req);
423
+ const { prompt, targetDir, operatorCli, executionMode, freshContext } = await readBody(req);
407
424
  if (!prompt || !prompt.trim()) return send(res, 400, { error: "prompt gerekli" });
408
425
  const cfg = store.loadConfig();
409
426
  // Operatör, uzman agent'lardan bağımsız bir CLI'dır (claude/codex/gemini/opencode);
@@ -437,6 +454,8 @@ const server = http.createServer(async (req, res) => {
437
454
  }
438
455
  const mode = ["auto", "fast", "balanced", "deep"].includes(executionMode) ? executionMode : "auto";
439
456
  const t = store.addTask(prompt.trim(), targetDir, selectedCli, mode);
457
+ // --fresh esdegeri: bu gorevde proje profili (.crewctl/CONTEXT.md) yuklenmez.
458
+ if (freshContext === true) { t.freshContext = true; store.saveTask("pending", t); }
440
459
  engine.wake();
441
460
  broadcast("queue", snapshot());
442
461
  return send(res, 200, t);
@@ -484,8 +503,11 @@ const server = http.createServer(async (req, res) => {
484
503
  }
485
504
  if (pathname === "/api/cli/discover" && req.method === "POST") {
486
505
  // "Yeniden Tara" kullanicinin acik talebidir: onbellegi tumden atla, her CLI'yi yokla.
487
- cliStatus = cliRegistry.discoverInstalled({ force: true });
506
+ // OpenCode modelleri persist edilmedigi icin son bilinen listeyi fallback ver; taze
507
+ // liste gelirse persistOpenCodeModels ile guncellenir. cfg ise model onerisini
508
+ // kullanicinin modelPreferences desenlerine gore urettirir.
488
509
  const cfg = store.loadConfig();
510
+ cliStatus = cliRegistry.discoverInstalled({ force: true, cfg, openCodeModels: cfg.openCodeModelCache?.models });
489
511
  cfg.cliDiscoveryCache = cliRegistry.discoveryCacheFrom(cliStatus);
490
512
  const body = await readBody(req);
491
513
  if (Array.isArray(body.ignoredAdapters)) {
@@ -493,6 +515,7 @@ const server = http.createServer(async (req, res) => {
493
515
  }
494
516
  let changed = cliRegistry.addMissingAgents(cfg, cliStatus);
495
517
  if (cliRegistry.ensureValidOperator(cfg, cliStatus)) changed = true;
518
+ if (persistOpenCodeModels(cfg, cliStatus)) changed = true;
496
519
  if (changed || Array.isArray(body.ignoredAdapters)) store.saveConfig(cfg);
497
520
  await refreshCliHealth(true);
498
521
  return send(res, 200, { cliStatus, changed, config: store.loadConfig() });
@@ -698,7 +721,6 @@ const server = http.createServer(async (req, res) => {
698
721
  }
699
722
  }
700
723
 
701
- // Statik
702
724
  serveStatic(res, pathname === "/" ? "index.html" : pathname.slice(1));
703
725
  });
704
726
 
@@ -125,11 +125,19 @@ const FALLBACK_CONFIG = {
125
125
  discoveryIgnoredAdapters: [],
126
126
  liveDiff: true, liveDiffIntervalMs: 2500,
127
127
  versioning: true, versioningRetention: 20,
128
+ // Ajan hapsi: "workspace" = her CLI'nin native sandbox'i ile calisma klasoru DISINA yazma/ag
129
+ // engellenir (Docker/Git gerektirmez; codex mac/Linux/Windows'ta OS-native zorlar). "off" =
130
+ // eski davranis. extraWritableDirs: calisma klasoru disinda ek yazilabilir mutlak yollar.
131
+ sandbox: { mode: "workspace", extraWritableDirs: [] },
132
+ // Proje context: her calisma klasorunun .crewctl/CONTEXT.md profili gorev acilisinda yuklenir,
133
+ // bitiminde operator revize eder. false = eski global hafiza davranisi.
134
+ projectContext: true, projectContextCharBudget: 6000,
128
135
  notify: { webhookUrl: "", onComplete: true, onFailed: true },
129
136
  operator: { roleFile: "roles/operator.md", maxRounds: 6, maxDelegationsPerRound: 8, maxInfrastructureRecoveryRounds: 2, protocolRetries: 1 },
137
+ resilience: { transientRetries: 2, retryBaseSeconds: 3, maxFailoverAgents: 1 },
130
138
  cliSettings: {
131
139
  codex: { model: "", reasoningEffort: "medium", serviceTier: "fast" },
132
- opencode: { model: "" },
140
+ opencode: { model: "", modelPreferences: ["*deepseek*free*", "*free*", "*"], modelExclude: ["ollama/*", "lmstudio/*", "llamacpp/*", "local/*", "localhost/*"] },
133
141
  },
134
142
  skills: { enabled: [], autoMatch: true, catalogLimit: 12, maxSkillsPerAssignment: 3, charBudget: 2400, referenceCharBudget: 1200 },
135
143
  agents: {}, riskyPatterns: [], schedules: [],
@@ -146,9 +154,24 @@ function normalizeConfig(cfg) {
146
154
  ...cfg,
147
155
  skills: { enabled: [], autoMatch: true, catalogLimit: 12, maxSkillsPerAssignment: 3, charBudget: 2400, referenceCharBudget: 1200, ...(cfg.skills || {}) },
148
156
  schedules: Array.isArray(cfg.schedules) ? cfg.schedules : [],
157
+ // Her yuklemede hapis semasini garanti et; boylece eski config.json'lar yukseltilince
158
+ // varsayilan olarak "workspace" hapsini kazanir. Gecersiz mode "workspace"e duser.
159
+ sandbox: {
160
+ mode: ["off", "workspace"].includes(cfg.sandbox?.mode) ? cfg.sandbox.mode : "workspace",
161
+ extraWritableDirs: Array.isArray(cfg.sandbox?.extraWritableDirs)
162
+ ? cfg.sandbox.extraWritableDirs.filter((x) => typeof x === "string" && x.trim()).map(String)
163
+ : [],
164
+ },
149
165
  cliSettings: {
150
166
  codex: { model: "", reasoningEffort: "medium", serviceTier: "fast", ...legacyCodex, ...(current.codex || {}) },
151
- opencode: { model: legacyOpenCodeModel, ...(current.opencode || {}) },
167
+ // Desenler mevcut config'lere de eklenir ki kullanici bunlari Ayarlar'da gorup
168
+ // kendi aboneligine gore duzenleyebilsin. Kendi degerini yazan kullanicininki korunur.
169
+ opencode: {
170
+ model: legacyOpenCodeModel,
171
+ modelPreferences: ["*deepseek*free*", "*free*", "*"],
172
+ modelExclude: ["ollama/*", "lmstudio/*", "llamacpp/*", "local/*", "localhost/*"],
173
+ ...(current.opencode || {}),
174
+ },
152
175
  },
153
176
  };
154
177
  if (cfg.operator && typeof cfg.operator === "object") {
@@ -351,6 +374,31 @@ function appendMemory(title, body) {
351
374
  fs.appendFileSync(path.join(MEM, "log.md"), `\n## ${stamp} — ${title}\n${body}\n`);
352
375
  }
353
376
 
377
+ // ---- Proje context (path'e ozel) ----
378
+ // Global log.md'nin aksine, her calisma klasorunun KENDI birikmis proje profili vardir:
379
+ // <cwd>/.crewctl/CONTEXT.md. Gorev acilisinda operatore yuklenir (tum kodu bastan taramaya
380
+ // gerek kalmasin), gorev bitiminde operator tarafindan REVIZE edilir (append degil). Calisma
381
+ // klasorunde durdugu icin seffaf, elle duzenlenebilir ve git'e girebilir.
382
+ function projectContextPath(cwd) {
383
+ return path.join(cwd, ".crewctl", "CONTEXT.md");
384
+ }
385
+ function readProjectContext(cwd) {
386
+ if (!cwd) return "";
387
+ try { return fs.readFileSync(projectContextPath(cwd), "utf8"); } catch { return ""; }
388
+ }
389
+ function writeProjectContext(cwd, content) {
390
+ if (!cwd) return false;
391
+ try {
392
+ fs.mkdirSync(path.join(cwd, ".crewctl"), { recursive: true });
393
+ atomicWrite(projectContextPath(cwd), String(content == null ? "" : content));
394
+ return true;
395
+ } catch { return false; }
396
+ }
397
+ function clearProjectContext(cwd) {
398
+ if (!cwd) return false;
399
+ try { fs.rmSync(projectContextPath(cwd), { force: true }); return true; } catch { return false; }
400
+ }
401
+
354
402
  // ---- Gunluk cagri butcesi ----
355
403
  function callFile() {
356
404
  return path.join(STATE, `calls-${new Date().toISOString().slice(0, 10)}.txt`);
@@ -416,6 +464,10 @@ module.exports = {
416
464
  rejectTask,
417
465
  getMemory,
418
466
  appendMemory,
467
+ readProjectContext,
468
+ writeProjectContext,
469
+ clearProjectContext,
470
+ projectContextPath,
419
471
  getCallCount,
420
472
  bumpCallCount,
421
473
  getDailyUsage,
@@ -460,26 +460,46 @@ function startMission(e){callCards.clear();teamNodes.clear();missionCalls=0;miss
460
460
  function ensureTeamNode(name,role){if(!name)return null;if(teamNodes.has(name))return teamNodes.get(name);if($('#teamMap .empty'))$('#teamMap').innerHTML='';if(teamNodes.size){const link=document.createElement('div');link.className='team-link';$('#teamMap').appendChild(link)}const node=document.createElement('div'),glyph=role==='operator'?'OP':String(name).replace(/[^a-zA-Z0-9]/g,'').slice(0,2).toUpperCase()||'AI';node.className='team-node '+(role==='operator'?'operator':'');node.style.setProperty('--nacc',agentColor(name));node.innerHTML=`<div class="node-orb"><span class="node-glyph">${esc(glyph)}</span><span class="node-status"></span></div><div class="node-info"><div class="nrole">${role==='operator'?'Takım Operatörü':'Uzman Agent'}</div><div class="nname">${esc(name)}</div><div class="nstate">hazır</div></div>`;$('#teamMap').appendChild(node);const item={node,state:node.querySelector('.nstate')};teamNodes.set(name,item);return item}
461
461
  function setTeamState(name,state,text,role){const n=ensureTeamNode(name,role);if(!n)return;n.node.classList.remove('working','done','failed');if(state)n.node.classList.add(state);n.state.textContent=text}
462
462
  function clearLive(){callCards.clear();teamNodes.clear();missionCalls=0;missionMessages=0;liveStep=0;$('#agentGrid').innerHTML='<div class="empty">Canlı akış temizlendi.</div>';$('#feed').innerHTML='<div class="empty">Henüz etkinlik yok.</div>';$('#teamMap').innerHTML='<div class="empty" style="width:100%">Henüz aktif ekip yok.</div>';$('#missionTitle').textContent='Takım görevi bekleniyor';$('#missionSub').textContent='Operatör bir hedef aldığında ekibini burada kuracak.';setMissionStatus(null);updateMissionStats()}
463
- function ensureCall(e){if(callCards.has(e.callId))return callCards.get(e.callId);if($('#agentGrid .empty'))$('#agentGrid').innerHTML='';const step=++liveStep,isOp=String(e.stage||'').startsWith('operator'),glyph=isOp?'OP':String(e.agent||'AI').replace(/[^a-zA-Z0-9]/g,'').slice(0,2).toUpperCase()||'AI';const box=document.createElement('div');box.className='agent active'+(isOp?' operatorcall':'');box.style.setProperty('--agent-accent',agentColor(e.agent));box.dataset.step=step;box.innerHTML=`<div class="agent-head"><div class="agent-avatar">${esc(glyph)}</div><div class="agent-identity"><div class="agent-kicker"><span class="agent-step">RUN ${String(step).padStart(2,'0')}</span><span class="agent-role">${isOp?'operatör':'uzman'} · ${esc(e.stage||'')}</span></div><div class="agent-name">${esc(e.agent)}</div></div><div class="agent-state"><span class="state-pulse"></span><span class="agent-state-text">başlıyor</span></div></div><div class="agent-progress"><span></span></div><div class="friendly-error" hidden></div><details class="terminal-wrap" open><summary>CLI çalışma kaydı</summary><pre class="terminal"></pre></details>`;$('#agentGrid').appendChild(box);box.scrollIntoView({block:'nearest',behavior:'smooth'});const item={box,term:box.querySelector('.terminal'),state:box.querySelector('.agent-state-text'),friendly:box.querySelector('.friendly-error'),details:box.querySelector('.terminal-wrap'),step,raw:'',stdout:'',stderr:''};callCards.set(e.callId,item);return item}
463
+ function ensureCall(e){if(callCards.has(e.callId))return callCards.get(e.callId);if($('#agentGrid .empty'))$('#agentGrid').innerHTML='';const step=++liveStep,isOp=String(e.stage||'').startsWith('operator'),glyph=isOp?'OP':String(e.agent||'AI').replace(/[^a-zA-Z0-9]/g,'').slice(0,2).toUpperCase()||'AI';const box=document.createElement('div');box.className='agent active'+(isOp?' operatorcall':'');box.style.setProperty('--agent-accent',agentColor(e.agent));box.dataset.step=step;box.innerHTML=`<div class="agent-head"><div class="agent-avatar">${esc(glyph)}</div><div class="agent-identity"><div class="agent-kicker"><span class="agent-step">RUN ${String(step).padStart(2,'0')}</span><span class="agent-role">${isOp?'operatör':'uzman'} · ${esc(e.stage||'')}</span></div><div class="agent-name">${esc(e.agent)}</div></div><div class="agent-state"><span class="state-pulse"></span><span class="agent-state-text">başlıyor</span></div></div><div class="agent-progress"><span></span></div><div class="friendly-error" hidden></div><details class="terminal-wrap" open><summary>CLI çalışma kaydı</summary><pre class="terminal"></pre></details>`;$('#agentGrid').appendChild(box);box.scrollIntoView({block:'nearest',behavior:'smooth'});const item={box,term:box.querySelector('.terminal'),state:box.querySelector('.agent-state-text'),friendly:box.querySelector('.friendly-error'),details:box.querySelector('.terminal-wrap'),step,raw:'',stdout:'',stderr:'',cmd:''};callCards.set(e.callId,item);return item}
464
464
  /* Ham CLI çıktısını teşhise çeviren tablo. engine.js'teki classifyCliError ile AYNI kodları ve
465
465
  aynı sırayı kullanır: kota bitmesi ile hız sınırı, oturum sorunu ile kurulum sorunu ayrı
466
466
  teşhislerdir — kullanıcının "limitim mi bitti, CLI mı açılmadı" sorusu tam olarak budur.
467
467
  Sıra önemli: /not found/ gibi geniş desenler kimlik kontrollerinin ARKASINDA kalmalı. */
468
+ /* Oturum/kurulum tavsiyesi CLI'ya gore degisir. Sabit tek metin (Gemini'ninki) opencode
469
+ hatasinda yanlis komut oneriyordu. Adapter, process.started olayindaki cmd'den turetilir;
470
+ src/cli-registry.js adapterId + AUTH_HINTS ile AYNI mantik. */
471
+ function adapterOf(cmd){const b=String(cmd||'').split(/[\\/]/).pop().toLowerCase().replace(/\.(cmd|bat|exe|ps1)$/,'');if(b.includes('codex'))return 'codex';if(b.includes('claude'))return 'claude';if(b.includes('gemini'))return 'gemini';if(b.includes('opencode'))return 'opencode';return 'custom'}
472
+ const AUTH_HINTS={
473
+ codex:'Terminalde `codex login` çalıştırın (veya OPENAI_API_KEY tanımlayın).',
474
+ claude:'Terminalde `claude` çalıştırıp oturum açın (veya ANTHROPIC_API_KEY tanımlayın).',
475
+ gemini:'Terminalde `gemini` çalıştırıp "Login with Google" seçin (veya GEMINI_API_KEY tanımlayın).',
476
+ opencode:'Terminalde `opencode auth login` çalıştırıp sağlayıcıyı seçin. "401 No provider available" = seçili modelin sağlayıcısında geçerli kimlik yok.',
477
+ };
478
+ const authHint=a=>AUTH_HINTS[a]||'Bu CLI\'nin kendi oturum açma komutunu terminalde çalıştırın.';
479
+ const INSTALL_HINTS={
480
+ codex:'`npm install -g @openai/codex`',
481
+ claude:'`npm install -g @anthropic-ai/claude-code`',
482
+ gemini:'`npm install -g @google/gemini-cli`',
483
+ opencode:'`npm install -g opencode-ai`',
484
+ };
468
485
  const FAILURE_RULES=[
469
486
  [/CLI_STALLED|çıktı üretmedi|cikti uretmedi/i,'uzun süre sessiz kaldı','CLI bir süre ilerleme ürettikten sonra uzun süre yeni çıktı vermediği için otomatik durduruldu.','Önceki çalışma kayıtları korundu; operatör bu oturumda alternatif bir agente geçecek.'],
470
- [/API key not valid|API_KEY_INVALID|invalid[_ ]api[_ ]key|incorrect api key/i,'API anahtarını reddetti','Anahtar geçersiz ya da bu model için yetkili değil.','Ayarlar → Agent\'lar bölümünden anahtarı yenileyin (Gemini: GEMINI_API_KEY / GOOGLE_API_KEY).'],
471
- [/set an auth method|GEMINI_API_KEY|GOOGLE_API_KEY|unauthorized|unauthenticated|authentication|login required|not logged in|please (run )?login|credentials? (not found|missing|expired)|PERMISSION_DENIED|\b40[13]\b/i,'oturum açmamış','CLI kimlik doğrulaması yapılmadan çalıştırıldı; bu bir kurulum değil oturum sorunudur.','Terminalde oturum açın (Gemini: `gemini` çalıştırıp "Login with Google" seçin ya da GEMINI_API_KEY tanımlayın).'],
487
+ /* "No provider available" 401 ile gelir ama oturum sorunu DEĞİLDİR: seçili model hiçbir
488
+ sağlayıcıya çözülemiyordur. Genel 401/403 kuralından ÖNCE gelmeli. */
489
+ [/no provider available/i,'modeli çözümleyemedi','Seçili model hiçbir yapılandırılmış sağlayıcıya çözümlenemedi — bu bir oturum sorunu değil.','Ayarlar → Agent\'lar bölümünde bu agent için açık bir model seçin. Model boşken OpenCode kendi son kullandığı modele düşer; o model erişilemezse görev bu hatayla durur.'],
490
+ [/API key not valid|API_KEY_INVALID|invalid[_ ]api[_ ]key|incorrect api key/i,'API anahtarını reddetti','Anahtar geçersiz ya da bu model için yetkili değil.',a=>`Ayarlar → Agent'lar bölümünden anahtarı yenileyin. ${authHint(a)}`],
491
+ [/set an auth method|GEMINI_API_KEY|GOOGLE_API_KEY|unauthorized|unauthenticated|authentication|login required|not logged in|please (run )?login|credentials? (not found|missing|expired)|PERMISSION_DENIED|\b40[13]\b/i,'oturum açmamış','CLI kimlik doğrulaması yapılmadan çalıştırıldı; bu bir kurulum değil oturum sorunudur.',a=>authHint(a)],
472
492
  [/RESOURCE_EXHAUSTED|quota exceeded|exceeded your current quota|daily limit|günlük limit|out of credits|insufficient (credit|balance|quota)|billing/i,'kotası doldu','Günlük/aylık kullanım hakkınız veya bakiyeniz bitti — beklemek tek başına çözmez.','Kota yenilenene kadar başka bir agent seçin, planı yükseltin veya kotası olan bir hesabın anahtarını kullanın.'],
473
493
  [/rate.?limit|too many requests|\b429\b/i,'hız sınırına takıldı','Kotanız bitmedi; çok kısa sürede çok fazla istek gönderildi.','Birkaç dakika bekleyip tekrar deneyin; operatör bu turda alternatif bir uzman kullanabilir.'],
474
494
  [/overloaded|\bUNAVAILABLE\b|\b(503|500)\b|internal error|try again later/i,'sağlayıcı hatası aldı','Model sağlayıcısı geçici olarak aşırı yüklü ya da hata döndürdü.','Sizin tarafınızda bir sorun yok; birkaç dakika sonra tekrar deneyin.'],
475
495
  [/location is not supported|not available in your country|region.*not supported/i,'bölge kısıtına takıldı','Seçilen model bulunduğunuz bölgede kullanıma kapalı.','Desteklenen bir bölge/hesap kullanın veya başka sağlayıcının agent\'ını seçin.'],
476
496
  [/ENOTFOUND|EAI_AGAIN|ECONNRESET|ETIMEDOUT|getaddrinfo|fetch failed|socket hang up|network (error|unreachable)|ECONNREFUSED|proxy/i,'ağa bağlanamadı','DNS, proxy veya internet bağlantısı nedeniyle sağlayıcıya ulaşılamadı.','Bağlantınızı ve varsa kurumsal proxy ayarlarınızı kontrol edin.'],
477
- [/not recognized as an internal|is not recognized|command not found|spawn .*ENOENT|\bENOENT\b|no such file or directory/i,'başlatılamadı','CLI komutu bulunamadı; kurulu değil veya PATH üzerinde görünmüyor.','CLI\'yi kurun (Gemini: `npm install -g @google/gemini-cli`) ve Ayarlar → Agent\'lar bölümünde komut adını doğrulayın.'],
497
+ [/not recognized as an internal|is not recognized|command not found|spawn .*ENOENT|\bENOENT\b|no such file or directory/i,'başlatılamadı','CLI komutu bulunamadı; kurulu değil veya PATH üzerinde görünmüyor.',a=>`${INSTALL_HINTS[a]?`Kurulum: ${INSTALL_HINTS[a]}. `:''}Ayarlar → Agent'lar bölümünde komut adını doğrulayın.`],
478
498
  [/timeout|timed out|zaman asim|zaman aşım/i,'zaman aşımına uğradı','CLI ayrılan süre içinde tamamlanamadı.','Agent ayarlarından timeout değerini artırın veya görevi daha küçük parçalara bölün.'],
479
499
  ];
480
- function friendlyFailure(text,agent){for(const [re,title,body,action] of FAILURE_RULES){if(re.test(text))return {title:`${agent} ${title}`,body,action}}return {title:`${agent} görevi tamamlayamadı`,body:'CLI sıfırdan farklı çıkış koduyla kapandı.',action:'Ham çıktı aşağıdaki “CLI çalışma kaydı” bölümünde saklandı; nedeni orada görebilirsiniz.'}}
500
+ function friendlyFailure(text,agent,cmd){const adapter=adapterOf(cmd);for(const [re,title,body,action] of FAILURE_RULES){if(re.test(text))return {title:`${agent} ${title}`,body,action:typeof action==='function'?action(adapter):action}}return {title:`${agent} görevi tamamlayamadı`,body:'CLI sıfırdan farklı çıkış koduyla kapandı.',action:'Ham çıktı aşağıdaki “CLI çalışma kaydı” bölümünde saklandı; nedeni orada görebilirsiniz.'}}
481
501
  function parseOperatorView(raw){try{let text=raw.trim(),m=text.match(/```(?:json)?\s*([\s\S]*?)```/i);if(m)text=m[1];const data=JSON.parse(text);if(Array.isArray(data.assignments)){const names=[...new Set(data.assignments.map(x=>x.agent))];return {title:data.status==='continue'?'Operatör yeni ekip turu oluşturdu':'Takım planı hazır',body:`${data.assignments.length} delegasyon · ${(data.completionCriteria||[]).length} kabul kriteri`,pills:names}}if(data.status==='complete')return {title:'Operatör görevi tamamladı',body:String(data.final||'Nihai sonuç hazır.'),pills:[]}}catch{}return null}
482
- function activity(e){const c=ensureCall(e);const role=String(e.stage||'').startsWith('operator')?'operator':'agent';if(e.kind==='stdout'||e.kind==='stderr'){const span=document.createElement('span');if(e.kind==='stderr')span.className='stderr';span.textContent=e.text;c.term.appendChild(span);c.raw+=e.text;c[e.kind]+=e.text;c.term.scrollTop=c.term.scrollHeight;c.state.textContent='çalışıyor';setTeamState(e.agent,'working','çalışıyor',role)}else if(e.kind==='process.progress'){const sec=Math.max(1,Math.round((e.elapsedMs||0)/1000));c.state.textContent=`yanıt bekleniyor · ${sec} sn`;setTeamState(e.agent,'working',`${sec} sn · yanıt bekleniyor`,role)}else if(e.kind==='process.finished'){c.box.classList.remove('active');const failed=Number(e.exitCode)!==0,stalled=e.reason==='silence-timeout';const dur=(e.durationMs/1000).toFixed(1);c.state.textContent=stalled?`yanıt vermedi · ${dur}s`:failed?`başarısız · ${dur}s`:`tamamlandı · ${dur}s${e.usage?` · ${usageLine(e.usage)}`:''}`;setTeamState(e.agent,failed?'failed':'done',stalled?'otomatik durduruldu':failed?'erişilemiyor':'teslim etti',role);if(failed){c.box.classList.add('error');const f=friendlyFailure(stalled?'CLI_STALLED':c.raw,e.agent);c.friendly.hidden=false;c.friendly.innerHTML=`<b>${esc(f.title)}</b>${esc(f.body)}<br><small>${esc(f.action)}</small>`;c.details.open=false}else if(role==='operator'){const view=parseOperatorView(c.stdout||c.raw);if(view){c.friendly.hidden=false;c.friendly.classList.add('success');c.friendly.innerHTML=`<b>${esc(view.title)}</b>${esc(view.body)}${view.pills.length?`<div class="plan-pills">${view.pills.map(x=>`<span>${esc(x)}</span>`).join('')}</div>`:''}`;c.details.open=false}}else{const clean=String(c.stdout||'').replace(/tokens used[\s\S]*$/i,'').trim();c.friendly.hidden=false;c.friendly.classList.add('success');c.friendly.innerHTML=`<b>Teslimat operatöre gönderildi</b>${esc(clean.slice(0,420)||'Agent görevini başarıyla tamamladı.')}<br><small>Komutlar ve ayrıntılı çıktı “Canlı CLI çıktısı” bölümünde saklandı.</small>`;c.details.open=false}}else if(e.kind==='process.failed'||e.kind==='process.timeout'||e.kind==='process.silence-timeout'){c.box.classList.remove('active');c.box.classList.add('error');c.state.textContent=e.kind==='process.silence-timeout'?'yanıt vermedi · durduruldu':'hata';setTeamState(e.agent,'failed',e.kind==='process.silence-timeout'?'otomatik durduruldu':'hata',role)}else if(e.kind==='process.started'){missionCalls++;updateMissionStats();c.state.textContent='başlatıldı';setTeamState(e.agent,'working',role==='operator'?'ekibi yönetiyor':'görevi yürütüyor',role)}}
502
+ function activity(e){const c=ensureCall(e);const role=String(e.stage||'').startsWith('operator')?'operator':'agent';if(e.kind==='stdout'||e.kind==='stderr'){const span=document.createElement('span');if(e.kind==='stderr')span.className='stderr';span.textContent=e.text;c.term.appendChild(span);c.raw+=e.text;c[e.kind]+=e.text;c.term.scrollTop=c.term.scrollHeight;c.state.textContent='çalışıyor';setTeamState(e.agent,'working','çalışıyor',role)}else if(e.kind==='process.progress'){const sec=Math.max(1,Math.round((e.elapsedMs||0)/1000));c.state.textContent=`yanıt bekleniyor · ${sec} sn`;setTeamState(e.agent,'working',`${sec} sn · yanıt bekleniyor`,role)}else if(e.kind==='process.finished'){c.box.classList.remove('active');const failed=Number(e.exitCode)!==0,stalled=e.reason==='silence-timeout';const dur=(e.durationMs/1000).toFixed(1);c.state.textContent=stalled?`yanıt vermedi · ${dur}s`:failed?`başarısız · ${dur}s`:`tamamlandı · ${dur}s${e.usage?` · ${usageLine(e.usage)}`:''}`;setTeamState(e.agent,failed?'failed':'done',stalled?'otomatik durduruldu':failed?'erişilemiyor':'teslim etti',role);if(failed){c.box.classList.add('error');const f=friendlyFailure(stalled?'CLI_STALLED':c.raw,e.agent,c.cmd);c.friendly.hidden=false;c.friendly.innerHTML=`<b>${esc(f.title)}</b>${esc(f.body)}<br><small>${esc(f.action)}</small>`;c.details.open=false}else if(role==='operator'){const view=parseOperatorView(c.stdout||c.raw);if(view){c.friendly.hidden=false;c.friendly.classList.add('success');c.friendly.innerHTML=`<b>${esc(view.title)}</b>${esc(view.body)}${view.pills.length?`<div class="plan-pills">${view.pills.map(x=>`<span>${esc(x)}</span>`).join('')}</div>`:''}`;c.details.open=false}}else{const clean=String(c.stdout||'').replace(/tokens used[\s\S]*$/i,'').trim();c.friendly.hidden=false;c.friendly.classList.add('success');c.friendly.innerHTML=`<b>Teslimat operatöre gönderildi</b>${esc(clean.slice(0,420)||'Agent görevini başarıyla tamamladı.')}<br><small>Komutlar ve ayrıntılı çıktı “Canlı CLI çıktısı” bölümünde saklandı.</small>`;c.details.open=false}}else if(e.kind==='process.failed'||e.kind==='process.timeout'||e.kind==='process.silence-timeout'){c.box.classList.remove('active');c.box.classList.add('error');c.state.textContent=e.kind==='process.silence-timeout'?'yanıt vermedi · durduruldu':'hata';setTeamState(e.agent,'failed',e.kind==='process.silence-timeout'?'otomatik durduruldu':'hata',role)}else if(e.kind==='process.started'){missionCalls++;updateMissionStats();c.cmd=e.cmd||'';c.state.textContent='başlatıldı';setTeamState(e.agent,'working',role==='operator'?'ekibi yönetiyor':'görevi yürütüyor',role)}}
483
503
 
484
504
  /* ---- Birlesik akis: mesajlar + sistem olaylari ---- */
485
505
  function feedReady(){const feed=$('#feed');if(feed.querySelector('.empty'))feed.innerHTML='';return feed}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omerrgocmen/crewctl",
3
- "version": "1.1.1",
3
+ "version": "1.3.1",
4
4
  "description": "CrewCtl — Zero-dependency Node.js orchestrator that runs installed CLI coding agents as one operator-led team.",
5
5
  "keywords": [
6
6
  "ai",