@omerrgocmen/crewctl 1.1.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/orchestrator/README.md +15 -4
- package/orchestrator/config.default.json +20 -1
- package/orchestrator/src/checkpoints.js +0 -4
- package/orchestrator/src/cli-registry.js +187 -21
- package/orchestrator/src/cli.js +6 -1
- package/orchestrator/src/engine.js +320 -36
- package/orchestrator/src/schedule.js +0 -2
- package/orchestrator/src/server.js +30 -8
- package/orchestrator/src/store.js +54 -2
- package/orchestrator/web/index.html +26 -6
- package/package.json +1 -1
package/orchestrator/README.md
CHANGED
|
@@ -367,10 +367,21 @@ checkpoint'i oluşturulur; böylece geri alma da geri alınabilir. Sürümler `s
|
|
|
367
367
|
tutulur (`versioningRetention`, varsayılan 20 sürüm/klasör) ve yalnızca motor **boşta** iken geri yüklenir.
|
|
368
368
|
Bu, Git yerine geçmez; kritik iş için normal sürüm kontrolünüzü sürdürün.
|
|
369
369
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
370
|
+
### 🧱 Ajan hapsi (sandbox)
|
|
371
|
+
|
|
372
|
+
`config.json` → `sandbox` alanı ajanı **çalışma klasörüne** hapseder. **Docker veya Git GEREKTİRMEZ**, her PC'de (Windows/macOS/Linux) çalışır:
|
|
373
|
+
|
|
374
|
+
| `sandbox.mode` | Davranış |
|
|
375
|
+
|----------------|----------|
|
|
376
|
+
| `"workspace"` (varsayılan) | Ajan yalnızca çalışma klasörüne yazabilir. **Codex**: `-s workspace-write` ile dışarıya yazma + ağ, OS-native olarak engellenir (mac Seatbelt / Linux Landlock+seccomp / Windows restricted-token+ACL). **Claude**: orkestratörün kendi dizinleri (config/memory/kurulum) `permissions.deny` ile gizlenir. **Tüm CLI'lar**: prompt'ta katı "yalnız bu klasör" talimatı. |
|
|
377
|
+
| `"off"` | Eski davranış (hapis yok). |
|
|
378
|
+
|
|
379
|
+
- `sandbox.extraWritableDirs`: çalışma klasörü dışında izin verilen ek yazılabilir mutlak yollar (monorepo/paylaşılan bağımlılık için). Codex'e `writable_roots` olarak geçer.
|
|
380
|
+
- **Dürüst sınır:** Codex/Claude/Cursor dahil tüm OS-native sandbox'lar **okumayı** kasten serbest bırakır; `workspace` modu dışarıya *yazmayı* keser ama okumayı tam engellemez. Okumayı da tümüyle hapsetmek yalnızca konteynerle mümkündür (Docker gerekir; bu projede kapsam dışı). Codex tarafında okuma+yazma birlikte, mac/Linux'ta çekirdek düzeyinde hapsedilir.
|
|
381
|
+
|
|
382
|
+
> ⚠️ `workspace` modu güçlü bir koruma sağlar ama tam bir izolasyon garantisi değildir. Yine de izole çalışma
|
|
383
|
+
> klasörü kullanın, önemli dosyaları sürüm kontrolünde tutun ve **web panelini güvenilmeyen bir ağa açmayın**
|
|
384
|
+
> — ayar API'si CLI komutlarını değiştirebilir.
|
|
374
385
|
|
|
375
386
|
## 🗄️ Depolama
|
|
376
387
|
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
"liveDiffIntervalMs": 2500,
|
|
15
15
|
"versioning": true,
|
|
16
16
|
"versioningRetention": 20,
|
|
17
|
+
"sandbox": {
|
|
18
|
+
"_comment": "Ajan hapsi. mode: 'workspace' = ajan yalnizca calisma klasorune yazabilir, disariya yazma/ag engellenir (Docker/Git GEREKMEZ; codex Win/mac/Linux'ta OS-native zorlar, digerleri en-iyi-caba + prompt). 'off' = eski davranis. extraWritableDirs: calisma klasoru disinda izin verilen ek yazilabilir mutlak yollar (monorepo icin).",
|
|
19
|
+
"mode": "workspace",
|
|
20
|
+
"extraWritableDirs": []
|
|
21
|
+
},
|
|
22
|
+
"_projectContext_comment": "Her calisma klasorunun .crewctl/CONTEXT.md proje profili gorev acilisinda operatore yuklenir (tum kodu bastan taramaya gerek kalmaz), gorev bitiminde operator profili REVIZE eder (changelog degil). Gorev basina '--fresh' ile atlanabilir. false = eski global hafiza.",
|
|
23
|
+
"projectContext": true,
|
|
24
|
+
"projectContextCharBudget": 6000,
|
|
17
25
|
"notify": {
|
|
18
26
|
"_comment": "Görev bitince/başarısız olunca webhookUrl'e {text,...} POST edilir (Slack incoming webhook ile uyumlu). Boş webhookUrl = bildirim kapalı.",
|
|
19
27
|
"webhookUrl": "",
|
|
@@ -28,9 +36,20 @@
|
|
|
28
36
|
"protocolRetries": 2,
|
|
29
37
|
"passFastPath": true
|
|
30
38
|
},
|
|
39
|
+
"resilience": {
|
|
40
|
+
"_comment": "Gecici saglayici hatalarinda (rate limit / asiri yuk / ag) delegasyon ayni agent ile ustel bekleyerek yeniden denenir; kalici hatada is ayni yetenekteki saglikli bir agente devredilir. Operatore geri donup yeni plan turu harcamak son caredir.",
|
|
41
|
+
"transientRetries": 2,
|
|
42
|
+
"retryBaseSeconds": 3,
|
|
43
|
+
"maxFailoverAgents": 1
|
|
44
|
+
},
|
|
31
45
|
"cliSettings": {
|
|
32
46
|
"codex": { "model": "", "reasoningEffort": "medium", "serviceTier": "fast" },
|
|
33
|
-
"opencode": {
|
|
47
|
+
"opencode": {
|
|
48
|
+
"_comment": "model: acikca bir model yazarsaniz otomatik secim tumden atlanir (en guvenilir yol; `opencode models` ciktisindan bir satir). Bos birakilirsa modelPreferences desenleri sirayla denenir ve ILK eslesen kullanilir; `*` joker karakterdir. Saglayici adlari kisiden kisiye degisir (kendi aboneliginiz opencode-go, minimax, openrouter, kendi sunucunuz olabilir), bu yuzden burada saglayici adi sabitlenmez — kendi tercihinizi one almak icin listeyi duzenleyin, or. \"opencode-go/*\" en basa. modelExclude: erisilebilir varsayilmayacak saglayicilar; kendi yerel sunucunuzu kullanmak istiyorsaniz ilgili satiri silin.",
|
|
49
|
+
"model": "",
|
|
50
|
+
"modelPreferences": ["*deepseek*free*", "*free*", "*"],
|
|
51
|
+
"modelExclude": ["ollama/*", "lmstudio/*", "llamacpp/*", "local/*", "localhost/*"]
|
|
52
|
+
}
|
|
34
53
|
},
|
|
35
54
|
"skills": {
|
|
36
55
|
"_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.",
|
|
@@ -67,7 +67,6 @@ function walkFileList(cwd) {
|
|
|
67
67
|
return out;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// Yedeklenecek dosya listesi (calisma klasorune GORE goreli yollar).
|
|
71
70
|
function fileList(cwd) {
|
|
72
71
|
if (isGitRepo(cwd)) {
|
|
73
72
|
const git = gitFileList(cwd);
|
|
@@ -155,7 +154,6 @@ function restoreCheckpoint(id, opts = {}) {
|
|
|
155
154
|
}
|
|
156
155
|
const filesDir = path.join(CP_ROOT, path.basename(String(id)), "files");
|
|
157
156
|
const keep = new Set(manifest.files);
|
|
158
|
-
// (1) Surumden sonra olusturulmus (surumde olmayan) dosyalari sil.
|
|
159
157
|
let deleted = 0;
|
|
160
158
|
for (const rel of fileList(cwd).files) {
|
|
161
159
|
if (keep.has(rel)) continue;
|
|
@@ -163,7 +161,6 @@ function restoreCheckpoint(id, opts = {}) {
|
|
|
163
161
|
if (!target) continue;
|
|
164
162
|
try { if (fs.existsSync(target)) { fs.rmSync(target, { force: true }); deleted++; } } catch {}
|
|
165
163
|
}
|
|
166
|
-
// (2) Surumdeki dosyalari birebir geri yaz.
|
|
167
164
|
let restored = 0;
|
|
168
165
|
for (const rel of manifest.files) {
|
|
169
166
|
const src = path.join(filesDir, rel);
|
|
@@ -174,7 +171,6 @@ function restoreCheckpoint(id, opts = {}) {
|
|
|
174
171
|
return { ok: true, id: manifest.id, cwd, restored, deleted, redoId };
|
|
175
172
|
}
|
|
176
173
|
|
|
177
|
-
// Listeleme: dosya listesini disari vermeden ozet meta doner (istege bagli cwd filtresi).
|
|
178
174
|
function listCheckpoints(cwd) {
|
|
179
175
|
try {
|
|
180
176
|
ensureRoot();
|
|
@@ -31,6 +31,8 @@ const DEFINITIONS = {
|
|
|
31
31
|
defaultArgs: ["--approval-mode", "yolo"],
|
|
32
32
|
// Gemini de akis halinde cikti verir; uzun sessizlik takilma isaretidir.
|
|
33
33
|
silenceTimeoutSeconds: 180,
|
|
34
|
+
// --skip-trust her Gemini surumunde yok; kesifte yoklanip destekleniyorsa eklenir.
|
|
35
|
+
optionalFlags: ["--skip-trust"],
|
|
34
36
|
description: "Gemini CLI analiz, uygulama ve web arastirma agenti",
|
|
35
37
|
capabilities: ["implementation", "planning", "analysis", "research", "web"],
|
|
36
38
|
roleFile: "roles/executor.md",
|
|
@@ -100,18 +102,29 @@ function abortActiveProbes() {
|
|
|
100
102
|
return count;
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
// Saglayici adlari koda gomulmez: kullanicinin aboneligi farkli olabilir ve katalog degisir.
|
|
106
|
+
// Sirali joker desenler; ilk eslesen kazanir. cliSettings.opencode uzerinden degistirilebilir.
|
|
107
|
+
const DEFAULT_OPENCODE_MODEL_PREFERENCES = ["*deepseek*free*", "*free*", "*"];
|
|
108
|
+
// Yerel/LAN saglayicilar erisilebilir varsayilamaz: kapali bir Ollama "Unable to connect" verir.
|
|
109
|
+
const DEFAULT_OPENCODE_MODEL_EXCLUDE = ["ollama/*", "lmstudio/*", "llamacpp/*", "local/*", "localhost/*"];
|
|
110
|
+
|
|
111
|
+
// Kullanici deseni regex ozel karakteri icerse bile duz metin sayilmali.
|
|
112
|
+
function globToRegExp(pattern) {
|
|
113
|
+
const escaped = String(pattern).replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
114
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
115
|
+
}
|
|
116
|
+
function patternList(value, fallback) {
|
|
117
|
+
const list = Array.isArray(value) ? value.map(String).map((x) => x.trim()).filter(Boolean) : [];
|
|
118
|
+
return (list.length ? list : fallback).map(globToRegExp);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function selectOpenCodeModel(models, settings = {}) {
|
|
104
122
|
const list = Array.isArray(models) ? models.map(String).filter(Boolean) : [];
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
(
|
|
110
|
-
(x) => x.startsWith("minimax-coding-plan/"),
|
|
111
|
-
(x) => !x.startsWith("ollama/"),
|
|
112
|
-
];
|
|
113
|
-
for (const match of priorities) {
|
|
114
|
-
const found = list.find(match);
|
|
123
|
+
const exclude = patternList(settings.modelExclude, DEFAULT_OPENCODE_MODEL_EXCLUDE);
|
|
124
|
+
const preferences = patternList(settings.modelPreferences, DEFAULT_OPENCODE_MODEL_PREFERENCES);
|
|
125
|
+
const usable = list.filter((model) => !exclude.some((rule) => rule.test(model)));
|
|
126
|
+
for (const rule of preferences) {
|
|
127
|
+
const found = usable.find((model) => rule.test(model));
|
|
115
128
|
if (found) return found;
|
|
116
129
|
}
|
|
117
130
|
return "";
|
|
@@ -143,6 +156,31 @@ function listOpenCodeModels(command) {
|
|
|
143
156
|
} catch { return []; }
|
|
144
157
|
}
|
|
145
158
|
|
|
159
|
+
// Liste bos kalirsa --model eklenmez ve opencode kendi "son kullanilan" modeline duser; o model
|
|
160
|
+
// erisilemezse gorev "No provider available"/"Unable to connect" ile olur. Bu yuzden tembel doldurulur.
|
|
161
|
+
let openCodeModelsAttempted = false;
|
|
162
|
+
function ensureOpenCodeModels(command) {
|
|
163
|
+
// Negatif sonuc da onbelleklenir: aksi halde bos donduren bir kurulumda her effectiveAgent
|
|
164
|
+
// cagrisi yeniden spawn eder ve istek yolunda event loop'u kilitlerdi.
|
|
165
|
+
if (OPEN_CODE_MODELS.length || openCodeModelsAttempted) return OPEN_CODE_MODELS;
|
|
166
|
+
openCodeModelsAttempted = true;
|
|
167
|
+
OPEN_CODE_MODELS = listOpenCodeModels(RESOLVED.get("opencode") || command || "opencode");
|
|
168
|
+
return OPEN_CODE_MODELS;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Oturum acma talimati CLI'ya gore degisir; tek bir sabit metin opencode hatasinda kullaniciya
|
|
172
|
+
// gemini komutu onerdiriyordu. Adapter adi ya da komut yolu kabul eder.
|
|
173
|
+
const AUTH_HINTS = {
|
|
174
|
+
codex: "Terminalde `codex login` calistirin (veya OPENAI_API_KEY tanimlayin).",
|
|
175
|
+
claude: "Terminalde `claude` calistirip oturum acin (veya ANTHROPIC_API_KEY tanimlayin).",
|
|
176
|
+
gemini: "Terminalde `gemini` calistirip 'Login with Google' secin (veya GEMINI_API_KEY tanimlayin).",
|
|
177
|
+
opencode: "Terminalde `opencode auth login` calistirip saglayiciyi secin; '401 No provider available' hatasi secili modelin saglayicisinda gecerli kimlik olmadigi anlamina gelir.",
|
|
178
|
+
};
|
|
179
|
+
function authHint(adapterOrCommand) {
|
|
180
|
+
const id = AUTH_HINTS[adapterOrCommand] ? adapterOrCommand : adapterId(adapterOrCommand);
|
|
181
|
+
return AUTH_HINTS[id] || "Bu CLI'nin kendi oturum acma komutunu terminalde calistirin.";
|
|
182
|
+
}
|
|
183
|
+
|
|
146
184
|
function adapterId(command) {
|
|
147
185
|
const base = path.basename(String(command || "")).toLowerCase().replace(/\.(cmd|bat|exe|ps1)$/, "");
|
|
148
186
|
if (base.includes("codex")) return "codex";
|
|
@@ -220,6 +258,18 @@ function normalizeAgentAdapters(cfg) {
|
|
|
220
258
|
return changed;
|
|
221
259
|
}
|
|
222
260
|
|
|
261
|
+
// Ajan hapsi acik mi? cfg.sandbox.mode === "workspace" ise CLI'lar calisma klasoru DISINA
|
|
262
|
+
// yazamaz/aga cikamaz. cfg verilmeyen (test/edge) cagrilar hapissiz kalir; boylece mevcut
|
|
263
|
+
// arguman-esitlik testleri aynen gecer. Gercek calistirmada normalizeConfig mode'u daima
|
|
264
|
+
// doldurur (varsayilan "workspace").
|
|
265
|
+
function confineWorkspace(cfg) {
|
|
266
|
+
return cfg?.sandbox?.mode === "workspace";
|
|
267
|
+
}
|
|
268
|
+
function sandboxWritableRoots(cfg) {
|
|
269
|
+
const roots = cfg?.sandbox?.extraWritableDirs;
|
|
270
|
+
return Array.isArray(roots) ? roots.filter((x) => typeof x === "string" && x.trim()).map(String) : [];
|
|
271
|
+
}
|
|
272
|
+
|
|
223
273
|
// cfg verilirse cfg.cliSettings[adapter] o CLI'nin varsayilan model ayari olarak uygulanir.
|
|
224
274
|
// Oncelik: agent.model (profil bazli) > cliSettings[adapter].model (global) > CLI varsayilani.
|
|
225
275
|
function effectiveAgent(agent, cfg) {
|
|
@@ -234,6 +284,23 @@ function effectiveAgent(agent, cfg) {
|
|
|
234
284
|
const commands = new Set(["exec", "review", "resume", "apply"]);
|
|
235
285
|
if (!copy.args.some((arg) => commands.has(arg))) copy.args.unshift("exec", "--skip-git-repo-check");
|
|
236
286
|
else if (copy.args.includes("exec") && !copy.args.includes("--skip-git-repo-check")) copy.args.splice(copy.args.indexOf("exec") + 1, 0, "--skip-git-repo-check");
|
|
287
|
+
// Hapis: calisma klasoru (spawn cwd = codex "workspace") disina yazma ve ag engellenir.
|
|
288
|
+
// AYRICA: `codex exec` bayraksiz calistiginda read-only sandbox'a duser; yazma reddedilir
|
|
289
|
+
// ama surec 0 doner, motor delegasyonu "tamamlandi" sayar ve hicbir dosya uretmeden turlar
|
|
290
|
+
// yanar. Yani bu bayrak yalnizca hapis degil, otonom kosumun calismasi icin de sarttir.
|
|
291
|
+
// Native sandbox — mac Seatbelt / Linux Landlock+seccomp / Windows restricted-token+ACL —
|
|
292
|
+
// Docker/Git GEREKTIRMEZ. Kullanici acikca kendi -s/--sandbox veya bypass bayragini
|
|
293
|
+
// koyduysa ona dokunmayiz. Bayrak, model/effort/tier PUSH'undan ONCE ve exec/skip-git'ten
|
|
294
|
+
// HEMEN SONRA eklenir; testlerin args.slice(0,2) ve slice(-6) beklentileri korunur.
|
|
295
|
+
const alreadySandboxed = copy.args.some((arg) => arg === "-s" || arg === "--sandbox" || arg === "--dangerously-bypass-approvals-and-sandbox" || arg === "--full-auto");
|
|
296
|
+
if (confineWorkspace(cfg) && !alreadySandboxed) {
|
|
297
|
+
const anchor = copy.args.indexOf("--skip-git-repo-check");
|
|
298
|
+
const at = anchor >= 0 ? anchor + 1 : Math.max(copy.args.indexOf("exec") + 1, 0);
|
|
299
|
+
const flags = ["-s", "workspace-write"];
|
|
300
|
+
const roots = sandboxWritableRoots(cfg);
|
|
301
|
+
if (roots.length) flags.push("-c", `sandbox_workspace_write.writable_roots=${JSON.stringify(roots)}`);
|
|
302
|
+
copy.args.splice(at, 0, ...flags);
|
|
303
|
+
}
|
|
237
304
|
const model = String(copy.model || settings.model || "").trim();
|
|
238
305
|
const effort = ["low", "medium", "high", "xhigh", "max", "ultra"].includes(String(settings.reasoningEffort)) ? String(settings.reasoningEffort) : "";
|
|
239
306
|
const serviceTier = /^[A-Za-z0-9._-]{1,64}$/.test(String(settings.serviceTier || "")) ? String(settings.serviceTier) : "";
|
|
@@ -248,6 +315,10 @@ function effectiveAgent(agent, cfg) {
|
|
|
248
315
|
if (adapter === "gemini") {
|
|
249
316
|
// Otonom, non-interaktif calisma icin onay modu sart; yoksa izin bekleyip takilir.
|
|
250
317
|
if (!copy.args.includes("--approval-mode") && !copy.args.includes("-y") && !copy.args.includes("--yolo")) copy.args.push("--approval-mode", "yolo");
|
|
318
|
+
// Gemini "guvenilmeyen" klasorde yolo'yu SESSIZCE default'a dusurur ve onay bekler; otonom
|
|
319
|
+
// kosumda onaylayan olmadigi icin sessizlik zaman asimina dusup karantinaya girer.
|
|
320
|
+
// --skip-trust yalnizca o oturum icindir, kalici ayarlara dokunmaz.
|
|
321
|
+
if (!copy.args.includes("--skip-trust") && cliSupportsFlag(copy.cmd, "--skip-trust")) copy.args.push("--skip-trust");
|
|
251
322
|
}
|
|
252
323
|
if (adapter === "opencode") {
|
|
253
324
|
if (!copy.args.includes("run")) copy.args.unshift("run");
|
|
@@ -257,7 +328,9 @@ function effectiveAgent(agent, cfg) {
|
|
|
257
328
|
if (formatAt >= 0) copy.args.splice(formatAt, 2);
|
|
258
329
|
copy.args.splice(copy.args.indexOf("run") + 1, 0, "--format", "json");
|
|
259
330
|
const hasModel = copy.args.includes("--model") || copy.args.includes("-m");
|
|
260
|
-
|
|
331
|
+
// Sira: agent modeli > global CLI ayari > otomatik secim (bkz. ensureOpenCodeModels).
|
|
332
|
+
const model = copy.model || String(settings.model || "").trim()
|
|
333
|
+
|| selectOpenCodeModel(OPEN_CODE_MODELS.length ? OPEN_CODE_MODELS : ensureOpenCodeModels(copy.cmd), settings);
|
|
261
334
|
if (!hasModel && model) copy.args.splice(copy.args.indexOf("run") + 3, 0, "--model", model);
|
|
262
335
|
if (model) copy.model = model;
|
|
263
336
|
if (!copy.args.includes("{PROMPT}") && !copy.args.includes("{PROMPT_FILE}")) {
|
|
@@ -267,6 +340,44 @@ function effectiveAgent(agent, cfg) {
|
|
|
267
340
|
return copy;
|
|
268
341
|
}
|
|
269
342
|
|
|
343
|
+
// Bayrak destegi --help'ten okunur: kosulsuz eklemek eski surumlerde "unknown argument" ile her
|
|
344
|
+
// calistirmayi kirar.
|
|
345
|
+
//
|
|
346
|
+
// SENKRON SPAWN YALNIZCA KESIF SIRASINDA. effectiveAgent HTTP istek yolunda cagriliyor ve
|
|
347
|
+
// oradaki spawnSync event loop'u kilitleyip baglantiyi ECONNRESET ile dusuruyor (sunucu-akis
|
|
348
|
+
// testi bunu yakaladi); effectiveAgent yalnizca hazir sonucu okur, bilinmiyorsa bayrak eklenmez.
|
|
349
|
+
// Anahtar komuttur: ayni adapteri farkli binary/surumle kullanan agentler karismamali.
|
|
350
|
+
const FLAG_SUPPORT = new Map();
|
|
351
|
+
function flagSupportKey(command, flag) {
|
|
352
|
+
return `${command}${flag}`;
|
|
353
|
+
}
|
|
354
|
+
function probeFlagSupport(command, flags) {
|
|
355
|
+
let help = "";
|
|
356
|
+
try {
|
|
357
|
+
const result = spawnSync(command, ["--help"], {
|
|
358
|
+
encoding: "utf8", timeout: 15000, windowsHide: true,
|
|
359
|
+
shell: isWin && (!path.isAbsolute(command) || /\.(cmd|bat)$/i.test(command)),
|
|
360
|
+
});
|
|
361
|
+
help = `${result.stdout || ""}\n${result.stderr || ""}`;
|
|
362
|
+
} catch {}
|
|
363
|
+
const supported = [];
|
|
364
|
+
for (const flag of flags) {
|
|
365
|
+
const ok = Boolean(help) && help.includes(flag);
|
|
366
|
+
FLAG_SUPPORT.set(flagSupportKey(command, flag), ok);
|
|
367
|
+
if (ok) supported.push(flag);
|
|
368
|
+
}
|
|
369
|
+
return supported;
|
|
370
|
+
}
|
|
371
|
+
// Desteklenmeyenler DE isaretlenir; yoksa "bilinmiyor" kalir ve sonraki probe (saglik testi
|
|
372
|
+
// sirasinda) yeniden yoklar — kacinmak istedigimiz spawnSync tam olarak budur.
|
|
373
|
+
function restoreFlagSupport(command, allFlags, supportedFlags) {
|
|
374
|
+
const supported = new Set(supportedFlags || []);
|
|
375
|
+
for (const flag of allFlags || []) FLAG_SUPPORT.set(flagSupportKey(command, flag), supported.has(flag));
|
|
376
|
+
}
|
|
377
|
+
function cliSupportsFlag(command, flag) {
|
|
378
|
+
return FLAG_SUPPORT.get(flagSupportKey(command, flag)) === true;
|
|
379
|
+
}
|
|
380
|
+
|
|
270
381
|
function probeCommand(command, definition) {
|
|
271
382
|
try {
|
|
272
383
|
const result = spawnSync(command, definition.versionArgs, {
|
|
@@ -282,7 +393,7 @@ function probeCommand(command, definition) {
|
|
|
282
393
|
}
|
|
283
394
|
}
|
|
284
395
|
|
|
285
|
-
function probe(id, definition) {
|
|
396
|
+
function probe(id, definition, cfg) {
|
|
286
397
|
let result = probeCommand(definition.command, definition);
|
|
287
398
|
let installedPath = "";
|
|
288
399
|
if (!result.installed) {
|
|
@@ -300,13 +411,22 @@ function probe(id, definition) {
|
|
|
300
411
|
}
|
|
301
412
|
}
|
|
302
413
|
if (result.installed) RESOLVED.set(id, result.resolvedCommand);
|
|
414
|
+
// Bilinen bayrak tekrar yoklanmaz: probe() saglik testinden de cagriliyor ve o sirada
|
|
415
|
+
// sunucu istek servis ediyor olabilir (bkz. probeFlagSupport'taki ECONNRESET notu).
|
|
416
|
+
if (result.installed && definition.optionalFlags?.length) {
|
|
417
|
+
const command = result.resolvedCommand || definition.command;
|
|
418
|
+
const unknown = definition.optionalFlags.filter((flag) => !FLAG_SUPPORT.has(flagSupportKey(command, flag)));
|
|
419
|
+
if (unknown.length) probeFlagSupport(command, unknown);
|
|
420
|
+
result.supportedFlags = definition.optionalFlags.filter((flag) => cliSupportsFlag(command, flag));
|
|
421
|
+
}
|
|
303
422
|
if (result.installed && id === "opencode") {
|
|
423
|
+
const settings = (cfg && cfg.cliSettings && cfg.cliSettings.opencode) || {};
|
|
304
424
|
OPEN_CODE_MODELS = listOpenCodeModels(result.resolvedCommand || definition.command);
|
|
305
425
|
result.models = OPEN_CODE_MODELS.slice();
|
|
306
|
-
result.recommendedModel = selectOpenCodeModel(OPEN_CODE_MODELS);
|
|
426
|
+
result.recommendedModel = String(settings.model || "").trim() || selectOpenCodeModel(OPEN_CODE_MODELS, settings);
|
|
307
427
|
result.ready = Boolean(result.recommendedModel);
|
|
308
428
|
result.readinessError = result.ready ? "" : (OPEN_CODE_MODELS.length
|
|
309
|
-
? "
|
|
429
|
+
? "Otomatik secim kurallarina uyan bir OpenCode modeli bulunamadi. Ayarlardan bir model secin veya cliSettings.opencode.modelPreferences desenlerini duzenleyin."
|
|
310
430
|
: "OpenCode kurulu, ancak model listesi okunamadi. Once saglayici girisini tamamlayin.");
|
|
311
431
|
}
|
|
312
432
|
return result;
|
|
@@ -347,16 +467,51 @@ function cachedProbeUsable(entry) {
|
|
|
347
467
|
return Boolean(entry && entry.installed && entry.resolvedCommand && resolvesOnPath(entry.resolvedCommand));
|
|
348
468
|
}
|
|
349
469
|
|
|
470
|
+
// OpenCode model listesi persist EDILMEZ; her acilista `opencode models` ile bellekten
|
|
471
|
+
// uretilir. O cagri acilista gecici olarak yavas/basarisiz olursa (soguk ilk cagri, auth
|
|
472
|
+
// oturmamis, ag) liste bos kalir ve panel model dropdown'i o process boyunca bos gorunur.
|
|
473
|
+
// codex icin codexModelCache ile cozulen bu kirilganligi opencode icin de kapatiriz: prob
|
|
474
|
+
// bos donerse son basarili liste (config'te saklanan) fallback olarak kullanilir. Fallback
|
|
475
|
+
// ayni zamanda OPEN_CODE_MODELS'i doldurur ki otomatik model secimi de calissin.
|
|
476
|
+
// settings: fallback yolunda da kullanicinin acik modeli / modelPreferences desenleri gecerli
|
|
477
|
+
// olmali; aksi halde bayat listeden varsayilan kurallarla secim yapilirdi.
|
|
478
|
+
function applyOpenCodeFallback(entry, fallback, settings = {}) {
|
|
479
|
+
if (!entry || entry.id !== "opencode" || !entry.installed) return entry;
|
|
480
|
+
if ((entry.models && entry.models.length) || !Array.isArray(fallback) || !fallback.length) return entry;
|
|
481
|
+
const models = fallback.map(String).filter(Boolean);
|
|
482
|
+
if (!models.length) return entry;
|
|
483
|
+
OPEN_CODE_MODELS = models.slice();
|
|
484
|
+
entry.models = models.slice();
|
|
485
|
+
entry.recommendedModel = String(settings.model || "").trim() || selectOpenCodeModel(models, settings);
|
|
486
|
+
entry.ready = Boolean(entry.recommendedModel);
|
|
487
|
+
entry.modelsFromCache = true;
|
|
488
|
+
if (entry.ready) entry.readinessError = "";
|
|
489
|
+
return entry;
|
|
490
|
+
}
|
|
491
|
+
|
|
350
492
|
function discoverInstalled(options = {}) {
|
|
351
493
|
const cache = options.cache?.version === DISCOVERY_CACHE_VERSION ? options.cache.results || {} : {};
|
|
352
494
|
const force = options.force === true;
|
|
495
|
+
const openCodeFallback = Array.isArray(options.openCodeModels) ? options.openCodeModels : [];
|
|
353
496
|
return Object.entries(DEFINITIONS).map(([id, definition]) => {
|
|
354
497
|
const cached = cache[id];
|
|
355
498
|
if (!force && !ALWAYS_PROBE.has(id) && cachedProbeUsable(cached)) {
|
|
356
499
|
RESOLVED.set(id, cached.resolvedCommand);
|
|
357
|
-
|
|
500
|
+
// Eski sema onbelleginde supportedFlags yok. Yoklama burada, HTTP dinleme baslamadan yapilir.
|
|
501
|
+
let supportedFlags = cached.supportedFlags;
|
|
502
|
+
if (!Array.isArray(supportedFlags) && definition.optionalFlags?.length) {
|
|
503
|
+
supportedFlags = probeFlagSupport(cached.resolvedCommand, definition.optionalFlags);
|
|
504
|
+
} else {
|
|
505
|
+
restoreFlagSupport(cached.resolvedCommand, definition.optionalFlags, supportedFlags);
|
|
506
|
+
}
|
|
507
|
+
return { id, ...definition, installed: true, version: cached.version || "kurulu", error: "", resolvedCommand: cached.resolvedCommand, fromCache: true, supportedFlags: supportedFlags || [] };
|
|
358
508
|
}
|
|
359
|
-
|
|
509
|
+
// cfg: model onerisi kullanicinin modelPreferences desenlerine gore uretilir.
|
|
510
|
+
// fallback: prob bos donerse son bilinen model listesi korunur.
|
|
511
|
+
const result = { id, ...definition, ...probe(id, definition, options.cfg) };
|
|
512
|
+
return id === "opencode"
|
|
513
|
+
? applyOpenCodeFallback(result, openCodeFallback, (options.cfg && options.cfg.cliSettings && options.cfg.cliSettings.opencode) || {})
|
|
514
|
+
: result;
|
|
360
515
|
});
|
|
361
516
|
}
|
|
362
517
|
|
|
@@ -366,7 +521,8 @@ function discoveryCacheFrom(discovered) {
|
|
|
366
521
|
const results = {};
|
|
367
522
|
for (const cli of discovered || []) {
|
|
368
523
|
if (cli.installed && cli.resolvedCommand && !ALWAYS_PROBE.has(cli.id)) {
|
|
369
|
-
|
|
524
|
+
// supportedFlags saklanmazsa onbellekli acilista Gemini --skip-trust sessizce duserdi.
|
|
525
|
+
results[cli.id] = { installed: true, version: cli.version || "kurulu", resolvedCommand: cli.resolvedCommand, supportedFlags: cli.supportedFlags || [] };
|
|
370
526
|
}
|
|
371
527
|
}
|
|
372
528
|
return { version: DISCOVERY_CACHE_VERSION, checkedAt: new Date().toISOString(), results };
|
|
@@ -378,7 +534,17 @@ function discoveryCacheFrom(discovered) {
|
|
|
378
534
|
function buildCommand(command, args) {
|
|
379
535
|
if (!isWin) return { file: command, args, shell: false };
|
|
380
536
|
const bare = !path.isAbsolute(command) && !/[\\/]/.test(command);
|
|
381
|
-
if (bare)
|
|
537
|
+
if (bare) {
|
|
538
|
+
// shell:true iken Node argumanlari birlestirip cmd.exe'ye verir ve OTOMATIK TIRNAK KOYMAZ.
|
|
539
|
+
// Bosluk iceren yol argumanlari (or. "C:\Users\John Doe\...\x.settings.json" veya
|
|
540
|
+
// {PROMPT_FILE}) ikiye bolunurdu; bu da --settings/--file yolunu bozardi. Bosluk veya
|
|
541
|
+
// kabuk metakarakteri iceren argumanlari tirnakla; sade bayrak/model adlari dokunulmaz.
|
|
542
|
+
const shellQuote = (value) => {
|
|
543
|
+
const v = String(value);
|
|
544
|
+
return /[\s"^&|<>()%!]/.test(v) ? `"${v.replace(/"/g, '\\"')}"` : v;
|
|
545
|
+
};
|
|
546
|
+
return { file: command, args: args.map(shellQuote), shell: true };
|
|
547
|
+
}
|
|
382
548
|
if (!/\.(cmd|bat)$/i.test(command)) return { file: command, args, shell: false };
|
|
383
549
|
const quote = (value) => `"${String(value).replace(/"/g, '""')}"`;
|
|
384
550
|
return { file: process.env.ComSpec || "cmd.exe", args: ["/d", "/s", "/c", `"${[command, ...args].map(quote).join(" ")}"`], shell: false, verbatim: true };
|
|
@@ -447,7 +613,7 @@ function classifyHealthFailure(text) {
|
|
|
447
613
|
function testInstalledCli(id, options = {}) {
|
|
448
614
|
const definition = DEFINITIONS[id];
|
|
449
615
|
if (!definition) return Promise.resolve({ id, installed: false, health: { status: "unknown", label: "Bilinmiyor", detail: "Tanımsız CLI" } });
|
|
450
|
-
const found = probe(id, definition);
|
|
616
|
+
const found = probe(id, definition, options.cfg);
|
|
451
617
|
if (!found.installed) return Promise.resolve({ id, installed: false, version: found.version, error: found.error, health: { status: "not-installed", label: "Kurulu değil", detail: found.error || "CLI bulunamadı" } });
|
|
452
618
|
const agent = effectiveAgent({ adapter: id, cmd: definition.command, args: definition.defaultArgs.slice() }, options.cfg);
|
|
453
619
|
const prompt = healthPrompt(id);
|
|
@@ -679,4 +845,4 @@ function ensureValidOperator(cfg, discovered) {
|
|
|
679
845
|
return changed;
|
|
680
846
|
}
|
|
681
847
|
|
|
682
|
-
module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, discoveryCacheFrom, healthCheckAll, listCodexModels, abortActiveProbes, currentProbeGeneration, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator };
|
|
848
|
+
module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, authHint, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, discoveryCacheFrom, applyOpenCodeFallback, healthCheckAll, listCodexModels, abortActiveProbes, currentProbeGeneration, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator };
|
package/orchestrator/src/cli.js
CHANGED
|
@@ -28,6 +28,7 @@ Task secenekleri:
|
|
|
28
28
|
--dir <klasor> Calisma klasoru
|
|
29
29
|
--operator <cli> codex | claude | gemini | opencode
|
|
30
30
|
--mode <mod> auto | fast | balanced | deep
|
|
31
|
+
--fresh Proje profilini yukleme; temiz sayfadan basla
|
|
31
32
|
|
|
32
33
|
Ornek:
|
|
33
34
|
crewctl task "Testleri duzelt" --dir . --mode balanced
|
|
@@ -66,6 +67,9 @@ function addTask(args) {
|
|
|
66
67
|
const targetDir = takeOption(args, "--dir");
|
|
67
68
|
const operator = takeOption(args, "--operator");
|
|
68
69
|
const mode = takeOption(args, "--mode") || "auto";
|
|
70
|
+
// --fresh: bu gorevde proje profilini (.crewctl/CONTEXT.md) yukleme; temiz sayfadan basla.
|
|
71
|
+
const fresh = args.includes("--fresh");
|
|
72
|
+
if (fresh) args.splice(args.indexOf("--fresh"), 1);
|
|
69
73
|
if (!VALID_MODES.has(mode)) throw new Error(`Gecersiz mod: ${mode}`);
|
|
70
74
|
const unknown = args.find((arg) => arg.startsWith("--"));
|
|
71
75
|
if (unknown) throw new Error(`Bilinmeyen secenek: ${unknown}`);
|
|
@@ -75,8 +79,9 @@ function addTask(args) {
|
|
|
75
79
|
const selectedOperator = operator || cfg.operator?.cli;
|
|
76
80
|
const resolvedDir = targetDir ? path.resolve(targetDir) : undefined;
|
|
77
81
|
const task = store.addTask(prompt, resolvedDir, selectedOperator, mode);
|
|
82
|
+
if (fresh) { task.freshContext = true; store.saveTask("pending", task); }
|
|
78
83
|
console.log(`Eklendi: ${task.id}`);
|
|
79
|
-
console.log(`Mod: ${mode} | Operator: ${selectedOperator || "varsayilan"}`);
|
|
84
|
+
console.log(`Mod: ${mode} | Operator: ${selectedOperator || "varsayilan"}${fresh ? " | Temiz sayfa (--fresh)" : ""}`);
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
function approvals() {
|