From 405a07462d574726d7bf27263977565835b84931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Haydar=20=C3=96zt=C3=BCrk?= <128489693+alexturkey@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:40:40 +0300 Subject: [PATCH 1/6] fix(codex): ChatGPT oturumunu koru ve kullanim limitini ayri teshis et - Codex alt sureclerine OPENAI_API_KEY aktarilmiyor; ortamdaki gecersiz anahtar Codex'i API key moduna gecirip 401 ile asil durumu gizliyordu. API key modu CODEX_AUTH_MODE=api_key ile acikca secilir. - "You've hit your usage limit" ciktisi saglikta "Kullanim limiti doldu", motorda QUOTA_EXCEEDED olarak siniflandiriliyor; yenilenme zamani mesajdan cikariliyor. Onceden CLI_FAILED sayilip bosuna tekrar deneniyordu. Co-Authored-By: Claude Opus 5 (1M context) --- .agents/context/cli-registry.md | 13 ++++++++++++- orchestrator/src/cli-registry.js | 23 +++++++++++++++++++++-- orchestrator/src/engine.js | 7 +++++++ orchestrator/test/cli.test.js | 23 +++++++++++++++++++++++ orchestrator/test/team-flow.test.js | 5 +++++ 5 files changed, 68 insertions(+), 3 deletions(-) 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/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/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/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")); From def1688fa8949bd63adce6d6693117fcf4a8f67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Haydar=20=C3=96zt=C3=BCrk?= <128489693+alexturkey@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:40:40 +0300 Subject: [PATCH 2/6] feat(web): uzman ve CLI saglik durumunu ayarlarda goster - Her uzman kartinda "Aktif" kutusunun altina saglik satiri eklendi: Hazir / Kullanim limiti doldu (yenilenme zamaniyla) / Giris gerekli / Test ediliyor / Kapali. Aktif ama hazir olmayan uzmanda "operator bu uzmani kullanmiyor" yazar. - "Kurulu CLI taramasi" rozetleri yalnizca kurulu olmaya degil saglik testine gore renkleniyor ve etiket gosteriyor. - Dashboard cli-health SSE olayini dinliyor; rozetler ve satirlar kaydedilmemis taslaklara dokunmadan canli yenileniyor. Sunucu saglik sonucunu yayinliyordu ama arayuz hic gostermiyordu; limiti dolu Codex "Aktif" ve yesil nokta ile hazir gibi gorunuyordu. Co-Authored-By: Claude Opus 5 (1M context) --- .agents/context/settings.md | 12 +++++++++++- orchestrator/test/ui-smoke.test.js | 25 +++++++++++++++++++++++++ orchestrator/web/index.html | 17 +++++++++++++---- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/.agents/context/settings.md b/.agents/context/settings.md index c6dba92..028a8ae 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,7 @@ 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. - 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 +44,15 @@ Config şeması için `configuration.md`, CLI keşfi için `cli-registry.md`, ro ## Major Changes +### 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/orchestrator/test/ui-smoke.test.js b/orchestrator/test/ui-smoke.test.js index 34a164c..8837ce1 100644 --- a/orchestrator/test/ui-smoke.test.js +++ b/orchestrator/test/ui-smoke.test.js @@ -252,4 +252,29 @@ 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"); + assert.match(html, //, "CLI taramasi rozetleri canli yenilenebilmeli"); + const pick = (name) => html.match(new RegExp(`^(?:const ${name}=.*|function ${name}\\(.*)$`, "m"))[0]; + const source = ["HEALTH_TONE", "cliHealth", "healthTone", "healthReset", "agentHealthHtml", "cliScanBadge", "adapterOf"].map(pick).join("\n"); + 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" } }, + ] }; + const ui = new Function("STATE", "esc", `${source}\nreturn { agentHealthHtml, cliScanBadge };`)(STATE, esc); + 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/); +} console.log("ui smoke ok"); diff --git a/orchestrator/web/index.html b/orchestrator/web/index.html index 43dc7bc..6d3840b 100644 --- a/orchestrator/web/index.html +++ b/orchestrator/web/index.html @@ -478,6 +478,15 @@

CrewCtl Command Center

const CLI_SETTINGS_DEFAULTS={codex:{model:'',reasoningEffort:'medium',serviceTier:'fast'},claude:{model:''},opencode:{model:''}}; function cloneCliSettings(value){const source=value&&typeof value==='object'?value:{};return Object.fromEntries(Object.entries(CLI_SETTINGS_DEFAULTS).map(([id,defaults])=>[id,{...defaults,...(source[id]||{})}]))} function adapterOf(a){const b=String(a?.adapter||a?.cmd||'').toLowerCase();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 b||'custom'} +// Saglik testi durumu. "Aktif" yalnizca izin verir; operator bir uzmani ancak saglik testi "ready" +// ise gorur (engine.js agentUsable). Kullanici bu farki ayarlar ekraninda gorebilmeli. +const HEALTH_TONE={ready:'ok',testing:'dim',unknown:'dim',quota:'warn',timeout:'warn','version-incompatible':'warn','auth-required':'err','not-installed':'err',failed:'err'}; +function cliHealth(id){return (STATE.cliStatus||[]).find(x=>x.id===id)?.health||null} +function healthTone(h){return `var(--${HEALTH_TONE[h?.status]||'err'})`} +function healthReset(h){const m=String(h?.detail||'').match(/yenilenme: ([^.]+)/);return m?m[1].trim():''} +function agentHealthHtml(a,i){const h=cliHealth(adapterOf(a))||a.health,reset=healthReset(h);let color='var(--dim)',text;if(a.enabled===false)text='○ Kapalı · operatör bu uzmanı kullanmaz';else if(!h)text='○ Sağlık testi bekleniyor';else if(h.status==='ready'){color=healthTone(h);text='● Hazır · operatör kullanabilir'}else if(h.status==='testing')text='○ Test ediliyor…';else{color=healthTone(h);text=`● ${h.label||'Kullanılamıyor'}${reset?` · yenilenme: ${reset}`:''} · operatör bu uzmanı kullanmıyor`}return `
${esc(text)}
`} +function cliScanBadge(x){const h=x.health,base=x.installed&&(x.id!=='opencode'||x.ready!==false)?'var(--ok)':x.installed?'var(--warn)':'var(--dim)',color=x.installed&&h&&h.status!=='testing'&&h.status!=='unknown'?healthTone(h):base,version=x.id==='opencode'&&x.installed?(x.ready===false?'model seçilmeli':`${x.version||'kurulu'} · model hazır`):(x.version||'kurulu değil'),state=x.installed&&h?.label?` · ${h.label}`:'';return `${x.installed?'●':'○'} ${esc(x.id)} ${esc(version)}${esc(state)}`} +function refreshHealthViews(){const box=$('#cliScanBadges');if(box)box.innerHTML=(STATE.cliStatus||[]).map(cliScanBadge).join(' ');document.querySelectorAll('[data-agent-health]').forEach(el=>{const i=Number(el.dataset.agentHealth);if(draftAgents[i])el.outerHTML=agentHealthHtml(draftAgents[i],i)})} function commandAdapter(command){const b=String(command||'').replace(/\\/g,'/').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'} function normalizeDraftAgentAdapters(){for(const agent of draftAgents){const inferred=commandAdapter(agent.cmd),configured=String(agent.adapter||'').toLowerCase(),next=inferred!=='custom'?inferred:(CLI_LABEL[configured]?configured:'custom');if(inferred!=='custom'&&configured&&configured!==inferred){const template=(STATE.cliStatus||[]).find(x=>x.id===inferred);agent.args=[...(template?.defaultArgs||[])];delete agent.model;delete agent.modelOverride}agent.adapter=next}} function setAgentCommand(index,value){const agent=draftAgents[index];if(!agent)return;agent.cmd=String(value||'').trim();const inferred=commandAdapter(agent.cmd),previous=String(agent.adapter||'').toLowerCase();agent.adapter=inferred;if(previous!==inferred){const template=(STATE.cliStatus||[]).find(x=>x.id===inferred);agent.args=[...(template?.defaultArgs||[])];delete agent.model;delete agent.modelOverride}renderAgents($('#settingsContent'))} @@ -689,13 +698,13 @@

CrewCtl Command Center

/* ---- SSE ---- */ async function resultEvent(r){if(r.kind==='operator-chat'){if(chatParent&&chatParent.id===r.parentTaskId){if(r.status==='failed'){const pending=$('#chatHistory [data-pending]');if(pending){pending.removeAttribute('data-pending');pending.textContent='Operatör yanıt veremedi: '+(r.error||'Bilinmeyen hata')}$('#chatState').textContent='Sohbet başarısız.'}else{const found=await api(`/api/tasks/${r.parentTaskId}`);chatParent=found.task;renderChat();$('#chatState').textContent='Yanıt hazır.'}}return}const fileCount=(r.changes?.created||[]).length+(r.changes?.modified||[]).length+(r.changes?.deleted||[]).length,warnings=(r.delivery?.warnings||[]).length;$('#missionTitle').textContent='Görev tamamlandı';$('#missionSub').textContent=`${r.operator||'Operatör'} · ${r.rounds||0} takım turu · ${fileCount} dosya değişikliği${r.usage?` · ${usageLine(r.usage)}`:''}`;teamNodes.forEach(n=>{n.node.classList.remove('working');n.node.classList.add('done');n.state.textContent='tamamlandı'});missionCompleted(`${r.operator||'Operatör'} · ${r.rounds||0} tur · ${fileCount} dosya değişikliği${r.delivery?.verification?` · ✓ ${r.delivery.verification}`:''}${warnings?` · ${warnings} uyarı`:''}`,r.summary);showToast('ok','Görev tamamlandı',r.summary||r.prompt||'Teslimat hazır.');logEvent({...r,level:'ok',msg:'Operatör görevi tamamladı'})} -function dispatchDashboardEvent(type,data){if(type==='status')applyStatus(data);else if(type==='queue')renderQueue(data);else if(type==='activity')activity(data);else if(type==='message')message(data);else if(type==='log')logEvent(data);else if(type==='result')resultEvent(data);else if(type==='notify')desktopNotify(data);else if(type==='schedules'){STATE.schedules=data;renderScheduleList()}} +function dispatchDashboardEvent(type,data){if(type==='status')applyStatus(data);else if(type==='queue')renderQueue(data);else if(type==='activity')activity(data);else if(type==='message')message(data);else if(type==='log')logEvent(data);else if(type==='result')resultEvent(data);else if(type==='notify')desktopNotify(data);else if(type==='schedules'){STATE.schedules=data;renderScheduleList()}else if(type==='cli-health'){STATE.cliStatus=data;refreshHealthViews()}} // Görev bitince/başarısız olunca işletim sistemi bildirimi göster (panel arka planda olsa da görünür). // İzin bir kullanıcı hareketinde istenir (ensureNotifyPermission). Webhook'tan bağımsızdır ve yapılandırma gerektirmez. function ensureNotifyPermission(){try{if('Notification'in window&&Notification.permission==='default')Notification.requestPermission()}catch{}} function desktopNotify(o){try{if(!('Notification'in window)||Notification.permission!=='granted'||replaying)return;const done=o.status!=='failed';const title=done?'✅ CrewCtl — Görev tamamlandı':'❌ CrewCtl — Görev başarısız';const extra=done?(o.verification?`\n✓ ${String(o.verification).slice(0,140)}`:''):(o.error?`\n${String(o.error).slice(0,180)}`:'');const n=new Notification(title,{body:String(o.prompt||o.id||'').slice(0,180)+extra,tag:String(o.id||'')});n.onclick=()=>{try{window.focus()}catch{}n.close()}}catch{}} function receiveDashboardEvent(type,event){const data=JSON.parse(event.data);if(bootstrapping)bufferedEvents.push({type,data});else dispatchDashboardEvent(type,data)} -function connectDashboardEvents(){es=new EventSource('/api/events');for(const type of ['status','queue','activity','message','log','result','notify','schedules'])es.addEventListener(type,event=>receiveDashboardEvent(type,event))} +function connectDashboardEvents(){es=new EventSource('/api/events');for(const type of ['status','queue','activity','message','log','result','notify','schedules','cli-health'])es.addEventListener(type,event=>receiveDashboardEvent(type,event))} async function bootstrapDashboard(){connectDashboardEvents();let replayed=[];try{const s=await api('/api/state');STATE=s;try{selectedProjectId=localStorage.getItem('crewctl.activeProject')||''}catch{}applyStatus(s.status);renderQueue(s.queue);$('#targetDir').placeholder=s.workingDirAbs||'';refreshOperatorSelect();renderProjectSelect();setupMentions();csTypeChanged();renderScheduleList();if(!s.config.autonomousConsentAcceptedAt)showConsent();try{replayed=await restoreDashboardRun()}catch(error){showToast('err','Görev akışı geri yüklenemedi',error.message)}}catch(error){alert(error.message)}finally{bootstrapping=false;const seen=new Set(replayed.filter(e=>e.type==='activity'||e.type==='message'||e.type==='log').map(e=>JSON.stringify(e))),pending=bufferedEvents;bufferedEvents=[];for(const event of pending){if(seen.has(JSON.stringify(event.data)))continue;dispatchDashboardEvent(event.type,event.data)}}} // Sayfa (ör. Ekip Akışı'na geçiş) bfcache'e girerken açık SSE bağlantısını kapat — aksi halde // donmuş bağlantılar birikip tarayıcının host başına bağlantı limitini doldurur ve sonraki @@ -834,10 +843,10 @@

CrewCtl Command Center

function agentModelControl(a,i){const adapter=adapterOf(a),selected=String(a.model||'');if(adapter==='opencode')return ``;if(adapter==='claude')return ``;if(adapter==='codex'){const models=[...codexModelCatalog],inherited=draftCliSettings.codex?.model||'Codex varsayılanı';if(selected&&!models.some(x=>x.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});return ``}return ''} function refreshAgentCodexModels(){document.querySelectorAll('.agentCodexModel').forEach(select=>{const index=+select.dataset.agentIndex,selected=String(draftAgents[index]?.model||''),inherited=draftCliSettings.codex?.model||'Codex varsayılanı',models=[...codexModelCatalog];if(selected&&!models.some(x=>x.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});select.innerHTML=``+models.map(m=>``).join('');select.value=selected})} function refreshAgentClaudeModels(){document.querySelectorAll('.agentClaudeModel').forEach(select=>{const index=+select.dataset.agentIndex,selected=String(draftAgents[index]?.model||'');select.innerHTML=claudeModelOptions(selected,'agent');select.value=selected})} -function renderAgents(c){const found=(STATE.cliStatus||[]).map(x=>`${x.installed?'●':'○'} ${esc(x.id)} ${esc(x.id==='opencode'&&x.installed?(x.ready===false?'model seçilmeli':`${x.version||'kurulu'} · model hazır`):(x.version||'kurulu değil'))}`).join(' '),open=(STATE.cliStatus||[]).find(x=>x.id==='opencode'),platform=STATE.platform==='win32'?'windows':STATE.platform==='darwin'?'macos':'linux',install=open?.install?.[platform]; +function renderAgents(c){const found=`${(STATE.cliStatus||[]).map(cliScanBadge).join(' ')}`,open=(STATE.cliStatus||[]).find(x=>x.id==='opencode'),platform=STATE.platform==='win32'?'windows':STATE.platform==='darwin'?'macos':'linux',install=open?.install?.[platform]; c.innerHTML=`
Kurulu CLI taraması${found}
${draftIgnoredAdapters.length?``:''}

Kurulu CLI'lar otomatik tanınır. Otomatik oluşturulmuş bir agent'ı silerseniz tercihiniz korunur ve sonraki taramada geri eklenmez.

${open&&!open.installed?`
OpenCode kurulu değil
Bu cihaz için kurulum:
${esc(install||'npm install -g opencode-ai')}
Sağlayıcı bağlantısı: ${esc(open.install?.auth||'opencode auth login')}
`:''}
` +`
Buradaki agent'lar operatörün altında çalışan uzmanlardır. Operatör bunlardan biri değildir — ayrı "Operatör" sekmesinde bir CLI olarak seçilir. Bir uzmanı silmek operatörü etkilemez. Agent'ın rolü görev türünü bağlayıcı biçimde sınırlar: executor uygulama, reviewer inceleme, planner planlama yapar. Dengeli uygulama görevlerinde hazır üç rol planner → executor → reviewer sırasıyla aynı turda çalıştırılır.
` - +draftAgents.map((a,i)=>`
${esc(a.name)}uzman
${esc(a.adapter||'otomatik')}
Bilinen CLI komutlarında adapter ve güvenli varsayılan argümanlar otomatik eşitlenir.
${agentModelControl(a,i)}
`).join('') + +draftAgents.map((a,i)=>`
${esc(a.name)}uzman
${esc(a.adapter||'otomatik')}
${agentHealthHtml(a,i)}
Bilinen CLI komutlarında adapter ve güvenli varsayılan argümanlar otomatik eşitlenir.
${agentModelControl(a,i)}
`).join('') +`
Yeni CLI agent
CLI'ı seçin; komut ve önerilen argümanlar otomatik doldurulsun.
` +`
Uzman rollerini düzenle (Markdown)
${workerRoles().map(r=>``).join('')}
Düzenlemek için bir rol seçin.
`; if(currentRole&&workerRoles().includes(currentRole))loadRole()} From 952656f59494158d3441eeb82e7de3f940563fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Haydar=20=C3=96zt=C3=BCrk?= <128489693+alexturkey@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:40:40 +0300 Subject: [PATCH 3/6] fix(web): CLI tarama rozetleri dar pencerede ortadan bolunmesin Rozetleri canli yenilemek icin eklenen kapsayici satir ici davranip rozet yazisini kelime kelime boluyordu. Kapsayici flex-wrap oldu, rozetler nowrap; genislik daralinca rozetler butun halinde alt satira geciyor. Co-Authored-By: Claude Opus 5 (1M context) --- .agents/context/settings.md | 2 +- orchestrator/test/ui-smoke.test.js | 4 +++- orchestrator/web/index.html | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.agents/context/settings.md b/.agents/context/settings.md index 028a8ae..bfefcf2 100644 --- a/.agents/context/settings.md +++ b/.agents/context/settings.md @@ -22,7 +22,7 @@ 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. +- "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. - 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. diff --git a/orchestrator/test/ui-smoke.test.js b/orchestrator/test/ui-smoke.test.js index 8837ce1..101c633 100644 --- a/orchestrator/test/ui-smoke.test.js +++ b/orchestrator/test/ui-smoke.test.js @@ -257,7 +257,9 @@ assert.match(html, /JSON'dan içe aktar/, "ice aktarma butonu olmali"); { 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"); - assert.match(html, //, "CLI taramasi rozetleri canli yenilenebilmeli"); + // 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 = ["HEALTH_TONE", "cliHealth", "healthTone", "healthReset", "agentHealthHtml", "cliScanBadge", "adapterOf"].map(pick).join("\n"); const esc = (value) => String(value ?? "").replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[ch])); diff --git a/orchestrator/web/index.html b/orchestrator/web/index.html index 6d3840b..5001d18 100644 --- a/orchestrator/web/index.html +++ b/orchestrator/web/index.html @@ -485,7 +485,7 @@

CrewCtl Command Center

function healthTone(h){return `var(--${HEALTH_TONE[h?.status]||'err'})`} function healthReset(h){const m=String(h?.detail||'').match(/yenilenme: ([^.]+)/);return m?m[1].trim():''} function agentHealthHtml(a,i){const h=cliHealth(adapterOf(a))||a.health,reset=healthReset(h);let color='var(--dim)',text;if(a.enabled===false)text='○ Kapalı · operatör bu uzmanı kullanmaz';else if(!h)text='○ Sağlık testi bekleniyor';else if(h.status==='ready'){color=healthTone(h);text='● Hazır · operatör kullanabilir'}else if(h.status==='testing')text='○ Test ediliyor…';else{color=healthTone(h);text=`● ${h.label||'Kullanılamıyor'}${reset?` · yenilenme: ${reset}`:''} · operatör bu uzmanı kullanmıyor`}return `
${esc(text)}
`} -function cliScanBadge(x){const h=x.health,base=x.installed&&(x.id!=='opencode'||x.ready!==false)?'var(--ok)':x.installed?'var(--warn)':'var(--dim)',color=x.installed&&h&&h.status!=='testing'&&h.status!=='unknown'?healthTone(h):base,version=x.id==='opencode'&&x.installed?(x.ready===false?'model seçilmeli':`${x.version||'kurulu'} · model hazır`):(x.version||'kurulu değil'),state=x.installed&&h?.label?` · ${h.label}`:'';return `${x.installed?'●':'○'} ${esc(x.id)} ${esc(version)}${esc(state)}`} +function cliScanBadge(x){const h=x.health,base=x.installed&&(x.id!=='opencode'||x.ready!==false)?'var(--ok)':x.installed?'var(--warn)':'var(--dim)',color=x.installed&&h&&h.status!=='testing'&&h.status!=='unknown'?healthTone(h):base,version=x.id==='opencode'&&x.installed?(x.ready===false?'model seçilmeli':`${x.version||'kurulu'} · model hazır`):(x.version||'kurulu değil'),state=x.installed&&h?.label?` · ${h.label}`:'';return `${x.installed?'●':'○'} ${esc(x.id)} ${esc(version)}${esc(state)}`} function refreshHealthViews(){const box=$('#cliScanBadges');if(box)box.innerHTML=(STATE.cliStatus||[]).map(cliScanBadge).join(' ');document.querySelectorAll('[data-agent-health]').forEach(el=>{const i=Number(el.dataset.agentHealth);if(draftAgents[i])el.outerHTML=agentHealthHtml(draftAgents[i],i)})} function commandAdapter(command){const b=String(command||'').replace(/\\/g,'/').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'} function normalizeDraftAgentAdapters(){for(const agent of draftAgents){const inferred=commandAdapter(agent.cmd),configured=String(agent.adapter||'').toLowerCase(),next=inferred!=='custom'?inferred:(CLI_LABEL[configured]?configured:'custom');if(inferred!=='custom'&&configured&&configured!==inferred){const template=(STATE.cliStatus||[]).find(x=>x.id===inferred);agent.args=[...(template?.defaultArgs||[])];delete agent.model;delete agent.modelOverride}agent.adapter=next}} @@ -843,7 +843,7 @@

CrewCtl Command Center

function agentModelControl(a,i){const adapter=adapterOf(a),selected=String(a.model||'');if(adapter==='opencode')return ``;if(adapter==='claude')return ``;if(adapter==='codex'){const models=[...codexModelCatalog],inherited=draftCliSettings.codex?.model||'Codex varsayılanı';if(selected&&!models.some(x=>x.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});return ``}return ''} function refreshAgentCodexModels(){document.querySelectorAll('.agentCodexModel').forEach(select=>{const index=+select.dataset.agentIndex,selected=String(draftAgents[index]?.model||''),inherited=draftCliSettings.codex?.model||'Codex varsayılanı',models=[...codexModelCatalog];if(selected&&!models.some(x=>x.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});select.innerHTML=``+models.map(m=>``).join('');select.value=selected})} function refreshAgentClaudeModels(){document.querySelectorAll('.agentClaudeModel').forEach(select=>{const index=+select.dataset.agentIndex,selected=String(draftAgents[index]?.model||'');select.innerHTML=claudeModelOptions(selected,'agent');select.value=selected})} -function renderAgents(c){const found=`${(STATE.cliStatus||[]).map(cliScanBadge).join(' ')}`,open=(STATE.cliStatus||[]).find(x=>x.id==='opencode'),platform=STATE.platform==='win32'?'windows':STATE.platform==='darwin'?'macos':'linux',install=open?.install?.[platform]; +function renderAgents(c){const found=`${(STATE.cliStatus||[]).map(cliScanBadge).join(' ')}`,open=(STATE.cliStatus||[]).find(x=>x.id==='opencode'),platform=STATE.platform==='win32'?'windows':STATE.platform==='darwin'?'macos':'linux',install=open?.install?.[platform]; c.innerHTML=`
Kurulu CLI taraması${found}
${draftIgnoredAdapters.length?``:''}

Kurulu CLI'lar otomatik tanınır. Otomatik oluşturulmuş bir agent'ı silerseniz tercihiniz korunur ve sonraki taramada geri eklenmez.

${open&&!open.installed?`
OpenCode kurulu değil
Bu cihaz için kurulum:
${esc(install||'npm install -g opencode-ai')}
Sağlayıcı bağlantısı: ${esc(open.install?.auth||'opencode auth login')}
`:''}
` +`
Buradaki agent'lar operatörün altında çalışan uzmanlardır. Operatör bunlardan biri değildir — ayrı "Operatör" sekmesinde bir CLI olarak seçilir. Bir uzmanı silmek operatörü etkilemez. Agent'ın rolü görev türünü bağlayıcı biçimde sınırlar: executor uygulama, reviewer inceleme, planner planlama yapar. Dengeli uygulama görevlerinde hazır üç rol planner → executor → reviewer sırasıyla aynı turda çalıştırılır.
` +draftAgents.map((a,i)=>`
${esc(a.name)}uzman
${esc(a.adapter||'otomatik')}
${agentHealthHtml(a,i)}
Bilinen CLI komutlarında adapter ve güvenli varsayılan argümanlar otomatik eşitlenir.
${agentModelControl(a,i)}
`).join('') From 372c8200d30a4cf9165c22d4bae172a777e84858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Haydar=20=C3=96zt=C3=BCrk?= <128489693+alexturkey@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:40:41 +0300 Subject: [PATCH 4/6] feat(web): ortak duyarli yerlesim ve tek saglik kaynagi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - app.css: dort sayfa ve tum modallar icin ortak "Duyarli yerlesim" bolumu. Kapsayici sarar, rozet bolunmez; izgara cocuklari min-width:0; header sabit degil min-height; uzun metin break-word; tiles auto-fit; ekip seridi dar ekranda sarilir. - app.js: paylasilan CrewHealth. Ayarlar, CLI kartlari, operator secimleri ve Ekip Akisi ayni saglik sonucunu ayni sekilde gosterir. Ekip Akisi'nda girissiz Gemini artik "hazir · sirada" gorunmuyor. - server-flow testi NO_OPEN ile calisiyor; kullanicinin tarayicisinda olu test sekmesi acmiyor. 390 px'te ana icerik ~97 px tasiyordu, board/code/flow header'i icerigin ustune biniyordu, ayar sekmeleri 330 px tasiyordu. Headless Chrome taramasi: 17 ekran x 4 genislik = 68/68 temiz. Co-Authored-By: Claude Opus 5 (1M context) --- .agents/context/dashboard.md | 15 +++++++++-- .agents/context/settings.md | 2 +- .agents/context/team-flow.md | 12 ++++++++- orchestrator/test/server-flow.test.js | 4 ++- orchestrator/test/ui-smoke.test.js | 37 +++++++++++++++++++++++++-- orchestrator/web/app.css | 29 ++++++++++++++++++++- orchestrator/web/app.js | 18 +++++++++++++ orchestrator/web/flow.html | 14 +++++++--- orchestrator/web/index.html | 22 ++++++++-------- 9 files changed, 131 insertions(+), 22 deletions(-) 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 bfefcf2..ccf2c2c 100644 --- a/.agents/context/settings.md +++ b/.agents/context/settings.md @@ -22,7 +22,7 @@ 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. +- "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. Yorum `app.js` `CrewHealth`'tedir. - 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. 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/test/server-flow.test.js b/orchestrator/test/server-flow.test.js index 8980253..e14fd52 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 = ""; diff --git a/orchestrator/test/ui-smoke.test.js b/orchestrator/test/ui-smoke.test.js index 101c633..d4d0035 100644 --- a/orchestrator/test/ui-smoke.test.js +++ b/orchestrator/test/ui-smoke.test.js @@ -261,14 +261,19 @@ assert.match(html, /JSON'dan içe aktar/, "ice aktarma butonu olmali"); 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 = ["HEALTH_TONE", "cliHealth", "healthTone", "healthReset", "agentHealthHtml", "cliScanBadge", "adapterOf"].map(pick).join("\n"); + const source = ["cliHealth", "agentHealthOf", "agentHealthHtml", "cliScanBadge", "cliHealthBadge", "operatorOptionLabel", "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" } }, ] }; - const ui = new Function("STATE", "esc", `${source}\nreturn { agentHealthHtml, cliScanBadge };`)(STATE, esc); + const ui = new Function("STATE", "esc", "window", `${source}\nreturn { agentHealthHtml, cliScanBadge, cliHealthBadge, operatorOptionLabel };`)(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/); @@ -278,5 +283,33 @@ assert.match(html, /JSON'dan içe aktar/, "ice aktarma butonu olmali"); 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", "hazir operator secenegi sade kalmali"); + 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