@omerrgocmen/crewctl 1.3.2 → 1.3.3

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.
@@ -46,6 +46,10 @@
46
46
  },
47
47
  "cliSettings": {
48
48
  "codex": { "model": "", "reasoningEffort": "medium", "serviceTier": "fast" },
49
+ "claude": {
50
+ "_comment": "Bos model Claude Code hesap varsayilanini kullanir. Arayuz guncel modelleri tam surum kimlikleriyle sunar ve secilen degeri `claude --model <name>` olarak uygular.",
51
+ "model": ""
52
+ },
49
53
  "opencode": {
50
54
  "_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.",
51
55
  "model": "",
@@ -323,6 +323,10 @@ function effectiveAgent(agent, cfg) {
323
323
  else copy.args.push("--output-format", "stream-json");
324
324
  // stream-json, -p (print) modunda --verbose ZORUNLU kilar; yoksa claude hata verir.
325
325
  if (!copy.args.includes("--verbose")) copy.args.push("--verbose");
326
+ // Claude Code model secimini oturum baslangicinda --model ile kabul eder. Alias kullanmak
327
+ // (sonnet/opus/haiku vb.) sağlayiciya gore guncel, erisilebilir surume cozumlenmesini saglar.
328
+ const model = String(copy.model || settings.model || "").trim();
329
+ if (model && !copy.args.includes("--model") && !copy.args.includes("-m")) copy.args.push("--model", model);
326
330
  }
327
331
  if (adapter === "gemini") {
328
332
  // Otonom, non-interaktif calisma icin onay modu sart; yoksa izin bekleyip takilir.
@@ -745,6 +749,112 @@ function listCodexModels(options = {}) {
745
749
  });
746
750
  }
747
751
 
752
+ // Claude Code'un Agent SDK kontrol protokolu, oturum acilisinda o kullanicinin
753
+ // hesabina/saglayicisina/kurum politikasina gore gercekten secilebilir modelleri dondurur.
754
+ // Statik model kimlikleri tutmak hem hizla eskir hem de baska bir kullanicida gorunmeyen
755
+ // bir modeli secilebilir gosterir. Bu normalizer yalnizca UI'nin ihtiyaci olan alanlari
756
+ // alir; initialize yanitindaki hesap bilgilerini disari sizdirmaz.
757
+ function normalizeClaudeModels(models) {
758
+ const seen = new Set();
759
+ const normalized = [];
760
+ for (const model of Array.isArray(models) ? models : []) {
761
+ const id = String(model?.value || "").trim();
762
+ if (!id || seen.has(id)) continue;
763
+ seen.add(id);
764
+ normalized.push({
765
+ id,
766
+ resolvedModel: String(model.resolvedModel || "").trim(),
767
+ displayName: String(model.displayName || id).trim(),
768
+ description: String(model.description || "").trim(),
769
+ supportsEffort: model.supportsEffort === true,
770
+ supportedEffortLevels: Array.isArray(model.supportedEffortLevels)
771
+ ? model.supportedEffortLevels.map(String).filter(Boolean)
772
+ : [],
773
+ supportsAdaptiveThinking: model.supportsAdaptiveThinking === true,
774
+ supportsFastMode: model.supportsFastMode === true,
775
+ supportsAutoMode: model.supportsAutoMode === true,
776
+ });
777
+ }
778
+ return normalized;
779
+ }
780
+
781
+ function listClaudeModels(options = {}) {
782
+ const args = ["--output-format", "stream-json", "--verbose", "--input-format", "stream-json", "--no-session-persistence"];
783
+ const command = buildCommand(options.command || RESOLVED.get("claude") || DEFINITIONS.claude.command, args);
784
+ const timeoutMs = Math.max(5000, Number(options.timeoutMs || 20000));
785
+ const requestId = "crewctl-claude-models";
786
+ return new Promise((resolve, reject) => {
787
+ let buffer = "", stderr = "", settled = false;
788
+ let child, unregister = () => {};
789
+ const finish = (error, value) => {
790
+ if (settled) return;
791
+ settled = true;
792
+ clearTimeout(timer);
793
+ unregister();
794
+ terminateProbeTree(child);
795
+ if (error) reject(error); else resolve(value);
796
+ };
797
+ const timer = setTimeout(
798
+ () => finish(new Error(`Claude Code model listesi ${Math.round(timeoutMs / 1000)} saniyede alinamadi.`)),
799
+ timeoutMs,
800
+ );
801
+ try {
802
+ child = spawn(command.file, command.args, {
803
+ shell: command.shell,
804
+ windowsHide: true,
805
+ windowsVerbatimArguments: !!command.verbatim,
806
+ stdio: ["pipe", "pipe", "pipe"],
807
+ env: {
808
+ ...process.env,
809
+ CLAUDE_CODE_ENTRYPOINT: "sdk-ts",
810
+ CLAUDE_AGENT_SDK_CLIENT_APP: "crewctl",
811
+ },
812
+ });
813
+ } catch (error) {
814
+ finish(error);
815
+ return;
816
+ }
817
+ unregister = registerProbe(child);
818
+ child.stdout?.setEncoding("utf8");
819
+ child.stderr?.setEncoding("utf8");
820
+ child.stdout?.on("data", (chunk) => {
821
+ buffer += String(chunk);
822
+ const lines = buffer.split(/\r?\n/);
823
+ buffer = lines.pop() || "";
824
+ for (const line of lines) {
825
+ let message;
826
+ try { message = JSON.parse(line); } catch { continue; }
827
+ if (message.type !== "control_response" || message.response?.request_id !== requestId) continue;
828
+ if (message.response?.subtype !== "success") {
829
+ finish(new Error(message.response?.error || "Claude Code model listesi alinamadi."));
830
+ return;
831
+ }
832
+ const models = message.response?.response?.models;
833
+ if (!Array.isArray(models)) {
834
+ finish(new Error("Bu Claude Code surumu dinamik model katalogunu desteklemiyor."));
835
+ return;
836
+ }
837
+ finish(null, normalizeClaudeModels(models));
838
+ return;
839
+ }
840
+ });
841
+ child.stderr?.on("data", (chunk) => {
842
+ stderr = `${stderr}${String(chunk)}`.slice(-2048);
843
+ });
844
+ child.on("error", (error) => finish(error));
845
+ child.on("close", (code) => {
846
+ if (settled) return;
847
+ finish(new Error(stderr.trim() || `Claude Code model katalogu okunamadan cikti (kod ${code}).`));
848
+ });
849
+ child.stdin?.on("error", () => {});
850
+ child.stdin?.write(`${JSON.stringify({
851
+ type: "control_request",
852
+ request_id: requestId,
853
+ request: { subtype: "initialize" },
854
+ })}\n`);
855
+ });
856
+ }
857
+
748
858
  function addMissingAgents(cfg, discovered) {
749
859
  cfg.agents ||= {};
750
860
  const ignored = new Set(Array.isArray(cfg.discoveryIgnoredAdapters) ? cfg.discoveryIgnoredAdapters.map(String) : []);
@@ -857,4 +967,4 @@ function ensureValidOperator(cfg, discovered) {
857
967
  return changed;
858
968
  }
859
969
 
860
- 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 };
970
+ module.exports = { DEFINITIONS, KNOWN_CLIS: Object.keys(DEFINITIONS), adapterId, authHint, normalizeAgentAdapter, normalizeAgentAdapters, effectiveAgent, preparePromptArgs, operatorSpec, buildCommand, agentEnvironment, discoverInstalled, discoveryCacheFrom, applyOpenCodeFallback, healthCheckAll, listCodexModels, listClaudeModels, normalizeClaudeModels, abortActiveProbes, currentProbeGeneration, selectOpenCodeModel, parseOpenCodeModels, addMissingAgents, ensureValidOperator };
@@ -28,6 +28,7 @@ const HEALTH_CACHE_VERSION = 2;
28
28
  const CODEX_MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
29
29
  let codexModelRefreshDue = false;
30
30
  let codexModelCache = { checkedAt: 0, models: [] };
31
+ let claudeModelCache = { checkedAt: 0, models: [] };
31
32
 
32
33
  // OpenCode model listesini config'e yaz: prob taze (cache-disi) liste dondurdugunde saklariz
33
34
  // ki bir sonraki acilista `opencode models` gecici olarak yavas/basarisiz olsa bile dropdown
@@ -201,6 +202,13 @@ async function getCodexModels(force = false) {
201
202
  store.saveConfig(cfg);
202
203
  return models;
203
204
  }
205
+ async function getClaudeModels(force = false) {
206
+ if (!force && claudeModelCache.models.length && Date.now() - claudeModelCache.checkedAt < CODEX_MODEL_CACHE_TTL_MS) return claudeModelCache.models;
207
+ if (engineBusy()) return claudeModelCache.models;
208
+ const models = await cliRegistry.listClaudeModels({ timeoutMs: 20000 });
209
+ claudeModelCache = { checkedAt: Date.now(), models };
210
+ return models;
211
+ }
204
212
  async function refreshCliHealth(force = false) {
205
213
  if (healthRunning) return cliStatus;
206
214
  const cfg = store.loadConfig();
@@ -594,6 +602,7 @@ const server = http.createServer(async (req, res) => {
594
602
  // liste gelirse persistOpenCodeModels ile guncellenir. cfg ise model onerisini
595
603
  // kullanicinin modelPreferences desenlerine gore urettirir.
596
604
  const cfg = store.loadConfig();
605
+ claudeModelCache = { checkedAt: 0, models: [] };
597
606
  cliStatus = cliRegistry.discoverInstalled({ force: true, cfg, openCodeModels: cfg.openCodeModelCache?.models });
598
607
  cfg.cliDiscoveryCache = cliRegistry.discoveryCacheFrom(cliStatus);
599
608
  const body = await readBody(req);
@@ -615,6 +624,10 @@ const server = http.createServer(async (req, res) => {
615
624
  try { return send(res, 200, { models: await getCodexModels(false) }); }
616
625
  catch (error) { return send(res, 502, { error: `Codex modelleri alınamadı: ${error.message}` }); }
617
626
  }
627
+ if (pathname === "/api/claude/models" && req.method === "GET") {
628
+ try { return send(res, 200, { models: await getClaudeModels(false) }); }
629
+ catch (error) { return send(res, 502, { error: `Claude Code modelleri alınamadı: ${error.message}` }); }
630
+ }
618
631
  if (pathname === "/api/projects" && req.method === "GET") {
619
632
  return send(res, 200, { projects: store.listProjects() });
620
633
  }
@@ -759,7 +772,7 @@ const server = http.createServer(async (req, res) => {
759
772
  cliRegistry.normalizeAgentAdapters(cfg);
760
773
  if (cfg.cliSettings.codex.reasoningEffort && !["low", "medium", "high", "xhigh", "max", "ultra"].includes(cfg.cliSettings.codex.reasoningEffort)) return send(res, 400, { error: "cliSettings.codex.reasoningEffort gecersiz" });
761
774
  if (cfg.cliSettings.codex.serviceTier && !/^[A-Za-z0-9._-]{1,64}$/.test(cfg.cliSettings.codex.serviceTier)) return send(res, 400, { error: "cliSettings.codex.serviceTier gecersiz" });
762
- for (const id of ["codex", "opencode"]) {
775
+ for (const id of ["codex", "claude", "opencode"]) {
763
776
  if (cfg.cliSettings[id].model != null && typeof cfg.cliSettings[id].model !== "string") return send(res, 400, { error: `cliSettings.${id}.model metin olmali` });
764
777
  }
765
778
  if (cfg.skills !== undefined) {
@@ -137,6 +137,7 @@ const FALLBACK_CONFIG = {
137
137
  resilience: { transientRetries: 2, retryBaseSeconds: 3, maxFailoverAgents: 1 },
138
138
  cliSettings: {
139
139
  codex: { model: "", reasoningEffort: "medium", serviceTier: "fast" },
140
+ claude: { model: "" },
140
141
  opencode: { model: "", modelPreferences: ["*deepseek*free*", "*free*", "*"], modelExclude: ["ollama/*", "lmstudio/*", "llamacpp/*", "local/*", "localhost/*"] },
141
142
  },
142
143
  skills: { enabled: [], autoMatch: true, catalogLimit: 12, maxSkillsPerAssignment: 3, charBudget: 2400, referenceCharBudget: 1200 },
@@ -167,6 +168,7 @@ function normalizeConfig(cfg) {
167
168
  },
168
169
  cliSettings: {
169
170
  codex: { model: "", reasoningEffort: "medium", serviceTier: "fast", ...legacyCodex, ...(current.codex || {}) },
171
+ claude: { model: "", ...(current.claude || {}) },
170
172
  // Desenler mevcut config'lere de eklenir ki kullanici bunlari Ayarlar'da gorup
171
173
  // kendi aboneligine gore duzenleyebilsin. Kendi degerini yazan kullanicininki korunur.
172
174
  opencode: {
@@ -15,7 +15,7 @@
15
15
  --headerbg:#0a0f1acc;--input:#0b111c;--term:#05080f;--termtx:#c7d3e6;--nodebg:#0b1220;--glow:#5e8bff26;--shadow:0 14px 44px #0009;--appbg:radial-gradient(1200px 560px at 12% -10%,#5e8bff16,transparent),radial-gradient(1000px 520px at 92% -8%,#a98bff10,transparent),var(--bg)}
16
16
  [data-theme=light]{--bg:#edf1f8;--panel:#ffffff;--panel2:#f3f6fc;--line:#dce4ef;--tx:#131a27;--dim:#5a6779;--acc:#3a63f0;--ok:#0ca678;--warn:#a76a10;--err:#dc434e;--purple:#7350f0;
17
17
  --headerbg:#ffffffe6;--input:#f4f7fd;--term:#0d1420;--termtx:#c7d3e6;--nodebg:#f7f9fd;--glow:#3a63f01c;--shadow:0 14px 40px #7a90b820;--appbg:radial-gradient(1200px 560px at 12% -10%,#3a63f012,transparent),radial-gradient(1000px 520px at 92% -8%,#7350f00e,transparent),var(--bg)}
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))}
18
+ [data-theme=light] .team-node.operator{border-color:color-mix(in srgb,var(--nacc) 40%,var(--line));background:linear-gradient(150deg,color-mix(in srgb,var(--nacc) 10%,var(--panel2)),var(--nodebg) 60%)}
19
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}
20
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}
21
21
  [data-theme=light] .fi-failure,[data-theme=light] .fi-blocked{background:#fdecef}
@@ -109,20 +109,29 @@ details summary{cursor:pointer;color:var(--dim);font-size:12px} pre{white-space:
109
109
  .live-details>summary{list-style:none;display:flex;align-items:center;gap:8px;font-weight:700}.live-details>summary::-webkit-details-marker{display:none}.live-details>summary:before{content:'›';font-size:18px;color:var(--dim)}.live-details[open]>summary:before{transform:rotate(90deg)}.live-details .live-body{padding-top:12px}
110
110
  /* Ekip haritasi — operasyon dugumleri */
111
111
  .mission-top{display:flex;align-items:center;gap:10px}
112
- .mission-title{font-weight:750;font-size:15px;letter-spacing:-.15px}.mission-sub{color:var(--dim);font-size:11px}
113
- .team-map{display:flex;align-items:center;gap:0;overflow-x:auto;padding:18px 8px 8px;min-height:154px;scrollbar-width:thin}
114
- .team-node{--nacc:var(--acc);position:relative;min-width:154px;display:flex;flex-direction:column;align-items:center;text-align:center;color:var(--tx);transition:filter .25s,transform .25s}
115
- .node-orb{position:relative;isolation:isolate;width:68px;height:68px;display:grid;place-items:center;border:1px solid transparent;border-radius:50%;background:radial-gradient(circle at 36% 28%,color-mix(in srgb,var(--nacc) 15%,var(--nodebg)),var(--nodebg) 58%) padding-box,conic-gradient(from 210deg,var(--nacc),#38577d 36%,var(--line) 61%,var(--nacc)) border-box;box-shadow:inset 0 0 0 6px color-mix(in srgb,var(--nacc) 7%,transparent),inset 0 -12px 24px #0003,0 14px 30px #0006;transition:box-shadow .25s,transform .25s}
116
- .node-orb:before{content:'';position:absolute;inset:-6px;border:1px dashed color-mix(in srgb,var(--nacc) 38%,transparent);border-radius:50%;animation:nodeOrbit 12s linear infinite}.node-orb:after{content:'';position:absolute;z-index:-1;inset:10px;border:1px solid color-mix(in srgb,var(--nacc) 30%,transparent);border-radius:50%;box-shadow:inset 0 0 18px color-mix(in srgb,var(--nacc) 10%,transparent)}
117
- .node-glyph{position:relative;z-index:1;font:850 14px/1 ui-monospace,Consolas,monospace;letter-spacing:1px;color:var(--nacc);text-shadow:0 0 12px color-mix(in srgb,var(--nacc) 65%,transparent)}
118
- .node-status{position:absolute;z-index:2;right:0;bottom:5px;width:13px;height:13px;border:3px solid var(--panel);border-radius:50%;background:var(--dim);box-shadow:0 0 0 1px var(--line)}
119
- .node-info{min-width:132px;margin-top:10px;padding:7px 10px;border:1px solid var(--line);border-radius:10px;background:linear-gradient(180deg,color-mix(in srgb,var(--panel2) 86%,transparent),var(--nodebg));box-shadow:0 8px 22px #0003}
120
- .team-node .nrole{font-size:8px;letter-spacing:1px;text-transform:uppercase;color:var(--dim)}
121
- .team-node .nname{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:750;font-size:12px;margin-top:1px}.team-node .nstate{font-size:9.5px;color:var(--dim);margin-top:2px}
122
- .team-node.operator .node-orb{background:linear-gradient(var(--nodebg),var(--nodebg)) padding-box,conic-gradient(from 210deg,var(--nacc),#64449a 42%,var(--line) 65%,var(--nacc)) border-box;box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--nacc) 9%,transparent),0 12px 30px #0005}.team-node.operator .node-glyph{color:var(--nacc)}
123
- .team-node.working{transform:translateY(-3px)}.team-node.working .node-orb{box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--ok) 10%,transparent),0 0 0 5px color-mix(in srgb,var(--ok) 8%,transparent),0 12px 34px color-mix(in srgb,var(--ok) 24%,transparent)}.team-node.working .node-status{background:var(--ok);box-shadow:0 0 10px var(--ok)}
124
- .team-node.done .node-status{background:var(--acc)}.team-node.failed .node-status{background:var(--err);box-shadow:0 0 9px var(--err)}.team-node.failed .node-orb{filter:saturate(.7)}
125
- .team-link{position:relative;height:2px;min-width:66px;flex:1;margin:0 -18px 54px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--acc) 48%,var(--line)) 0 5px,transparent 5px 12px);background-size:24px 2px;animation:teamRail 1.1s linear infinite;filter:drop-shadow(0 0 4px color-mix(in srgb,var(--acc) 22%,transparent))}.team-link:before{content:'';position:absolute;left:0;top:50%;width:7px;height:7px;border-radius:50%;background:var(--acc);box-shadow:0 0 9px var(--acc);transform:translate(-50%,-50%);opacity:0}.team-link:after{content:'';position:absolute;right:-1px;top:50%;width:7px;height:7px;border-top:2px solid var(--acc);border-right:2px solid var(--acc);transform:translateY(-50%) rotate(45deg);filter:drop-shadow(0 0 4px var(--acc))}.team-link.active{background:linear-gradient(90deg,transparent,var(--acc),transparent);box-shadow:0 0 12px color-mix(in srgb,var(--acc) 70%,transparent)}.team-link.active:before{opacity:1;animation:teamPulse 1.15s ease-in-out infinite}.team-link.active:after{border-color:var(--ok);filter:drop-shadow(0 0 5px var(--ok))}
112
+ .mission-title{font-weight:750;font-size:15px;letter-spacing:-.15px}.mission-sub{color:var(--dim);font-size:11px;margin-top:1px}
113
+ /* Butunlesik takim akisi paneli — bolum basliklari */
114
+ .flow-seg{margin-top:16px}
115
+ .flow-seg-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
116
+ .flow-seg-head .lbl{font-size:9.5px;font-weight:800;letter-spacing:1.3px;text-transform:uppercase;color:var(--dim)}
117
+ .flow-seg-head .ln{flex:1;height:1px;background:linear-gradient(90deg,color-mix(in srgb,var(--line) 85%,transparent),transparent)}
118
+ .flow-seg-head .hint{font-size:10.5px;color:var(--dim)}
119
+ /* Ekip seridi operator + uzman dugumleri yatay boru hatti cipleri */
120
+ .team-map{display:flex;align-items:center;gap:0;overflow-x:auto;padding:18px 10px 26px;scrollbar-width:thin}
121
+ .team-node{--nacc:var(--acc);position:relative;flex:none;display:flex;align-items:center;gap:13px;padding:12px 20px 12px 12px;border:1px solid color-mix(in srgb,var(--nacc) 26%,var(--line));border-radius:16px;background:linear-gradient(150deg,color-mix(in srgb,var(--nacc) 10%,var(--nodebg)),var(--nodebg) 62%);box-shadow:0 10px 26px #0004;color:var(--tx);transition:border-color .25s,box-shadow .25s,transform .2s}
122
+ .node-orb{position:relative;isolation:isolate;flex:none;width:54px;height:54px;display:grid;place-items:center;border:1px solid transparent;border-radius:50%;background:radial-gradient(circle at 36% 28%,color-mix(in srgb,var(--nacc) 22%,var(--nodebg)),var(--nodebg) 60%) padding-box,conic-gradient(from 210deg,var(--nacc),#38577d 40%,var(--line) 66%,var(--nacc)) border-box;box-shadow:inset 0 -7px 16px #0003,0 7px 16px #0004}
123
+ .node-orb:before{content:'';position:absolute;inset:-5px;border:1px dashed color-mix(in srgb,var(--nacc) 36%,transparent);border-radius:50%;opacity:0}
124
+ .team-node.working .node-orb:before{opacity:1;animation:nodeOrbit 9s linear infinite}
125
+ .node-glyph{position:relative;z-index:1;font:850 15px/1 ui-monospace,Consolas,monospace;letter-spacing:.5px;color:var(--nacc);text-shadow:0 0 11px color-mix(in srgb,var(--nacc) 60%,transparent)}
126
+ .node-status{position:absolute;z-index:2;right:-1px;bottom:1px;width:14px;height:14px;border:3px solid var(--nodebg);border-radius:50%;background:var(--dim)}
127
+ .node-info{min-width:0;text-align:left}
128
+ .team-node .nrole{font-size:8.5px;letter-spacing:1px;text-transform:uppercase;color:var(--dim)}
129
+ .team-node .nname{max-width:170px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:750;font-size:14.5px;margin-top:2px}.team-node .nstate{font-size:11.5px;color:var(--dim);margin-top:2px}
130
+ .team-node.operator .node-orb{background:radial-gradient(circle at 36% 28%,color-mix(in srgb,var(--nacc) 20%,var(--nodebg)),var(--nodebg) 60%) padding-box,conic-gradient(from 210deg,var(--nacc),#64449a 44%,var(--line) 68%,var(--nacc)) border-box}.team-node.operator .node-glyph{color:var(--nacc)}
131
+ .team-node.working{transform:translateY(-2px);border-color:color-mix(in srgb,var(--ok) 46%,var(--line));box-shadow:0 12px 30px color-mix(in srgb,var(--ok) 18%,transparent)}.team-node.working .nstate{color:var(--ok)}.team-node.working .node-status{background:var(--ok);box-shadow:0 0 9px var(--ok)}
132
+ .team-node.done{border-color:color-mix(in srgb,var(--acc) 38%,var(--line))}.team-node.done .node-status{background:var(--acc)}
133
+ .team-node.failed{border-color:color-mix(in srgb,var(--err) 48%,var(--line))}.team-node.failed .node-status{background:var(--err);box-shadow:0 0 8px var(--err)}.team-node.failed .node-orb{filter:saturate(.7)}
134
+ .team-link{position:relative;z-index:2;flex:none;align-self:center;height:2px;width:66px;margin:0 15px 0 -1px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--acc) 46%,var(--line)) 0 5px,transparent 5px 12px);background-size:24px 2px;animation:teamRail 1.1s linear infinite;filter:drop-shadow(0 0 3px color-mix(in srgb,var(--acc) 20%,transparent))}.team-link:before{content:'';position:absolute;left:0;top:50%;width:6px;height:6px;border-radius:50%;background:var(--acc);box-shadow:0 0 8px var(--acc);transform:translate(-50%,-50%);opacity:0}.team-link:after{content:'';position:absolute;right:-1px;top:50%;width:6px;height:6px;border-top:2px solid var(--acc);border-right:2px solid var(--acc);transform:translateY(-50%) rotate(45deg);filter:drop-shadow(0 0 3px var(--acc))}.team-link.active{background:linear-gradient(90deg,transparent,var(--acc),transparent);box-shadow:0 0 10px color-mix(in srgb,var(--acc) 60%,transparent)}.team-link.active:before{opacity:1;animation:teamPulse 1.15s ease-in-out infinite}.team-link.active:after{border-color:var(--ok);filter:drop-shadow(0 0 4px var(--ok))}
126
135
  @keyframes nodeOrbit{to{transform:rotate(360deg)}}@keyframes teamRail{to{background-position:24px 0}}@keyframes teamPulse{from{left:0}to{left:100%}}
127
136
  /* Agent calisma panelleri */
128
137
  .agent-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(330px,1fr));gap:14px}
@@ -147,23 +156,25 @@ details summary{cursor:pointer;color:var(--dim);font-size:12px} pre{white-space:
147
156
  .friendly-error b{display:block;color:#ff8f97;margin-bottom:3px;overflow-wrap:anywhere}.friendly-error small{color:#d5aeb2}
148
157
  .friendly-error.success{border-color:#42d39255;background:#10261e;color:#d7ffec}.friendly-error.success b{color:var(--ok)}.friendly-error.success small{color:#a7cfbd}
149
158
  .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}
150
- /* Birlesik akis — zaman cizelgesi (olay tipine gore renkli, uzun icerik katlanir) */
151
- .feed{max-height:360px;overflow:auto;display:flex;flex-direction:column;gap:2px;padding-left:2px}
152
- .feed-item{position:relative;display:flex;gap:9px;padding:6px 8px 6px 4px;border-radius:9px;transition:background .14s}
153
- .feed-item:hover{background:color-mix(in srgb,var(--panel2) 55%,transparent)}
154
- .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)}
155
- .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))}
159
+ /* Birlesik akis — sistem satirlari ince tek satir, mesaj/teslimat renk-kodlu belirgin kart */
160
+ .feed{max-height:460px;overflow:auto;display:flex;flex-direction:column;gap:6px;padding-right:2px}
161
+ .feed-item{position:relative;display:flex;gap:11px;padding:10px 13px;border-radius:12px;transition:background .14s,border-color .18s}
162
+ .fi-log{gap:9px;padding:5px 9px;border-radius:8px}
163
+ .fi-log:hover{background:color-mix(in srgb,var(--panel2) 50%,transparent)}
164
+ .fi-delegation,.fi-result,.fi-failure,.fi-blocked{border:1px solid color-mix(in srgb,var(--evc) 24%,var(--line));border-left:3px solid var(--evc);background:color-mix(in srgb,var(--evc) 6%,var(--panel2));box-shadow:0 6px 18px #00000026}
165
+ .fi-delegation:hover,.fi-result:hover,.fi-failure:hover,.fi-blocked:hover{border-color:color-mix(in srgb,var(--evc) 42%,var(--line));border-left-color:var(--evc)}
166
+ .fi-dot{position:relative;z-index:1;flex:none;margin-top:1px;width:23px;height:23px;border-radius:50%;display:grid;place-items:center;font:800 11px/1 ui-monospace,Consolas,monospace;color:var(--evc,var(--dim));border:1px solid color-mix(in srgb,var(--evc,var(--line)) 45%,var(--line));background:color-mix(in srgb,var(--evc,var(--dim)) 15%,var(--panel))}
167
+ .fi-log .fi-dot{width:19px;height:19px;font-size:10px}
156
168
  .fi-main{min-width:0;flex:1}
157
- .fi-head{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:11.5px;line-height:1.5}
158
- .fi-from{font-weight:700}.fi-arrow{color:var(--dim);opacity:.55}.fi-to{font-weight:600;color:var(--tx)}
159
- .fi-tag{font:10px ui-monospace,Consolas,monospace;color:var(--dim);border:1px solid var(--line);border-radius:5px;padding:0 5px}
169
+ .fi-head{display:flex;align-items:center;gap:7px;flex-wrap:wrap;font-size:12px;line-height:1.5}
170
+ .fi-from{font-weight:750}.fi-arrow{color:var(--dim);opacity:.55}.fi-to{font-weight:700;color:var(--tx)}
171
+ .fi-tag{font:10px ui-monospace,Consolas,monospace;color:var(--dim);border:1px solid var(--line);border-radius:6px;padding:1px 6px;background:color-mix(in srgb,var(--panel) 55%,transparent)}
160
172
  .fi-head time{margin-left:auto;color:var(--dim);font:10px ui-monospace,Consolas,monospace;flex:none}
161
- .fi-msg{color:var(--tx)}
162
- .fi-body{margin-top:3px;font-size:12px;line-height:1.55;color:var(--dim);white-space:pre-wrap;word-break:break-word}
163
- .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)}
164
- .fi-more{margin-top:3px;font-size:11px;color:var(--acc);background:none;border:0;padding:1px 0}.fi-more:hover{text-decoration:underline}
173
+ .fi-msg{color:var(--tx);font-size:12px}
174
+ .fi-body{margin-top:6px;font-size:11.5px;line-height:1.6;color:color-mix(in srgb,var(--tx) 78%,var(--dim));white-space:pre-wrap;word-break:break-word}
175
+ .fi-body.clamp{max-height:106px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 66%,transparent);mask-image:linear-gradient(180deg,#000 66%,transparent)}
176
+ .fi-more{margin-top:6px;font-size:11px;font-weight:600;color:var(--acc);background:color-mix(in srgb,var(--acc) 10%,transparent);border:1px solid color-mix(in srgb,var(--acc) 28%,var(--line));border-radius:7px;padding:3px 10px}.fi-more:hover{background:color-mix(in srgb,var(--acc) 17%,transparent)}
165
177
  .fi-delegation{--evc:var(--purple)}.fi-result{--evc:var(--ok)}.fi-failure,.fi-blocked{--evc:var(--err)}
166
- .fi-failure,.fi-blocked{background:color-mix(in srgb,var(--err) 7%,transparent)}
167
178
  .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)}
168
179
  .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}
169
180
  /* Teslimat */
@@ -332,10 +343,16 @@ details summary{cursor:pointer;color:var(--dim);font-size:12px} pre{white-space:
332
343
  <section class="stack">
333
344
  <div class="card mission-card" id="missionCard">
334
345
  <div class="mission-top"><div><div class="mission-title" id="missionTitle">Takım görevi bekleniyor</div><div class="mission-sub" id="missionSub">Operatör bir hedef aldığında ekibini burada kuracak.</div></div><div class="grow"></div><span class="mission-status" id="missionStatus" hidden><span class="sdot"></span><span id="missionStatusText">Hazır</span></span><span class="badge" id="missionStats">0 mesaj · 0 çağrı</span></div>
335
- <div id="teamMap" class="team-map"><div class="empty" style="width:100%">Henüz aktif ekip yok.</div></div>
336
346
  <div class="mission-banner" id="missionBanner"><div class="mb-ico" id="mbIco">✓</div><div style="min-width:0"><div class="mb-title" id="mbTitle"></div><div class="mb-answer" id="mbAnswer" hidden></div><div class="mb-sub" id="mbSub"></div></div></div>
347
+ <div class="flow-seg">
348
+ <div class="flow-seg-head"><span class="lbl">Ekip</span><span class="ln"></span><span class="hint">operatör + uzman ajanlar</span></div>
349
+ <div id="teamMap" class="team-map"><div class="empty" style="width:100%">Henüz aktif ekip yok.</div></div>
350
+ </div>
351
+ <div class="flow-seg">
352
+ <div class="flow-seg-head"><span class="lbl">Canlı akış</span><span class="ln"></span><span class="hint">canlı görev günlüğü</span></div>
353
+ <div id="feed" class="feed"><div class="empty">Henüz etkinlik yok.</div></div>
354
+ </div>
337
355
  </div>
338
- <div class="card"><div class="command-head"><h2>Takım akışı</h2><div class="grow"></div><span class="muted">Canlı görev günlüğü</span></div><div id="feed" class="feed"><div class="empty">Henüz etkinlik yok.</div></div></div>
339
356
  <details class="card live-details"><summary>Teknik ayrıntılar <span class="muted">Canlı agent terminalleri</span></summary><div class="live-body"><div class="command-head"><span class="muted">Operatör ve uzmanların ham çıktıları</span><div class="grow"></div><button class="sm" onclick="clearLive()">Temizle</button></div><div id="agentGrid" class="agent-grid"><div class="empty">Bir görev başladığında terminal çıktıları burada görünür.</div></div></div></details>
340
357
  </section>
341
358
  </main>
@@ -458,7 +475,7 @@ function cliColor(id){return CLI_COLOR[id]||CLI_COLOR.custom}
458
475
  // aktivite olaylarinda dogrudan CLI id'siyle gelir; uzman ise config.agents anahtaridir.
459
476
  function agentCli(name){if(!name)return 'custom';const key=String(name).toLowerCase();if(CLI_COLOR[key]&&key!=='custom')return key;const a=STATE.config.agents&&STATE.config.agents[name];return a?adapterOf(a):'custom'}
460
477
  function agentColor(name){return cliColor(agentCli(name))}
461
- const CLI_SETTINGS_DEFAULTS={codex:{model:'',reasoningEffort:'medium',serviceTier:'fast'},opencode:{model:''}};
478
+ const CLI_SETTINGS_DEFAULTS={codex:{model:'',reasoningEffort:'medium',serviceTier:'fast'},claude:{model:''},opencode:{model:''}};
462
479
  function cloneCliSettings(value){const source=value&&typeof value==='object'?value:{};return Object.fromEntries(Object.entries(CLI_SETTINGS_DEFAULTS).map(([id,defaults])=>[id,{...defaults,...(source[id]||{})}]))}
463
480
  function adapterOf(a){const b=String(a?.adapter||a?.cmd||'').toLowerCase();if(b.includes('codex'))return 'codex';if(b.includes('claude'))return 'claude';if(b.includes('gemini'))return 'gemini';if(b.includes('opencode'))return 'opencode';return b||'custom'}
464
481
  function commandAdapter(command){const b=String(command||'').replace(/\\/g,'/').split('/').pop().toLowerCase().replace(/\.(cmd|bat|exe|ps1)$/,'');if(b.includes('codex'))return 'codex';if(b.includes('claude'))return 'claude';if(b.includes('gemini'))return 'gemini';if(b.includes('opencode'))return 'opencode';return 'custom'}
@@ -681,15 +698,20 @@ window.addEventListener('pagehide',()=>{try{es?.close()}catch{}});
681
698
  window.addEventListener('pageshow',e=>{if(e.persisted)location.reload()});
682
699
 
683
700
  /* ---- Ayarlar ---- */
684
- function normalizeDraftAgentModels(){for(const agent of draftAgents){const adapter=adapterOf(agent);if(adapter==='claude'||adapter==='gemini'){delete agent.model;delete agent.modelOverride}else if(adapter==='opencode'&&agent.autoDiscovered&&agent.model&&agent.modelOverride!==true)delete agent.model}}
701
+ function normalizeDraftAgentModels(){for(const agent of draftAgents){const adapter=adapterOf(agent);if(adapter==='gemini'){delete agent.model;delete agent.modelOverride}else if(adapter==='opencode'&&agent.autoDiscovered&&agent.model&&agent.modelOverride!==true)delete agent.model}}
685
702
  function setAgentModel(index,value){const agent=draftAgents[index];if(!agent)return;const model=String(value||'').trim();if(model)agent.model=model;else delete agent.model;if(agent.autoDiscovered){if(model)agent.modelOverride=true;else delete agent.modelOverride}}
686
- function openSettings(){draftAgents=Object.entries(STATE.config.agents||{}).map(([name,a])=>({name,...JSON.parse(JSON.stringify(a))}));draftCliSettings=cloneCliSettings(STATE.config.cliSettings);normalizeDraftAgentAdapters();normalizeDraftAgentModels();draftIgnoredAdapters=[...(STATE.config.discoveryIgnoredAdapters||[])];$('#settings').classList.add('open');showTab('agents');ensureCodexModelCatalog().then(()=>{if(tab==='agents')refreshAgentCodexModels()}).catch(()=>{})}
703
+ function openSettings(){draftAgents=Object.entries(STATE.config.agents||{}).map(([name,a])=>({name,...JSON.parse(JSON.stringify(a))}));draftCliSettings=cloneCliSettings(STATE.config.cliSettings);normalizeDraftAgentAdapters();normalizeDraftAgentModels();draftIgnoredAdapters=[...(STATE.config.discoveryIgnoredAdapters||[])];$('#settings').classList.add('open');showTab('agents');Promise.allSettled([ensureCodexModelCatalog(),ensureClaudeModelCatalog()]).then(()=>{if(tab==='agents'){refreshAgentCodexModels();refreshAgentClaudeModels()}})}
687
704
  function closeSettings(){$('#settings').classList.remove('open')}
688
705
  function showTab(name){tab=name;document.querySelectorAll('.tabs button').forEach(b=>b.classList.toggle('active',b.dataset.tab===name));const c=$('#settingsContent');if(name==='agents')renderAgents(c);else if(name==='clis')renderClis(c);else if(name==='operator')renderOperator(c);else if(name==='skills')renderSkills(c);else renderGeneral(c)}
689
706
  function openCodeGroups(models){const groups=new Map();for(const model of [...new Set(models||[])]){const provider=String(model).split('/')[0];if(!groups.has(provider))groups.set(provider,[]);groups.get(provider).push(model)}const preferred=['opencode','opencode-go','minimax-coding-plan'];const providers=[...groups.keys()].sort((a,b)=>{const rank=x=>x==='ollama'?999:(preferred.includes(x)?preferred.indexOf(x):100);return rank(a)-rank(b)||a.localeCompare(b)});return providers.map(provider=>({provider,models:groups.get(provider).sort()}))}
690
707
  function openCodeModelOptions(selected,scope='global'){const status=(STATE.cliStatus||[]).find(x=>x.id==='opencode'),models=[...(status?.models||[])];if(selected&&!models.includes(selected))models.push(selected);const inherited=draftCliSettings.opencode?.model?`CLI ayarını kullan (${draftCliSettings.opencode.model})`:`CLI ayarını kullan (otomatik${status?.recommendedModel?`: ${status.recommendedModel}`:''})`,first=scope==='agent'?inherited:`Otomatik önerilen${status?.recommendedModel?` (${status.recommendedModel})`:''}`;return `<option value="">${esc(first)}</option>`+openCodeGroups(models).map(group=>`<optgroup label="${esc(group.provider)}">${group.models.map(model=>`<option value="${esc(model)}" ${model===selected?'selected':''}>${esc(model)}</option>`).join('')}</optgroup>`).join('')}
691
- function agentModelControl(a,i){const adapter=adapterOf(a),selected=String(a.model||'');if(adapter==='opencode')return `<label>OpenCode modeli</label><select onchange="setAgentModel(${i},this.value)">${openCodeModelOptions(selected,'agent')}</select>`;if(adapter==='codex'){const models=[...codexModelCatalog],inherited=draftCliSettings.codex?.model||'Codex varsayılanı';if(selected&&!models.some(x=>x.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});return `<label>Model</label><select class="agentCodexModel" data-agent-index="${i}" onchange="setAgentModel(${i},this.value)"><option value="">CLI ayarını kullan (${esc(inherited)})</option>${models.map(m=>`<option value="${esc(m.id)}" ${m.id===selected?'selected':''}>${esc(m.displayName||m.id)}</option>`).join('')}</select>`}return ''}
708
+ let claudeModelCatalog=[],claudeModelsLoaded=false,claudeModelsPromise=null;
709
+ async function ensureClaudeModelCatalog(){if(claudeModelsLoaded)return claudeModelCatalog;if(!claudeModelsPromise)claudeModelsPromise=api('/api/claude/models').then(response=>{claudeModelCatalog=response.models||[];claudeModelsLoaded=true;return claudeModelCatalog}).finally(()=>{claudeModelsPromise=null});return claudeModelsPromise}
710
+ function claudeModelLabel(model){const name=model.displayName||model.id,detail=model.description||model.resolvedModel||'',id=model.id||'';return [name,detail,id&&id!==name?`kimlik: ${id}`:''].filter(Boolean).join(' · ')}
711
+ function claudeModelOptions(selected,scope='global'){const defaultModel=claudeModelCatalog.find(model=>model.id==='default'),models=claudeModelCatalog.filter(model=>model.id!=='default');if(selected&&!models.some(model=>model.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});const inheritedId=draftCliSettings.claude?.model||'',inheritedModel=claudeModelCatalog.find(model=>model.id===inheritedId),inherited=inheritedId?(inheritedModel?claudeModelLabel(inheritedModel):inheritedId):(defaultModel?claudeModelLabel(defaultModel):'Claude Code varsayılanı'),first=scope==='agent'?`CLI ayarını kullan (${inherited})`:`Otomatik — ${defaultModel?claudeModelLabel(defaultModel):'hesap/sağlayıcı/kurum varsayılanı'}`;return `<option value="">${esc(first)}</option>`+models.map(model=>`<option value="${esc(model.id)}" ${model.id===selected?'selected':''}>${esc(claudeModelLabel(model))}</option>`).join('')}
712
+ function agentModelControl(a,i){const adapter=adapterOf(a),selected=String(a.model||'');if(adapter==='opencode')return `<label>OpenCode modeli</label><select onchange="setAgentModel(${i},this.value)">${openCodeModelOptions(selected,'agent')}</select>`;if(adapter==='claude')return `<label>Claude Code modeli</label><select class="agentClaudeModel" data-agent-index="${i}" onchange="setAgentModel(${i},this.value)">${claudeModelOptions(selected,'agent')}</select>`;if(adapter==='codex'){const models=[...codexModelCatalog],inherited=draftCliSettings.codex?.model||'Codex varsayılanı';if(selected&&!models.some(x=>x.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});return `<label>Model</label><select class="agentCodexModel" data-agent-index="${i}" onchange="setAgentModel(${i},this.value)"><option value="">CLI ayarını kullan (${esc(inherited)})</option>${models.map(m=>`<option value="${esc(m.id)}" ${m.id===selected?'selected':''}>${esc(m.displayName||m.id)}</option>`).join('')}</select>`}return ''}
692
713
  function refreshAgentCodexModels(){document.querySelectorAll('.agentCodexModel').forEach(select=>{const index=+select.dataset.agentIndex,selected=String(draftAgents[index]?.model||''),inherited=draftCliSettings.codex?.model||'Codex varsayılanı',models=[...codexModelCatalog];if(selected&&!models.some(x=>x.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});select.innerHTML=`<option value="">CLI ayarını kullan (${esc(inherited)})</option>`+models.map(m=>`<option value="${esc(m.id)}">${esc(m.displayName||m.id)}</option>`).join('');select.value=selected})}
714
+ function refreshAgentClaudeModels(){document.querySelectorAll('.agentClaudeModel').forEach(select=>{const index=+select.dataset.agentIndex,selected=String(draftAgents[index]?.model||'');select.innerHTML=claudeModelOptions(selected,'agent');select.value=selected})}
693
715
  function renderAgents(c){const found=(STATE.cliStatus||[]).map(x=>`<span class="badge" style="color:${x.installed&&(x.id!=='opencode'||x.ready!==false)?'var(--ok)':x.installed?'var(--warn)':'var(--dim)'}">${x.installed?'●':'○'} ${esc(x.id)} ${esc(x.id==='opencode'&&x.installed?(x.ready===false?'model seçilmeli':`${x.version||'kurulu'} · model hazır`):(x.version||'kurulu değil'))}</span>`).join(' '),open=(STATE.cliStatus||[]).find(x=>x.id==='opencode'),platform=STATE.platform==='win32'?'windows':STATE.platform==='darwin'?'macos':'linux',install=open?.install?.[platform];
694
716
  c.innerHTML=`<div class="item"><div class="row" style="flex-wrap:wrap"><b>Kurulu CLI taraması</b>${found}<div class="grow"></div>${draftIgnoredAdapters.length?`<button class="sm" onclick="restoreDiscovered()">Gizlenenleri geri getir (${draftIgnoredAdapters.length})</button>`:''}<button class="sm" onclick="scanClis()">Yeniden Tara</button></div><p class="muted">Kurulu CLI'lar otomatik tanınır. Otomatik oluşturulmuş bir agent'ı silerseniz tercihiniz korunur ve sonraki taramada geri eklenmez.</p>${open&&!open.installed?`<div style="border:1px solid var(--warn);border-radius:8px;padding:8px"><b style="color:var(--warn)">OpenCode kurulu değil</b><div class="muted">Bu cihaz için kurulum:</div><code>${esc(install||'npm install -g opencode-ai')}</code><div class="muted" style="margin-top:4px">Sağlayıcı bağlantısı: <code>${esc(open.install?.auth||'opencode auth login')}</code></div></div>`:''}</div>`
695
717
  +`<div class="hint">Buradaki agent'lar <b>operatörün altında çalışan uzmanlardır</b>. Operatör bunlardan biri değildir — ayrı "Operatör" sekmesinde bir CLI olarak seçilir. Bir uzmanı silmek operatörü etkilemez. Agent'ın <b>rolü görev türünü bağlayıcı biçimde sınırlar</b>: executor uygulama, reviewer inceleme, planner planlama yapar. Dengeli uygulama görevlerinde hazır üç rol <b>planner → executor → reviewer</b> sırasıyla aynı turda çalıştırılır.</div>`
@@ -699,7 +721,7 @@ function renderAgents(c){const found=(STATE.cliStatus||[]).map(x=>`<span class="
699
721
  if(currentRole&&workerRoles().includes(currentRole))loadRole()}
700
722
  function removeDraftAgent(index){const agent=draftAgents[index];if(agent?.autoDiscovered){const adapter=adapterOf(agent);if(CLI_LABEL[adapter]&&!draftIgnoredAdapters.includes(adapter))draftIgnoredAdapters.push(adapter)}draftAgents.splice(index,1);renderAgents($('#settingsContent'))}
701
723
  function addAgentFromCli(){const adapter=$('#newAgentCli')?.value||'custom',template=(STATE.cliStatus||[]).find(x=>x.id===adapter),requested=$('#newAgentName')?.value.trim();let base=requested||(adapter==='custom'?'yeni-agent':`${adapter}-agent`),name=base,n=2;while(draftAgents.some(a=>a.name===name))name=`${base}-${n++}`;draftAgents.push({name,adapter,cmd:template?.command||(adapter==='custom'?'':adapter),args:[...(template?.defaultArgs||[])],enabled:true,autoDiscovered:false,description:template?.description||'',capabilities:[...(template?.capabilities||['implementation'])],roleFile:template?.roleFile||'roles/executor.md',costTier:'standard',timeoutSeconds:template?.timeoutSeconds||STATE.config.agentTimeoutSeconds||900,silenceTimeoutSeconds:template?.silenceTimeoutSeconds||STATE.config.cliSilenceTimeoutSeconds||300});renderAgents($('#settingsContent'));notify(`${cliLabel(adapter)} varsayılanlarıyla ${name} oluşturuldu.`)}
702
- async function scanClis(){try{const localDrafts=draftAgents.map(a=>JSON.parse(JSON.stringify(a))),localCliSettings=cloneCliSettings(draftCliSettings);const r=await api('/api/cli/discover','POST',{ignoredAdapters:draftIgnoredAdapters});STATE.cliStatus=r.cliStatus;STATE.config={...r.config,cliSettings:localCliSettings,discoveryIgnoredAdapters:[...draftIgnoredAdapters]};draftCliSettings=localCliSettings;const serverAgents=Object.entries(r.config.agents||{}).map(([name,a])=>({name,...JSON.parse(JSON.stringify(a))}));for(const agent of serverAgents){if(!localDrafts.some(a=>a.name===agent.name||adapterOf(a)===adapterOf(agent)))localDrafts.push(agent)}draftAgents=localDrafts;showTab(tab);refreshOperatorSelect();notify(r.changed?'CLI taraması güncellendi; yerel taslaklar korundu.':'Tarama tamamlandı; yerel taslaklar korundu.')}catch(e){notify(e.message,true)}}
724
+ async function scanClis(){try{const localDrafts=draftAgents.map(a=>JSON.parse(JSON.stringify(a))),localCliSettings=cloneCliSettings(draftCliSettings);const r=await api('/api/cli/discover','POST',{ignoredAdapters:draftIgnoredAdapters});STATE.cliStatus=r.cliStatus;STATE.config={...r.config,cliSettings:localCliSettings,discoveryIgnoredAdapters:[...draftIgnoredAdapters]};draftCliSettings=localCliSettings;claudeModelCatalog=[];claudeModelsLoaded=false;const serverAgents=Object.entries(r.config.agents||{}).map(([name,a])=>({name,...JSON.parse(JSON.stringify(a))}));for(const agent of serverAgents){if(!localDrafts.some(a=>a.name===agent.name||adapterOf(a)===adapterOf(agent)))localDrafts.push(agent)}draftAgents=localDrafts;showTab(tab);refreshOperatorSelect();notify(r.changed?'CLI taraması güncellendi; yerel taslaklar korundu.':'Tarama tamamlandı; yerel taslaklar korundu.')}catch(e){notify(e.message,true)}}
703
725
  async function restoreDiscovered(){draftIgnoredAdapters=[];await scanClis()}
704
726
  async function selectRole(name){currentRole=name;await loadRole()}
705
727
  async function loadRole(){const r=await api(`/api/roles/${encodeURIComponent(currentRole)}`);const ed=$('#roleEditor');if(!ed)return;ed.innerHTML=`<label>roles/${esc(currentRole)}</label><textarea id="roleText">${esc(r.content)}</textarea><button class="primary sm" onclick="saveRole()">Rolü Kaydet</button> <button class="danger sm" onclick="deleteRoleFile()">Sil</button>`}
@@ -710,8 +732,9 @@ async function deleteRoleFile(){if(!await appConfirm({title:'Rol silinsin mi?',b
710
732
  let codexModelCatalog=[],codexModelsPromise=null;
711
733
  async function ensureCodexModelCatalog(){if(codexModelCatalog.length)return codexModelCatalog;if(!codexModelsPromise)codexModelsPromise=api('/api/codex/models').then(response=>{codexModelCatalog=response.models||[];return codexModelCatalog}).finally(()=>{codexModelsPromise=null});return codexModelsPromise}
712
734
  async function loadCodexModels(selected){const select=$('#codexModel');if(!select)return;try{await ensureCodexModelCatalog();if($('#codexModel')!==select)return;select.innerHTML='<option value="">Codex varsayılanı</option>'+codexModelCatalog.map(m=>`<option value="${esc(m.id)}">${esc(m.displayName||m.id)}</option>`).join('');if(selected&&!codexModelCatalog.some(m=>m.id===selected))select.insertAdjacentHTML('beforeend',`<option value="${esc(selected)}">${esc(selected)} (kayıtlı)</option>`);select.value=selected||'';const hint=$('#codexModelHint');if(hint)hint.textContent=`Codex hesabından ${codexModelCatalog.length} aktif model alındı; boş seçim CLI varsayılanını kullanır.`;updateCodexCapabilities()}catch(error){if($('#codexModel')!==select)return;select.innerHTML='<option value="">Codex varsayılanı</option>';if(selected){select.insertAdjacentHTML('beforeend',`<option value="${esc(selected)}">${esc(selected)} (kayıtlı)</option>`);select.value=selected}const hint=$('#codexModelHint');if(hint)hint.textContent='Aktif model listesi alınamadı: '+error.message}}
735
+ async function loadClaudeModels(selected){const select=$('#claudeModel');if(!select)return;try{await ensureClaudeModelCatalog();if($('#claudeModel')!==select)return;select.innerHTML=claudeModelOptions(selected,'global');select.value=selected||'';const hint=$('#claudeModelHint');if(hint)hint.textContent=`Bu Claude Code hesabı, sağlayıcısı ve kurum politikası için ${claudeModelCatalog.filter(model=>model.id!=='default').length} seçilebilir model bildirildi. Liste bir model isteği çalıştırılmadan Claude Code'dan alınır.`;refreshAgentClaudeModels()}catch(error){if($('#claudeModel')!==select)return;claudeModelCatalog=[];select.innerHTML=claudeModelOptions(selected,'global');select.value=selected||'';const hint=$('#claudeModelHint');if(hint)hint.textContent='Claude Code model kataloğu alınamadı; yalnızca varsayılan veya önceden kayıtlı seçim kullanılabilir: '+error.message}}
713
736
  function updateCodexCapabilities(){const settings=draftCliSettings.codex||(draftCliSettings.codex={...CLI_SETTINGS_DEFAULTS.codex}),chosen=$('#codexModel')?.value||'',model=codexModelCatalog.find(x=>x.id===chosen)||(chosen?'':codexModelCatalog.find(x=>x.isDefault)),effort=$('#codexEffort'),tier=$('#codexServiceTier');settings.model=chosen;if(effort){const values=(model?.reasoningEfforts||[]).map(x=>x.id);if(!values.length)values.push('low','medium','high','xhigh','max','ultra');const current=settings.reasoningEffort||'medium';effort.innerHTML=values.map(x=>`<option value="${esc(x)}">${esc(x)}</option>`).join('');effort.value=values.includes(current)?current:(model?.defaultReasoningEffort||values[0]);settings.reasoningEffort=effort.value}if(tier){const values=(model?.serviceTiers||[]).map(x=>({id:x.id,label:x.name||x.id}));if(!values.length)values.push({id:'fast',label:'fast'},{id:'priority',label:'priority'});const current=settings.serviceTier||'fast';tier.innerHTML=values.map(x=>`<option value="${esc(x.id)}">${esc(x.label)}${x.label!==x.id?' ('+esc(x.id)+')':''}</option>`).join('');tier.value=values.some(x=>x.id===current)?current:(model?.defaultServiceTier||values[0].id);settings.serviceTier=tier.value}}
714
- function renderClis(c){const statuses=STATE.cliStatus||[],available=statuses.filter(x=>x.installed&&CLI_LABEL[x.id]);const ids=available.length?available.map(x=>x.id):Object.keys(CLI_LABEL),cards=[];for(const id of ids){const status=statuses.find(x=>x.id===id),defaults=CLI_SETTINGS_DEFAULTS[id],settings=defaults?(draftCliSettings[id]||(draftCliSettings[id]={...defaults})):null;if(id==='codex')cards.push(`<div class="item"><div class="row"><b>Codex</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><div class="grid2"><div><label>Model</label><select id="codexModel" onchange="draftCliSettings.codex.model=this.value;updateCodexCapabilities()"><option value="">Modeller yükleniyor…</option></select><div id="codexModelHint" class="muted">Codex hesabındaki aktif modeller getiriliyor.</div></div><div><label>Reasoning effort</label><select id="codexEffort" onchange="draftCliSettings.codex.reasoningEffort=this.value"><option>${esc(settings.reasoningEffort)}</option></select></div><div><label>Servis hızı</label><select id="codexServiceTier" onchange="draftCliSettings.codex.serviceTier=this.value"><option>${esc(settings.serviceTier)}</option></select></div></div></div>`);else if(id==='opencode')cards.push(`<div class="item"><div class="row"><b>OpenCode</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><label>Model</label><select id="opencodeModel" onchange="draftCliSettings.opencode.model=this.value">${openCodeModelOptions(settings.model,'global')}</select><div class="muted">Tüm sağlayıcılar listelenir; boş seçim uygun modeli otomatik seçer.</div></div>`);else cards.push(`<div class="item"><div class="row"><b>${esc(cliLabel(id))}</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><div class="muted">Bu CLI için doğrulanmış model kataloğu yok; model seçimi sunulmaz ve CLI varsayılanı kullanılır.</div></div>`) }c.innerHTML=`<div class="hint">Model seçimi yalnızca doğrulanmış katalog sağlayan CLI'larda gösterilir. Seçilen model o CLI'nın <b>tüm kullanımlarına</b> uygulanır; agent ekranındaki boş override alanı miras alınan modeli adıyla gösterir.</div>${cards.join('')}`;if(ids.includes('codex'))loadCodexModels(draftCliSettings.codex.model||'')}
737
+ function renderClis(c){const statuses=STATE.cliStatus||[],available=statuses.filter(x=>x.installed&&CLI_LABEL[x.id]);const ids=available.length?available.map(x=>x.id):Object.keys(CLI_LABEL),cards=[];for(const id of ids){const status=statuses.find(x=>x.id===id),defaults=CLI_SETTINGS_DEFAULTS[id],settings=defaults?(draftCliSettings[id]||(draftCliSettings[id]={...defaults})):null;if(id==='codex')cards.push(`<div class="item"><div class="row"><b>Codex</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><div class="grid2"><div><label>Model</label><select id="codexModel" onchange="draftCliSettings.codex.model=this.value;updateCodexCapabilities()"><option value="">Modeller yükleniyor…</option></select><div id="codexModelHint" class="muted">Codex hesabındaki aktif modeller getiriliyor.</div></div><div><label>Reasoning effort</label><select id="codexEffort" onchange="draftCliSettings.codex.reasoningEffort=this.value"><option>${esc(settings.reasoningEffort)}</option></select></div><div><label>Servis hızı</label><select id="codexServiceTier" onchange="draftCliSettings.codex.serviceTier=this.value"><option>${esc(settings.serviceTier)}</option></select></div></div></div>`);else if(id==='claude')cards.push(`<div class="item"><div class="row"><b>Claude Code</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><label>Model</label><select id="claudeModel" onchange="draftCliSettings.claude.model=this.value"><option value="">Modeller yükleniyor…</option></select><div id="claudeModelHint" class="muted">Bu kullanıcının Claude Code hesabı, sağlayıcısı ve kurum politikası için kullanılabilir modeller getiriliyor.</div></div>`);else if(id==='opencode')cards.push(`<div class="item"><div class="row"><b>OpenCode</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><label>Model</label><select id="opencodeModel" onchange="draftCliSettings.opencode.model=this.value">${openCodeModelOptions(settings.model,'global')}</select><div class="muted">Tüm sağlayıcılar listelenir; boş seçim uygun modeli otomatik seçer.</div></div>`);else cards.push(`<div class="item"><div class="row"><b>${esc(cliLabel(id))}</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><div class="muted">Bu CLI için doğrulanmış model kataloğu yok; model seçimi sunulmaz ve CLI varsayılanı kullanılır.</div></div>`) }c.innerHTML=`<div class="hint">Model seçimi yalnızca doğrulanmış katalog sağlayan CLI'larda gösterilir. Seçilen model o CLI'nın <b>tüm kullanımlarına</b> uygulanır; agent ekranındaki boş override alanı miras alınan modeli adıyla gösterir.</div>${cards.join('')}`;if(ids.includes('codex'))loadCodexModels(draftCliSettings.codex.model||'');if(ids.includes('claude'))loadClaudeModels(draftCliSettings.claude.model||'')}
715
738
  function renderOperator(c){const o=STATE.config.operator||{},opts=operatorCliOptions(true),curCli=opts.some(x=>x.cli===o.cli)?o.cli:opts[0]?.cli;c.innerHTML=`<div class="hint">Operatör, uzman agent'lardan <b>bağımsız bir CLI'dır</b>. Burada yalnızca ekip yönetimi ve tur sınırları ayarlanır. CLI modellerini “CLI'lar ve Modeller” sekmesinden seçebilirsiniz.</div><div class="item"><div class="grid2"><div><label>Operatör CLI'sı — operator.md'yi çalıştıracak CLI</label><select id="defaultOperator">${opts.map(x=>`<option value="${x.cli}" ${x.cli===curCli?'selected':''}>${esc(x.label)}</option>`).join('')||'<option value="">Kurulu CLI yok</option>'}</select></div><div><label>Maksimum takım turu</label><input id="maxRounds" type="number" value="${o.maxRounds||6}"></div><div><label>Tur başına maksimum delegasyon</label><input id="maxDelegations" type="number" value="${o.maxDelegationsPerRound||8}"></div><div><label>JSON protokol tekrarları</label><input id="protocolRetries" type="number" value="${o.protocolRetries??1}"></div></div></div><div class="item"><label>Operatör rolü — roles/operator.md (akışı buradan kurgularsınız)</label><div id="operatorRoleBox" class="muted">yükleniyor…</div></div>`;loadOperatorRole()}
716
739
  async function loadOperatorRole(){try{const r=await api('/api/roles/operator.md');const box=$('#operatorRoleBox');if(box)box.innerHTML=`<textarea id="operatorRoleText">${esc(r.content)}</textarea><button class="primary sm" style="margin-top:6px" onclick="saveOperatorRole()">Operatör rolünü kaydet</button>`}catch(e){const box=$('#operatorRoleBox');if(box)box.textContent=e.message}}
717
740
  async function saveOperatorRole(){await api('/api/roles/operator.md','PUT',{content:$('#operatorRoleText').value});notify('Operatör rolü kaydedildi')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omerrgocmen/crewctl",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
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",