diff --git a/apps/desktop/assets/codex-bridge/bridge.py b/apps/desktop/assets/codex-bridge/bridge.py index 7a7ea64..9f40f0a 100644 --- a/apps/desktop/assets/codex-bridge/bridge.py +++ b/apps/desktop/assets/codex-bridge/bridge.py @@ -246,6 +246,23 @@ def catalog_refresh_loop(): threading.Thread(target=catalog_refresh_loop, daemon=True).start() +# ---- AITURK is hatti: isi gonderebilen AITURK komutu (is_hatti.py). Kurulum sonradan gelebilir: yoksa 2 dk'da bir bakilir. +HAT_KOMUT = [None] + +def hat_komut_loop(): + while True: + try: + if (CFG.get("aiturk_hat") or {}).get("enabled") is not False: + bulunan = is_hatti.komut_bul() + if bulunan != HAT_KOMUT[0]: + HAT_KOMUT[0] = bulunan + log(f"AITURK is hatti: {'acik, komut ' + bulunan if bulunan else 'kapali (aiturk is komutu yok)'}") + except Exception as e: + log(f"AITURK is hatti komutu aranamadi: {type(e).__name__}: {e}") + time.sleep(600 if HAT_KOMUT[0] else 120) + +threading.Thread(target=hat_komut_loop, daemon=True).start() + LAST_MODELS = {} # hesap kimligi -> son basarili model listesi (chatgpt.com hata verirse bu verilir) CACHE_DIR = os.path.join(LOGDIR, "son-liste") @@ -913,7 +930,8 @@ class H(http.server.BaseHTTPRequestHandler): durum, _, sayi = (IDE_DURUM[0] or "kapali, 0 model").partition(", ") return self._send_json(200, {"ok": ZSTD_OK, "surum": SURUM, "kod": KOD, "zstd": ZSTD_OK, "ide_durum": durum, "ide_model": int((sayi.split() or ["0"])[0]), - "ozet": bool((CFG.get("ozet_kopru") or {}).get("enabled", False))}) + "ozet": bool((CFG.get("ozet_kopru") or {}).get("enabled", False)), + "is_hatti": bool(HAT_KOMUT[0])}) if self.command == "POST" and path.rstrip("/").endswith("/stop"): self._send_json(200, {"ok": True}) log("AITURK IDE istegiyle kapatiliyor") @@ -1272,7 +1290,8 @@ class H(http.server.BaseHTTPRequestHandler): # Model, sohbetin modelinin soylenerek degistirilebildigini bilsin; yoksa "degistiremem" diyor. if CFG.get("sesle_model", True) and CFG.get("model_bilgi", True) and sohbet and model_gecis.bilgi_ekle(req): govde_degisti = True - if is_hatti.masaustu_mu(kaynak) and is_hatti.ekle(req, is_hatti.talimat(CFG.get("aiturk_hat"))): + if is_hatti.masaustu_mu(kaynak) and is_hatti.ekle( + req, is_hatti.talimat(CFG.get("aiturk_hat"), HAT_KOMUT[0], sohbet)): govde_degisti = True except Exception as e: log(f"[{n} {slug}] is hatti talimati atlandi: {type(e).__name__}: {e}") diff --git a/apps/desktop/assets/codex-bridge/is_hatti.py b/apps/desktop/assets/codex-bridge/is_hatti.py index df38f36..50ec847 100644 --- a/apps/desktop/assets/codex-bridge/is_hatti.py +++ b/apps/desktop/assets/codex-bridge/is_hatti.py @@ -1,16 +1,21 @@ """Codex IDE'ye "is kucuk degilse AITURK yapacak" talimati (11 Ekim 2026). -Kullanici karari: talimat yalniz Codex IDE'nin (masaustu uygulamasi) gordugu sistem talimatinda durur; Codex CLI -talimati hic gormez, varligindan haberi olmaz. Karari secili model verir; kucuk modellerin yanilmasi kabul edilir, -ayri bir yazilimla onune gecilmez. +Kullanici karari: Codex Baglama acikken talimat Codex IDE'nin (masaustu uygulamasi) gordugu sistem talimatinda durur; +Codex CLI talimati hic gormez, varligindan haberi olmaz. Karari secili model verir; kucuk modellerin yanilmasi kabul +edilir, ayri bir yazilimla onune gecilmez. Bu yuzden talimat hicbir dosyaya yazilmaz (AGENTS.md'yi CLI da okur): kopru, yalniz masaustu uygulamasindan gelen isteklere, istek gecerken ekler. Istemci `originator` basligindan ayirt edilir (olcum 11 Ekim 2026: masaustu "Codex Desktop", etkilesimli CLI "codex-tui", `codex exec` "codex_exec"). -Ayar (bridge.json): "aiturk_hat": {"enabled": true, "komut": "", "talimat": ""}. -Komut tanimli degilse talimat eklenmez: gonderme yolu olmayan bir "AITURK'e gonder" talimati modeli yaniltir. +Hat, Codex Baglama'nin parcasidir: ayri bir dugmesi yoktur, bu bilgisayarda isi gonderebilen bir AITURK komutu +(`aiturk is`) bulundugu surece aciktir. Komut yoksa talimat eklenmez: gonderme yolu olmayan bir "AITURK'e gonder" +talimati modeli yaniltir. Ayar (bridge.json, istege bagli): "aiturk_hat": {"enabled": false} kapatir; +{"komut": "..."} komutu elle verir; {"talimat": "..."} metni degistirir. """ +import os +import shutil +import subprocess MASAUSTU = {"codex desktop"} ISARET = "[AITURK iş hattı]" @@ -18,9 +23,10 @@ VARSAYILAN = ( ISARET + " Bu Codex, AITURK'e bağlı. Şu kurala göre çalış:\n" "1. Basit soruyu, bilgi isteğini ya da tek küçük düzeltmeyi kendin yap.\n" "2. Kullanıcı \"sen yap\" derse işi kendin yap.\n" - "3. İş küçük değilse (birden çok dosya, birkaç adım, yeni özellik, araştırma gerektiren iş) kendin yapma; " - "işi şu komutla AITURK'e gönder, bitmesini bekle ve sonucu kullanıcıya kısaca aktar:\n{komut}\n" - "Emin değilsen işi küçük say ve kendin yap." + "3. İş küçük değilse (birden çok dosya, birkaç adım, yeni özellik, araştırma gerektiren iş) kendin yapma. " + "İşin tam metnini (kullanıcının isteği ve gereken bağlam) UTF-8 bir dosyaya yaz, işi şu komutla AITURK'e gönder, " + "bitmesini bekle ve sonucu kullanıcıya kısaca aktar:\n{komut}\n" + "Komut \"işi sen yap\" derse ya da hata verirse işi kendin yap. Emin değilsen işi küçük say ve kendin yap." ) @@ -28,11 +34,55 @@ def masaustu_mu(originator): return str(originator or "").strip().lower() in MASAUSTU -def talimat(ayar): - """Eklenecek metin; hat kapaliysa ya da komut yoksa None.""" - if not isinstance(ayar, dict) or not ayar.get("enabled"): +def adaylar(ortam=None): + """Isi gonderebilecek AITURK komutu adaylari, oncelik sirasiyla (kurulu AITURK, PATH, hat deneme kurulumu).""" + ortam = os.environ if ortam is None else ortam + yerel = ortam.get("LOCALAPPDATA") or "" + liste = [] + if yerel: + liste.append(os.path.join(yerel, "TurkServis", "AITURK", "program", "bin", "aiturk.cmd")) + bulunan = shutil.which("aiturk") + if bulunan: + liste.append(bulunan) + if yerel: + liste.append(os.path.join(yerel, "TurkServis", "AITURK-is-hatti", "Scripts", "aiturk.exe")) + return [y for i, y in enumerate(liste) if y and y not in liste[:i]] + + +def destekliyor(yol, calistir=subprocess.run): + """Bu AITURK `is` komutunu taniyor mu? (Eski surumler tanimaz: "No such command 'is'.")""" + if not os.path.isfile(yol): + return False + try: + sonuc = calistir([yol, "is", "--help"], capture_output=True, timeout=40, cwd=os.path.dirname(yol), + creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0)) + return sonuc.returncode == 0 + except Exception: + return False + + +def komut_bul(ortam=None, destek=destekliyor): + """Isi gonderebilen ilk AITURK komutunun yolu; yoksa None.""" + for yol in adaylar(ortam): + if destek(yol): + return yol + return None + + +def komut_satiri(yol, sohbet=""): + """Modelin calistiracagi hazir satir. Sohbet kimligi kopruden gelir (sohbetler karismasin); klasor verilmez: + komut Codex'in o anki klasorunde calisir, AITURK de isi ayni klasorde yapar.""" + sohbet = "".join(c for c in str(sohbet or "") if c.isalnum() or c in "-_") + return f'"{yol}" is' + (f" --sohbet {sohbet}" if sohbet else "") + ' --dosya ""' + + +def talimat(ayar, bulunan=None, sohbet=""): + """Eklenecek metin; hat kapaliysa ya da gonderme komutu yoksa None. + bulunan: bu bilgisayarda bulunan AITURK komutunun yolu (komut_bul); ayardaki "komut" onun onune gecer.""" + ayar = ayar if isinstance(ayar, dict) else {} + if ayar.get("enabled") is False: return None - komut = str(ayar.get("komut") or "").strip() + komut = str(ayar.get("komut") or "").strip() or (komut_satiri(bulunan, sohbet) if bulunan else "") if not komut: return None return str(ayar.get("talimat") or VARSAYILAN).replace("{komut}", komut) diff --git a/apps/desktop/assets/codex-bridge/test_is_hatti.py b/apps/desktop/assets/codex-bridge/test_is_hatti.py index 6f3546d..1dabdcc 100644 --- a/apps/desktop/assets/codex-bridge/test_is_hatti.py +++ b/apps/desktop/assets/codex-bridge/test_is_hatti.py @@ -10,16 +10,52 @@ class Hat(unittest.TestCase): for other in ("codex-tui", "codex_exec", "codex_cli_rs", "", None, "Codex Desktop Helper"): self.assertFalse(h.masaustu_mu(other), other) - def test_no_instruction_without_the_switch_or_without_a_send_command(self): + def test_no_instruction_when_switched_off_or_without_a_send_command(self): self.assertIsNone(h.talimat(None)) - self.assertIsNone(h.talimat({"enabled": False, "komut": "aiturk gonder"})) - self.assertIsNone(h.talimat({"enabled": True})) + self.assertIsNone(h.talimat({})) self.assertIsNone(h.talimat({"enabled": True, "komut": " "})) + self.assertIsNone(h.talimat({"enabled": False, "komut": "aiturk gonder"})) + self.assertIsNone(h.talimat({"enabled": False}, "C:/a/aiturk.exe", "s1")) text = h.talimat({"enabled": True, "komut": "aiturk gonder --dosya is.md"}) self.assertIn("aiturk gonder --dosya is.md", text) self.assertIn("sen yap", text) self.assertEqual(h.talimat({"enabled": True, "komut": "x", "talimat": "ozel {komut}"}), "ozel x") + def test_on_by_default_once_a_send_command_exists_on_this_computer(self): + text = h.talimat(None, "C:/Program/aiturk.exe", "019a-bc_d") + self.assertIn('"C:/Program/aiturk.exe" is --sohbet 019a-bc_d --dosya', text) + self.assertIn('"C:/Program/aiturk.exe" is --dosya', h.talimat({}, "C:/Program/aiturk.exe", "")) + # Sohbet kimligi komut satirina yazilir: kabuk karakteri tasiyamaz. + self.assertIn("--sohbet abrm-rf ", h.komut_satiri("x", 'a"b; rm -rf /')) + # Ayardaki komut bulunanin onune gecer. + self.assertIn("elle", h.talimat({"komut": "elle"}, "C:/Program/aiturk.exe")) + + def test_the_first_candidate_that_knows_the_command_is_used(self): + import os + import tempfile + with tempfile.TemporaryDirectory() as d: + eski = os.path.join(d, "TurkServis", "AITURK", "program", "bin", "aiturk.cmd") + yeni = os.path.join(d, "TurkServis", "AITURK-is-hatti", "Scripts", "aiturk.exe") + for yol in (eski, yeni): + os.makedirs(os.path.dirname(yol)) + open(yol, "w").close() + ortam = {"LOCALAPPDATA": d} + self.assertEqual(h.adaylar(ortam)[0], eski) + self.assertEqual(h.komut_bul(ortam, destek=lambda y: y == yeni), yeni) + self.assertEqual(h.komut_bul(ortam, destek=lambda y: True), eski) + self.assertIsNone(h.komut_bul(ortam, destek=lambda y: False)) + + class Sonuc: + returncode = 2 + self.assertFalse(h.destekliyor(eski, calistir=lambda *a, **k: Sonuc())) + Sonuc.returncode = 0 + self.assertTrue(h.destekliyor(eski, calistir=lambda *a, **k: Sonuc())) + self.assertFalse(h.destekliyor(os.path.join(d, "yok.exe"), calistir=lambda *a, **k: Sonuc())) + + def patlar(*a, **k): + raise OSError("x") + self.assertFalse(h.destekliyor(eski, calistir=patlar)) + def test_instruction_is_put_first_once_and_never_repeated(self): req = {"input": [{"type": "message", "role": "user", "content": "selam"}]} text = h.talimat({"enabled": True, "komut": "aiturk gonder"})