diff --git a/.agents/context/cli-registry.md b/.agents/context/cli-registry.md index d915c70..a8d8b81 100644 --- a/.agents/context/cli-registry.md +++ b/.agents/context/cli-registry.md @@ -2,7 +2,7 @@ **Source:** `orchestrator/src/cli-registry.js` -**Last verified:** 2026-07-25 +**Last verified:** 2026-09-21 ## Purpose @@ -31,6 +31,8 @@ Codex, Claude, Gemini ve OpenCode kurulumlarını keşfeder; agent profillerini - CLI çağrıları non-interactive ve otonom olmalı; desteklenmeyen/eski bayraklar temizlenmelidir. - Windows Unicode kullanıcı yolları ve `.cmd/.bat` çalıştırma davranışı korunmalıdır. - Codex ve Gemini için adapter sessizlik sınırı 180, Claude için stream-json akışıyla 240, OpenCode için 300 saniyedir. +- Codex alt süreçlerinde varsayılan olarak ChatGPT oturumu kullanılır; `OPENAI_API_KEY` alt sürece aktarılmaz. API key modu yalnızca `CODEX_AUTH_MODE=api_key` ile seçilir. +- Codex'in "You've hit your usage limit" çıktısı sağlıkta `quota` / "Kullanım limiti doldu", motorda `QUOTA_EXCEEDED` olarak sınıflandırılır; yenilenme zamanı mesajdan çıkarılır. Genel auth/hız/CLI hatası sayılmaz. ## Interactions @@ -44,6 +46,15 @@ Codex, Claude, Gemini ve OpenCode kurulumlarını keşfeder; agent profillerini ## Major Changes +### 2026-09-21 — Codex ChatGPT oturumu ve kullanım limiti teşhisi + +- **Change:** Codex sağlık, model listesi ve runtime alt süreçleri varsayılan olarak `OPENAI_API_KEY` olmadan başlatılıyor. "You've hit your usage limit" çıktısı ayrı olarak tanınıyor ve yenilenme zamanıyla raporlanıyor. +- **Reason:** Ortamdaki geçersiz `OPENAI_API_KEY` Codex'i API key moduna geçirip `401` döndürüyor ve asıl durumu gizliyordu. Plan hakkı bitince gelen mesaj da motorda `CLI_FAILED` sayılıyor, agent karantinaya alınmadan tekrar deneniyordu. +- **Impact:** Codex, ChatGPT girişini kullanır; hakkı bitmişse sağlıkta "Kullanım limiti doldu", görevde `QUOTA_EXCEEDED` (karantina) görünür. Diğer CLI ortamlarına dokunulmaz. +- **Compatibility:** API key kullanan kurulumlar `CODEX_AUTH_MODE=api_key` ayarını açıkça eklemelidir. +- **Verification:** `npm.cmd test`; Codex 0.154.0 ile gerçek `codex exec --json` çıktısı ve `OPENAI_API_KEY` ortamdayken uygulamanın gerçek sağlık testi `quota` / "Kullanım limiti doldu" döndü. +- **Files:** `orchestrator/src/cli-registry.js`, `orchestrator/src/engine.js`, `orchestrator/test/cli.test.js`, `orchestrator/test/team-flow.test.js` + ### 2026-07-24 — Claude modeli kullanıcı oturumundan dinamik keşfediliyor - **Change:** `listClaudeModels()`, Claude Code'u stream-json kontrol protokolüyle başlatıp `initialize` yanıtındaki `models` alanını okuyor; statik Fable/Opus/Sonnet/Haiku listesi kullanılmıyor. diff --git a/.agents/context/dashboard.md b/.agents/context/dashboard.md index d27613d..ca59e10 100644 --- a/.agents/context/dashboard.md +++ b/.agents/context/dashboard.md @@ -2,7 +2,7 @@ **Source:** `orchestrator/web/index.html` -**Last verified:** 2026-07-18 +**Last verified:** 2026-09-21 ## Purpose @@ -15,7 +15,7 @@ Motor durumunu, yeni görev oluşturmayı, kuyrukları, aktif ekip haritasını, - İlk otonom engine başlatmada kalıcı consent modalı gösterilir. - Görev alanı ve kuyruk sol sütunda; misyon, ekip haritası, feed ve teknik terminaller sağ sütundadır. - Dashboard açılışta aktif görevin olay geçmişini otomatik replay eder; aktif görev yoksa en son tamamlanan veya başarısız görev geri yüklenir. Bootstrap sırasında gelen SSE olayları replay bitene kadar tamponlanır ve yinelenmeden uygulanır. -- `/api/events` SSE olayları dashboard içinde `status`, `queue`, `activity`, `message`, `log`, `result` ve `schedules` handler'larına dağıtılır. +- `/api/events` SSE olayları dashboard içinde `status`, `queue`, `activity`, `message`, `log`, `result`, `schedules` ve `cli-health` handler'larına dağıtılır. - CLI prosesleri için başlama, ilerleme, stdout/stderr, timeout, sessizlik timeout ve bitiş durumları görselleştirilir. - Canlı kodlama görünümü ayrı `code.html` sayfasındadır; dashboard header'ından açılır. - Tema tercihi `localStorage` içinde saklanır. @@ -31,6 +31,8 @@ Motor durumunu, yeni görev oluşturmayı, kuyrukları, aktif ekip haritasını, `CLI_META` ile birebir). Feed mesajları gönderenin, terminal kartları ve ekip haritası düğümleri ilgili agent'ın CLI rengini alır (`--agent-accent` / `--nacc` inline). Çalışıyor/hata durumları renk değil ayrı ipuçlarıyla (nabız, ilerleme çubuğu, kırmızı halka, `failure`/`blocked` kırmızı) belirtilir. +- Duyarlı yerleşim dört sayfa için ortaktır ve `app.css` "Duyarlı yerleşim" bölümündedir. Kurallar: kapsayıcı sarar, rozet/hap/çip bölünmez; ızgara çocukları `min-width:0`; header sabit değil `min-height`; uzun metin `overflow-wrap: break-word` (anywhere değil); `tiles` `auto-fit`. Sayfaya özel sabit genişlik/yükseklik eklenirken bu kurallar bozulmamalıdır. +- CLI sağlık yorumu tek yerdedir: `app.js` `window.CrewHealth` (renk tonu, etiket, yenilenme zamanı, hazır mı). Kaynak sunucunun `cliStatus[].health` sonucudur; agent için yoksa `agent.health` kullanılır. Sağlık gösteren her yer bunu çağırır, kendi yorumunu yazmaz. ## Interactions @@ -43,6 +45,15 @@ Görevler için `tasks.md`, ayarlar için `settings.md`, klasör seçici için ` ## Major Changes +### 2026-09-21 — Ortak duyarlı yerleşim ve tek sağlık kaynağı + +- **Change:** `app.css`'e dört sayfa ve tüm modallar için ortak "Duyarlı yerleşim" bölümü eklendi. `app.js`'e paylaşılan `CrewHealth` yardımcısı eklendi. Ana ekran, ayarlar, CLI kartları, operatör seçimleri ve Ekip Akışı aynı sağlık yorumunu kullanıyor. `server-flow` testi `NO_OPEN` ile çalışıyor. +- **Reason:** 390 px'te ana içerik ekrandan ~97 px taşıyordu. Board/code/flow header'ı sabit 58 px olduğu için sarılan butonlar içeriğin üstüne biniyordu. Ayarlar sekmeleri 330 px taşıyordu, Canlı Kod istatistik kutuları ekrana sığmıyordu. Ayrıca aynı sağlık bilgisi bazı yerlerde hiç kullanılmıyor, bazı yerlerde yanlış gösteriliyordu (Ekip Akışı'nda girişsiz Gemini "hazır · sırada"). Test sunucusu her çalışmada kullanıcının tarayıcısında ölü bir sekme açıyordu. +- **Impact:** 1440/1024/768/390 px'te 17 ekran durumu (4 sayfa, 6 ayar sekmesi, 7 modal) taşmasız. Hazır olmayan CLI her yerde aynı etiket ve renkle görünüyor. +- **Compatibility:** Geniş ekran görünümü değişmedi. Mantık ve config şeması aynı. `app.js` defer yüklendiği için sayfalar `CrewHealth`'i çağrı anında ve korumalı kullanır. +- **Verification:** `npm.cmd test`. Headless Chrome ile her ekranı dört genişlikte açıp taşmaları ölçen tarama: ilk çalıştırmada birden çok gerçek sorun, düzeltmeden sonra 68/68 temiz; ekran görüntüleri gözle de kontrol edildi. +- **Files:** `orchestrator/web/app.css`, `orchestrator/web/app.js`, `orchestrator/web/index.html`, `orchestrator/web/flow.html`, `orchestrator/test/ui-smoke.test.js`, `orchestrator/test/server-flow.test.js` + ### 2026-07-18 — CLI marka renk standardı ve terminal taşma düzeltmesi - **Change:** Feed mesajları, canlı terminal kartları ve ekip haritası düğümleri artık duruma göre diff --git a/.agents/context/settings.md b/.agents/context/settings.md index c6dba92..c9b93d9 100644 --- a/.agents/context/settings.md +++ b/.agents/context/settings.md @@ -2,7 +2,7 @@ **Sources:** `orchestrator/web/index.html`, `orchestrator/src/server.js`, `orchestrator/src/cli-registry.js`, `orchestrator/config.default.json` -**Last verified:** 2026-07-25 +**Last verified:** 2026-09-21 ## Purpose @@ -22,6 +22,9 @@ Agent profillerini, rol dosyalarını, CLI-geneli model politikalarını, operat - Otomatik keşfedilen agent silinirse adapter gizleme listesine eklenir ve sonraki taramada geri oluşturulmaz. - Bilinen bir CLI komutu düzenlendiğinde agent adapter'ı, varsayılan argümanları ve model sahipliği UI taslağında birlikte eşitlenir. - Operatör uzman agent değildir; ayrı CLI seçimidir ve rolü `roles/operator.md` olarak sabittir. +- "Aktif" kutusu yalnızca izindir. Operatör bir uzmanı ancak sağlık testi `ready` ise görür (`engine.js` `agentUsable`). Bu yüzden her uzman kartında "Aktif" altında sağlık satırı, "Kurulu CLI taraması" rozetlerinde de sağlık etiketi ve rengi gösterilir (`ok`/`warn`/`err`/`dim`). Kod bu iki görünümü `cli-health` SSE olayıyla canlı yeniler ve kaydedilmemiş taslaklara dokunmaz. Rozet kapsayıcısı `flex-wrap` ile dar pencerede satır satır dizilir; tek rozetin yazısı `nowrap` ile bölünmez. Aynı sağlık sonucu "CLI'lar ve Modeller" kartlarında rozet olarak ve iki operatör listesinde (Operatör sekmesi, görev seçenekleri) `operatorOptionLabel` ile gösterilir; hazır olmayan CLI seçilebilir kalır ama adının yanında durumu yazar. Hazır olan da "— Hazır" diye açıkça etiketlenir. Operatör kutularının altında (Operatör sekmesi, görev seçenekleri, görev düzenleme) seçili CLI'ın durum satırı durur. Metin, sunucunun görev eklerken uyguladığı kuralla aynıdır (`server.js` `TRANSIENT_HEALTH`): testing/unknown/timeout "görev yine de başlatılabilir", gerçek arızalar "bu seçimle görev başlatılamaz". Yorum `app.js` `CrewHealth`'tedir. +- Sayısal ayarların sınırları ve varsayılanları **tek kaynaktadır**: `store.js` `NUMERIC_LIMITS`. Varsayılanlar `config.default.json` şablonundan okunur (`readSeedConfig`). Sunucu `/api/config` doğrulaması bu tabloyu kullanır ve aşımda 400 döner. Arayüz aynı tabloyu `/api/state` → `numericLimits` üzerinden okur; kutular `numAttrs`/`numValue` ile min/max, sınıra çekme ve boş-girdi varsayılanı alır. Arayüzde sabit sınır veya varsayılan kopyası tutulmaz. Aralıklar: operatör turu 1–50, delegasyon 1–32, protokol tekrarı 0–5, günlük bütçe 0–100000 (0 = sınırsız), agent timeout 10–86400, bellek/bağlam bütçesi 0–1000000, agent başına timeout 0–86400 (0 = genel ayar). 0 geçerli bir değerdir; `||varsayılan` kısayolu kullanılmaz. Odaklı sayı kutusunun üstünde fare tekerleği değeri değiştirmez (`app.js`). +- Motorun kendi çalışma zamanı yedekleri (ör. `protocolRetries ?? 1`, `maxRounds || 6`) alan hiç yoksa devreye girer ve şablon varsayılanından farklı olabilir (şablonda `protocolRetries` 2). Bu ayrı bir iştir; henüz tek kaynağa bağlanmadı. - Uzman rolü yalnızca prompt metni değil, izin verilen görev türüdür: executor uygulama, reviewer inceleme, planner planlama yapar. - Config kaydı server tarafı doğrulamasından geçer; en az bir uzman gerekir. - MCP sekmesi `draftMcp` taslağı üzerinde çalışır (`draftAgents` ile aynı desen); Kaydet basılana kadar config'e dokunmaz. Kaydetmede adsız sunucular atılır, adlar/açıklamalar trim'lenir, boş argüman satırları düşer, `ANAHTAR=değer` satırları ortam değişkenine çevrilir (değer içindeki `=` korunur) ve URL verildiyse `command`/`args` gönderilmez. @@ -43,6 +46,33 @@ Config şeması için `configuration.md`, CLI keşfi için `cli-registry.md`, ro ## Major Changes +### 2026-09-21 — Sayısal ayar sınırları tek kaynağa indirildi + +- **Change:** Sınır ve varsayılan tablosu `server.js` işleyicisinden `store.js` `NUMERIC_LIMITS`'e taşındı ve `/api/state` ile yayınlanıyor. Arayüzdeki 8 min/max kopyası ile 8 sabit varsayılan kopyası silindi; kutular tabloyu `numAttrs`/`numValue` ile okuyor. Şablon okuma `readSeedConfig`'e toplandı. +- **Reason:** Aynı sınırlar sunucu ve arayüzde ayrı yazılıydı (teknik borç). Değişiklik öncesi 5 senaryolu ölçümde bir hata da bulundu: günlük bütçe 0 ("sınırsız") iken arayüz `||150` yüzünden 150 gösteriyor, Kaydet sessizce 150 yazıyordu. Ayrıca arayüzdeki boş-girdi varsayılanı (protokol tekrarı 1) şablondakiyle (2) uyuşmuyordu. +- **Impact:** Sınır tek yerde değişir, sunucu ve arayüz birlikte değişir. Bütçe 0 korunuyor. +- **Compatibility:** Hata mesajları ve aralıklar aynı. Ölçülen 15 davranışın 12'si birebir aynı; değişen 3'ü hedeflenenler (tablonun yayınlanması, bütçe 0'ın gösterilmesi ve korunması). +- **Verification:** Yalıtılmış sunucuyla (gerçek config ve CLI'lara dokunmadan) önce/sonra ölçümü; `npm.cmd test`; ui-smoke arayüzdeki her sınır anahtarının tabloda olduğunu ve sabit kopya kalmadığını denetliyor. +- **Files:** `orchestrator/src/store.js`, `orchestrator/src/server.js`, `orchestrator/web/index.html`, `orchestrator/test/ui-smoke.test.js`, `orchestrator/test/server-flow.test.js` + +### 2026-09-21 — Operatör seçimi durumu gösteriyor; sayısal ayarlar sınırlı + +- **Change:** Operatör kutularına durum satırı eklendi ve hazır CLI de "— Hazır" diye etiketleniyor. Sayı kutularına min/max ve kaydetmede sınıra çekme eklendi. Sunucu sayısal ayarları doğruluyor. Odaklı sayı kutusunda fare tekerleği artık değeri değiştirmiyor. +- **Reason:** Operatör listesi kapalıyken seçili CLI'ın kullanılabilir olup olmadığı görünmüyordu. Ayarlar kaydırılırken fare tekerleği delegasyon ve protokol tekrarını -12 ve -17'ye indirmiş, bu değerler kaydedilmişti. Motor bunları sessizce "turda tek delegasyon, sıfır tekrar"a çeviriyordu; günlük bütçe eksi olsaydı her çağrı engellenirdi. +- **Impact:** Kullanıcı operatörü seçerken sonucunu görür. Geçersiz sayı kaydedilemez. Kullanıcının config'indeki -12/-17 değerleri 8/1 varsayılanlarına geri alındı. +- **Compatibility:** Geçerli mevcut config'ler etkilenmez. Sınır dışı değer gönderen API/ham JSON kaydı artık 400 alır. +- **Verification:** `npm.cmd test` (ui-smoke: etiket/satır/clamp; server-flow: eksi değerde 400, 0 bütçede 200). Headless Chrome'da korumasız kutuda 5 tekerlek hareketi 8→3, korumalıda 8→8. Operatör satırı Claude/Codex için ve görev seçeneklerinde doğrulandı. Duyarlı tarama 68/68 temiz. +- **Files:** `orchestrator/web/index.html`, `orchestrator/web/app.js`, `orchestrator/src/server.js`, `orchestrator/test/ui-smoke.test.js`, `orchestrator/test/server-flow.test.js` + +### 2026-09-21 — Uzman ve CLI sağlık durumu ayarlarda görünür + +- **Change:** Her uzman kartında "Aktif" kutusunun altına sağlık satırı eklendi: Hazır, Kullanım limiti doldu (yenilenme zamanıyla), Giriş gerekli, Test ediliyor veya Kapalı. "Kurulu CLI taraması" rozetleri yalnızca kurulu olmaya göre değil, sağlık testine göre renkleniyor ve etiket gösteriyor. Dashboard artık `cli-health` SSE olayını dinliyor. +- **Reason:** Sunucu sağlık sonucunu üretip yayınlıyordu ama arayüz bunu hiç göstermiyordu. Limiti dolu Codex, "Aktif ✔" ve yeşil nokta ile hazır gibi görünüyor, kullanıcı operatörün onu deneyip zaman kaybedeceğini sanıyordu. +- **Impact:** Kullanıcı hangi uzmanın operatör tarafından gerçekten kullanılabildiğini ayarlar ekranında görür. Aktif ama hazır olmayan uzmanlarda "operatör bu uzmanı kullanmıyor" yazar. +- **Compatibility:** Yalnızca görünüm değişti; config şeması, kaydetme gövdesi ve motor davranışı aynı. +- **Verification:** `npm.cmd test`; `ui-smoke` yardımcı fonksiyonları gerçek HTML'den çıkarıp koşturuyor. Canlı panelde headless Chrome ile rozetler ve dört uzman satırı doğrulandı. +- **Files:** `orchestrator/web/index.html`, `orchestrator/test/ui-smoke.test.js` + ### 2026-07-25 — MCP Araçları sekmesi (form tabanlı) - **Change:** İçe dönük MCP artık Genel sekmesindeki ham JSON alanı yerine kendi sekmesinde form tabanlı yönetiliyor: sunucu kartları (ad · açıklama · komut/URL · argümanlar · ortam · görev türleri · yazma yetkisi · agent kısıtı), ekle/sil, hazır şablonlar ve "fiilen verilecek türler" önizlemesi. `draftMcp` taslağı eklendi. diff --git a/.agents/context/team-flow.md b/.agents/context/team-flow.md index 713719d..bebfa0c 100644 --- a/.agents/context/team-flow.md +++ b/.agents/context/team-flow.md @@ -2,7 +2,7 @@ **Source:** `orchestrator/web/flow.html` -**Last verified:** 2026-07-17 +**Last verified:** 2026-09-21 ## Purpose @@ -17,6 +17,7 @@ Operatörün ekip kurma ve delegasyon akışını, agent filosunu, aktif/geçmi - `message` olayları delegation/result/failure/blocked durumlarını üretir; delegasyon pulse'ı hedef agent renginde agente, sonuç/failure pulse'ı operatör renginde merkeze hareket eder. - `activity` olayları proses durumu ve kısa canlı çıktıyı; `log` görev başlangıcı/hatasını; `result` final KPI'larını günceller. - İlk yüklemede aktif veya en son done/failed task'in kayıtlı olayları yeniden oynatılır, ardından SSE canlı akışı devam eder. +- Boştaki agent'ın durum metni sağlık sonucundan gelir (`setIdleText`, `app.js` `CrewHealth`). Hazırsa "hazır · sırada", değilse renkli etiket gösterilir ("Giriş gerekli" gibi); üzerine gelince "operatör bu uzmanı kullanmıyor" ve hata ayrıntısı çıkar. `cli-health` SSE olayı sayfayı yenilemeden günceller. ## Contracts and invariants @@ -36,6 +37,15 @@ Operatörün ekip kurma ve delegasyon akışını, agent filosunu, aktif/geçmi ## Major Changes +### 2026-09-21 — Ajan filosu gerçek sağlık durumunu gösteriyor + +- **Change:** Boştaki agent için sabit "hazır · sırada" metni, config'deki sağlık sonucuna dayanan `setIdleText` ile değiştirildi. `cli-health` olayı dinleniyor ve dar kartta metin "…" ile kısalıyor. +- **Reason:** Girişsiz Gemini ve limiti dolu Codex "hazır · sırada" görünüyordu; operatör bu agent'ları kullanmaz (`engine.js` `agentUsable`). +- **Impact:** Filo listesi motorun gerçek seçimleriyle uyumlu. +- **Compatibility:** Çalışan/tamamlanan/başarısız metinleri değişmedi. +- **Verification:** `node orchestrator/test/ui-smoke.test.js` ve headless Chrome görüntüsü (1440 px). +- **Files:** `orchestrator/web/flow.html`, `orchestrator/web/app.js` + ### 2026-07-17 — Agent renkli yönlü veri akışı - **Change:** Sabit yeşil operatör-agent bağı kaldırıldı; bağlantı hattı ve ok ucu hedef agentın CLI rengini alıyor. Aktif bağlantıda operatörden agente ölçülü hızda sürekli hareket eden üç veri paketi gösteriliyor; tekil delegasyon/sonuç pulse renkleri de yönün sahibine bağlandı. diff --git a/orchestrator/src/cli-registry.js b/orchestrator/src/cli-registry.js index dc0746e..cbcab48 100644 --- a/orchestrator/src/cli-registry.js +++ b/orchestrator/src/cli-registry.js @@ -657,6 +657,12 @@ function preparePromptArgs(args, prompt) { // kaldirmak probe'lari yavaslatir ve abortActiveProbes onlari oldururken yarim proses birakirdi. function agentEnvironment(agent, mcpEnvConfig = null) { const env = { ...process.env }; + // ChatGPT oturumu ile API anahtari ayni anda ortama girerse Codex CLI API anahtari yolunu + // secer; gecersiz/eskimis anahtar 401 dondurur ve asil durumu (or. kullanim limiti) gizler. + // Varsayilan ChatGPT girisidir; API anahtari yalnizca CODEX_AUTH_MODE=api_key ile korunur. + if (agent.adapter === "codex" && String(env.CODEX_AUTH_MODE || "chatgpt").toLowerCase() !== "api_key") { + delete env.OPENAI_API_KEY; + } // OpenCode'un otonom izinleri surumden bagimsiz olarak config icerigiyle aktarilir. const autonomous = agent.adapter === "opencode" ? { var: "OPENCODE_CONFIG_CONTENT", patch: { permission: { "*": "allow" } } } @@ -682,8 +688,21 @@ function agentEnvironment(agent, mcpEnvConfig = null) { return env; } +// Codex ChatGPT plan hakki bittiginde: "You've hit your usage limit. ... try again at Sep 22nd, +// 2026 10:58 AM." Bu bir oturum ya da gecici hiz sorunu DEGILDIR; sifirlanma zamanina kadar surer. +const CODEX_USAGE_LIMIT = /hit your usage limit|usage_limit_reached/i; + +function usageLimitReset(text) { + const match = String(text || "").match(/try again at ([^."}\r\n]+)/i); + return match ? match[1].trim() : ""; +} + function classifyHealthFailure(text) { const raw = String(text || ""); + if (CODEX_USAGE_LIMIT.test(raw)) { + const reset = usageLimitReset(raw); + return { status: "quota", label: "Kullanım limiti doldu", detail: `ChatGPT planının Codex kullanım hakkı bitti${reset ? `; yenilenme: ${reset}` : ""}. ${raw}` }; + } if (/requires a newer version|upgrade to the latest|version.*unsupported|unsupported.*version|model metadata.*not found|unsupported.*model/i.test(raw)) return { status: "version-incompatible", label: "Sürüm uyumsuz", detail: raw }; if (/api key|unauthorized|unauthenticated|authentication|login required|not logged in|sign in|giriÅŸ gerekli/i.test(raw)) return { status: "auth-required", label: "Giriş gerekli", detail: raw }; if (/rate.?limit|quota|too many requests|billing|credit/i.test(raw)) return { status: "quota", label: "Kota veya ödeme gerekli", detail: raw }; @@ -767,7 +786,7 @@ function listCodexModels(options = {}) { if (error) reject(error); else resolve(value); }; const timer = setTimeout(() => finish(new Error(`Codex model listesi ${Math.round(timeoutMs / 1000)} saniyede alınamadı.`)), timeoutMs); - try { child = spawn(command.file, command.args, { shell: command.shell, windowsHide: true, windowsVerbatimArguments: !!command.verbatim }); } + try { child = spawn(command.file, command.args, { env: agentEnvironment({ adapter: "codex" }), shell: command.shell, windowsHide: true, windowsVerbatimArguments: !!command.verbatim }); } catch (error) { finish(error); return; } unregister = registerProbe(child); child.stdout?.setEncoding("utf8"); @@ -1032,4 +1051,4 @@ function ensureValidOperator(cfg, discovered) { return changed; } -module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, authHint, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, discoveryCacheFrom, applyOpenCodeFallback, healthCheckAll, listCodexModels, listClaudeModels, normalizeClaudeModels, abortActiveProbes, currentProbeGeneration, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator }; +module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, authHint, CODEX_USAGE_LIMIT, usageLimitReset, classifyHealthFailure, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, discoveryCacheFrom, applyOpenCodeFallback, healthCheckAll, listCodexModels, listClaudeModels, normalizeClaudeModels, abortActiveProbes, currentProbeGeneration, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator }; diff --git a/orchestrator/src/engine.js b/orchestrator/src/engine.js index f1a8ceb..2de1a40 100644 --- a/orchestrator/src/engine.js +++ b/orchestrator/src/engine.js @@ -335,6 +335,13 @@ function classifyCliError(error) { return of("PROVIDER_UNAVAILABLE", "Seçili model hiçbir yapılandırılmış sağlayıcıya çözümlenemedi (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."); } + // Codex plan hakki bitti: beklemekle kisa surede gecmez, sifirlanma zamani mesajda yazar. + // Genel desenlerden ONCE gelmeli; aksi halde CLI_FAILED'a duser ve agent bosuna tekrar denenir. + if (cliRegistry.CODEX_USAGE_LIMIT.test(raw)) { + const reset = cliRegistry.usageLimitReset(raw); + return of("QUOTA_EXCEEDED", `Codex kullanım limiti doldu (ChatGPT plan hakkı bitti)${reset ? `; yenilenme: ${reset}` : ""}.`, + "Limit yenilenene kadar Codex kullanılamaz; başka bir agent seçin, https://chatgpt.com/codex/settings/usage adresinden kredi alın veya planı yükseltin."); + } if (/API key not valid|API_KEY_INVALID|invalid[_ ]api[_ ]key|incorrect api key/i.test(raw)) { return of("AUTH_INVALID", "API anahtarı geçersiz veya reddedildi.", `Bu agent'ın API anahtarını yenileyin. ${cliRegistry.authHint(adapter)}`); } diff --git a/orchestrator/src/server.js b/orchestrator/src/server.js index f2efd27..4dc745a 100644 --- a/orchestrator/src/server.js +++ b/orchestrator/src/server.js @@ -461,6 +461,7 @@ const server = http.createServer(async (req, res) => { roles: store.listRoles(), skills: skillRegistry.allSkills().map((s) => ({ name: s.name, file: s.file, description: s.description, category: s.category, appliesTo: s.appliesTo, match: s.match })), cliStatus, + numericLimits: store.NUMERIC_LIMITS, schedules: cfg.schedules || [], projects: cfg.projects || [], platform: process.platform, @@ -783,6 +784,17 @@ const server = http.createServer(async (req, res) => { if (cfg.maxConcurrentTasks !== undefined && (!Number.isInteger(cfg.maxConcurrentTasks) || cfg.maxConcurrentTasks < 1 || cfg.maxConcurrentTasks > 8)) { return send(res, 400, { error: "maxConcurrentTasks 1-8 arasi tam sayi olmali" }); } + // Sayisal ayarlar: eksi/asiri deger motoru sessizce bozar (or. dailyCallBudget<0 her cagriyi + // engeller). Sinirlar store.NUMERIC_LIMITS'te tek kaynakta; arayuz ayni tabloyu /api/state'ten okur. + for (const [key, { min, max }] of Object.entries(store.NUMERIC_LIMITS)) { + const field = key.startsWith("agents.*.") ? key.slice("agents.*.".length) : null; + const values = field + ? Object.entries(cfg.agents).map(([name, agent]) => [`agents.${name}.${field}`, agent?.[field]]) + : [[key, key.split(".").reduce((obj, part) => (obj == null ? undefined : obj[part]), cfg)]]; + for (const [label, value] of values) { + if (value !== undefined && (!Number.isInteger(value) || value < min || value > max)) return send(res, 400, { error: `${label} ${min}-${max} arasi tam sayi olmali` }); + } + } // Es zamanli gorev, izolasyon olmadan tehlikelidir: iki gorev ayni klasorde birbirinin // dosyalarini ezer. Sessizce kabul etmek yerine kullaniciya sebebi soyle. if (Number(cfg.maxConcurrentTasks) > 1 && cfg.worktree?.mode !== "task") { diff --git a/orchestrator/src/store.js b/orchestrator/src/store.js index 3a37f36..929ba5d 100644 --- a/orchestrator/src/store.js +++ b/orchestrator/src/store.js @@ -246,13 +246,39 @@ function shippedSkillSlugs() { .map((f) => f.replace(/\.md$/i, "")); } catch { return []; } } +// Yeni kurulumun baslangic config'i: config.default.json sablonu, yoksa FALLBACK_CONFIG. +// Varsayilan degerlerin TEK kaynagi budur (ilk config uretimi ve NUMERIC_LIMITS varsayilanlari). +function readSeedConfig() { + const template = path.join(ASSETS, "config.default.json"); + try { return JSON.parse(fs.existsSync(template) ? fs.readFileSync(template, "utf8") : JSON.stringify(FALLBACK_CONFIG)); } + catch { return JSON.parse(JSON.stringify(FALLBACK_CONFIG)); } +} + +// Sayisal ayarlarin gecerli araliklari ve varsayilanlari: TEK KAYNAK. Sunucu /api/config +// dogrulamasi ve arayuz sayi kutulari (min/max, sinira cekme, bos girdi varsayilani) bunu okur +// (/api/state -> numericLimits). 0'in anlami olan alanlarda (butce=sinirsiz, agent +// timeout=genel ayar) min 0'dir. "agents.*." her uzmanin kendi alanidir. +function buildNumericLimits() { + const seed = readSeedConfig(); + const pick = (dotted) => dotted.split(".").reduce((obj, key) => (obj == null ? undefined : obj[key]), seed); + const limit = (min, max, dflt) => ({ min, max, default: dflt }); + return { + "operator.maxRounds": limit(1, 50, pick("operator.maxRounds")), + "operator.maxDelegationsPerRound": limit(1, 32, pick("operator.maxDelegationsPerRound")), + "operator.protocolRetries": limit(0, 5, pick("operator.protocolRetries")), + dailyCallBudget: limit(0, 100000, pick("dailyCallBudget")), + agentTimeoutSeconds: limit(10, 86400, pick("agentTimeoutSeconds")), + memoryCharBudget: limit(0, 1000000, pick("memoryCharBudget")), + teamContextCharBudget: limit(0, 1000000, pick("teamContextCharBudget")), + "agents.*.timeoutSeconds": limit(0, 86400, pick("agentTimeoutSeconds")), + }; +} +const NUMERIC_LIMITS = buildNumericLimits(); + function loadConfig() { const file = path.join(ROOT, "config.json"); if (!fs.existsSync(file)) { - const template = path.join(ASSETS, "config.default.json"); - const seed = fs.existsSync(template) ? fs.readFileSync(template, "utf8") : JSON.stringify(FALLBACK_CONFIG, null, 2); - let seedObj; - try { seedObj = JSON.parse(seed); } catch { seedObj = { ...FALLBACK_CONFIG }; } + const seedObj = readSeedConfig(); // Ilk kurulumda tum paket becerilerini etkinlestir (kullanici Ayarlar'dan kapatabilir). // Sonraki yuklemelerde dosya var oldugundan bu blok CALISMAZ; kullanicinin secimi korunur. seedObj.skills = seedObj.skills || {}; @@ -591,6 +617,7 @@ module.exports = { ASSETS, WORK_BASE, ensureDirs, + NUMERIC_LIMITS, loadConfig, normalizeConfig, saveConfig, diff --git a/orchestrator/test/cli.test.js b/orchestrator/test/cli.test.js index 9224cef..ac05892 100644 --- a/orchestrator/test/cli.test.js +++ b/orchestrator/test/cli.test.js @@ -79,6 +79,29 @@ function main() { assert.ok(rootsArg && rootsArg.includes("C:/shared"), "extraWritableDirs writable_roots olarak gecmeli"); assert.ok(!rootsArg.includes('" "') && !rootsArg.includes('""'), "bos/whitespace yollar elenmeli"); + // Codex ChatGPT oturumu: ortamdaki OPENAI_API_KEY alt surece sizarsa Codex API key moduna + // gecip 401 dondurur ve asil durumu (or. kullanim limiti) gizler. + const originalApiKey = process.env.OPENAI_API_KEY; + const originalAuthMode = process.env.CODEX_AUTH_MODE; + try { + process.env.OPENAI_API_KEY = "test-invalid-key"; + delete process.env.CODEX_AUTH_MODE; + assert.ok(!Object.prototype.hasOwnProperty.call(cliRegistry.agentEnvironment({ adapter: "codex" }), "OPENAI_API_KEY"), "ChatGPT Codex cagrisina ortam API anahtari sizmamali"); + assert.equal(cliRegistry.agentEnvironment({ adapter: "opencode" }).OPENAI_API_KEY, "test-invalid-key", "diger CLI ortamlari etkilenmemeli"); + process.env.CODEX_AUTH_MODE = "api_key"; + assert.equal(cliRegistry.agentEnvironment({ adapter: "codex" }).OPENAI_API_KEY, "test-invalid-key", "API key modu acikca secilirse anahtar korunmali"); + } finally { + if (originalApiKey == null) delete process.env.OPENAI_API_KEY; else process.env.OPENAI_API_KEY = originalApiKey; + if (originalAuthMode == null) delete process.env.CODEX_AUTH_MODE; else process.env.CODEX_AUTH_MODE = originalAuthMode; + } + + // Codex 0.154.0'in plan hakki bitince verdigi gercek cikti (2026-09-21, codex exec --json). + const usageLimitOutput = '{"type":"error","message":"You\'ve hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 22nd, 2026 10:58 AM."}'; + const usageHealth = cliRegistry.classifyHealthFailure(usageLimitOutput); + assert.equal(usageHealth.status, "quota", "kullanim limiti saglik testinde kota olarak gorunmeli"); + assert.equal(usageHealth.label, "Kullanım limiti doldu"); + assert.ok(usageHealth.detail.includes("Sep 22nd, 2026 10:58 AM"), "yenilenme zamani detayda gorunmeli"); + // buildCommand bare-shell dali: bosluk iceren yol argumanlari (--settings/--file) shell:true // altinda ikiye bolunmesin diye tirnaklanmali; sade bayrak/model adlari dokunulmamali. if (process.platform === "win32") { diff --git a/orchestrator/test/server-flow.test.js b/orchestrator/test/server-flow.test.js index 8980253..fd96589 100644 --- a/orchestrator/test/server-flow.test.js +++ b/orchestrator/test/server-flow.test.js @@ -67,7 +67,9 @@ async function requestTolerant(method, urlPath, body, attempts = 3) { async function main() { const server = spawn(process.execPath, [path.join(REPO, "src", "server.js")], { - env: { ...process.env, CLI_TEAM_ROOT: ROOT, PORT: String(PORT) }, + // NO_OPEN: test sunucusu kullanicinin tarayicisinda sekme acmamali (gecici veriyle + // "saglik testi bekleniyor" gosteren olu sekmeler birakiyordu). + env: { ...process.env, CLI_TEAM_ROOT: ROOT, PORT: String(PORT), NO_OPEN: "1" }, cwd: WORK, }); let serverLog = ""; @@ -163,6 +165,20 @@ async function main() { assert.equal((await putConc(0)).status, 400, "maxConcurrentTasks alt siniri zorunlu"); assert.equal((await putConc(9)).status, 400, "maxConcurrentTasks ust siniri zorunlu"); assert.equal((await putConc(1.5)).status, 400, "maxConcurrentTasks tam sayi olmali"); + // Regresyon: fare tekerlegi operator sayilarini -12/-17'ye indirip kaydettirmisti; motor bunu + // sessizce "tek delegasyon, sifir tekrar"a ceviriyordu. dailyCallBudget<0 ise her cagriyi engeller. + const putNum = (patch) => requestTolerant("PUT", "/api/config", { ...liveCfg, ...patch }); + // Arayuz sinirlari sunucudaki ayni tablodan okur; /api/state bu tabloyu yayinlamali. + const stateLimits = (await requestTolerant("GET", "/api/state")).body.numericLimits; + assert.deepEqual(stateLimits["operator.maxDelegationsPerRound"], { min: 1, max: 32, default: 8 }, "/api/state numericLimits yayinlamali"); + const badOperator = await putNum({ operator: { ...liveCfg.operator, maxDelegationsPerRound: -12 } }); + assert.equal(badOperator.status, 400, "eksi delegasyon siniri reddedilmeli"); + assert.match(badOperator.body.error, /operator\.maxDelegationsPerRound 1-32/); + assert.equal((await putNum({ operator: { ...liveCfg.operator, protocolRetries: -17 } })).status, 400, "eksi protokol tekrari reddedilmeli"); + assert.equal((await putNum({ dailyCallBudget: -1 })).status, 400, "eksi gunluk butce reddedilmeli"); + assert.equal((await putNum({ dailyCallBudget: 0 })).status, 200, "0 gunluk butce sinirsiz anlamina gelir ve kabul edilmeli"); + assert.equal((await putNum({ operator: { ...liveCfg.operator, protocolRetries: 0 } })).status, 200, "0 protokol tekrari gecerli"); + assert.equal((await putNum({})).status, 200, "gecerli config yeniden kaydedilebilmeli"); // Izolasyon olmadan es zamanlilik veri kaybina yol acar; API bunu SESSIZCE kabul etmemeli. const unsafe = await putConc(2, { mode: "off" }); assert.equal(unsafe.status, 400, "izolasyon kapaliyken es zamanlilik reddedilmeli"); diff --git a/orchestrator/test/team-flow.test.js b/orchestrator/test/team-flow.test.js index 5c33791..4dce2a3 100644 --- a/orchestrator/test/team-flow.test.js +++ b/orchestrator/test/team-flow.test.js @@ -383,6 +383,11 @@ async function main() { assert.equal(code("'gemini' is not recognized as an internal or external command"), "CLI_NOT_FOUND"); assert.equal(code("User location is not supported for the API use"), "REGION_BLOCKED"); assert.equal(code("API key not valid. Please pass a valid API key."), "AUTH_INVALID"); + // Regresyon: Codex plan hakki bitince gelen gercek mesaj CLI_FAILED'a dusuyor, agent + // karantinaya alinmadan bosuna tekrar deneniyordu. + const codexUsage = classifyCliError(new Error('codex cikis kodu 1.\n{"type":"error","message":"You\'ve hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 22nd, 2026 10:58 AM."}')); + assert.equal(codexUsage.code, "QUOTA_EXCEEDED"); + assert.ok(codexUsage.summary.includes("Sep 22nd, 2026 10:58 AM"), "yenilenme zamani ozette gorunmeli"); // Kota/bolge beklemekle gecmez -> karantina; hiz siniri ve gecici yuk gecer -> karantina yok. assert.ok(QUARANTINE_CLI_ERRORS.has("QUOTA_EXCEEDED") && QUARANTINE_CLI_ERRORS.has("REGION_BLOCKED")); assert.ok(!QUARANTINE_CLI_ERRORS.has("RATE_LIMIT") && !QUARANTINE_CLI_ERRORS.has("MODEL_OVERLOADED")); diff --git a/orchestrator/test/ui-smoke.test.js b/orchestrator/test/ui-smoke.test.js index 34a164c..2704a06 100644 --- a/orchestrator/test/ui-smoke.test.js +++ b/orchestrator/test/ui-smoke.test.js @@ -252,4 +252,90 @@ assert.match(html, /JSON'dan içe aktar/, "ice aktarma butonu olmali"); // Rol kapisi form ciktisi uzerinde de gecerli: docs plan/review'e gider, uzak (write) gitmez. assert.deepEqual(mcpModule.resolveForAssignment({ mcp: normalized }, { kind: "plan", agentName: "w1" }).map((s) => s.name), ["docs"], "form ciktisinda rol kapisi calismali"); } +// Agent ayarlari saglik durumunu gostermeli: "Aktif" yalnizca izindir; saglik testi hazir +// degilse operator uzmani kullanmaz ve bu kullaniciya acikca yazilmalidir. +{ + assert.match(html, /'schedules','cli-health'\]\)es\.addEventListener/, "dashboard cli-health olayini dinlemeli"); + assert.match(html, /type==='cli-health'\)\{STATE\.cliStatus=data;refreshHealthViews\(\)\}/, "cli-health olayi saglik gorunumlerini yenilemeli"); + // Rozetler dar pencerede satir satir dizilmeli; tek rozetin yazisi ikiye bolunmemeli. + assert.match(html, //, "CLI taramasi rozetleri canli yenilenebilmeli ve sarmali"); + assert.match(html, /class="badge" style="color:\$\{color\};white-space:nowrap"/, "CLI rozeti ortadan bolunmemeli"); + const pick = (name) => html.match(new RegExp(`^(?:const ${name}=.*|function ${name}\\(.*)$`, "m"))[0]; + const source = ["cliHealth", "agentHealthOf", "agentHealthHtml", "cliScanBadge", "cliHealthBadge", "operatorOptionLabel", "operatorBlocked", "operatorHealthLine", "clampNum", "numLimit", "numAttrs", "numValue", "adapterOf"].map(pick).join("\n"); + // Saglik yorumu tek yerde (app.js CrewHealth); sayfalar onu cagirir. Test de ayni kodu yukler. + const appJs = fs.readFileSync(path.join(__dirname, "..", "web", "app.js"), "utf8"); + const win = { addEventListener() {} }; + new Function("window", "document", "matchMedia", appJs)(win, { addEventListener() {} }, () => ({ matches: false })); + assert.ok(win.CrewHealth, "app.js paylasilan CrewHealth yardimcisini tanimlamali"); + const esc = (value) => String(value ?? "").replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[ch])); + const STATE = { cliStatus: [ + { id: "codex", installed: true, version: "codex-cli 0.154.0", health: { status: "quota", label: "Kullanım limiti doldu", detail: "ChatGPT planının Codex kullanım hakkı bitti; yenilenme: Sep 22nd, 2026 10:58 AM. ham" } }, + { id: "claude", installed: true, version: "2.1.278", health: { status: "ready", label: "Hazır", detail: "Gerçek sağlık testi başarılı." } }, + { id: "gemini", installed: true, version: "0.18.4", health: { status: "auth-required", label: "Giriş gerekli", detail: "auth" } }, + ], numericLimits: require("../src/store").NUMERIC_LIMITS }; + const ui = new Function("STATE", "esc", "window", `${source}\nreturn { agentHealthHtml, cliScanBadge, cliHealthBadge, operatorOptionLabel, operatorHealthLine, clampNum, numAttrs, numValue };`)(STATE, esc, win); + const codexLine = ui.agentHealthHtml({ adapter: "codex", cmd: "codex" }, 0); + assert.match(codexLine, /var\(--warn\)/, "kullanim limiti uyari renginde gorunmeli"); + assert.match(codexLine, /Kullanım limiti doldu · yenilenme: Sep 22nd, 2026 10:58 AM · operatör bu uzmanı kullanmıyor/); + assert.match(ui.agentHealthHtml({ adapter: "claude", cmd: "claude" }, 1), /var\(--ok\).*Hazır · operatör kullanabilir/); + assert.match(ui.agentHealthHtml({ adapter: "gemini", cmd: "gemini" }, 2), /var\(--err\).*Giriş gerekli/); + assert.match(ui.agentHealthHtml({ adapter: "codex", cmd: "codex", enabled: false }, 0), /Kapalı · operatör bu uzmanı kullanmaz/); + assert.match(ui.cliScanBadge(STATE.cliStatus[0]), /color:var\(--warn\).*codex codex-cli 0\.154\.0 · Kullanım limiti doldu/, "kurulu ama limiti dolu CLI yesil gorunmemeli"); + assert.match(ui.cliScanBadge(STATE.cliStatus[1]), /color:var\(--ok\).*· Hazır/); + assert.match(ui.cliScanBadge({ id: "opencode", installed: false }), /color:var\(--dim\).*○ opencode kurulu değil/); + // Ayni saglik bilgisi uygulamanin kalan noktalarinda da kullanilir. + // Regresyon: CLI sekmesinde saglik testi cliStatus'tayken uzman karti "Saglik testi bekleniyor" diyordu. + assert.match(ui.agentHealthHtml({ adapter: "claude", cmd: "claude", health: { status: "quota", label: "eski" } }, 1), /Hazır/, "guncel cliStatus sonucu eski agent kaydindan once gelmeli"); + assert.match(ui.agentHealthHtml({ adapter: "custom", cmd: "wrapper", health: { status: "ready", label: "Hazır" } }, 3), /Hazır/, "cliStatus'ta olmayan agent kendi kaydindaki sonucu kullanmali"); + assert.match(ui.cliHealthBadge(STATE.cliStatus[0]), /data-cli-health="codex".*● Kullanım limiti doldu · yenilenme: Sep 22nd, 2026 10:58 AM/, "CLI kartinda saglik rozeti olmali"); + assert.equal(ui.operatorOptionLabel({ cli: "codex", label: "Codex" }), "Codex — Kullanım limiti doldu · yenilenme: Sep 22nd, 2026 10:58 AM", "hazir olmayan operator secenegi durumunu gostermeli"); + assert.equal(ui.operatorOptionLabel({ cli: "claude", label: "Claude Code" }), "Claude Code — Hazır", "hazir operator de acikca etiketlenmeli; yazi yoklugu tahmin ettirir"); + // Secim kutusu kapaliyken de secili operatorun durumu altinda gorunmeli; metin sunucunun 409 kuraliyla ayni. + assert.match(ui.operatorHealthLine("claude"), /var\(--ok\).*Hazır · görevler bu CLI ile yürütülür/); + assert.match(ui.operatorHealthLine("codex"), /var\(--warn\).*Kullanım limiti doldu · yenilenme: Sep 22nd, 2026 10:58 AM · bu seçimle görev başlatılamaz/); + assert.match(ui.operatorHealthLine("gemini"), /var\(--err\).*Giriş gerekli · bu seçimle görev başlatılamaz/); + STATE.cliStatus.push({ id: "opencode", installed: true, health: { status: "timeout", label: "Yanıt zaman aşımına uğradı" } }); + assert.match(ui.operatorHealthLine("opencode"), /görev yine de başlatılabilir/, "sunucu timeout'u gecici sayar; arayuz 'baslatilamaz' dememeli"); + STATE.cliStatus.pop(); + assert.match(html, /refreshOperatorHealth\(\$\('#defaultOperator'\)\)/, "Operator sekmesi durum satirini gostermeli"); + assert.match(html, /\.join\(''\);refreshOperatorHealth\(el\)\}/, "gorev secenekleri/duzenleme operator kutulari durum satirini gostermeli"); + // Sayi kutulari: sinirlar ve varsayilanlar sunucudaki tek kaynaktan (store.NUMERIC_LIMITS) gelir. + const limits = STATE.numericLimits; + assert.equal(ui.numValue("operator.maxDelegationsPerRound", "-12"), limits["operator.maxDelegationsPerRound"].min, "eksi deger alt sinira cekilmeli"); + assert.equal(ui.numValue("operator.maxDelegationsPerRound", "99"), limits["operator.maxDelegationsPerRound"].max, "asiri deger ust sinira cekilmeli"); + assert.equal(ui.numValue("operator.protocolRetries", ""), limits["operator.protocolRetries"].default, "bos girdi sablon varsayilanina donmeli"); + assert.equal(ui.numValue("operator.protocolRetries", undefined), limits["operator.protocolRetries"].default, "kayitta olmayan alan sablon varsayilanini gostermeli"); + // Regresyon: "||150" kisayolu 0'i (sinirsiz) 150 gosteriyor, Kaydet sessizce 150 yaziyordu. + assert.equal(ui.numValue("dailyCallBudget", 0), 0, "0 butce (sinirsiz) korunmali"); + assert.equal(ui.numAttrs("dailyCallBudget"), `min="${limits.dailyCallBudget.min}" max="${limits.dailyCallBudget.max}" step="1"`); + // Arayuzde kullanilan her anahtar sunucu tablosunda olmali; kopya sinir/varsayilan kalmamali. + const usedKeys = [...html.matchAll(/num(?:Attrs|Value)\('([^']+)'/g)].map((m) => m[1]); + assert.ok(usedKeys.length >= 16, "sayi kutulari ve kaydetme ortak sinir fonksiyonlarini kullanmali"); + for (const key of new Set(usedKeys)) assert.ok(limits[key], `${key} store.NUMERIC_LIMITS'te tanimli olmali`); + for (const [key, l] of Object.entries(limits)) assert.ok(Number.isInteger(l.min) && Number.isInteger(l.max) && Number.isInteger(l.default) && l.min <= l.default && l.default <= l.max, `${key} varsayilani sinirlar icinde olmali`); + assert.doesNotMatch(html, /\|\|(6|8|150|900|8000|30000)\}/, "sayi kutularinda sabit varsayilan kopyasi kalmamali"); + assert.doesNotMatch(html, /clampNum\([^)]*,\s*\d+,\s*\d+,\s*\d+\)/, "sabit sinirli clampNum cagrisi kalmamali"); + assert.match(appJs, /document\.addEventListener\("wheel"/, "fare tekerlegi odakli sayi kutusunu degistirmemeli"); + assert.match(html, /\$\{esc\(operatorOptionLabel\(x\)\)\}/, "Operator sekmesi ayni etiket fonksiyonunu kullanmali"); + // Ekip Akisi bosta duran uzmani saglik sonucuna gore yazar; sabit "hazir · sirada" yaniltir. + assert.match(flowHtml, /function setIdleText\(r\)/, "flow.html saglik tabanli bosta metni icermeli"); + assert.doesNotMatch(flowHtml, /r\.stt\.textContent='hazır · sırada';r\.count/, "gorev basinda bosta metni sabit yazilmamali"); + assert.match(flowHtml, /es\.addEventListener\('cli-health'/, "flow.html saglik guncellemesini dinlemeli"); +} +// Duyarli yerlesim: dort sayfa ayni ortak kurallari app.css'ten alir. +{ + const appCss = fs.readFileSync(path.join(__dirname, "..", "web", "app.css"), "utf8"); + for (const page of [html, flowHtml, codeHtml, boardHtml]) assert.match(page, //, "her sayfa ortak app.css'i yuklemeli"); + assert.match(appCss, /header \{ height: auto; min-height: 58px; \}/, "header sabit yukseklikle sarilan satirlari icerigin ustune bindirmemeli"); + assert.match(appCss, /\.badge, \.pill, \.chip, \.mission-status \{ white-space: nowrap; \}/, "rozet/hap ortadan bolunmemeli"); + assert.match(appCss, /main > \*, \.wrap > \*, \.board > \*/, "izgara cocuklari kolonu ekrandan tasirmamali"); + assert.match(appCss, /\.tabs, \.dialog-head, \.dialog-foot \{ flex-wrap: wrap; \}/, "sekmeler ve pencere basliklari sarilmali"); + assert.match(appCss, /overflow-wrap: break-word;/, "uzun metin kutuda kirilmali; anywhere kelimeleri ortadan boler"); + assert.doesNotMatch(appCss, /overflow-wrap: anywhere/, "anywhere 'TAMAMLANDI' gibi etiketleri harf harf boluyordu"); + assert.match(appCss, /main \{ grid-template-columns: minmax\(0, 1fr\); \}/, "dar ekranda ana kolon icerikle genislememeli"); + assert.match(appCss, /\.tiles \{ grid-template-columns: repeat\(auto-fit, minmax\(88px, 1fr\)\)/, "istatistik kutulari sigdigi kadar sutuna dagilmali"); + assert.match(appCss, /\.team-map \{ flex-wrap: wrap;/, "ekip seridi dar ekranda sarilmali"); +} +// Test sunucusu kullanicinin tarayicisinda sekme acmamali. +assert.match(fs.readFileSync(path.join(__dirname, "server-flow.test.js"), "utf8"), /NO_OPEN: "1"/, "server-flow testi NO_OPEN ile calismali"); console.log("ui smoke ok"); diff --git a/orchestrator/web/app.css b/orchestrator/web/app.css index 6dbafdd..f06923a 100644 --- a/orchestrator/web/app.css +++ b/orchestrator/web/app.css @@ -1,7 +1,8 @@ /* app.css — paylasilan CILA katmani (uc sayfada inline