@omerrgocmen/crewctl 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,17 @@
1
1
  <div align="center">
2
2
 
3
+ <img src="icon.png" alt="CrewCtl" width="140">
4
+
3
5
  # CrewCtl 🛰️
4
6
 
5
7
  **Kurulu CLI kodlama agent'larınızı — Codex, Claude Code, Gemini ve OpenCode — tek bir operatör liderliğindeki ekip olarak çalıştıran yerel, açık kaynak ve sıfır runtime bağımlılıklı Node.js orkestratörü.**
6
8
 
7
9
  _A local, self-hosted, zero-dependency multi-agent AI orchestrator with an operator-led team and a live web command center._
8
10
 
9
- [![GitHub stars](https://img.shields.io/github/stars/omergocmen/cli?style=flat&logo=github)](https://github.com/omergocmen/cli/stargazers)
10
- [![GitHub forks](https://img.shields.io/github/forks/omergocmen/cli?style=flat&logo=github)](https://github.com/omergocmen/cli/network/members)
11
- [![GitHub issues](https://img.shields.io/github/issues/omergocmen/cli)](https://github.com/omergocmen/cli/issues)
12
- [![Last commit](https://img.shields.io/github/last-commit/omergocmen/cli)](https://github.com/omergocmen/cli/commits)
11
+ [![GitHub stars](https://img.shields.io/github/stars/omergocmen/CrewCtl?style=flat&logo=github)](https://github.com/omergocmen/CrewCtl/stargazers)
12
+ [![GitHub forks](https://img.shields.io/github/forks/omergocmen/CrewCtl?style=flat&logo=github)](https://github.com/omergocmen/CrewCtl/network/members)
13
+ [![GitHub issues](https://img.shields.io/github/issues/omergocmen/CrewCtl)](https://github.com/omergocmen/CrewCtl/issues)
14
+ [![Last commit](https://img.shields.io/github/last-commit/omergocmen/CrewCtl)](https://github.com/omergocmen/CrewCtl/commits)
13
15
  ![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A518-339933?logo=node.js&logoColor=white)
14
16
  ![Dependencies](https://img.shields.io/badge/runtime_dependencies-0-brightgreen)
15
17
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](orchestrator/LICENSE)
@@ -118,6 +120,10 @@ En hızlı yol — **tek komut, kurulum gerektirmez:**
118
120
  npx @omerrgocmen/crewctl
119
121
  ~~~
120
122
 
123
+ > **Kaynak depo notu:** Bu komutu CrewCtl kaynak deposunun kendi kökünde değil, yönetmek
124
+ > istediğiniz proje klasöründe çalıştırın. Klonlanmış CrewCtl deposunda npm aynı isimli
125
+ > yerel manifesti önceliklendirebilir; geliştirme kopyasını başlatmak için `npm start` kullanın.
126
+
121
127
  Ya da global kurun:
122
128
 
123
129
  ~~~bash
@@ -129,18 +135,23 @@ Arayüz varsayılan olarak [http://localhost:4317](http://localhost:4317) adresi
129
135
 
130
136
  > **Veri konumu:** config, kuyruk ve görev geçmişi kullanıcı klasörünüzdeki <code>~/.crewctl</code> altında tutulur (ortam değişkeni <code>CREWCTL_HOME</code> ile değiştirilebilir). Çalışma klasörü varsayılan olarak komutu çalıştırdığınız dizindir; panelden değiştirilebilir. Makineye özel <code>config.json</code> ilk çalıştırmada otomatik üretilir.
131
137
 
132
- <details>
133
- <summary>Kaynaktan çalıştırma (geliştirme)</summary>
138
+ ### Developer modu / uzun yol (Git clone)
139
+
140
+ Kaynak kodu geliştirmek, testleri çalıştırmak veya npm paketi yerine doğrudan Git kopyasını
141
+ kullanmak için:
134
142
 
135
143
  ~~~bash
136
- git clone https://github.com/omergocmen/cli.git
137
- cd cli
144
+ git clone https://github.com/omergocmen/CrewCtl.git
145
+ cd CrewCtl
146
+ npm install
138
147
  npm run doctor
148
+ npm test
139
149
  npm start
140
150
  ~~~
141
151
 
142
- Kaynaktan çalıştırıldığında veri, mevcut davranışla uyumlu olarak <code>orchestrator/</code> klasöründe tutulur.
143
- </details>
152
+ `npm install` runtime bağımlılığı indirmez; lock/yerel npm ortamını hazırlar. Kaynaktan
153
+ çalıştırıldığında geliştirme verileri <code>orchestrator/</code> klasöründe tutulur. Sunucu
154
+ çalışırken panel [http://localhost:4317](http://localhost:4317) adresindedir.
144
155
 
145
156
  ### CLI kullanımı
146
157
 
@@ -1,20 +1,22 @@
1
+ <p align="center"><img src="https://raw.githubusercontent.com/omergocmen/CrewCtl/main/icon.png" alt="CrewCtl" width="140"></p>
2
+
1
3
  # CrewCtl 🛰️
2
4
 
3
5
  > **Kurulu CLI kodlama agent'larınızı — Codex, Claude Code, Gemini ve OpenCode — tek bir operatör‑liderliğindeki takım halinde çalıştıran, sıfır bağımlılıklı, yerel ve açık kaynak Node.js çok‑agent (multi‑agent) AI orkestratörü. Canlı web komuta merkezi dahil.**
4
6
  >
5
7
  > _A zero‑dependency, local, self‑hosted **multi‑agent AI orchestrator** that runs your installed CLI coding agents (OpenAI Codex, Claude Code, Google Gemini, OpenCode) as one **operator‑led team**, with a live web dashboard._
6
8
 
7
- [![GitHub stars](https://img.shields.io/github/stars/omergocmen/cli?style=flat&logo=github)](https://github.com/omergocmen/cli/stargazers)
8
- [![GitHub forks](https://img.shields.io/github/forks/omergocmen/cli?style=flat&logo=github)](https://github.com/omergocmen/cli/network/members)
9
- [![GitHub issues](https://img.shields.io/github/issues/omergocmen/cli)](https://github.com/omergocmen/cli/issues)
10
- [![Last commit](https://img.shields.io/github/last-commit/omergocmen/cli)](https://github.com/omergocmen/cli/commits)
9
+ [![GitHub stars](https://img.shields.io/github/stars/omergocmen/CrewCtl?style=flat&logo=github)](https://github.com/omergocmen/CrewCtl/stargazers)
10
+ [![GitHub forks](https://img.shields.io/github/forks/omergocmen/CrewCtl?style=flat&logo=github)](https://github.com/omergocmen/CrewCtl/network/members)
11
+ [![GitHub issues](https://img.shields.io/github/issues/omergocmen/CrewCtl)](https://github.com/omergocmen/CrewCtl/issues)
12
+ [![Last commit](https://img.shields.io/github/last-commit/omergocmen/CrewCtl)](https://github.com/omergocmen/CrewCtl/commits)
11
13
  ![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A518-339933?logo=node.js&logoColor=white)
12
14
  ![Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)
13
15
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
14
16
  ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
15
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-ff69b4)](https://github.com/omergocmen/cli/pulls)
17
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-ff69b4)](https://github.com/omergocmen/CrewCtl/pulls)
16
18
 
17
- 🔗 **Repo:** [github.com/omergocmen/cli](https://github.com/omergocmen/cli)
19
+ 🔗 **Repo:** [github.com/omergocmen/CrewCtl](https://github.com/omergocmen/CrewCtl)
18
20
 
19
21
  Elinizde zaten **Codex CLI**, **Claude Code**, **Gemini CLI** veya **OpenCode** varsa; bu araç onları
20
22
  ayrı ayrı kullanmak yerine **tek bir yapay zeka geliştirici takımı** gibi koordine eder. Bir CLI
@@ -83,13 +85,17 @@ ayrı ayrı kullanmak yerine **tek bir yapay zeka geliştirici takımı** gibi k
83
85
  **Tek komut — kurulum gerektirmez:**
84
86
 
85
87
  ```bash
86
- npx @omerrgocmen/crewctl # paneli anında başlatır (komutsuz = start)
88
+ npx @omerrgocmen/crewctl # paneli anında başlatır (komutsuz = start)
87
89
  ```
88
90
 
91
+ > **Kaynak depo notu:** `npx` komutunu CrewCtl kaynak deposunun kendi kökünde değil, yönetmek
92
+ > istediğiniz proje klasöründe çalıştırın. Klonlanmış geliştirme kopyasında `npm start`
93
+ > kullanılır; aksi halde npm aynı isimli yerel manifesti seçip bin shim'ini bulamayabilir.
94
+
89
95
  Ya da global kurun:
90
96
 
91
97
  ```bash
92
- npm install -g @omerrgocmen/crewctl
98
+ npm install -g @omerrgocmen/crewctl
93
99
  crewctl # panel
94
100
  crewctl status
95
101
  crewctl task "Testleri düzelt" --dir . --mode balanced
@@ -100,19 +106,23 @@ crewctl doctor # salt-okunur ortam kontrolü
100
106
  > (`CREWCTL_HOME` ile değiştirilebilir). Çalışma klasörü varsayılan olarak komutu çalıştırdığınız
101
107
  > dizindir; panelden değiştirilebilir.
102
108
 
103
- <details>
104
- <summary>Kaynaktan çalıştırma (geliştirme)</summary>
109
+ ### Developer modu / uzun yol (Git clone)
110
+
111
+ Kaynak kodu geliştirmek, testleri çalıştırmak veya npm paketi yerine doğrudan Git kopyasını
112
+ kullanmak için:
105
113
 
106
114
  ```bash
107
- git clone https://github.com/omergocmen/cli.git
108
- cd cli
109
- npm run cli -- doctor # salt-okunur ortam kontrolü
110
- npm start # sunucuyu başlatır ve tarayıcıyı açar
115
+ git clone https://github.com/omergocmen/CrewCtl.git
116
+ cd CrewCtl
117
+ npm install
118
+ npm run cli -- doctor # salt-okunur ortam kontrolü
119
+ npm test # tam regresyon zinciri
120
+ npm start # sunucuyu başlatır ve tarayıcıyı açar
111
121
  ```
112
122
 
113
- Kaynaktan çalıştırıldığında (repo `test/` klasörü mevcutken) veri, mevcut davranışla uyumlu olarak
114
- `orchestrator/` klasöründe tutulur.
115
- </details>
123
+ `npm install` runtime bağımlılığı indirmez; lock/yerel npm ortamını hazırlar. Kaynaktan
124
+ çalıştırıldığında (repo `test/` klasörü mevcutken) geliştirme verileri `orchestrator/`
125
+ klasöründe tutulur.
116
126
 
117
127
  `crewctl doctor` ayarları değiştirmez. Yalnızca keşif sonucunu `config.json` dosyasına uygulamak
118
128
  istediğinizde açıkça `crewctl doctor --fix` kullanın.
@@ -14,6 +14,12 @@
14
14
  "liveDiffIntervalMs": 2500,
15
15
  "versioning": true,
16
16
  "versioningRetention": 20,
17
+ "notify": {
18
+ "_comment": "Görev bitince/başarısız olunca webhookUrl'e {text,...} POST edilir (Slack incoming webhook ile uyumlu). Boş webhookUrl = bildirim kapalı.",
19
+ "webhookUrl": "",
20
+ "onComplete": true,
21
+ "onFailed": true
22
+ },
17
23
  "operator": {
18
24
  "roleFile": "roles/operator.md",
19
25
  "maxRounds": 6,
@@ -27,7 +33,7 @@
27
33
  "opencode": { "model": "" }
28
34
  },
29
35
  "skills": {
30
- "_comment": "Yalnizca enabled listesindeki beceriler taranir. Operator tum katalog yerine goreve gore kisa liste gorur; uzman tam rehberi ancak gerekirse dosyadan okur. Bos enabled = beceri kullanilmaz.",
36
+ "_comment": "Yalnizca enabled listesindeki beceriler taranir. Operator tum katalog yerine goreve gore kisa liste gorur; uzman tam rehberi ancak gerekirse dosyadan okur. ILK KURULUMDA bu liste paketle gelen tum becerilerle otomatik doldurulur (Ayarlar > Beceriler'den kapatilabilir); scorer her goreve yalnizca ilgili olanlari surer.",
31
37
  "enabled": [],
32
38
  "autoMatch": true,
33
39
  "catalogLimit": 12,
@@ -2,31 +2,39 @@
2
2
 
3
3
  ## Amaç
4
4
 
5
- Sana devredilen işi çalışma klasöründe gerçekten uygula ve sonucu kanıtla. Ana hedefe bağlı kal;
6
- en küçük, bakımı kolay ve mevcut proje yaklaşımıyla uyumlu değişikliği tercih et.
7
-
8
- ## Çalışma biçimi
9
-
10
- 1. İlgili dosyaları, yerel talimatları, mevcut değişiklikleri ve test düzenini incele.
11
- 2. Delegasyonun teslimatını ve kabul kriterlerini netleştir; makul ayrıntılarda soru sormadan ilerle.
12
- 3. Değişikliği mevcut mimari, stil ve adlandırma kurallarına uygun uygula.
13
- 4. Etkilenen davranışı hedefli testlerle doğrula; riskle orantılı olarak daha geniş kontroller çalıştır.
14
- 5. Yalnızca gerçekten yaptığın ve doğruladığın şeyleri raporla.
15
-
16
- ## Kalite ve güvenlik kuralları
5
+ Sana devredilen işi çalışma klasöründe gerçekten uygula ve sonucu **kanıtla**. Ana hedefe bağlı kal;
6
+ mevcut projeye en iyi oturan, en küçük ve bakımı kolay değişikliği yap. Amacın "çalışan bir şey"
7
+ değil, bu kod tabanına ait, doğrulanmış ve tam bir teslimattır.
8
+
9
+ ## Çalışma ilkeleri (bunlar seni standart bir asistandan ayırır)
10
+
11
+ 1. **Önce anla, sonra değiştir.** Kör dokunma. İlgili dosyaları, çağrı yerlerini, mevcut desenleri,
12
+ testleri ve yerel talimatları OKU. Varsayım yerine kod tabanındaki gerçeği esas al.
13
+ 2. **En küçük doğru değişiklik.** Sorunu çözen en dar diff'i yaz. İstenmeyen refactor, yeniden
14
+ adlandırma, bağımlılık veya yeni soyutlama ekleme. Var olanı yeniden yazmak yerine düzenle.
15
+ 3. **Çevreye uy.** Komşu kodun stilini, adlandırmasını, hata yönetimini ve mimari desenini birebir
16
+ taklit et. Kendi tercihini projeye dayatma; kod sanki hep oradaymış gibi görünsün.
17
+ 4. **Tamamla, kısma.** `TODO`, yer tutucu, boş gövde, "buraya X gelecek" bırakma. Uçları bağla:
18
+ importlar, tipler, hata yolları, kenar durumlar. Yarım iş, başarısız iştir.
19
+ 5. **Kanıtla.** Değişikliği en dar ölçekte gerçekten çalıştır (hedefli test/derleme/lint). Testi sen
20
+ çalıştırmadıysan "geçti" deme. Test yoksa ve riskliyse davranışı doğrulayan minimal bir kontrol ekle.
21
+ 6. **Dürüst ol.** Çalıştıramadığın bir kontrolü başarı gibi sunma; sahte çıktı, gizlenmiş hata veya
22
+ gevşetilmiş test üretme. Bilmediğini bildiğin gibi raporla.
23
+
24
+ ## Güvenlik ve kapsam
17
25
 
18
26
  - Kullanıcının mevcut veya ilgisiz değişikliklerini koru; geri alma, silme ya da üzerine yazma.
19
- - Yetki verilen kapsamın dışına çıkma. Gereksiz refactor, bağımlılık veya yeni soyutlama ekleme.
20
- - Hataları gizlemek için testleri gevşetme, kontrolleri kapatma veya sahte başarı üretme.
27
+ - Yetki verilen kapsamın dışına çıkma. Planda olmayan riskli gerekiyorsa yapma, BLOCKED bildir.
21
28
  - Gizli bilgi yazdırma; yıkıcı işlem, dış sisteme yazma, deploy, push veya kullanıcı adına iletişim
22
- gerekiyorsa açık yetki olmadan yapma.
23
- - Doğrulama çalıştırılamadıysa bunu başarı gibi sunma; nedenini ve kalan riski belirt.
29
+ için açık yetki olmadan hareket etme.
30
+ - Sana beceri rehberi verildiyse önce özetini uygula; yetmezse rehber dosyasını OKU ve prosedürüne uy.
24
31
  - Küçük ve güvenli bir sapma hedefi daha doğru karşılıyorsa gerekçesini raporla. Kapsamı veya riski
25
- anlamlı biçimde değiştiren sapmada dur.
32
+ anlamlı biçimde değiştiren sapmada dur ve BLOCKED bildir.
26
33
 
27
34
  ## Çıktı sözleşmesi
28
35
 
29
- Operatöre kısa, taranabilir ve kanıta dayalı bir teslimat raporu ver:
36
+ Operatöre kısa, taranabilir ve kanıta dayalı bir teslimat raporu ver. İç düşünce dökümü veya ham log
37
+ yığma; operatörün karar vereceği somut bilgiyi ver:
30
38
 
31
39
  ```text
32
40
  DURUM: COMPLETED
@@ -30,8 +30,10 @@ Motor her çağrıda çalışma evresini, agent kataloğunu, çalışma modunu v
30
30
  ## Beceriler
31
31
 
32
32
  - Motorun görev için tarayıp verdiği kısa listeden gerçekten ilgili en fazla birkaç beceriyi delegasyonun
33
- `skills` alanına ekle. Yalnızca listedeki adları kullan; uygun beceri yoksa alanı boş bırak. Uzman ayrıntılı
34
- rehberi ihtiyaç halinde dosyadan okuyacaktır.
33
+ `skills` alanına ekle. Kısa listede işe uyan bir beceri VARSA, ilgili `implement`, `plan` ve `review`
34
+ delegasyonlarında bunu iliştirmek beklenendir — beceriler kullanıcının koyduğu proje standardıdır ve
35
+ teslimat kalitesini yükseltir; ilgili beceriyi boşuna atlama. Yalnızca listedeki adları kullan; gerçekten
36
+ uygun beceri yoksa alanı boş bırak. Uzman ayrıntılı rehberi ihtiyaç halinde dosyadan okuyacaktır.
35
37
  - "Beceriler (OTORİTER kaynak)" bölümü sistemin beceri envanteridir ve tek doğru kaynaktır. Beceri sayısı, adı
36
38
  veya varlığı sorulduğunda daima bu bölümü esas al; çalıştığın CLI'nin kendi dahili becerilerini bu sistemin
37
39
  becerileri gibi sayma veya karıştırma. Bu bölüm hiç yoksa sistemde etkin beceri yok demektir.
@@ -7,6 +7,11 @@ doğrulanabilir bir plana dönüştür. Bu rolde çözümü uygulamaz, dosya de
7
7
  işlem çalıştırmazsın. Projeyi anlamak için yalnızca salt okunur inceleme ve keşif araçlarını
8
8
  kullanabilirsin.
9
9
 
10
+ İyi bir plan, uygulayıcının "ne yapacağım?" diye durup düşünmesini ortadan kaldırır. Bunu
11
+ başarmak için tahminle değil **gerçek kod tabanıyla** çalış: dosya adı, fonksiyon, komut veya API
12
+ yazmadan önce projede gerçekten var olduklarını inceleyerek doğrula. Uydurma bir yol, planın
13
+ tamamını çürütür.
14
+
10
15
  ## Sorumluluklar
11
16
 
12
17
  - Ana hedefi, kapsamı, kapsam dışını ve ölçülebilir kabul kriterlerini ayır.
@@ -6,6 +6,15 @@ Teslimatı kullanıcı hedefi, kabul kriterleri ve mevcut proje davranışına k
6
6
  doğrula. Uygulayıcının raporunu kanıt kabul etme; dosyaları, farkları ve test sonuçlarını kendin
7
7
  incele. Bu rolde çözümü değiştirmezsin.
8
8
 
9
+ ## İnceleme ilkeleri
10
+
11
+ - **Rapora değil koda güven.** "Yaptım/geçti" ifadesini doğrulama sayma; değişen dosyayı ve etkilediği
12
+ çağrı yerlerini kendin oku.
13
+ - **Çalıştırabildiğini çalıştır.** Hedefli testleri veya salt okunur kontrolleri gerçekten yürüt ve
14
+ gözlemlediğin sonucu kanıt yaz. Çalıştıramadığını `NOT RUN` işaretle, uydurma.
15
+ - **Kanıtsız reddetme, göz boyamayla onaylama.** Her bulguya dosya/konum ve somut kanıt bağla; bulgu
16
+ yoksa yapay sorun icat etme, gerçek riski de örtme.
17
+
9
18
  ## İnceleme sırası
10
19
 
11
20
  1. Ana hedefi ve delegasyon kapsamını çıkar.
@@ -41,7 +41,10 @@ const DEFINITIONS = {
41
41
  // OpenCode saglayici/model kombinasyonuna gore uzun sure sessiz kalabilir. Varsayilan
42
42
  // agent timeout'u, basarili bir calismayi SIGTERM ile yarida kesmeyecek kadar genis tut.
43
43
  timeoutSeconds: 1800,
44
- silenceTimeoutSeconds: 180,
44
+ // OpenCode adim baslattiktan sonra model/provider yanitini beklerken uzun sure hic cikti
45
+ // akitmayabilir (ozellikle yavas/rate-limitli modeller). 180sn cok agresifti ve calisan bir
46
+ // kosmayi yariyordu; sessizlik payini genis tut. Gercek takilma yine timeoutSeconds ile yakalanir.
47
+ silenceTimeoutSeconds: 300,
45
48
  // Bazi eski OpenCode surumleri --auto bayragini tanimaz. Otonom izinler
46
49
  // engine tarafinda OPENCODE_CONFIG_CONTENT ile surumden bagimsiz aktarilir.
47
50
  defaultArgs: ["run", "--format", "json", "Attached file contains the full task. Follow it exactly, make the changes, and report what you did.", "--file", "{PROMPT_FILE}"],
@@ -59,6 +62,34 @@ const DEFINITIONS = {
59
62
  const RESOLVED = new Map();
60
63
  let OPEN_CODE_MODELS = [];
61
64
 
65
+ // En fazla bir CLI'yi (or. codex) ayni anda hem canli saglik/model probe'u hem de motorun
66
+ // operatör kosumu calistirirsa, codex gibi bazi CLI'lar ayni is dizininde ikinci exec oturumunu
67
+ // SIGTERM ile keser. Probe'lar "en iyi caba"dir; motor onceliklidir. Aktif probe cocuklarini
68
+ // izleyip motor bir goreve baslarken hepsini sonlandiriyoruz.
69
+ const ACTIVE_PROBES = new Set();
70
+ function registerProbe(child) {
71
+ if (!child) return () => {};
72
+ ACTIVE_PROBES.add(child);
73
+ return () => ACTIVE_PROBES.delete(child);
74
+ }
75
+ // Uctan uca proses agacini oldur (codex kabuk altinda alt proses baslatabilir).
76
+ function terminateProbeTree(child) {
77
+ if (!child) return;
78
+ try {
79
+ if (isWin && child.pid) spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], { windowsHide: true, stdio: "ignore" });
80
+ else child.kill("SIGKILL");
81
+ } catch {}
82
+ }
83
+ // Motor bir goreve baslarken cagirir: uctaki tum canli probe'lari sonlandirir ki operatörun
84
+ // CLI'si (codex vb.) temiz bir alanda calissin. Probe'lar sonuc olarak "timeout/failed" doner;
85
+ // bu zararsizdir, motor bosaldiginda yeniden denenir.
86
+ function abortActiveProbes() {
87
+ const count = ACTIVE_PROBES.size;
88
+ for (const child of ACTIVE_PROBES) terminateProbeTree(child);
89
+ ACTIVE_PROBES.clear();
90
+ return count;
91
+ }
92
+
62
93
  function selectOpenCodeModel(models) {
63
94
  const list = Array.isArray(models) ? models.map(String).filter(Boolean) : [];
64
95
  const priorities = [
@@ -366,10 +397,12 @@ function testInstalledCli(id, options = {}) {
366
397
  prepared.cleanup();
367
398
  return resolve({ id, installed: true, version: found.version, health: classifyHealthFailure(error.message) });
368
399
  }
400
+ const unregister = registerProbe(child);
369
401
  const timer = setTimeout(() => {
370
402
  if (settled) return;
371
403
  settled = true;
372
- try { child.kill(); } catch {}
404
+ unregister();
405
+ terminateProbeTree(child);
373
406
  prepared.cleanup();
374
407
  resolve({ id, installed: true, version: found.version, health: { status: "timeout", label: "Yanıt zaman aşımına uğradı", detail: `Sağlık testi ${Math.round(timeoutMs / 1000)} saniyede tamamlanmadı.` } });
375
408
  }, timeoutMs);
@@ -381,6 +414,7 @@ function testInstalledCli(id, options = {}) {
381
414
  if (settled) return;
382
415
  settled = true;
383
416
  clearTimeout(timer);
417
+ unregister();
384
418
  prepared.cleanup();
385
419
  const failure = classifyHealthFailure(error.message);
386
420
  resolve({ id, installed: true, version: found.version, health: failure });
@@ -389,6 +423,7 @@ function testInstalledCli(id, options = {}) {
389
423
  if (settled) return;
390
424
  settled = true;
391
425
  clearTimeout(timer);
426
+ unregister();
392
427
  prepared.cleanup();
393
428
  const raw = `${stdout}\n${stderr}`.trim();
394
429
  if (code === 0 && /HEALTH_OK/i.test(raw)) resolve({ id, installed: true, version: found.version, health: { status: "ready", label: "Hazır", detail: "Gerçek sağlık testi başarılı." } });
@@ -409,17 +444,19 @@ function listCodexModels(options = {}) {
409
444
  const timeoutMs = Math.max(5000, Number(options.timeoutMs || 20000));
410
445
  return new Promise((resolve, reject) => {
411
446
  let buffer = "", settled = false, modelRequestSent = false;
412
- let child;
447
+ let child, unregister = () => {};
413
448
  const finish = (error, value) => {
414
449
  if (settled) return;
415
450
  settled = true;
416
451
  clearTimeout(timer);
417
- try { child?.kill(); } catch {}
452
+ unregister();
453
+ terminateProbeTree(child);
418
454
  if (error) reject(error); else resolve(value);
419
455
  };
420
456
  const timer = setTimeout(() => finish(new Error(`Codex model listesi ${Math.round(timeoutMs / 1000)} saniyede alınamadı.`)), timeoutMs);
421
457
  try { child = spawn(command.file, command.args, { shell: command.shell, windowsHide: true, windowsVerbatimArguments: !!command.verbatim }); }
422
458
  catch (error) { finish(error); return; }
459
+ unregister = registerProbe(child);
423
460
  child.stdout?.setEncoding("utf8");
424
461
  child.stderr?.setEncoding("utf8");
425
462
  child.stdout?.on("data", (chunk) => {
@@ -576,4 +613,4 @@ function ensureValidOperator(cfg, discovered) {
576
613
  return changed;
577
614
  }
578
615
 
579
- module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, healthCheckAll, listCodexModels, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator };
616
+ module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, healthCheckAll, listCodexModels, abortActiveProbes, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator };
@@ -415,12 +415,22 @@ function normalizeAssignments(value, cfg, operatorName, usedIds, taskText = "")
415
415
  // Beceriler kullanici-kapilidir: operator yalnizca cfg.skills.enabled icindekileri iliştirebilir.
416
416
  // Tanimsiz/etkin olmayan beceri sessizce dusurulur; asla gorevi olduren bir hata degildir.
417
417
  const hasExplicitSkills = Object.prototype.hasOwnProperty.call(raw, "skills");
418
+ const autoMatchOn = cfg.skills?.autoMatch !== false;
418
419
  const requestedSkills = Array.isArray(raw.skills)
419
420
  ? raw.skills
420
- : (!hasExplicitSkills && cfg.skills?.autoMatch !== false
421
+ : (!hasExplicitSkills && autoMatchOn
421
422
  ? skillRegistry.suggest(cfg, `${taskText}\n${instruction}`, kind)
422
423
  : []);
423
- const skills = skillRegistry.resolveForAssignment(requestedSkills, cfg).map((skill) => skill.name);
424
+ let skills = skillRegistry.resolveForAssignment(requestedSkills, cfg).map((skill) => skill.name);
425
+ // Operator kodlama/planlama/review isinde beceri iliştirmeyi atlarsa (or. skills:[] dönerse),
426
+ // kullanicinin etkin becerilerinden goreve GERCEKTEN uyanlari otomatik ekle. suggest() yalnizca
427
+ // lexical eslesme oldugunda skill döner; alakasiz beceri asla iliştirilmez. Boylece "operator
428
+ // becerileri kullanmiyor" durumu ortadan kalkar.
429
+ if (!skills.length && autoMatchOn && ["implement", "plan", "review"].includes(kind)) {
430
+ skills = skillRegistry.resolveForAssignment(
431
+ skillRegistry.suggest(cfg, `${taskText}\n${instruction}`, kind), cfg
432
+ ).map((skill) => skill.name);
433
+ }
424
434
  let agent = requestedAgent;
425
435
  const unavailable = new Set(cfg.runtimeUnavailableAgents || []);
426
436
  if (unavailable.has(agent) || !supportsKind(cfg.agents[agent], kind)) {
@@ -688,6 +698,7 @@ class Engine extends EventEmitter {
688
698
  const failure = classifyCliError(error);
689
699
  this.publish("result", { id: task.id, kind: "operator-chat", parentTaskId: task.parentTaskId, status: "failed", error: failure.summary }, task.id);
690
700
  }
701
+ this.notifyOutcome(task, "failed", { error: error.message });
691
702
  this.emit("queue");
692
703
  }
693
704
  } finally {
@@ -757,6 +768,10 @@ class Engine extends EventEmitter {
757
768
  this.emit("status", this.status());
758
769
  this.publish("activity", { ...base, kind: "process.started", cmd: agent.cmd, args: rawArgs, cwd });
759
770
 
771
+ // Motor onceliklidir: canli CLI saglik/model probe'lari (or. codex app-server / codex exec)
772
+ // ayni CLI'yi ayni anda calistirirsa codex ikinci exec oturumunu SIGTERM ile keser. Bu yuzden
773
+ // operatör/agent kosumuna girmeden uctaki tum probe'lari sonlandiriyoruz.
774
+ cliRegistry.abortActiveProbes();
760
775
  let child;
761
776
  try { child = spawn(file, args, { cwd, env: cliRegistry.agentEnvironment(agent), windowsHide: true, shell: command.shell, windowsVerbatimArguments: !!command.verbatim }); }
762
777
  catch (error) { return reject(error); }
@@ -846,6 +861,15 @@ class Engine extends EventEmitter {
846
861
  });
847
862
  }
848
863
 
864
+ // Katalogda dosya yazabilen (implement) kullanilabilir bir uzman var mi? Operatorun kendisi ve
865
+ // bu oturumda karantinaya alinan agentler haric. Tek executor (or. opencode) sessizlik nedeniyle
866
+ // dustugunde takim fiilen dosya uretemez hale gelir; bu kontrol o durumu erken yakalar.
867
+ hasUsableImplementAgent(cfg, operatorName) {
868
+ return Object.entries(cfg.agents || {}).some(([name, agent]) =>
869
+ name !== operatorName && agent.enabled !== false && agentUsable(agent)
870
+ && !this.unhealthyAgents.has(name) && supportsKind(agent, "implement"));
871
+ }
872
+
849
873
  agentCatalog(cfg, operatorName) {
850
874
  return Object.entries(cfg.agents)
851
875
  .filter(([name, agent]) => name !== operatorName && agent.enabled !== false && agentUsable(agent) && !this.unhealthyAgents.has(name))
@@ -1156,6 +1180,22 @@ class Engine extends EventEmitter {
1156
1180
  recoveryRounds++;
1157
1181
  this.publish("log", { level: "warn", msg: `CLI altyapi hatasi tur butcesinden dusulmedi; ${maxRecoveryRounds - recoveryRounds} ek kurtarma turu kaldi.` }, task.id);
1158
1182
  }
1183
+ // Uygulama agenti kalmadi guvenligi: Gorev acikca dosya olusturma/degistirme gerektiriyor
1184
+ // fakat katalogda 'implement' yapabilen kullanilabilir hicbir agent kalmadiysa (or. tek
1185
+ // executor opencode sessizlik nedeniyle karantinaya alindi) ve simdiye kadar tamamlanmis bir
1186
+ // uygulama isi da yoksa, HICBIR ek tur dosya uretemez. Operatoru planlayici/gap-analizi
1187
+ // dongusunde bosuna dondurup tur ve cagri butcesini yakmak yerine net, eyleme donuk bir
1188
+ // hatayla dur (kullanicinin gordugu "circle seklinde uzayip gitme" sorununu bu keser).
1189
+ if (taskRequiresDelegation(task.prompt)
1190
+ && !this.hasUsableImplementAgent(cfg, operatorCli)
1191
+ && !Object.values(state.results).some((result) => result.kind === "implement" && result.status === "completed")) {
1192
+ const dead = [...this.unhealthyAgents.keys()];
1193
+ throw new Error(
1194
+ "Bu gorev dosya olusturma/degistirme gerektiriyor fakat kullanilabilir bir uygulama (implement) agenti yok" +
1195
+ (dead.length ? ` (${dead.join(", ")} bu oturumda kullanim disi kaldi)` : "") +
1196
+ ". Ayarlar > Agent'lar'dan 'implementation' yetenekli bir agent (Codex/Claude/Gemini/OpenCode) ekleyip etkinlestirin veya opencode'un oturum/model durumunu kontrol edin, sonra gorevi tekrar calistirin."
1197
+ );
1198
+ }
1159
1199
  if (task.executionMode === "fast" && assignments.every((assignment) => state.results[assignment.id]?.status === "completed")) {
1160
1200
  const reports = assignments.map((assignment) => state.results[assignment.id].result).join("\n\n");
1161
1201
  this.publish("log", { level: "info", msg: "FAST mod: uzman teslimati basarili; ikinci operator degerlendirme cagrisi atlandi." }, task.id);
@@ -1351,9 +1391,26 @@ class Engine extends EventEmitter {
1351
1391
  store.appendMemory(`${task.id} [${status}] ${task.prompt}`, `${task.summary}\nDosyalar: ${[...changes.created, ...changes.modified, ...changes.deleted].join(", ")}`);
1352
1392
  this.publish("result", { id: task.id, prompt: task.prompt, status, dir: this._cwd, changes, summary: task.summary, delivery: task.delivery, operator: task.operatorCli, rounds: state.round }, task.id);
1353
1393
  this.publish("log", { level: "ok", msg: `GOREV tamamlandi: ${task.id}` }, task.id);
1394
+ this.notifyOutcome(task, status, { summary: task.summary, rounds: state.round, fileCount: files.length, verification: task.delivery.verification });
1354
1395
  this.emit("queue");
1355
1396
  this._textBefore = null;
1356
1397
  }
1398
+
1399
+ // Dis bildirim icin tek cikis noktasi: server.js bu event'i dinleyip (varsa) webhook'a POST atar.
1400
+ // Motoru HTTP'den bagimsiz tutar; bildirim basarisiz olsa bile gorev akisini etkilemez.
1401
+ notifyOutcome(task, status, extra = {}) {
1402
+ try {
1403
+ this.emit("notify", {
1404
+ status,
1405
+ id: task.id,
1406
+ prompt: task.prompt,
1407
+ operator: task.operatorCli || null,
1408
+ dir: this._cwd || task.targetDir || null,
1409
+ at: new Date().toISOString(),
1410
+ ...extra,
1411
+ });
1412
+ } catch {}
1413
+ }
1357
1414
  }
1358
1415
 
1359
1416
  module.exports = new Engine();
@@ -73,17 +73,55 @@ engine.on("activity", (d) => broadcast("activity", d));
73
73
  engine.on("message", (d) => broadcast("message", d));
74
74
  engine.on("filechange", (d) => broadcast("filechange", d));
75
75
  engine.on("queue", () => broadcast("queue", snapshot()));
76
+ engine.on("notify", (d) => { sendNotification(d); broadcast("notify", d); });
77
+
78
+ // Görev bitince/başarısız olunca kullanıcının yapılandırdığı webhook'a POST atar (Slack incoming
79
+ // webhook formatıyla uyumlu: {text}). Tamamen opsiyonel ve ateşle-unut; Node'un yerleşik fetch'ini
80
+ // kullanır (yeni bağımlılık yok). Hata görevi asla etkilemez. Yapılandırılmamışsa sessizce atlanır.
81
+ function sendNotification(outcome) {
82
+ try {
83
+ const cfg = store.loadConfig();
84
+ const n = cfg.notify || {};
85
+ const url = String(n.webhookUrl || "").trim();
86
+ if (!url || !/^https?:\/\//i.test(url)) return;
87
+ if (outcome.status === "failed" ? n.onFailed === false : n.onComplete === false) return;
88
+ const icon = outcome.status === "failed" ? "❌" : "✅";
89
+ const head = outcome.status === "failed" ? "Görev başarısız" : "Görev tamamlandı";
90
+ const detail = outcome.status === "failed"
91
+ ? (outcome.error ? `\nHata: ${String(outcome.error).slice(0, 300)}` : "")
92
+ : `${outcome.operator ? ` · ${outcome.operator}` : ""}${outcome.rounds ? ` · ${outcome.rounds} tur` : ""}${Number.isFinite(outcome.fileCount) ? ` · ${outcome.fileCount} dosya` : ""}${outcome.verification ? ` · ✓ ${String(outcome.verification).slice(0, 120)}` : ""}`;
93
+ const text = `${icon} CrewCtl — ${head}: ${String(outcome.prompt || outcome.id).slice(0, 200)}${detail}`;
94
+ const controller = new AbortController();
95
+ const timer = setTimeout(() => controller.abort(), 8000);
96
+ fetch(url, {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify({ text, crewctl: outcome }),
100
+ signal: controller.signal,
101
+ }).catch(() => {}).finally(() => clearTimeout(timer));
102
+ } catch {}
103
+ }
76
104
 
77
105
  function broadcastSchedules() {
78
106
  broadcast("schedules", store.loadConfig().schedules || []);
79
107
  }
80
108
 
109
+ // Pano/kuyruk listesi yalnizca ozet alanlari gosterir; teamState (tum agent ciktilari/mesajlari),
110
+ // conversation ve changes gibi agir ic alanlar liste yukunu gereksiz sisirir ve her SSE queue
111
+ // yayininda serilestirilir. Detay gorunumleri tam gorevi ayri /api/tasks/:id ile ceker; burada
112
+ // bu agir alanlari cikartip yayin boyutunu kuculturuz. (planPreview/delivery/summary korunur.)
113
+ function liteTask(task) {
114
+ const { teamState, conversation, changes, ...rest } = task;
115
+ return rest;
116
+ }
81
117
  function snapshot() {
82
118
  return {
83
- pending: store.listTasks("pending"),
84
- approval: store.listTasks("approval"),
85
- done: store.listTasks("done").filter((task) => task.kind !== "operator-chat").slice(-30).reverse(),
86
- failed: store.listTasks("failed").slice(-30).reverse(),
119
+ pending: store.listTasks("pending").map(liteTask),
120
+ approval: store.listTasks("approval").map(liteTask),
121
+ // Operator-chat gorevleri de done'a yazildigi icin filtreden sonra 30 gercek gorev kalsin
122
+ // diye biraz genis bir pencere (80) okunur; yine de sabit ve gecmisten bagimsizdir.
123
+ done: store.listRecentTasks("done", 80).filter((task) => task.kind !== "operator-chat").slice(-30).reverse().map(liteTask),
124
+ failed: store.listRecentTasks("failed", 30).reverse().map(liteTask),
87
125
  };
88
126
  }
89
127
 
@@ -122,8 +160,15 @@ function applyHealthToConfig(results) {
122
160
  }
123
161
 
124
162
  let healthRunning = false;
163
+ // Motor bir gorev calistirirken (or. operatör codex) ayni CLI'ye karsi canli probe acmak
164
+ // codex'in ikinci exec/app-server oturumunu SIGTERM ile kesmesine yol acar. Probe'lar en iyi
165
+ // cabadir; motor mesgulken canli probe atlanip son bilinen sonuc dondurulur.
166
+ function engineBusy() {
167
+ try { return Boolean(engine.status().current); } catch { return false; }
168
+ }
125
169
  async function getCodexModels(force = false) {
126
170
  if (!force && codexModelCache.models.length && Date.now() - codexModelCache.checkedAt < CODEX_MODEL_CACHE_TTL_MS) return codexModelCache.models;
171
+ if (engineBusy()) return codexModelCache.models;
127
172
  const models = await cliRegistry.listCodexModels({ timeoutMs: 20000 });
128
173
  codexModelCache = { checkedAt: Date.now(), models };
129
174
  const cfg = store.loadConfig();
@@ -139,6 +184,10 @@ async function refreshCliHealth(force = false) {
139
184
  broadcast("cli-health", cliStatus);
140
185
  return cliStatus;
141
186
  }
187
+ if (engineBusy()) {
188
+ broadcast("cli-health", cliStatus);
189
+ return cliStatus;
190
+ }
142
191
  healthRunning = true;
143
192
  cliStatus = cliStatus.map((cli) => ({ ...cli, health: { status: "testing", label: "Test ediliyor", detail: "Gerçek CLI sağlık testi çalışıyor." } }));
144
193
  broadcast("cli-health", cliStatus);
@@ -170,7 +219,7 @@ function readBody(req) {
170
219
  });
171
220
  });
172
221
  }
173
- const MIME = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css" };
222
+ const MIME = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".png": "image/png", ".ico": "image/x-icon", ".svg": "image/svg+xml", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp", ".webmanifest": "application/manifest+json", ".json": "application/json" };
174
223
  function serveStatic(res, file) {
175
224
  const p = path.resolve(WEB, file);
176
225
  if ((p !== WEB && !p.startsWith(WEB + path.sep)) || !fs.existsSync(p)) {
@@ -563,6 +612,11 @@ const server = http.createServer(async (req, res) => {
563
612
  if (!name.trim() || !agent.cmd || typeof agent.cmd !== "string") return send(res, 400, { error: `gecersiz agent: ${name}` });
564
613
  if (!Array.isArray(agent.args)) return send(res, 400, { error: `${name}.args dizi olmali` });
565
614
  }
615
+ if (cfg.notify !== undefined) {
616
+ if (typeof cfg.notify !== "object" || cfg.notify === null) return send(res, 400, { error: "notify nesne olmali" });
617
+ const url = String(cfg.notify.webhookUrl || "").trim();
618
+ if (url && !/^https?:\/\//i.test(url)) return send(res, 400, { error: "notify.webhookUrl http(s) ile baslamali veya bos olmali" });
619
+ }
566
620
  store.saveConfig(cfg);
567
621
  broadcast("status", engine.status());
568
622
  return send(res, 200, { ok: true });
@@ -642,7 +696,13 @@ function startupBanner() {
642
696
  console.log(` ${DIM}Ayrıntı için: npm run doctor${X}`);
643
697
  }
644
698
  console.log(`\n ${B}▶ Panel: ${C}${url}${X}`);
645
- console.log(` ${DIM}Panelde 'Başlat'a basıp bir görev gönderin. (tarayıcı otomatik açılmazsa yukarıdaki adresi açın)${X}\n`);
699
+ console.log(` ${DIM}Panelde 'Başlat'a basıp bir görev gönderin. (tarayıcı otomatik açılmazsa yukarıdaki adresi açın)${X}`);
700
+ // npx gecici onbellekten calisiyorsa kalici 'crewctl' komutu kurulmamistir; kullaniciya nasil
701
+ // kalici hale getirecegini hatirlat (aksi halde kapatinca 'crewctl' bulunamaz).
702
+ if (/[\\/]_npx[\\/]/.test(__dirname)) {
703
+ console.log(` ${DIM}Kalıcı 'crewctl' komutu için: ${Y}npm i -g @omerrgocmen/crewctl${X}`);
704
+ }
705
+ console.log("");
646
706
  openBrowser(url);
647
707
  }
648
708
 
@@ -125,6 +125,7 @@ const FALLBACK_CONFIG = {
125
125
  discoveryIgnoredAdapters: [],
126
126
  liveDiff: true, liveDiffIntervalMs: 2500,
127
127
  versioning: true, versioningRetention: 20,
128
+ notify: { webhookUrl: "", onComplete: true, onFailed: true },
128
129
  operator: { roleFile: "roles/operator.md", maxRounds: 6, maxDelegationsPerRound: 8, maxInfrastructureRecoveryRounds: 2, protocolRetries: 1 },
129
130
  cliSettings: {
130
131
  codex: { model: "", reasoningEffort: "medium", serviceTier: "fast" },
@@ -159,12 +160,31 @@ function normalizeConfig(cfg) {
159
160
  }
160
161
  // config.json kisiye ozeldir (gitignore). Yoksa config.default.json sablonundan uretilir;
161
162
  // boylece kullanici klonlayip `npm start` dedigi anda calisir ve kurulu CLI'lar keşifle eklenir.
163
+ // Paketle gelen beceri dosyalarinin slug'lari (dosya adi = frontmatter name, dogrulanir).
164
+ // Ilk config uretiminde beceriler VARSAYILAN OLARAK ETKIN gelsin diye kullanilir; boylece
165
+ // operator, kullanicinin becerilerini kodlama/planlama/review delegasyonlarinda kullanabilir.
166
+ function shippedSkillSlugs() {
167
+ try {
168
+ return fs.readdirSync(path.join(ASSETS, "skills"))
169
+ .filter((f) => f.toLowerCase().endsWith(".md"))
170
+ .map((f) => f.replace(/\.md$/i, ""));
171
+ } catch { return []; }
172
+ }
162
173
  function loadConfig() {
163
174
  const file = path.join(ROOT, "config.json");
164
175
  if (!fs.existsSync(file)) {
165
176
  const template = path.join(ASSETS, "config.default.json");
166
177
  const seed = fs.existsSync(template) ? fs.readFileSync(template, "utf8") : JSON.stringify(FALLBACK_CONFIG, null, 2);
167
- atomicWrite(file, seed);
178
+ let seedObj;
179
+ try { seedObj = JSON.parse(seed); } catch { seedObj = { ...FALLBACK_CONFIG }; }
180
+ // Ilk kurulumda tum paket becerilerini etkinlestir (kullanici Ayarlar'dan kapatabilir).
181
+ // Sonraki yuklemelerde dosya var oldugundan bu blok CALISMAZ; kullanicinin secimi korunur.
182
+ seedObj.skills = seedObj.skills || {};
183
+ if (!Array.isArray(seedObj.skills.enabled) || !seedObj.skills.enabled.length) {
184
+ const slugs = shippedSkillSlugs();
185
+ if (slugs.length) seedObj.skills.enabled = slugs;
186
+ }
187
+ atomicWrite(file, JSON.stringify(seedObj, null, 2));
168
188
  }
169
189
  return normalizeConfig(JSON.parse(fs.readFileSync(file, "utf8")));
170
190
  }
@@ -209,6 +229,20 @@ function listTasks(state) {
209
229
  .filter(Boolean)
210
230
  .sort((a, b) => (a.id < b.id ? -1 : 1));
211
231
  }
232
+ // Yalnizca en yeni `limit` gorevi okur. Gorev id'si zaman damgasiyla basladigi icin dosya adi
233
+ // siralamasi = kronolojik siralama; boylece done/failed gecmisi buyudukce (yuzlerce/binlerce
234
+ // dosya) her pano yenilemesinde TUM dosyalari okuyup ayristirma maliyeti sabit N'e iner.
235
+ function listRecentTasks(state, limit) {
236
+ const dir = path.join(Q, state);
237
+ if (!fs.existsSync(dir)) return [];
238
+ let names = fs.readdirSync(dir).filter((f) => f.endsWith(".json")).sort();
239
+ if (limit && names.length > limit) names = names.slice(-limit);
240
+ return names
241
+ .map((f) => {
242
+ try { return JSON.parse(fs.readFileSync(path.join(dir, f), "utf8")); } catch { return null; }
243
+ })
244
+ .filter(Boolean);
245
+ }
212
246
  function nextPending() {
213
247
  return listTasks("pending")[0] || null;
214
248
  }
@@ -344,6 +378,7 @@ module.exports = {
344
378
  writeRole,
345
379
  deleteRole,
346
380
  listTasks,
381
+ listRecentTasks,
347
382
  nextPending,
348
383
  addTask,
349
384
  addChatTask,
@@ -4,6 +4,11 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <title>Pano · CrewCtl</title>
7
+ <link rel="icon" href="/favicon.ico" sizes="any">
8
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
9
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
10
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
11
+ <link rel="manifest" href="/site.webmanifest">
7
12
  <style>
8
13
  *{box-sizing:border-box}
9
14
  :root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
@@ -4,6 +4,11 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <title>Canlı Kodlama · CrewCtl</title>
7
+ <link rel="icon" href="/favicon.ico" sizes="any">
8
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
9
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
10
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
11
+ <link rel="manifest" href="/site.webmanifest">
7
12
  <style>
8
13
  *{box-sizing:border-box}
9
14
  :root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
Binary file
@@ -4,6 +4,11 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <title>Ekip Akışı · CrewCtl</title>
7
+ <link rel="icon" href="/favicon.ico" sizes="any">
8
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
9
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
10
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
11
+ <link rel="manifest" href="/site.webmanifest">
7
12
  <style>
8
13
  *{box-sizing:border-box}
9
14
  :root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
@@ -4,6 +4,11 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <title>CrewCtl</title>
7
+ <link rel="icon" href="/favicon.ico" sizes="any">
8
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
9
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
10
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
11
+ <link rel="manifest" href="/site.webmanifest">
7
12
  <style>
8
13
  *{box-sizing:border-box}
9
14
  :root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
@@ -13,7 +18,7 @@
13
18
  [data-theme=light] .team-node.operator .node-info{border-color:color-mix(in srgb,var(--nacc) 38%,var(--line));background:linear-gradient(180deg,color-mix(in srgb,var(--nacc) 6%,var(--panel2)),var(--nodebg))}
14
19
  [data-theme=light] .friendly-error{background:#fdecee;color:#8f1f28;border-color:#d6404b55}[data-theme=light] .friendly-error b{color:#c02e39}[data-theme=light] .friendly-error small{color:#9a5a60}
15
20
  [data-theme=light] .friendly-error.success{background:#e9f8f0;color:#0d5c3c;border-color:#0f9d6355}[data-theme=light] .friendly-error.success b{color:var(--ok)}[data-theme=light] .friendly-error.success small{color:#4f7d68}
16
- [data-theme=light] .msg.failure,[data-theme=light] .msg.blocked{background:#fdecef}
21
+ [data-theme=light] .fi-failure,[data-theme=light] .fi-blocked{background:#fdecef}
17
22
  body{margin:0;background:var(--bg);color:var(--tx);font:14px/1.5 Inter,Segoe UI,system-ui,sans-serif;transition:background .3s,color .3s}
18
23
  button,input,textarea,select{font:inherit}
19
24
  button{cursor:pointer;color:var(--tx);background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:7px 11px;transition:border-color .18s,background .18s,transform .1s,box-shadow .18s}
@@ -85,16 +90,25 @@ details summary{cursor:pointer;color:var(--dim);font-size:12px} pre{white-space:
85
90
  .friendly-error b{display:block;color:#ff8f97;margin-bottom:3px}.friendly-error small{color:#d5aeb2}
86
91
  .friendly-error.success{border-color:#42d39255;background:#10261e;color:#d7ffec}.friendly-error.success b{color:var(--ok)}.friendly-error.success small{color:#a7cfbd}
87
92
  .plan-pills{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px}.plan-pills span{font-size:10px;border:1px solid #42d39255;border-radius:999px;padding:2px 7px}
88
- /* Birlesik akis (mesaj + olay) */
89
- .feed{max-height:340px;overflow:auto;display:flex;flex-direction:column;gap:6px}
90
- .msg{border-left:3px solid var(--purple);background:var(--panel2);border-radius:0 8px 8px 0;padding:7px 9px}
91
- .msg.result{border-color:var(--ok)}.msg.failure,.msg.blocked{border-color:var(--err);background:#27161d}
92
- .msg .route{font-size:11px;color:var(--purple);font-weight:700}
93
- .msg.result .route{color:var(--ok)}.msg.failure .route,.msg.blocked .route{color:var(--err)}
94
- .msg .body{font-size:12px;white-space:pre-wrap;max-height:90px;overflow:auto}
95
- .log{font:11.5px ui-monospace;color:var(--dim);padding:2px 2px}
96
- .log.error{color:var(--err)}.log.warn{color:var(--warn)}.log.ok{color:var(--ok)}.log.task{color:var(--acc)}
97
- .log time,.msg time{color:var(--dim);font:10px ui-monospace;margin-right:6px}
93
+ /* Birlesik akis — zaman cizelgesi (olay tipine gore renkli, uzun icerik katlanir) */
94
+ .feed{max-height:360px;overflow:auto;display:flex;flex-direction:column;gap:2px;padding-left:2px}
95
+ .feed-item{position:relative;display:flex;gap:9px;padding:6px 8px 6px 4px;border-radius:9px;transition:background .14s}
96
+ .feed-item:hover{background:color-mix(in srgb,var(--panel2) 55%,transparent)}
97
+ .feed-item:not(:last-child)::before{content:'';position:absolute;left:13px;top:25px;bottom:-2px;width:1px;background:color-mix(in srgb,var(--line) 65%,transparent)}
98
+ .fi-dot{position:relative;z-index:1;flex:none;margin-top:2px;width:19px;height:19px;border-radius:50%;display:grid;place-items:center;font:700 10px/1 ui-monospace,Consolas,monospace;color:var(--evc,var(--dim));border:1px solid color-mix(in srgb,var(--evc,var(--line)) 50%,var(--line));background:color-mix(in srgb,var(--evc,var(--dim)) 13%,var(--panel))}
99
+ .fi-main{min-width:0;flex:1}
100
+ .fi-head{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:11.5px;line-height:1.5}
101
+ .fi-from{font-weight:700}.fi-arrow{color:var(--dim);opacity:.55}.fi-to{font-weight:600;color:var(--tx)}
102
+ .fi-tag{font:10px ui-monospace,Consolas,monospace;color:var(--dim);border:1px solid var(--line);border-radius:5px;padding:0 5px}
103
+ .fi-head time{margin-left:auto;color:var(--dim);font:10px ui-monospace,Consolas,monospace;flex:none}
104
+ .fi-msg{color:var(--tx)}
105
+ .fi-body{margin-top:3px;font-size:12px;line-height:1.55;color:var(--dim);white-space:pre-wrap;word-break:break-word}
106
+ .fi-body.clamp{max-height:74px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 58%,transparent);mask-image:linear-gradient(180deg,#000 58%,transparent)}
107
+ .fi-more{margin-top:3px;font-size:11px;color:var(--acc);background:none;border:0;padding:1px 0}.fi-more:hover{text-decoration:underline}
108
+ .fi-delegation{--evc:var(--purple)}.fi-result{--evc:var(--ok)}.fi-failure,.fi-blocked{--evc:var(--err)}
109
+ .fi-failure,.fi-blocked{background:color-mix(in srgb,var(--err) 7%,transparent)}
110
+ .fi-log{--evc:var(--dim)}.fi-log.task{--evc:var(--acc)}.fi-log.ok{--evc:var(--ok)}.fi-log.error{--evc:var(--err)}.fi-log.warn{--evc:var(--warn)}.fi-log.stage{--evc:var(--purple)}.fi-log.info{--evc:var(--acc)}
111
+ .fi-log.error .fi-msg,.fi-log.warn .fi-msg{color:var(--evc)}.fi-log.ok .fi-msg,.fi-log.task .fi-msg{font-weight:600}
98
112
  /* Teslimat */
99
113
  .delivery{margin-top:8px;border-top:1px solid var(--line);padding-top:7px}
100
114
  .file-change{display:flex;gap:6px;align-items:center;font:11px ui-monospace;margin:3px 0}
@@ -380,7 +394,7 @@ function setMissionStatus(state,text){const el=$('#missionStatus'),tx=$('#missio
380
394
  function missionCompleted(info){setMissionStatus('completed','Tamamlandı');const i=$('#mbIco'),t=$('#mbTitle'),s=$('#mbSub');if(i)i.textContent='✓';if(t)t.textContent='Görev başarıyla tamamlandı';if(s)s.textContent=info||''}
381
395
  function missionFailed(info){setMissionStatus('failed','Başarısız');const i=$('#mbIco'),t=$('#mbTitle'),s=$('#mbSub');if(i)i.textContent='✕';if(t)t.textContent='Görev başarısız oldu';if(s)s.textContent=info||''}
382
396
  function showToast(kind,title,sub){let box=$('#toasts');if(!box){box=document.createElement('div');box.id='toasts';document.body.appendChild(box)}const t=document.createElement('div');t.className='toast '+(kind||'');t.innerHTML=`<div class="t-ico">${kind==='err'?'✕':'✓'}</div><div><div class="t-title">${esc(title)}</div>${sub?`<div class="t-sub">${esc(sub)}</div>`:''}</div>`;box.appendChild(t);setTimeout(()=>{t.classList.add('hide');setTimeout(()=>t.remove(),300)},5200)}
383
- async function addTask(){const prompt=$('#prompt').value.trim(),operatorCli=$('#taskOperator').value,targetDir=$('#targetDir').value.trim(),executionMode=$('#executionMode').value;if(!prompt)return;try{await api('/api/tasks','POST',{prompt,operatorCli,executionMode,targetDir:targetDir||undefined});$('#prompt').value='';showToast('ok','Görev kuyruğa eklendi','Operatör sıraya alındığında çalışmaya başlayacak.')}catch(e){showToast('err','Görev eklenemedi',e.message)}}
397
+ async function addTask(){const prompt=$('#prompt').value.trim(),operatorCli=$('#taskOperator').value,targetDir=$('#targetDir').value.trim(),executionMode=$('#executionMode').value;if(!prompt)return;ensureNotifyPermission();try{await api('/api/tasks','POST',{prompt,operatorCli,executionMode,targetDir:targetDir||undefined});$('#prompt').value='';showToast('ok','Görev kuyruğa eklendi','Operatör sıraya alındığında çalışmaya başlayacak.')}catch(e){showToast('err','Görev eklenemedi',e.message)}}
384
398
 
385
399
  /* ---- Kuyruk ---- */
386
400
  function deliveryHtml(t){const d=t.delivery;if(!d)return '';const labels={created:'eklendi',modified:'değişti',deleted:'silindi'};return `<div class="delivery"><div class="muted">📁 ${esc(d.location||'')}</div><div class="muted">⚡ ${(d.mode||'').toUpperCase()} · ${d.rounds||0} tur · ${(d.agents||[]).join(', ')}</div>${(d.files||[]).map(f=>`<div class="file-change"><span class="${esc(f.action)}">${esc(labels[f.action]||f.action)}</span><code>${esc(f.path)}</code></div>`).join('')||'<div class="muted">Dosya değişikliği yok.</div>'}${d.verification?`<div class="muted" style="margin-top:5px">✓ ${esc(d.verification)}</div>`:''}${(d.warnings||[]).map(w=>`<div style="margin-top:5px;color:var(--warn)">⚠ ${esc(w)}</div>`).join('')}</div>`}
@@ -425,8 +439,9 @@ function activity(e){const c=ensureCall(e);const role=String(e.stage||'').starts
425
439
 
426
440
  /* ---- Birlesik akis: mesajlar + sistem olaylari ---- */
427
441
  function feedReady(){const feed=$('#feed');if(feed.querySelector('.empty'))feed.innerHTML='';return feed}
428
- function message(e){missionMessages++;updateMissionStats();const feed=feedReady();ensureTeamNode(e.from,e.from===STATE.status.defaultOperator?'operator':'agent');ensureTeamNode(e.to,e.to===STATE.status.defaultOperator?'operator':'agent');document.querySelectorAll('.team-link').forEach(x=>{x.classList.add('active');setTimeout(()=>x.classList.remove('active'),1500)});const mt=e.messageType||'',plain=mt!=='failure'&&mt!=='blocked',col=plain?agentColor(e.from):'';const div=document.createElement('div');div.className='msg '+mt;if(plain)div.style.borderLeftColor=col;div.innerHTML=`<div class="route"${plain?` style="color:${col}"`:''}><time>${time(e.at)}</time>${esc(e.from)} → ${esc(e.to)} · ${esc(e.assignmentId||'')}</div><div class="body">${esc(e.body)}</div>`;feed.prepend(div)}
429
- function logEvent(e){if(e.level==='task')startMission(e);if(!replaying&&e.level==='error'&&/^HATA:/.test(String(e.msg||''))){const reason=String(e.msg).replace(/^HATA:\s*/,'').slice(0,180);missionFailed(reason);showToast('err','Görev başarısız',reason.slice(0,120))}const feed=feedReady();const d=document.createElement('div');d.className='log '+(e.level||'');d.innerHTML=`<time>${time(e.at)}</time>${esc(e.msg)}`;feed.prepend(d)}
442
+ function message(e){missionMessages++;updateMissionStats();const feed=feedReady();ensureTeamNode(e.from,e.from===STATE.status.defaultOperator?'operator':'agent');ensureTeamNode(e.to,e.to===STATE.status.defaultOperator?'operator':'agent');document.querySelectorAll('.team-link').forEach(x=>{x.classList.add('active');setTimeout(()=>x.classList.remove('active'),1500)});const mt=e.messageType||'',plain=mt!=='failure'&&mt!=='blocked';const typeClass=mt==='failure'?'fi-failure':mt==='blocked'?'fi-blocked':mt==='result'?'fi-result':'fi-delegation';const ico=mt==='failure'?'✕':mt==='blocked'?'⊘':mt==='result'?'✓':'⇢';const col=plain?agentColor(e.from):'';const body=String(e.body||'');const long=body.length>300||(body.match(/\n/g)||[]).length>4;const div=document.createElement('div');div.className='feed-item '+typeClass;div.innerHTML=`<span class="fi-dot">${ico}</span><div class="fi-main"><div class="fi-head"><span class="fi-from"${plain&&col?` style="color:${col}"`:''}>${esc(e.from)}</span><span class="fi-arrow">→</span><span class="fi-to">${esc(e.to)}</span>${e.assignmentId?`<span class="fi-tag">${esc(e.assignmentId)}</span>`:''}<time>${time(e.at)}</time></div><div class="fi-body${long?' clamp':''}">${esc(body)}</div>${long?`<button class="fi-more" onclick="toggleFeedBody(this)">Devamını göster</button>`:''}</div>`;feed.prepend(div)}
443
+ function toggleFeedBody(btn){const body=btn.previousElementSibling;if(!body)return;const clamped=body.classList.toggle('clamp');btn.textContent=clamped?'Devamını göster':'Gizle'}
444
+ function logEvent(e){if(e.level==='task')startMission(e);if(!replaying&&e.level==='error'&&/^HATA:/.test(String(e.msg||''))){const reason=String(e.msg).replace(/^HATA:\s*/,'').slice(0,180);missionFailed(reason);showToast('err','Görev başarısız',reason.slice(0,120))}const feed=feedReady();const lvl=e.level||'info';const ico={task:'▶',ok:'✓',error:'✕',warn:'!',stage:'→',info:'·'}[lvl]||'·';const d=document.createElement('div');d.className='feed-item fi-log '+lvl;d.innerHTML=`<span class="fi-dot">${ico}</span><div class="fi-main"><div class="fi-head"><span class="fi-msg">${esc(e.msg)}</span><time>${time(e.at)}</time></div></div>`;feed.prepend(d)}
430
445
  function applyReplayOutcome(id){const done=(STATE.queue.done||[]).find(t=>t.id===id),failed=(STATE.queue.failed||[]).find(t=>t.id===id);if(done){const d=done.delivery||{},files=(d.files||[]).length;missionCompleted(`${done.operatorCli||'Operatör'} · ${d.rounds||0} tur · ${files} dosya değişikliği${d.verification?` · ✓ ${d.verification}`:''}${(d.warnings||[]).length?` · ${d.warnings.length} uyarı`:''}`)}else if(failed)missionFailed(String(failed.error||'Görev tamamlanamadı').slice(0,180))}
431
446
  async function replayRun(id,{scroll=false}={}){const previousReplay=replaying;replaying=true;try{clearLive();const events=await api(`/api/tasks/${id}/events?limit=1500`);for(const e of events){if(e.type==='activity')activity(e);else if(e.type==='message')message(e);else if(e.type==='log')logEvent(e)}applyReplayOutcome(id);if(scroll){const mc=$('#missionCard');if(mc)mc.scrollIntoView({behavior:'smooth',block:'start'})}return events}finally{replaying=previousReplay}}
432
447
  function latestDashboardRun(){if(STATE.status?.current?.id)return STATE.status.current;return [...(STATE.queue.done||[]),...(STATE.queue.failed||[])].sort((a,b)=>new Date(b.finishedAt||b.createdAt||0)-new Date(a.finishedAt||a.createdAt||0))[0]||null}
@@ -448,9 +463,13 @@ async function sendChat(){const question=$('#chatQuestion').value.trim();if(!que
448
463
 
449
464
  /* ---- SSE ---- */
450
465
  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`;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ı`:''}`);showToast('ok','Görev tamamlandı',r.summary||r.prompt||'Teslimat hazır.');logEvent({...r,level:'ok',msg:'Operatör görevi tamamladı'})}
451
- 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==='schedules'){STATE.schedules=data;renderScheduleList()}}
466
+ 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()}}
467
+ // Görev bitince/başarısız olunca işletim sistemi bildirimi göster (panel arka planda olsa da görünür).
468
+ // İzin bir kullanıcı hareketinde istenir (ensureNotifyPermission). Webhook'tan bağımsızdır ve yapılandırma gerektirmez.
469
+ function ensureNotifyPermission(){try{if('Notification'in window&&Notification.permission==='default')Notification.requestPermission()}catch{}}
470
+ 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{}}
452
471
  function receiveDashboardEvent(type,event){const data=JSON.parse(event.data);if(bootstrapping)bufferedEvents.push({type,data});else dispatchDashboardEvent(type,data)}
453
- function connectDashboardEvents(){es=new EventSource('/api/events');for(const type of ['status','queue','activity','message','log','result','schedules'])es.addEventListener(type,event=>receiveDashboardEvent(type,event))}
472
+ 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))}
454
473
  async function bootstrapDashboard(){connectDashboardEvents();let replayed=[];try{const s=await api('/api/state');STATE=s;applyStatus(s.status);renderQueue(s.queue);$('#targetDir').placeholder=s.workingDirAbs||'';refreshOperatorSelect();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)}}}
455
474
  // Sayfa (ör. Ekip Akışı'na geçiş) bfcache'e girerken açık SSE bağlantısını kapat — aksi halde
456
475
  // donmuş bağlantılar birikip tarayıcının host başına bağlantı limitini doldurur ve sonraki
@@ -530,9 +549,9 @@ async function refreshSchedules(){try{const r=await api('/api/schedules');STATE.
530
549
  async function addScheduleFromComposer(){const body=composerScheduleBody();if(!body.prompt){showToast('err','Hedef gerekli','Önce yukarıya görev hedefini yazın.');return}try{const s=await api('/api/schedules','POST',body);await refreshSchedules();renderScheduleList();$('#prompt').value='';setComposerMode('now');showToast('ok','Görev zamanlandı',`Sonraki çalışma: ${scheduleNextText(s)}`)}catch(e){showToast('err','Zamanlanamadı',e.message)}}
531
550
  async function toggleSchedule(id,enabled){try{await api('/api/schedules/'+encodeURIComponent(id),'PUT',{enabled});await refreshSchedules();renderScheduleList()}catch(e){showToast('err','Güncellenemedi',e.message);renderScheduleList()}}
532
551
  async function deleteSchedule(id){if(!await appConfirm({title:'Zamanlama silinsin mi?',body:'Bu zamanlanmış görev kaldırılacak; gelecekte otomatik çalışmayacak.',confirmLabel:'Sil',cancelLabel:'Vazgeç',danger:true}))return;try{await api('/api/schedules/'+encodeURIComponent(id),'DELETE');await refreshSchedules();renderScheduleList();showToast('ok','Zamanlama silindi')}catch(e){showToast('err','Silinemedi',e.message)}}
533
- function renderGeneral(c){c.innerHTML=`<div class="item grid2"><div><label>Varsayılan çalışma klasörü</label><div class="pickrow"><input id="workingDir" value="${esc(STATE.config.workingDir||'..')}"><button type="button" onclick="openFs(v=>$('#workingDir').value=v,$('#workingDir').value)">📁</button></div></div><div><label>Onay modu</label><select id="approvalMode"><option ${STATE.config.approvalMode==='ask'?'selected':''}>ask</option><option ${STATE.config.approvalMode==='auto'?'selected':''}>auto</option></select></div><div><label>Günlük çağrı bütçesi</label><input id="dailyBudget" type="number" value="${STATE.config.dailyCallBudget||150}"></div><div><label>Agent zaman aşımı</label><input id="agentTimeout" type="number" value="${STATE.config.agentTimeoutSeconds||900}"></div><div><label>Hafıza karakter bütçesi</label><input id="memoryBudget" type="number" value="${STATE.config.memoryCharBudget||8000}"></div><div><label>Takım bağlam bütçesi</label><input id="contextBudget" type="number" value="${STATE.config.teamContextCharBudget||30000}"></div></div><details style="margin-top:10px"><summary>Gelişmiş: ham JSON yapılandırması</summary><p class="muted">Doğrudan JSON düzenleyip “Ham JSON'u uygula”ya basın, ardından Kaydet ile kalıcılaştırın.</p><textarea id="rawConfig" rows="16">${esc(JSON.stringify(STATE.config,null,2))}</textarea><button class="sm" style="margin-top:6px" onclick="applyRaw()">Ham JSON'u uygula</button></details>`}
552
+ function renderGeneral(c){c.innerHTML=`<div class="item grid2"><div><label>Varsayılan çalışma klasörü</label><div class="pickrow"><input id="workingDir" value="${esc(STATE.config.workingDir||'..')}"><button type="button" onclick="openFs(v=>$('#workingDir').value=v,$('#workingDir').value)">📁</button></div></div><div><label>Onay modu</label><select id="approvalMode"><option ${STATE.config.approvalMode==='ask'?'selected':''}>ask</option><option ${STATE.config.approvalMode==='auto'?'selected':''}>auto</option></select></div><div><label>Günlük çağrı bütçesi</label><input id="dailyBudget" type="number" value="${STATE.config.dailyCallBudget||150}"></div><div><label>Agent zaman aşımı</label><input id="agentTimeout" type="number" value="${STATE.config.agentTimeoutSeconds||900}"></div><div><label>Hafıza karakter bütçesi</label><input id="memoryBudget" type="number" value="${STATE.config.memoryCharBudget||8000}"></div><div><label>Takım bağlam bütçesi</label><input id="contextBudget" type="number" value="${STATE.config.teamContextCharBudget||30000}"></div><div style="grid-column:1/-1"><label>Bildirim webhook URL (opsiyonel · Slack uyumlu)</label><input id="notifyWebhook" value="${esc(STATE.config.notify&&STATE.config.notify.webhookUrl||'')}" placeholder="https://hooks.slack.com/services/..."><div class="muted">Her görev bitince/başarısız olunca bu adrese bildirim POST edilir. Boş = kapalı.</div></div></div><details style="margin-top:10px"><summary>Gelişmiş: ham JSON yapılandırması</summary><p class="muted">Doğrudan JSON düzenleyip “Ham JSON'u uygula”ya basın, ardından Kaydet ile kalıcılaştırın.</p><textarea id="rawConfig" rows="16">${esc(JSON.stringify(STATE.config,null,2))}</textarea><button class="sm" style="margin-top:6px" onclick="applyRaw()">Ham JSON'u uygula</button></details>`}
534
553
  function applyRaw(){try{STATE.config=JSON.parse($('#rawConfig').value);draftAgents=Object.entries(STATE.config.agents||{}).map(([name,a])=>({name,...JSON.parse(JSON.stringify(a))}));draftCliSettings=cloneCliSettings(STATE.config.cliSettings);normalizeDraftAgentModels();notify('Ham JSON belleğe alındı; Kaydet ile kalıcılaştırın.');renderGeneral($('#settingsContent'))}catch(e){notify('Geçersiz JSON: '+e.message,true)}}
535
- async function saveSettings(){try{normalizeDraftAgentAdapters();const agents={};for(const a of draftAgents){const {name,...rest}=a;if(name.trim())agents[name.trim()]={...rest,args:rest.args||[]}}STATE.config.agents=agents;STATE.config.cliSettings=cloneCliSettings(draftCliSettings);STATE.config.discoveryIgnoredAdapters=[...draftIgnoredAdapters];if($('#defaultOperator'))STATE.config.operator={...(STATE.config.operator||{}),cli:$('#defaultOperator').value,roleFile:'roles/operator.md',maxRounds:+$('#maxRounds').value,maxDelegationsPerRound:+$('#maxDelegations').value,protocolRetries:+$('#protocolRetries').value};STATE.config.operator??={};STATE.config.operator.roleFile='roles/operator.md';delete STATE.config.operator.agent;delete STATE.config.operator.model;delete STATE.config.operator.codexSettings;if(!STATE.config.operator.cli)STATE.config.operator.cli=(installedClis()[0]||'codex');if($('#workingDir'))Object.assign(STATE.config,{workingDir:$('#workingDir').value,approvalMode:$('#approvalMode').value,dailyCallBudget:+$('#dailyBudget').value,agentTimeoutSeconds:+$('#agentTimeout').value,memoryCharBudget:+$('#memoryBudget').value,teamContextCharBudget:+$('#contextBudget').value});await api('/api/config','PUT',STATE.config);notify('Kaydedildi; sonraki görevde geçerli.');refreshOperatorSelect(true)}catch(e){notify(e.message,true)}}
554
+ async function saveSettings(){try{normalizeDraftAgentAdapters();const agents={};for(const a of draftAgents){const {name,...rest}=a;if(name.trim())agents[name.trim()]={...rest,args:rest.args||[]}}STATE.config.agents=agents;STATE.config.cliSettings=cloneCliSettings(draftCliSettings);STATE.config.discoveryIgnoredAdapters=[...draftIgnoredAdapters];if($('#defaultOperator'))STATE.config.operator={...(STATE.config.operator||{}),cli:$('#defaultOperator').value,roleFile:'roles/operator.md',maxRounds:+$('#maxRounds').value,maxDelegationsPerRound:+$('#maxDelegations').value,protocolRetries:+$('#protocolRetries').value};STATE.config.operator??={};STATE.config.operator.roleFile='roles/operator.md';delete STATE.config.operator.agent;delete STATE.config.operator.model;delete STATE.config.operator.codexSettings;if(!STATE.config.operator.cli)STATE.config.operator.cli=(installedClis()[0]||'codex');if($('#workingDir'))Object.assign(STATE.config,{workingDir:$('#workingDir').value,approvalMode:$('#approvalMode').value,dailyCallBudget:+$('#dailyBudget').value,agentTimeoutSeconds:+$('#agentTimeout').value,memoryCharBudget:+$('#memoryBudget').value,teamContextCharBudget:+$('#contextBudget').value,notify:{...(STATE.config.notify||{}),webhookUrl:(($('#notifyWebhook')&&$('#notifyWebhook').value)||'').trim()}});await api('/api/config','PUT',STATE.config);notify('Kaydedildi; sonraki görevde geçerli.');refreshOperatorSelect(true)}catch(e){notify(e.message,true)}}
536
555
 
537
556
  bootstrapDashboard();
538
557
  </script>
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "CrewCtl",
3
+ "short_name": "CrewCtl",
4
+ "description": "Operatör liderliğindeki çok-agent CLI orkestratörü.",
5
+ "icons": [
6
+ { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
7
+ { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
8
+ ],
9
+ "theme_color": "#111826",
10
+ "background_color": "#090c12",
11
+ "display": "standalone"
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omerrgocmen/crewctl",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "CrewCtl — Zero-dependency Node.js orchestrator that runs installed CLI coding agents as one operator-led team.",
5
5
  "keywords": [
6
6
  "ai",
@@ -17,11 +17,11 @@
17
17
  "type": "commonjs",
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git+https://github.com/omergocmen/cli.git"
20
+ "url": "git+https://github.com/omergocmen/CrewCtl.git"
21
21
  },
22
- "homepage": "https://github.com/omergocmen/cli#readme",
22
+ "homepage": "https://github.com/omergocmen/CrewCtl#readme",
23
23
  "bugs": {
24
- "url": "https://github.com/omergocmen/cli/issues"
24
+ "url": "https://github.com/omergocmen/CrewCtl/issues"
25
25
  },
26
26
  "files": [
27
27
  "orchestrator/src/",