@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.
@@ -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
- > ⚠️ Otonom çalışma onayı bir sandbox değildir. Agent'lar çalışma klasöründeki dosyaları değiştirebilir,
371
- > komut çalıştırabilir ve CLI'ın verdiği yetki ölçüsünde daha geniş sisteme erişebilir. İzole çalışma
372
- > klasörü/repo kullanın, önemli dosyaları sürüm kontrolünde tutun ve **web panelini güvenilmeyen bir
373
- > ağa açmayın** — ayar API'si CLI komutlarını değiştirebilir.
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": { "model": "" }
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
- function selectOpenCodeModel(models) {
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 priorities = [
106
- (x) => x === "opencode/big-pickle",
107
- (x) => /^opencode\/.+free$/i.test(x),
108
- (x) => x.startsWith("opencode/"),
109
- (x) => x.startsWith("opencode-go/"),
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
- const model = copy.model || String(settings.model || "").trim() || selectOpenCodeModel(OPEN_CODE_MODELS);
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
- ? "Kullanilabilir otomatik OpenCode modeli bulunamadi. Ayarlardan erisilebilir bir model secin."
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
- return { id, ...definition, installed: true, version: cached.version || "kurulu", error: "", resolvedCommand: cached.resolvedCommand, fromCache: true };
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
- return { id, ...definition, ...probe(id, definition) };
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
- results[cli.id] = { installed: true, version: cli.version || "kurulu", resolvedCommand: cli.resolvedCommand };
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) return { file: command, args, shell: true };
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 };
@@ -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() {