@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
|
@@ -9,11 +9,49 @@ const checkpoints = require("./checkpoints");
|
|
|
9
9
|
|
|
10
10
|
const isWin = process.platform === "win32";
|
|
11
11
|
|
|
12
|
+
// Ajan hapsi (path-aware, Claude icin). cfg.sandbox.mode === "workspace" iken orkestratorun
|
|
13
|
+
// kendi dizinlerini (veri koku ROOT = config/memory/state, paket ASSETS, kurulum WORK_BASE)
|
|
14
|
+
// Claude'un gozunden gizler: Read/Edit/Write bu yollara permissions.deny ile kapatilir.
|
|
15
|
+
// Docker/Git GEREKTIRMEZ. Codex ayni korumayi effectiveAgent'ta OS-native sandbox ile alir;
|
|
16
|
+
// gemini/opencode icin path-scoped native kural olmadigindan yalnizca prompt talimati gecerli.
|
|
17
|
+
// Calisma klasoru bu korunan dizinlerden birinin ICINDEYSE (or. gelistirme modunda repo'nun
|
|
18
|
+
// kendisinde calismak) o dizin KORUNMAZ — mevcut is bozulmasin diye.
|
|
19
|
+
function claudeSandboxSettings(agent, cwd, cfg) {
|
|
20
|
+
if (cfg?.sandbox?.mode !== "workspace") return null;
|
|
21
|
+
const adapter = agent?.adapter || cliRegistry.adapterId(agent?.cmd);
|
|
22
|
+
if (adapter !== "claude") return null;
|
|
23
|
+
const norm = (p) => path.resolve(p).replace(/\\/g, "/");
|
|
24
|
+
// Windows dosya sistemi buyuk/kucuk harfe duyarsizdir: C:\Test ile c:\test ayni dizindir.
|
|
25
|
+
// Karsilastirmayi case-fold'la yap; aksi halde farkli case yazilmis bir cwd, kurulum
|
|
26
|
+
// dizinini yanlislikla "disarida" sanip DENY eder (ajan kendi calisma agacinin ustune
|
|
27
|
+
// erisemez, hatta cwd==kurulum dizini iken kendi klasorune yazamaz).
|
|
28
|
+
const fold = (p) => (isWin ? p.toLowerCase() : p);
|
|
29
|
+
const workDir = norm(cwd);
|
|
30
|
+
const workCmp = fold(workDir);
|
|
31
|
+
const overlaps = (dir) => {
|
|
32
|
+
const d = fold(dir);
|
|
33
|
+
return d === workCmp || d.startsWith(workCmp + "/") || workCmp.startsWith(d + "/");
|
|
34
|
+
};
|
|
35
|
+
const protectedDirs = [store.ROOT, store.ASSETS, store.WORK_BASE]
|
|
36
|
+
.map(norm)
|
|
37
|
+
.filter((dir, i, all) => all.findIndex((other) => fold(other) === fold(dir)) === i)
|
|
38
|
+
.filter((dir) => !overlaps(dir));
|
|
39
|
+
if (!protectedDirs.length) return null;
|
|
40
|
+
const deny = [];
|
|
41
|
+
for (const dir of protectedDirs) {
|
|
42
|
+
for (const tool of ["Read", "Edit", "Write"]) deny.push(`${tool}(${dir}/**)`);
|
|
43
|
+
}
|
|
44
|
+
return { permissions: { deny } };
|
|
45
|
+
}
|
|
46
|
+
|
|
12
47
|
function snapshotDir(dir) {
|
|
13
48
|
const map = new Map();
|
|
14
49
|
const ignored = (rel) => {
|
|
15
50
|
const r = rel.replace(/\\/g, "/");
|
|
51
|
+
// .crewctl/ orkestratorun kendi proje-context'ini (CONTEXT.md) tutar; gorev diff'lerinde,
|
|
52
|
+
// live-diff'te ve teslimat dosya listesinde gozukmesin (kullanicinin isi degil).
|
|
16
53
|
return r.includes("node_modules") || r.includes(".git/") || r.startsWith(".git") ||
|
|
54
|
+
r === ".crewctl" || r.startsWith(".crewctl/") ||
|
|
17
55
|
r.startsWith("orchestrator/queue") || r.startsWith("orchestrator/state") ||
|
|
18
56
|
r.startsWith("orchestrator/memory") || r.startsWith("orchestrator/node_modules");
|
|
19
57
|
};
|
|
@@ -223,6 +261,14 @@ function clip(value, limit = 12000) {
|
|
|
223
261
|
return text.length <= limit ? text : `${text.slice(0, limit)}\n...[kesildi]`;
|
|
224
262
|
}
|
|
225
263
|
|
|
264
|
+
// Sohbet odakli CLI'lar profili bazen ``` ile sarar; profil dosyasina ham metni yazariz.
|
|
265
|
+
// Yalnizca tum icerigi saran TEK bir dis fence'i soyar; ic kod bloklarina dokunmaz.
|
|
266
|
+
function stripCodeFence(text) {
|
|
267
|
+
const trimmed = String(text || "").trim();
|
|
268
|
+
const match = trimmed.match(/^```[a-zA-Z0-9]*\s*\n([\s\S]*?)\n?```$/);
|
|
269
|
+
return match ? match[1] : trimmed;
|
|
270
|
+
}
|
|
271
|
+
|
|
226
272
|
// Bas ve sonu koruyarak kirpar. Denetci raporlarinda VERDICT satiri metnin SONUNDadir;
|
|
227
273
|
// yalnizca bastan kirpmak operatorun karari gormemesine ve gereksiz yeniden inceleme
|
|
228
274
|
// turlarina yol acar.
|
|
@@ -257,20 +303,30 @@ function meaningfulErrorLine(raw) {
|
|
|
257
303
|
// daraltildi ve kimlik kontrollerinin arkasina alindi.
|
|
258
304
|
function classifyCliError(error) {
|
|
259
305
|
const raw = String(error?.message || error || "Bilinmeyen CLI hatasi");
|
|
260
|
-
|
|
306
|
+
// Hatanin geldigi CLI biliniyorsa tavsiye ona gore uretilir (runCli her hatayi etiketler).
|
|
307
|
+
const adapter = error?.adapter || "";
|
|
308
|
+
const agentName = error?.agentName || "";
|
|
309
|
+
const of = (code, summary, action) => ({ code, summary, action, agent: agentName, adapter, raw: clip(raw, 5000) });
|
|
261
310
|
if (/requires a newer version|upgrade to the latest|model metadata.*not found|unsupported.*model|model.*unsupported/i.test(raw)) {
|
|
262
311
|
return of("VERSION_INCOMPATIBLE", "CLI sürümü seçilen modeli desteklemiyor.", "CLI aracını güncelleyin veya desteklenen bir model seçin.");
|
|
263
312
|
}
|
|
264
313
|
if (/sessiz kaldi|cikti uretmedi|CLI_STALLED/i.test(raw)) {
|
|
265
314
|
return of("CLI_STALLED", "CLI uzun süre yeni çıktı üretmeyince otomatik durduruldu.", "Önceki ilerleme kayıtları korundu; operatör bu oturumda farklı bir agent kullanacak.");
|
|
266
315
|
}
|
|
316
|
+
// "No provider available" 401 ile gelir ama OTURUM sorunu degildir: secili model hicbir
|
|
317
|
+
// saglayiciya cozulemiyordur. Genel 401/403 kuralindan ONCE gelmeli, yoksa zaten girisli
|
|
318
|
+
// kullaniciya bosuna "oturum acin" denir.
|
|
319
|
+
if (/no provider available/i.test(raw)) {
|
|
320
|
+
return of("PROVIDER_UNAVAILABLE", "Seçili model hiçbir yapılandırılmış sağlayıcıya çözümlenemedi (oturum sorunu değil).",
|
|
321
|
+
"Ayarlar → Agent'lar bölümünde bu agent için açık bir model seçin. Model boşken OpenCode kendi son kullandığı modele düşer; o model erişilemezse görev bu hatayla durur.");
|
|
322
|
+
}
|
|
267
323
|
if (/API key not valid|API_KEY_INVALID|invalid[_ ]api[_ ]key|incorrect api key/i.test(raw)) {
|
|
268
|
-
return of("AUTH_INVALID", "API anahtarı geçersiz veya reddedildi.",
|
|
324
|
+
return of("AUTH_INVALID", "API anahtarı geçersiz veya reddedildi.", `Bu agent'ın API anahtarını yenileyin. ${cliRegistry.authHint(adapter)}`);
|
|
269
325
|
}
|
|
270
326
|
// Gemini CLI oturum acilmadiginda "Please set an Auth method" / "GEMINI_API_KEY environment
|
|
271
327
|
// variable not found" yazar; her ikisi de kurulum degil OTURUM sorunudur.
|
|
272
328
|
if (/set an auth method|GEMINI_API_KEY|GOOGLE_API_KEY|unauthorized|unauthenticated|authentication|login required|not logged in|please (run )?login|credentials? (not found|missing|expired)|PERMISSION_DENIED|\b40[13]\b/i.test(raw)) {
|
|
273
|
-
return of("AUTH_REQUIRED", "CLI oturum açmamış; kimlik doğrulaması gerekiyor.",
|
|
329
|
+
return of("AUTH_REQUIRED", "CLI oturum açmamış; kimlik doğrulaması gerekiyor.", `${cliRegistry.authHint(adapter)} Sonra görevi tekrar deneyin.`);
|
|
274
330
|
}
|
|
275
331
|
// Kota (gunluk/aylik hak bitti) ile hiz siniri (kisa sureli) AYRI teshislerdir: ilki
|
|
276
332
|
// beklemekle gecmez, ikincisi gecer. Kullanicinin sordugu "limitim mi bitti" ayrimi budur.
|
|
@@ -287,13 +343,16 @@ function classifyCliError(error) {
|
|
|
287
343
|
return of("REGION_BLOCKED", "Model bulunduğunuz bölgede kullanıma kapalı.", "Desteklenen bir bölge/hesap kullanın veya başka bir sağlayıcının agent'ını seçin.");
|
|
288
344
|
}
|
|
289
345
|
if (/ConnectionRefused|Unable to connect|provider hatasi|provider error|ECONNREFUSED/i.test(raw)) {
|
|
290
|
-
return of("PROVIDER_UNAVAILABLE", "Seçilen model sağlayıcısına
|
|
346
|
+
return of("PROVIDER_UNAVAILABLE", "Seçilen model sağlayıcısına bağlanılamadı veya model çözümlenemedi.",
|
|
347
|
+
adapter === "opencode"
|
|
348
|
+
? "Ayarlar → Agent'lar bölümünde bu agent için açık bir model seçin (`opencode models` listesinden). Model seçili değilken OpenCode kendi son kullandığı modele düşer ve bu model erişilemez olabilir."
|
|
349
|
+
: "Sağlayıcı bağlantısını kontrol edin veya başka bir model seçin.");
|
|
291
350
|
}
|
|
292
351
|
if (/ENOTFOUND|EAI_AGAIN|ECONNRESET|ETIMEDOUT|getaddrinfo|fetch failed|socket hang up|network (error|unreachable)|proxy/i.test(raw)) {
|
|
293
352
|
return of("NETWORK_ERROR", "Ağ bağlantısı kurulamadı (DNS/proxy/internet).", "İnternet veya kurumsal proxy ayarlarınızı kontrol edip görevi tekrar çalıştırın.");
|
|
294
353
|
}
|
|
295
354
|
if (/not recognized as an internal|is not recognized|command not found|spawn .*ENOENT|\bENOENT\b|no such file or directory/i.test(raw)) {
|
|
296
|
-
return of("CLI_NOT_FOUND", "CLI komutu bulunamadı; kurulu değil veya PATH üzerinde görünmüyor.", "CLI'
|
|
355
|
+
return of("CLI_NOT_FOUND", "CLI komutu bulunamadı; kurulu değil veya PATH üzerinde görünmüyor.", `${adapter && adapter !== "custom" ? `${adapter} CLI'sini kurun` : "CLI'yi kurun"} ve Ayarlar → Agent'lar bölümünde komut adını doğrulayın (kurulum komutları için Doctor ekranına bakın).`);
|
|
297
356
|
}
|
|
298
357
|
if (/timeout|timed out|zaman asim|zaman aşım/i.test(raw)) {
|
|
299
358
|
return of("TIMEOUT", "CLI ayrılan süre içinde tamamlanamadı.", "Agent ayarlarından timeout değerini artırın veya görevi daha küçük parçalara bölün.");
|
|
@@ -305,6 +364,21 @@ const RECOVERABLE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "RATE_L
|
|
|
305
364
|
// Karantina = bu oturumda tekrar denemek anlamsiz. Kota/bolge kisiti beklemekle gecmez;
|
|
306
365
|
// hiz siniri (RATE_LIMIT) ve gecici saglayici hatasi (MODEL_OVERLOADED) gecer, karantinaya alinmaz.
|
|
307
366
|
const QUARANTINE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "QUOTA_EXCEEDED", "REGION_BLOCKED", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "CLI_STALLED", "VERSION_INCOMPATIBLE"]);
|
|
367
|
+
// Birkac saniyede kendiliginden gecer; operatore geri tasiyip planlama turu harcamak yerine
|
|
368
|
+
// ayni agent ustel bekleme ile yeniden denenir.
|
|
369
|
+
const TRANSIENT_CLI_ERRORS = new Set(["RATE_LIMIT", "MODEL_OVERLOADED", "NETWORK_ERROR"]);
|
|
370
|
+
// Karantina suresi (sn). 0 = beklemekle duzelmez, kalici. Digerleri sure dolunca yeniden
|
|
371
|
+
// denenir (half-open); tekrar duserse sure katlanir (bkz. quarantineAgent).
|
|
372
|
+
const QUARANTINE_COOLDOWN_SECONDS = {
|
|
373
|
+
AUTH_REQUIRED: 180,
|
|
374
|
+
AUTH_INVALID: 180,
|
|
375
|
+
PROVIDER_UNAVAILABLE: 180,
|
|
376
|
+
CLI_STALLED: 300,
|
|
377
|
+
QUOTA_EXCEEDED: 0,
|
|
378
|
+
REGION_BLOCKED: 0,
|
|
379
|
+
CLI_NOT_FOUND: 0,
|
|
380
|
+
VERSION_INCOMPATIBLE: 0,
|
|
381
|
+
};
|
|
308
382
|
|
|
309
383
|
function resolveExecutionMode(task) {
|
|
310
384
|
if (["fast", "balanced", "deep"].includes(task.executionMode)) return task.executionMode;
|
|
@@ -379,7 +453,6 @@ function normalizeCliOutput(agent, stdout) {
|
|
|
379
453
|
const event = JSON.parse(line);
|
|
380
454
|
if (event.type === "text" && event.part?.text) texts.push(String(event.part.text));
|
|
381
455
|
if (event.type === "error") errors.push(String(event.error?.message || event.error || "OpenCode model/provider hatasi"));
|
|
382
|
-
// Bir kosumda birden fazla adim olabilir; hepsi toplanir.
|
|
383
456
|
if (event.type === "step_finish") {
|
|
384
457
|
const step = readStepUsage(event);
|
|
385
458
|
if (step) usage = addUsage(usage, step);
|
|
@@ -592,6 +665,15 @@ function compatibleAgentForKind(cfg, operatorName, kind) {
|
|
|
592
665
|
)?.[0] || "";
|
|
593
666
|
}
|
|
594
667
|
|
|
668
|
+
// Devretme adaylari. Karantina filtresi cagirana birakilir: sureli oldugu icin karar canli
|
|
669
|
+
// durumdan (isQuarantined) okunmalidir.
|
|
670
|
+
function compatibleAgentsForKind(cfg, operatorName, kind) {
|
|
671
|
+
return Object.entries(cfg.agents || {})
|
|
672
|
+
.filter(([name, agent]) =>
|
|
673
|
+
name !== operatorName && agent.enabled !== false && agentUsable(agent) && supportsKind(agent, kind))
|
|
674
|
+
.map(([name]) => name);
|
|
675
|
+
}
|
|
676
|
+
|
|
595
677
|
function nextAssignmentId(base, usedIds) {
|
|
596
678
|
let id = base;
|
|
597
679
|
let suffix = 2;
|
|
@@ -672,6 +754,7 @@ class Engine extends EventEmitter {
|
|
|
672
754
|
// Kalici config'i degistirmeyiz; kullanici kimlik bilgisini duzeltip motoru yeniden
|
|
673
755
|
// baslattiginda agent tekrar denenebilir.
|
|
674
756
|
this.unhealthyAgents = new Map();
|
|
757
|
+
this._retrySleepers = new Set();
|
|
675
758
|
// Canli kodlama akisi: gorev calisirken calisma klasorunu periyodik tarayan zamanlayici
|
|
676
759
|
// ve son yayinlanan degisiklik imzasi (ayni durum tekrar tekrar yayinlanmasin diye).
|
|
677
760
|
this._liveTimer = null;
|
|
@@ -818,6 +901,7 @@ class Engine extends EventEmitter {
|
|
|
818
901
|
stop() {
|
|
819
902
|
this.running = false;
|
|
820
903
|
this.wake();
|
|
904
|
+
this.cancelRetrySleeps();
|
|
821
905
|
this.stopLiveDiff();
|
|
822
906
|
if (this.activeChild) {
|
|
823
907
|
try { this.activeChild.kill(); } catch {}
|
|
@@ -842,6 +926,58 @@ class Engine extends EventEmitter {
|
|
|
842
926
|
});
|
|
843
927
|
}
|
|
844
928
|
|
|
929
|
+
// sleepWake'ten AYRIDIR: o bos donguye aittir ve yeni gorev eklendiginde uyanir. Bu bekleme
|
|
930
|
+
// yalnizca motor durdurulunca kesilmeli.
|
|
931
|
+
sleepRetry(ms) {
|
|
932
|
+
return new Promise((resolve) => {
|
|
933
|
+
const sleeper = { resolve, timer: null };
|
|
934
|
+
sleeper.timer = setTimeout(() => { this._retrySleepers.delete(sleeper); resolve(); }, ms);
|
|
935
|
+
this._retrySleepers.add(sleeper);
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
cancelRetrySleeps() {
|
|
940
|
+
for (const sleeper of this._retrySleepers) {
|
|
941
|
+
clearTimeout(sleeper.timer);
|
|
942
|
+
sleeper.resolve();
|
|
943
|
+
}
|
|
944
|
+
this._retrySleepers.clear();
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Sureli karantina: kullanici oturum acar veya saglayici toparlanirsa agent kendiliginden
|
|
948
|
+
// doner. Tekrar duserse ceza katlanir (en fazla 15 dk); suresi 0 olanlar kalici kalir.
|
|
949
|
+
quarantineAgent(name, code, cfg) {
|
|
950
|
+
const strikes = (this.unhealthyAgents.get(name)?.strikes || 0) + 1;
|
|
951
|
+
const baseSeconds = QUARANTINE_COOLDOWN_SECONDS[code] ?? 120;
|
|
952
|
+
const cooldownMs = baseSeconds > 0 ? Math.min(900, baseSeconds * strikes) * 1000 : 0;
|
|
953
|
+
this.unhealthyAgents.set(name, {
|
|
954
|
+
code, strikes, at: new Date().toISOString(),
|
|
955
|
+
until: cooldownMs ? Date.now() + cooldownMs : 0,
|
|
956
|
+
});
|
|
957
|
+
if (cfg) cfg.runtimeUnavailableAgents = this.quarantinedAgents();
|
|
958
|
+
this.publish("log", {
|
|
959
|
+
level: "warn",
|
|
960
|
+
msg: cooldownMs
|
|
961
|
+
? `${name} [${code}] nedeniyle ${Math.round(cooldownMs / 1000)} sn karantinada; sure dolunca otomatik yeniden denenecek.`
|
|
962
|
+
: `${name} [${code}] nedeniyle bu oturumda kullanim disi (beklemekle duzelmez).`,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
isQuarantined(name) {
|
|
967
|
+
const entry = this.unhealthyAgents.get(name);
|
|
968
|
+
if (!entry) return false;
|
|
969
|
+
if (entry.until && Date.now() >= entry.until) {
|
|
970
|
+
this.unhealthyAgents.delete(name);
|
|
971
|
+
this.publish("log", { level: "info", msg: `${name} karantina suresi doldu; yeniden deneme hakki verildi.` });
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
quarantinedAgents() {
|
|
978
|
+
return [...this.unhealthyAgents.keys()].filter((name) => this.isQuarantined(name));
|
|
979
|
+
}
|
|
980
|
+
|
|
845
981
|
async loop() {
|
|
846
982
|
while (this.running) {
|
|
847
983
|
const task = store.nextPending();
|
|
@@ -852,7 +988,7 @@ class Engine extends EventEmitter {
|
|
|
852
988
|
try {
|
|
853
989
|
await this.runTask(task);
|
|
854
990
|
} catch (error) {
|
|
855
|
-
if (!this.salvage(task, error)) {
|
|
991
|
+
if (!(await this.salvage(task, error))) {
|
|
856
992
|
const found = store.findTask(task.id);
|
|
857
993
|
const failure = classifyCliError(error);
|
|
858
994
|
task.status = "failed";
|
|
@@ -883,7 +1019,6 @@ class Engine extends EventEmitter {
|
|
|
883
1019
|
}
|
|
884
1020
|
}
|
|
885
1021
|
|
|
886
|
-
// Uzman agent'i (operatorun altindaki ekip) cfg.agents'ten cozup calistirir.
|
|
887
1022
|
invokeAgent(agentName, prompt, cfg, meta = {}) {
|
|
888
1023
|
const configuredAgent = cfg.agents[agentName];
|
|
889
1024
|
const agent = configuredAgent && cliRegistry.effectiveAgent(configuredAgent, cfg);
|
|
@@ -900,7 +1035,15 @@ class Engine extends EventEmitter {
|
|
|
900
1035
|
}
|
|
901
1036
|
|
|
902
1037
|
runCli(displayName, agent, prompt, cfg, meta = {}) {
|
|
903
|
-
return new Promise((resolve,
|
|
1038
|
+
return new Promise((resolve, rawReject) => {
|
|
1039
|
+
// Her hata hangi CLI'dan geldigini tasir; tavsiye metni buna gore uretilir. Etiket yokken
|
|
1040
|
+
// opencode hatasinda kullaniciya gemini komutu oneriliyordu. Bkz. classifyCliError.
|
|
1041
|
+
const reject = (cause) => {
|
|
1042
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
1043
|
+
if (!error.agentName) error.agentName = displayName;
|
|
1044
|
+
if (!error.adapter) error.adapter = agent.adapter || cliRegistry.adapterId(agent.cmd);
|
|
1045
|
+
rawReject(error);
|
|
1046
|
+
};
|
|
904
1047
|
const count = store.bumpCallCount();
|
|
905
1048
|
if (count > (cfg.dailyCallBudget || Number.MAX_SAFE_INTEGER)) {
|
|
906
1049
|
return reject(new Error(`Gunluk cagri butcesi asildi (${cfg.dailyCallBudget}).`));
|
|
@@ -928,10 +1071,23 @@ class Engine extends EventEmitter {
|
|
|
928
1071
|
}
|
|
929
1072
|
return String(arg);
|
|
930
1073
|
});
|
|
1074
|
+
const cwd = this._cwd || path.resolve(store.WORK_BASE, cfg.workingDir || ".");
|
|
1075
|
+
// Path-aware hapis: codex sandbox'i disariyi zaten OS-native kapatir (effectiveAgent).
|
|
1076
|
+
// Claude'un OS sandbox'i her platformda (ozellikle Windows'ta) olmadigi icin en azindan
|
|
1077
|
+
// orkestratorun kendi topragini (config/memory/kurulum dizini) permissions.deny ile gizler.
|
|
1078
|
+
// JSON'u arguman olarak gecmek Windows cmd.exe'de tirnaklari bozar; bu yuzden dosyaya yazip
|
|
1079
|
+
// yolunu veriyoruz (prompt dosyasiyla ayni desen).
|
|
1080
|
+
const sandboxSettings = claudeSandboxSettings(agent, cwd, cfg);
|
|
1081
|
+
if (sandboxSettings) {
|
|
1082
|
+
const sbDir = path.join(store.ROOT, "state", "prompts");
|
|
1083
|
+
fs.mkdirSync(sbDir, { recursive: true });
|
|
1084
|
+
const sbFile = path.join(sbDir, `${callId}.settings.json`);
|
|
1085
|
+
fs.writeFileSync(sbFile, JSON.stringify(sandboxSettings), "utf8");
|
|
1086
|
+
rawArgs.push("--settings", sbFile);
|
|
1087
|
+
}
|
|
931
1088
|
const command = cliRegistry.buildCommand(agent.cmd, rawArgs);
|
|
932
1089
|
const file = command.file;
|
|
933
1090
|
const args = command.args;
|
|
934
|
-
const cwd = this._cwd || path.resolve(store.WORK_BASE, cfg.workingDir || ".");
|
|
935
1091
|
const started = Date.now();
|
|
936
1092
|
let stdout = "", stderr = "", settled = false, timedOut = false, silenceTimedOut = false;
|
|
937
1093
|
let timer, silenceTimer, progressTimer;
|
|
@@ -1047,12 +1203,27 @@ class Engine extends EventEmitter {
|
|
|
1047
1203
|
hasUsableImplementAgent(cfg, operatorName) {
|
|
1048
1204
|
return Object.entries(cfg.agents || {}).some(([name, agent]) =>
|
|
1049
1205
|
name !== operatorName && agent.enabled !== false && agentUsable(agent)
|
|
1050
|
-
&& !this.
|
|
1206
|
+
&& !this.isQuarantined(name) && supportsKind(agent, "implement"));
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Sureli karantinadaki implement agentinin donusune kalan sure (ms). Geri donen yoksa ya da
|
|
1210
|
+
// bekleme makul siniri asiyorsa null; cagiran bunu "gorevi durdur" olarak yorumlar.
|
|
1211
|
+
msUntilImplementAgentReturns(cfg, operatorName, maxWaitMs = 5 * 60 * 1000) {
|
|
1212
|
+
const now = Date.now();
|
|
1213
|
+
const returns = Object.entries(cfg.agents || {})
|
|
1214
|
+
.filter(([name, agent]) => name !== operatorName && agent.enabled !== false
|
|
1215
|
+
&& agentUsable(agent) && supportsKind(agent, "implement"))
|
|
1216
|
+
.map(([name]) => this.unhealthyAgents.get(name)?.until || 0)
|
|
1217
|
+
.filter((until) => until > now)
|
|
1218
|
+
.sort((a, b) => a - b);
|
|
1219
|
+
if (!returns.length) return null;
|
|
1220
|
+
const waitMs = returns[0] - now;
|
|
1221
|
+
return waitMs <= maxWaitMs ? waitMs : null;
|
|
1051
1222
|
}
|
|
1052
1223
|
|
|
1053
1224
|
agentCatalog(cfg, operatorName) {
|
|
1054
1225
|
return Object.entries(cfg.agents)
|
|
1055
|
-
.filter(([name, agent]) => name !== operatorName && agent.enabled !== false && agentUsable(agent) && !this.
|
|
1226
|
+
.filter(([name, agent]) => name !== operatorName && agent.enabled !== false && agentUsable(agent) && !this.isQuarantined(name))
|
|
1056
1227
|
.map(([name, a]) => ({
|
|
1057
1228
|
name,
|
|
1058
1229
|
description: a.description || "",
|
|
@@ -1168,7 +1339,8 @@ class Engine extends EventEmitter {
|
|
|
1168
1339
|
skillSection +
|
|
1169
1340
|
`## Kullanici gorevi\n${task.prompt}\n` +
|
|
1170
1341
|
`## Calisma klasoru\n${this._cwd}\n` +
|
|
1171
|
-
|
|
1342
|
+
`SANDBOX: Butun is YALNIZCA bu klasorde yapilir. Bu klasor disina (ust dizinler, orkestrator/kurulum dizini, sistem dosyalari) dosya YAZMA/DEGISTIRME/SILME ve gerekmedikce disariyi OKUMA. Delegasyon talimatlarini da bu sinira gore yaz.\n` +
|
|
1343
|
+
`## Proje profili (bu klasorun birikmis baglami — tum kodu bastan taramak zorunda kalmayasin diye. IPUCUDUR, kanit degil: kritik kararlarda DOSYADAN dogrula, celiski gorursen profile guvenme. Gecmis teslimatlar mevcut gorevi TAMAMLANMIS SAYDIRMAZ.)\n${clip(memory || "(henuz proje profili yok — ilk gorevde olusturulacak)", cfg.projectContextCharBudget || cfg.memoryCharBudget || 8000)}\n` +
|
|
1172
1344
|
(phase === "review"
|
|
1173
1345
|
? `## Son bagimsiz denetim\n${(() => { const review = this.latestReview(state); return review ? `${review.id} (${review.agent}) → VERDICT: ${review.verdict}` : "Henuz tamamlanmis denetim yok."; })()}\n` +
|
|
1174
1346
|
`## Takim calisma kaydi\n${this.teamDigest(state, cfg)}\n`
|
|
@@ -1197,11 +1369,64 @@ class Engine extends EventEmitter {
|
|
|
1197
1369
|
return `${role}\n\n---\n## Takim agenti protokolu\n` +
|
|
1198
1370
|
`Operator sana asagidaki isi devretti. ${instructionByKind[assignment.kind] || instructionByKind.implement} ` +
|
|
1199
1371
|
`Planda olmayan riskli bir is gerekiyorsa yapma; BLOCKED olarak bildir. Yalnizca gercekten gozlemledigin veya dogruladigin sonuclari yaz.\n` +
|
|
1372
|
+
`SANDBOX: Butun is YALNIZCA calisma klasorunde (${this._cwd}) yapilir. Bu klasor disina dosya YAZMA/DEGISTIRME/SILME ve gerekmedikce disariyi OKUMA.\n` +
|
|
1200
1373
|
(skillRefs ? `## Uygulanacak beceriler\nBu is icin asagidaki beceri rehberleri secildi. Her satirda becerinin OZETI ve TAM rehberin DOSYA YOLU var. Ozet yeterliyse dogrudan uygula; daha fazla ayrinti gerekiyorsa ilgili dosyayi OKU ve prosedure uy. Ilgisiz bir sey varsa gormezden gel.\n${skillRefs}\n` : "") +
|
|
1201
1374
|
`## Ana hedef\n${task.prompt}\n## Delegasyon\nID: ${assignment.id}\n${assignment.instruction}\n` +
|
|
1202
1375
|
`## Onceki tamamlanan takim isleri\n${clip(JSON.stringify(completed, null, 2), cfg.teamContextCharBudget || 30000)}`;
|
|
1203
1376
|
}
|
|
1204
1377
|
|
|
1378
|
+
// Iki kademeli kurtarma: (1) gecici hata -> ayni agent, ustel bekleme; (2) kalici hata ->
|
|
1379
|
+
// ayni turu yapabilen saglikli baska agente devret. Ikisi de tukenirse firlatir ve operator
|
|
1380
|
+
// yeniden planlar. Amac, saniyelik bir 429 icin tam bir operator turu harcamamak.
|
|
1381
|
+
async runAssignmentWithRecovery(task, cfg, assignment, state) {
|
|
1382
|
+
const retries = Math.max(0, cfg.resilience?.transientRetries ?? 2);
|
|
1383
|
+
const maxFailovers = Math.max(0, cfg.resilience?.maxFailoverAgents ?? 1);
|
|
1384
|
+
const baseDelayMs = Math.max(250, (cfg.resilience?.retryBaseSeconds ?? 3) * 1000);
|
|
1385
|
+
const tried = new Set();
|
|
1386
|
+
let agentName = assignment.agent;
|
|
1387
|
+
let failure = null;
|
|
1388
|
+
|
|
1389
|
+
for (let failover = 0; ; failover++) {
|
|
1390
|
+
tried.add(agentName);
|
|
1391
|
+
for (let attempt = 0; ; attempt++) {
|
|
1392
|
+
try {
|
|
1393
|
+
const response = await this.invokeAgent(
|
|
1394
|
+
agentName,
|
|
1395
|
+
this.specialistPrompt(task, cfg, { ...assignment, agent: agentName }, state),
|
|
1396
|
+
cfg,
|
|
1397
|
+
{ stage: "delegate", assignmentId: assignment.id }
|
|
1398
|
+
);
|
|
1399
|
+
return { response, agent: agentName };
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
if (!this.running) throw error;
|
|
1402
|
+
failure = classifyCliError(error);
|
|
1403
|
+
if (!TRANSIENT_CLI_ERRORS.has(failure.code) || attempt >= retries) break;
|
|
1404
|
+
// Jitter: ayni anda dusen birden fazla delegasyon saglayiciya es zamanli geri donmesin.
|
|
1405
|
+
const waitMs = Math.min(30000, baseDelayMs * 2 ** attempt) + Math.floor(Math.random() * 750);
|
|
1406
|
+
this.publish("log", { level: "warn", msg: `${agentName} gecici hata verdi [${failure.code}]; ${Math.round(waitMs / 1000)} sn sonra yeniden deneniyor (${attempt + 2}/${retries + 1}).` }, task.id);
|
|
1407
|
+
this.publish("activity", { kind: "delegation.retry", assignmentId: assignment.id, agent: agentName, code: failure.code, attempt: attempt + 1, waitMs });
|
|
1408
|
+
await this.sleepRetry(waitMs);
|
|
1409
|
+
if (!this.running) throw error;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (QUARANTINE_CLI_ERRORS.has(failure.code)) this.quarantineAgent(agentName, failure.code, cfg);
|
|
1413
|
+
if (failover >= maxFailovers) break;
|
|
1414
|
+
const next = compatibleAgentsForKind(cfg, task.operatorCli, assignment.kind)
|
|
1415
|
+
.find((name) => !tried.has(name) && !this.isQuarantined(name));
|
|
1416
|
+
if (!next) break;
|
|
1417
|
+
this.publish("log", { level: "warn", msg: `${agentName} basarisiz [${failure.code}]; ayni is ${next} agentine devrediliyor (operator turu harcanmadan).` }, task.id);
|
|
1418
|
+
this.publish("activity", { kind: "delegation.failover", assignmentId: assignment.id, from: agentName, to: next, code: failure.code });
|
|
1419
|
+
agentName = next;
|
|
1420
|
+
}
|
|
1421
|
+
// Siniflandirici disaridaki catch'te ayni sonucu uretsin diye ham metinle firlatiyoruz.
|
|
1422
|
+
// CLI etiketi de tasinmali; yoksa oturum/kurulum tavsiyesi jenerige duser.
|
|
1423
|
+
const error = new Error(failure?.raw || "Delegasyon calistirilamadi.");
|
|
1424
|
+
error.agentName = failure?.agent || agentName;
|
|
1425
|
+
error.adapter = failure?.adapter || "";
|
|
1426
|
+
error.triedAgents = [...tried];
|
|
1427
|
+
throw error;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1205
1430
|
async runAssignments(task, cfg, assignments, state) {
|
|
1206
1431
|
const pending = assignments.slice();
|
|
1207
1432
|
while (pending.length) {
|
|
@@ -1229,32 +1454,30 @@ class Engine extends EventEmitter {
|
|
|
1229
1454
|
this.publish("log", { level: "stage", msg: `DELEGE ${assignment.id} -> ${assignment.agent}` }, task.id);
|
|
1230
1455
|
if (assignment.skills?.length) this.publish("log", { level: "info", msg: `Beceriler: ${assignment.skills.join(", ")}` }, task.id);
|
|
1231
1456
|
try {
|
|
1232
|
-
const response = await this.
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
{
|
|
1237
|
-
|
|
1238
|
-
const result = { ...assignment, status: "completed", result: response.text, durationMs: response.durationMs, callId: response.callId };
|
|
1457
|
+
const { response, agent: usedAgent } = await this.runAssignmentWithRecovery(task, cfg, assignment, state);
|
|
1458
|
+
const result = { ...assignment, agent: usedAgent, status: "completed", result: response.text, durationMs: response.durationMs, callId: response.callId };
|
|
1459
|
+
if (usedAgent !== assignment.agent) {
|
|
1460
|
+
result.failoverFrom = assignment.agent;
|
|
1461
|
+
this.publish("log", { level: "info", msg: `${assignment.id} devralan agent tarafindan tamamlandi: ${assignment.agent} -> ${usedAgent}.` }, task.id);
|
|
1462
|
+
}
|
|
1239
1463
|
if (assignment.kind === "review") {
|
|
1240
1464
|
result.verdict = extractVerdict(response.text);
|
|
1241
1465
|
this.publish("log", { level: "info", msg: `Denetim ${assignment.id}: VERDICT ${result.verdict || "BELIRSIZ"}` }, task.id);
|
|
1242
1466
|
}
|
|
1243
1467
|
state.results[assignment.id] = result;
|
|
1244
|
-
const message = { from:
|
|
1468
|
+
const message = { from: usedAgent, to: task.operatorCli, messageType: "result", assignmentId: assignment.id, body: response.text, at: new Date().toISOString() };
|
|
1245
1469
|
state.messages.push(message);
|
|
1246
1470
|
this.publish("message", message, task.id);
|
|
1247
1471
|
} catch (error) {
|
|
1472
|
+
// Karantina ve devretme runAssignmentWithRecovery'de tuketildi; buraya dusen gercekten
|
|
1473
|
+
// kurtarilamayandir.
|
|
1248
1474
|
const failure = classifyCliError(error);
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
}
|
|
1253
|
-
state.results[assignment.id] = { ...assignment, status: "failed", result: failure.summary, error: failure };
|
|
1254
|
-
const message = { from: assignment.agent, to: task.operatorCli, messageType: "failure", assignmentId: assignment.id, body: `${failure.summary}\n${failure.action}`, errorCode: failure.code, at: new Date().toISOString() };
|
|
1475
|
+
const tried = error.triedAgents?.length ? error.triedAgents : [assignment.agent];
|
|
1476
|
+
state.results[assignment.id] = { ...assignment, status: "failed", result: failure.summary, error: failure, triedAgents: tried };
|
|
1477
|
+
const message = { from: tried[tried.length - 1], to: task.operatorCli, messageType: "failure", assignmentId: assignment.id, body: `${failure.summary}\n${failure.action}`, errorCode: failure.code, at: new Date().toISOString() };
|
|
1255
1478
|
state.messages.push(message);
|
|
1256
1479
|
this.publish("message", message, task.id);
|
|
1257
|
-
this.publish("log", { level: "warn", msg: `${
|
|
1480
|
+
this.publish("log", { level: "warn", msg: `${tried.join(" > ")} kullanilamadi [${failure.code}]. Operator alternatif plan uretecek.` }, task.id);
|
|
1258
1481
|
}
|
|
1259
1482
|
task.teamState = state;
|
|
1260
1483
|
store.saveTask("pending", task);
|
|
@@ -1300,8 +1523,13 @@ class Engine extends EventEmitter {
|
|
|
1300
1523
|
this.emit("status", this.status());
|
|
1301
1524
|
this.emit("queue");
|
|
1302
1525
|
|
|
1303
|
-
cfg.runtimeUnavailableAgents =
|
|
1304
|
-
|
|
1526
|
+
cfg.runtimeUnavailableAgents = this.quarantinedAgents();
|
|
1527
|
+
// Proje context: varsayilan olarak bu calisma klasorunun .crewctl/CONTEXT.md profilini
|
|
1528
|
+
// yukleriz (path'e ozel, tum kodu bastan taramaya gerek kalmasin). task.freshContext ise
|
|
1529
|
+
// temiz sayfa. projectContext:false ise eski GLOBAL hafiza davranisi korunur.
|
|
1530
|
+
const memory = cfg.projectContext === false
|
|
1531
|
+
? store.getMemory(cfg.memoryCharBudget)
|
|
1532
|
+
: (task.freshContext ? "" : store.readProjectContext(this._cwd));
|
|
1305
1533
|
const state = task.teamState || { round: 0, plan: null, criteria: [], results: {}, messages: [], operatorDecisions: [], usedIds: [] };
|
|
1306
1534
|
const usedIds = new Set(state.usedIds || []);
|
|
1307
1535
|
|
|
@@ -1395,8 +1623,24 @@ class Engine extends EventEmitter {
|
|
|
1395
1623
|
if (taskRequiresDelegation(task.prompt)
|
|
1396
1624
|
&& !this.hasUsableImplementAgent(cfg, operatorCli)
|
|
1397
1625
|
&& !Object.values(state.results).some((result) => result.kind === "implement" && result.status === "completed")) {
|
|
1398
|
-
|
|
1399
|
-
|
|
1626
|
+
// Implement agenti yalnizca SURELI karantinadaysa gorevi oldurmek yerine bekleriz.
|
|
1627
|
+
// Kalici kisitlarda (kota bitti, CLI yok) null doner.
|
|
1628
|
+
const waitMs = this.msUntilImplementAgentReturns(cfg, operatorCli);
|
|
1629
|
+
let recovered = false;
|
|
1630
|
+
if (waitMs !== null) {
|
|
1631
|
+
this.publish("log", { level: "warn", msg: `Kullanilabilir implement agenti gecici olarak yok; karantina bitene kadar ${Math.ceil(waitMs / 1000)} sn bekleniyor.` }, task.id);
|
|
1632
|
+
await this.sleepRetry(waitMs + 250);
|
|
1633
|
+
// Bekleme sirasinda motor durdurulduysa gorevi "implement agenti yok" hatasiyla
|
|
1634
|
+
// basarisiz isaretlemek yaniltici olur; dongu kosulu zaten temiz cikisi saglar.
|
|
1635
|
+
if (!this.running) break;
|
|
1636
|
+
recovered = this.hasUsableImplementAgent(cfg, operatorCli);
|
|
1637
|
+
if (recovered) {
|
|
1638
|
+
cfg.runtimeUnavailableAgents = this.quarantinedAgents();
|
|
1639
|
+
this.publish("log", { level: "info", msg: "Implement agenti karantinadan dondu; gorev surduruluyor." }, task.id);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
const dead = this.quarantinedAgents();
|
|
1643
|
+
if (!recovered) throw new Error(
|
|
1400
1644
|
"Bu gorev dosya olusturma/degistirme gerektiriyor fakat kullanilabilir bir uygulama (implement) agenti yok" +
|
|
1401
1645
|
(dead.length ? ` (${dead.join(", ")} bu oturumda kullanim disi kaldi)` : "") +
|
|
1402
1646
|
". Ayarlar > Agent'lar'dan 'implementation' yetenekli bir agent (Codex/Claude/Gemini/OpenCode) ekleyip etkinlestirin veya opencode'un oturum/model durumunu kontrol edin, sonra gorevi tekrar calistirin."
|
|
@@ -1479,7 +1723,7 @@ class Engine extends EventEmitter {
|
|
|
1479
1723
|
// Gorev beklenmedik bir hatayla kesildiginde (protokol hatasi, butce asimi vb.) somut is
|
|
1480
1724
|
// uretilmisse gorevi failed yerine uyarili kismi teslimatla kapatir. Basarili olamazsa
|
|
1481
1725
|
// false doner ve normal hata akisi calisir.
|
|
1482
|
-
salvage(task, error) {
|
|
1726
|
+
async salvage(task, error) {
|
|
1483
1727
|
try {
|
|
1484
1728
|
if (task.kind === "operator-chat") return false;
|
|
1485
1729
|
if (!this._cwd || !this._snapBefore) return false;
|
|
@@ -1504,7 +1748,7 @@ class Engine extends EventEmitter {
|
|
|
1504
1748
|
changedFiles.slice(0, 40).map((file) => `- ${file}`).join("\n")
|
|
1505
1749
|
: `KISMI TESLIMAT: Gorev bir altyapi/protokol hatasiyla kesildi fakat tamamlanan is korundu.\n` +
|
|
1506
1750
|
completedWork.map((result) => `- ${result.id} (${result.agent}${result.verdict ? ` · VERDICT: ${result.verdict}` : ""})`).join("\n");
|
|
1507
|
-
this.complete(task, rescueState, "done", final, {
|
|
1751
|
+
await this.complete(task, rescueState, "done", final, {
|
|
1508
1752
|
warnings: [
|
|
1509
1753
|
`Gorev su hatayla kesildi: ${failure.summary}`,
|
|
1510
1754
|
...(operatorDirect ? [`Operator CLI (${task.operatorCli || "?"}) plan JSON'i yerine isi dogrudan yapti. Daha guvenilir orkestrasyon icin operator olarak JSON planlamaya uygun bir CLI (or. Codex veya Claude) secmeyi dusunun.`] : []),
|
|
@@ -1556,7 +1800,7 @@ class Engine extends EventEmitter {
|
|
|
1556
1800
|
this.emit("queue");
|
|
1557
1801
|
}
|
|
1558
1802
|
|
|
1559
|
-
complete(task, state, status, finalText, decision = {}) {
|
|
1803
|
+
async complete(task, state, status, finalText, decision = {}) {
|
|
1560
1804
|
const changes = diffSnapshots(this._snapBefore || new Map(), snapshotDir(this._cwd));
|
|
1561
1805
|
// Son periyodik taramadan sonra yapilan degisiklikleri de replay kaydina ve dashboard'a
|
|
1562
1806
|
// aktar; ardindan run --once yolunda da timer'in acik kalmamasini garanti et.
|
|
@@ -1601,6 +1845,45 @@ class Engine extends EventEmitter {
|
|
|
1601
1845
|
this.notifyOutcome(task, status, { summary: task.summary, rounds: state.round, fileCount: files.length, verification: task.delivery.verification });
|
|
1602
1846
|
this.emit("queue");
|
|
1603
1847
|
this._textBefore = null;
|
|
1848
|
+
// Proje profilini guncelle (sonuc zaten kullaniciya iletildi; motor mesgul kaldigindan
|
|
1849
|
+
// sonraki gorevle es zamanli operator cagrisi olmaz). Basarisizsa gorevi ASLA etkilemez.
|
|
1850
|
+
await this.reviseProjectContext(task, status, files);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// Gorev bitiminde bu calisma klasorunun .crewctl/CONTEXT.md proje profilini operatore REVIZE
|
|
1854
|
+
// ettirir (append degil): mimari/yapi/konvansiyon/komutlar guncel kalir, bayatlamaz. Yalnizca
|
|
1855
|
+
// basarili ve DOSYA DEGISTIREN gorevlerde calisir (bilgi sorulari profili degistirmez).
|
|
1856
|
+
async reviseProjectContext(task, status, files) {
|
|
1857
|
+
try {
|
|
1858
|
+
const cfg = this.cfg();
|
|
1859
|
+
if (cfg.projectContext === false) return;
|
|
1860
|
+
if (status !== "done" || !this._cwd) return;
|
|
1861
|
+
if (!Array.isArray(files) || !files.length) return;
|
|
1862
|
+
const operatorCli = task.operatorCli || cfg.operator?.cli;
|
|
1863
|
+
if (!operatorCli || !cliRegistry.operatorSpec(operatorCli, cfg)) return;
|
|
1864
|
+
const budget = cfg.projectContextCharBudget || 6000;
|
|
1865
|
+
const current = store.readProjectContext(this._cwd);
|
|
1866
|
+
const changed = files.map((f) => `${f.action}: ${f.path}`).join("\n");
|
|
1867
|
+
const prompt =
|
|
1868
|
+
`Sen bir projenin YASAYAN bilgi tabanini (proje profili) guncelliyorsun. Amac: sonraki gorevlerde ` +
|
|
1869
|
+
`ajan tum kodu bastan taramadan projeyi anlasin.\n` +
|
|
1870
|
+
`Asagida MEVCUT profil ve SON tamamlanan gorevin ozeti + degisen dosyalar var. Guncel, oz ve DOGRU ` +
|
|
1871
|
+
`bir proje profili YAZ (Markdown). Kurallar:\n` +
|
|
1872
|
+
`- Degisen gerceklere gore REVIZE et; silinen/tasinan/degisen seyleri guncelle, bayat bilgi birakma.\n` +
|
|
1873
|
+
`- Dayanikli bilgiyi tut: amac, mimari, klasor/dosya yapisi, giris noktalari, onemli moduller, ` +
|
|
1874
|
+
`konvansiyonlar, kurulum/calistirma/test komutlari, dikkat noktalari.\n` +
|
|
1875
|
+
`- Task-by-task changelog YAZMA; tek bir GUNCEL DURUM profili olsun.\n` +
|
|
1876
|
+
`- En fazla ~${budget} karakter. YALNIZCA profil metnini dondur; aciklama/selamlama/JSON/kod-bloğu isareti YAZMA.\n\n` +
|
|
1877
|
+
`## Mevcut profil\n${current || "(henuz yok — sifirdan olustur)"}\n\n` +
|
|
1878
|
+
`## Son tamamlanan gorev\n${task.prompt}\n\n## Sonuc ozeti\n${task.summary || ""}\n\n## Degisen dosyalar\n${changed}`;
|
|
1879
|
+
const response = await this.invokeOperator(operatorCli, prompt, cfg, { stage: "operator-context" });
|
|
1880
|
+
const profile = stripCodeFence(String(response?.text || "")).trim();
|
|
1881
|
+
if (profile && store.writeProjectContext(this._cwd, clip(profile, budget * 2))) {
|
|
1882
|
+
this.publish("log", { level: "info", msg: "Proje profili (.crewctl/CONTEXT.md) guncellendi." }, task.id);
|
|
1883
|
+
}
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
this.publish("log", { level: "warn", msg: `Proje profili guncellenemedi: ${error.message}` }, task.id);
|
|
1886
|
+
}
|
|
1604
1887
|
}
|
|
1605
1888
|
|
|
1606
1889
|
// Dis bildirim icin tek cikis noktasi: server.js bu event'i dinleyip (varsa) webhook'a POST atar.
|
|
@@ -1626,5 +1909,6 @@ module.exports._internals = {
|
|
|
1626
1909
|
normalizeAssignments, ensureBalancedRoleChain, extractVerdict, clipMiddle, snapshotDir, diffSnapshots,
|
|
1627
1910
|
captureTextSnapshot, buildLineDiff, describeFileDiff, isSensitiveDiffPath, taskRequiresDelegation,
|
|
1628
1911
|
parseJson, conversationalAnswer, classifyCliError, RECOVERABLE_CLI_ERRORS, QUARANTINE_CLI_ERRORS,
|
|
1629
|
-
|
|
1912
|
+
TRANSIENT_CLI_ERRORS, QUARANTINE_COOLDOWN_SECONDS, compatibleAgentsForKind,
|
|
1913
|
+
normalizeCliOutput, addUsage, usageTotal, claudeSandboxSettings,
|
|
1630
1914
|
};
|
|
@@ -27,7 +27,6 @@ function pad2(n) {
|
|
|
27
27
|
return String(n).padStart(2, "0");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
// Ham girdiyi dogrulanmis bir tetik nesnesine cevir; gecersizse Error at.
|
|
31
30
|
function normalizeTrigger(raw) {
|
|
32
31
|
if (!isPlainObject(raw)) throw new Error("trigger nesnesi gerekli.");
|
|
33
32
|
const type = String(raw.type || "").toLowerCase();
|
|
@@ -42,7 +41,6 @@ function normalizeTrigger(raw) {
|
|
|
42
41
|
if (!time) throw new Error('trigger.at "HH:MM" (00:00–23:59) olmali.');
|
|
43
42
|
const at = `${pad2(time.hours)}:${pad2(time.minutes)}`;
|
|
44
43
|
if (type === "daily") return { type, at };
|
|
45
|
-
// weekly
|
|
46
44
|
const days = Array.isArray(raw.days) ? [...new Set(raw.days.map(Number))].filter((d) => Number.isInteger(d) && d >= 0 && d <= 6).sort((a, b) => a - b) : [];
|
|
47
45
|
if (!days.length) throw new Error("trigger.days en az bir gun (0=Pazar .. 6=Cumartesi) icermeli.");
|
|
48
46
|
return { type, at, days };
|