@omerrgocmen/crewctl 1.0.2 → 1.1.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 +462 -462
- package/orchestrator/config.default.json +71 -71
- package/orchestrator/roles/operator.md +77 -77
- package/orchestrator/roles/reviewer.md +63 -63
- package/orchestrator/skills/acceptance-criteria.md +18 -18
- package/orchestrator/skills/accessibility-audit.md +16 -16
- package/orchestrator/skills/accessible-forms.md +16 -16
- package/orchestrator/skills/api-design.md +27 -27
- package/orchestrator/skills/api-documentation.md +16 -16
- package/orchestrator/skills/architecture-decision-record.md +18 -18
- package/orchestrator/skills/authentication-design.md +16 -16
- package/orchestrator/skills/authorization-review.md +16 -16
- package/orchestrator/skills/backward-compatibility.md +17 -17
- package/orchestrator/skills/changelog-writing.md +16 -16
- package/orchestrator/skills/ci-pipeline-design.md +16 -16
- package/orchestrator/skills/cli-design.md +16 -16
- package/orchestrator/skills/code-review.md +27 -27
- package/orchestrator/skills/configuration-management.md +16 -16
- package/orchestrator/skills/container-review.md +16 -16
- package/orchestrator/skills/contract-testing.md +16 -16
- package/orchestrator/skills/dashboard-design.md +16 -16
- package/orchestrator/skills/database-migration.md +16 -16
- package/orchestrator/skills/database-schema-design.md +16 -16
- package/orchestrator/skills/debugging.md +26 -26
- package/orchestrator/skills/dependency-review.md +16 -16
- package/orchestrator/skills/design-review.md +29 -29
- package/orchestrator/skills/design-system.md +16 -16
- package/orchestrator/skills/docs-api-reference.md +16 -16
- package/orchestrator/skills/docs-troubleshooting.md +16 -16
- package/orchestrator/skills/docs-tutorial.md +16 -16
- package/orchestrator/skills/docs-writing.md +25 -25
- package/orchestrator/skills/empty-error-loading-states.md +16 -16
- package/orchestrator/skills/end-to-end-testing.md +16 -16
- package/orchestrator/skills/error-handling.md +16 -16
- package/orchestrator/skills/frontend-design.md +28 -28
- package/orchestrator/skills/git-commit-writing.md +16 -16
- package/orchestrator/skills/graphql-design.md +16 -16
- package/orchestrator/skills/incident-runbook.md +18 -18
- package/orchestrator/skills/input-validation.md +16 -16
- package/orchestrator/skills/integration-testing.md +16 -16
- package/orchestrator/skills/interaction-design.md +16 -16
- package/orchestrator/skills/landing-page-design.md +16 -16
- package/orchestrator/skills/observability-design.md +16 -16
- package/orchestrator/skills/openapi-contract.md +16 -16
- package/orchestrator/skills/performance-profiling.md +16 -16
- package/orchestrator/skills/privacy-review.md +16 -16
- package/orchestrator/skills/property-based-testing.md +16 -16
- package/orchestrator/skills/pull-request-writing.md +16 -16
- package/orchestrator/skills/refactoring.md +16 -16
- package/orchestrator/skills/release-readiness.md +16 -16
- package/orchestrator/skills/responsive-design.md +16 -16
- package/orchestrator/skills/secrets-management.md +16 -16
- package/orchestrator/skills/secure-file-upload.md +16 -16
- package/orchestrator/skills/security-review.md +26 -26
- package/orchestrator/skills/semantic-versioning.md +17 -17
- package/orchestrator/skills/seo-on-page.md +16 -16
- package/orchestrator/skills/seo-structured-data.md +16 -16
- package/orchestrator/skills/seo-technical-audit.md +16 -16
- package/orchestrator/skills/sql-query-review.md +16 -16
- package/orchestrator/skills/supply-chain-security.md +16 -16
- package/orchestrator/skills/test-strategy.md +16 -16
- package/orchestrator/skills/threat-modeling.md +16 -16
- package/orchestrator/skills/unit-testing.md +16 -16
- package/orchestrator/skills/write-tests.md +28 -28
- package/orchestrator/src/cli-registry.js +682 -616
- package/orchestrator/src/cli.js +131 -131
- package/orchestrator/src/doctor.js +64 -64
- package/orchestrator/src/engine.js +246 -37
- package/orchestrator/src/server.js +795 -766
- package/orchestrator/src/skill-registry.js +272 -272
- package/orchestrator/src/store.js +426 -399
- package/orchestrator/web/OrbitControls.js +1417 -1417
- package/orchestrator/web/flow.html +740 -740
- package/orchestrator/web/index.html +601 -557
- package/orchestrator/web/jsm/postprocessing/EffectComposer.js +231 -231
- package/orchestrator/web/jsm/postprocessing/MaskPass.js +104 -104
- package/orchestrator/web/jsm/postprocessing/Pass.js +95 -95
- package/orchestrator/web/jsm/postprocessing/RenderPass.js +99 -99
- package/orchestrator/web/jsm/postprocessing/ShaderPass.js +77 -77
- package/orchestrator/web/jsm/postprocessing/UnrealBloomPass.js +415 -415
- package/orchestrator/web/jsm/shaders/CopyShader.js +45 -45
- package/orchestrator/web/jsm/shaders/LuminosityHighPassShader.js +66 -66
- package/orchestrator/web/three.module.min.js +6 -6
- package/package.json +2 -2
|
@@ -240,37 +240,71 @@ function extractVerdict(text) {
|
|
|
240
240
|
return /PASS/i.test(matches[matches.length - 1]) ? "PASS" : "FAIL";
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
// Siniflandirilamayan hatada "gemini cikis kodu 1." gibi bilgisiz bir ilk satir yerine metnin
|
|
244
|
+
// icindeki GERCEK hata cumlesini bulur; kullaniciya gosterilen tek satir buysa anlamli olmali.
|
|
245
|
+
function meaningfulErrorLine(raw) {
|
|
246
|
+
const all = String(raw).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
247
|
+
// Yigin izleri ve motorun kendi sardigi "<cli> cikis kodu N." satiri teshis tasimaz;
|
|
248
|
+
// gercek neden hemen arkalarindaki satirdadir.
|
|
249
|
+
const useful = all.filter((line) => !/^at\s|^node:internal|^[{}[\]]+$/.test(line) && !/cikis kodu/i.test(line));
|
|
250
|
+
const signal = useful.find((line) => /error|hata|failed|failure|denied|invalid|exceeded|limit|quota|unable|cannot|must/i.test(line) && line.length > 20);
|
|
251
|
+
return signal || useful[0] || all[0] || String(raw).trim();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Ham CLI ciktisini kullaniciya "ne oldu + ne yapmali" seklinde anlamli bir teshise cevirir.
|
|
255
|
+
// SIRA ONEMLIDIR: spesifik desenler once gelmeli. Ornegin Gemini'nin "GEMINI_API_KEY ...
|
|
256
|
+
// not found" mesaji bir PATH/kurulum sorunu DEGILDIR; genis /not found/ deseni bu yuzden
|
|
257
|
+
// daraltildi ve kimlik kontrollerinin arkasina alindi.
|
|
243
258
|
function classifyCliError(error) {
|
|
244
259
|
const raw = String(error?.message || error || "Bilinmeyen CLI hatasi");
|
|
260
|
+
const of = (code, summary, action) => ({ code, summary, action, raw: clip(raw, 5000) });
|
|
245
261
|
if (/requires a newer version|upgrade to the latest|model metadata.*not found|unsupported.*model|model.*unsupported/i.test(raw)) {
|
|
246
|
-
return
|
|
262
|
+
return of("VERSION_INCOMPATIBLE", "CLI sürümü seçilen modeli desteklemiyor.", "CLI aracını güncelleyin veya desteklenen bir model seçin.");
|
|
247
263
|
}
|
|
248
264
|
if (/sessiz kaldi|cikti uretmedi|CLI_STALLED/i.test(raw)) {
|
|
249
|
-
return
|
|
265
|
+
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
|
+
}
|
|
267
|
+
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.", "Agent ayarlarındaki API anahtarını yenileyin (Gemini için GEMINI_API_KEY / GOOGLE_API_KEY).");
|
|
250
269
|
}
|
|
251
|
-
|
|
252
|
-
|
|
270
|
+
// Gemini CLI oturum acilmadiginda "Please set an Auth method" / "GEMINI_API_KEY environment
|
|
271
|
+
// variable not found" yazar; her ikisi de kurulum degil OTURUM sorunudur.
|
|
272
|
+
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.", "Terminalde ilgili CLI ile oturum açın (Gemini: `gemini` çalıştırıp 'Login with Google' veya GEMINI_API_KEY tanımlayın), sonra görevi tekrar deneyin.");
|
|
253
274
|
}
|
|
254
|
-
|
|
255
|
-
|
|
275
|
+
// Kota (gunluk/aylik hak bitti) ile hiz siniri (kisa sureli) AYRI teshislerdir: ilki
|
|
276
|
+
// beklemekle gecmez, ikincisi gecer. Kullanicinin sordugu "limitim mi bitti" ayrimi budur.
|
|
277
|
+
if (/RESOURCE_EXHAUSTED|quota exceeded|exceeded your current quota|daily limit|günlük limit|gunluk limit|out of credits|insufficient (credit|balance|quota)|billing/i.test(raw)) {
|
|
278
|
+
return of("QUOTA_EXCEEDED", "Sağlayıcı kotanız doldu (günlük/aylık hak veya bakiye bitti).", "Kota yenilenene kadar bu agent kullanılamaz; başka bir agent seçin, faturalandırmayı yükseltin veya API anahtarını kotası olan bir hesapla değiştirin.");
|
|
256
279
|
}
|
|
257
|
-
if (/rate.?limit|too many requests
|
|
258
|
-
return
|
|
280
|
+
if (/rate.?limit|too many requests|\b429\b/i.test(raw)) {
|
|
281
|
+
return of("RATE_LIMIT", "İstek hızı sınırına takıldı (kota bitmedi, çok sık istek gönderildi).", "Kısa bir süre bekleyip tekrar deneyin; operatör bu turda alternatif bir agent kullanabilir.");
|
|
282
|
+
}
|
|
283
|
+
if (/overloaded|\bUNAVAILABLE\b|\b(503|500)\b|internal error|try again later/i.test(raw)) {
|
|
284
|
+
return of("MODEL_OVERLOADED", "Model sağlayıcısı geçici olarak aşırı yüklü ya da hata döndürdü.", "Sağlayıcı kaynaklı geçici bir sorun; birkaç dakika sonra tekrar deneyin.");
|
|
285
|
+
}
|
|
286
|
+
if (/location is not supported|not available in your country|region.*not supported/i.test(raw)) {
|
|
287
|
+
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.");
|
|
259
288
|
}
|
|
260
289
|
if (/ConnectionRefused|Unable to connect|provider hatasi|provider error|ECONNREFUSED/i.test(raw)) {
|
|
261
|
-
return
|
|
290
|
+
return of("PROVIDER_UNAVAILABLE", "Seçilen model sağlayıcısına bağlanılamadı.", "Sağlayıcı bağlantısını kontrol edin veya başka bir model seçin.");
|
|
291
|
+
}
|
|
292
|
+
if (/ENOTFOUND|EAI_AGAIN|ECONNRESET|ETIMEDOUT|getaddrinfo|fetch failed|socket hang up|network (error|unreachable)|proxy/i.test(raw)) {
|
|
293
|
+
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.");
|
|
262
294
|
}
|
|
263
|
-
if (/not recognized as an internal|
|
|
264
|
-
return
|
|
295
|
+
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'yi kurun (Gemini: `npm install -g @google/gemini-cli`) ve Ayarlar → Agent'lar bölümünde komut adını doğrulayın.");
|
|
265
297
|
}
|
|
266
298
|
if (/timeout|timed out|zaman asim|zaman aşım/i.test(raw)) {
|
|
267
|
-
return
|
|
299
|
+
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.");
|
|
268
300
|
}
|
|
269
|
-
return
|
|
301
|
+
return of("CLI_FAILED", clip(meaningfulErrorLine(raw), 500), "Ham CLI çıktısı teknik ayrıntılarda saklandı; sorun sürerse alternatif bir agent deneyin.");
|
|
270
302
|
}
|
|
271
303
|
|
|
272
|
-
const RECOVERABLE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "RATE_LIMIT", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "TIMEOUT", "CLI_STALLED", "CLI_FAILED", "VERSION_INCOMPATIBLE"]);
|
|
273
|
-
|
|
304
|
+
const RECOVERABLE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "RATE_LIMIT", "QUOTA_EXCEEDED", "MODEL_OVERLOADED", "NETWORK_ERROR", "REGION_BLOCKED", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "TIMEOUT", "CLI_STALLED", "CLI_FAILED", "VERSION_INCOMPATIBLE"]);
|
|
305
|
+
// Karantina = bu oturumda tekrar denemek anlamsiz. Kota/bolge kisiti beklemekle gecmez;
|
|
306
|
+
// hiz siniri (RATE_LIMIT) ve gecici saglayici hatasi (MODEL_OVERLOADED) gecer, karantinaya alinmaz.
|
|
307
|
+
const QUARANTINE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "QUOTA_EXCEEDED", "REGION_BLOCKED", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "CLI_STALLED", "VERSION_INCOMPATIBLE"]);
|
|
274
308
|
|
|
275
309
|
function resolveExecutionMode(task) {
|
|
276
310
|
if (["fast", "balanced", "deep"].includes(task.executionMode)) return task.executionMode;
|
|
@@ -302,32 +336,134 @@ function applyExecutionPolicy(base, mode) {
|
|
|
302
336
|
return cfg;
|
|
303
337
|
}
|
|
304
338
|
|
|
339
|
+
const EMPTY_USAGE = { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
340
|
+
|
|
341
|
+
function addUsage(target, extra) {
|
|
342
|
+
const sum = { ...(target || EMPTY_USAGE) };
|
|
343
|
+
for (const key of Object.keys(EMPTY_USAGE)) sum[key] = (sum[key] || 0) + (Number(extra?.[key]) || 0);
|
|
344
|
+
return sum;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function usageTotal(usage) {
|
|
348
|
+
return (Number(usage?.input) || 0) + (Number(usage?.output) || 0);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// OpenCode'un step_finish olayindaki token/maliyet alanlarini normalize eder. Alan adlari
|
|
352
|
+
// surumler arasinda degisebildigi icin eksik alan 0 sayilir; hicbir sayi yoksa null doner
|
|
353
|
+
// ki "veri yok" ile "sifir maliyet" ayirt edilebilsin.
|
|
354
|
+
function readStepUsage(event) {
|
|
355
|
+
const tokens = event.part?.tokens || event.tokens;
|
|
356
|
+
const cost = Number(event.part?.cost ?? event.cost);
|
|
357
|
+
if (!tokens && !Number.isFinite(cost)) return null;
|
|
358
|
+
return {
|
|
359
|
+
input: Number(tokens?.input) || 0,
|
|
360
|
+
output: Number(tokens?.output) || 0,
|
|
361
|
+
reasoning: Number(tokens?.reasoning) || 0,
|
|
362
|
+
cacheRead: Number(tokens?.cache?.read) || 0,
|
|
363
|
+
cacheWrite: Number(tokens?.cache?.write) || 0,
|
|
364
|
+
cost: Number.isFinite(cost) ? cost : 0,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// DIKKAT: text/error sozlesmesi degismemeli — operatorun JSON protokolu bu metni okuyor.
|
|
369
|
+
// usage yalnizca EK bir alandir; cikarilamadiginda null kalir ve hicbir akisi etkilemez.
|
|
305
370
|
function normalizeCliOutput(agent, stdout) {
|
|
306
|
-
if (agent.adapter !== "opencode" || !(agent.args || []).includes("json"))
|
|
371
|
+
if (agent.adapter !== "opencode" || !(agent.args || []).includes("json")) {
|
|
372
|
+
return { text: String(stdout || "").trim(), error: "", usage: null };
|
|
373
|
+
}
|
|
307
374
|
const texts = [], errors = [];
|
|
375
|
+
let usage = null;
|
|
308
376
|
for (const line of String(stdout || "").split(/\r?\n/)) {
|
|
309
377
|
if (!line.trim()) continue;
|
|
310
378
|
try {
|
|
311
379
|
const event = JSON.parse(line);
|
|
312
380
|
if (event.type === "text" && event.part?.text) texts.push(String(event.part.text));
|
|
313
381
|
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
|
+
if (event.type === "step_finish") {
|
|
384
|
+
const step = readStepUsage(event);
|
|
385
|
+
if (step) usage = addUsage(usage, step);
|
|
386
|
+
}
|
|
314
387
|
} catch {}
|
|
315
388
|
}
|
|
316
|
-
return { text: texts.join("\n").trim(), error: errors.join("\n").trim() };
|
|
389
|
+
return { text: texts.join("\n").trim(), error: errors.join("\n").trim(), usage };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Metindeki DENGELI suslu parantezli en dis JSON nesnesi adaylarini cikarir. String icindeki
|
|
393
|
+
// parantez/kacis karakterleri sayilmaz; boylece "final" metninde gecen bir { veya } eski
|
|
394
|
+
// "ilk-{ .. son-}" kestirmesini bozmaz. Ayrica model aciklama + nesne + ek aciklama yazdiginda
|
|
395
|
+
// (ya da birden fazla nesne dondurdugunde) her nesne ayri aday olur.
|
|
396
|
+
function jsonObjectCandidates(text) {
|
|
397
|
+
const src = String(text);
|
|
398
|
+
const found = [];
|
|
399
|
+
const stack = [];
|
|
400
|
+
let inString = false, escaped = false;
|
|
401
|
+
for (let i = 0; i < src.length; i++) {
|
|
402
|
+
const ch = src[i];
|
|
403
|
+
if (inString) {
|
|
404
|
+
if (escaped) escaped = false;
|
|
405
|
+
else if (ch === "\\") escaped = true;
|
|
406
|
+
else if (ch === '"') inString = false;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (ch === '"') inString = true;
|
|
410
|
+
else if (ch === "{") stack.push(i);
|
|
411
|
+
else if (ch === "}" && stack.length) {
|
|
412
|
+
const start = stack.pop();
|
|
413
|
+
if (!stack.length) found.push(src.slice(start, i + 1));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return found;
|
|
317
417
|
}
|
|
318
418
|
|
|
419
|
+
// Model bazen JSON'u tek tirnak, sondaki virgul veya akilli tirnakla yazar. Kati parse
|
|
420
|
+
// basarisiz oldugunda son care olarak bu yaygin sapmalari onarip bir kez daha dener.
|
|
421
|
+
function repairJsonText(candidate) {
|
|
422
|
+
return String(candidate)
|
|
423
|
+
.replace(/[“”]/g, '"')
|
|
424
|
+
.replace(/[‘’]/g, "'")
|
|
425
|
+
.replace(/,\s*([}\]])/g, "$1");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const PROTOCOL_KEYS = /"(status|assignments|summary|completionCriteria|final)"\s*:/;
|
|
429
|
+
|
|
319
430
|
function parseJson(text, label) {
|
|
431
|
+
const raw = String(text);
|
|
320
432
|
const candidates = [];
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
433
|
+
for (const fence of raw.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)) candidates.push(fence[1]);
|
|
434
|
+
candidates.push(raw.trim());
|
|
435
|
+
// Protokol anahtari iceren nesneler once denenir; ayni onem sirasinda SONDAKI nesne kazanir
|
|
436
|
+
// (modeller once dusunce/aciklama, en sona nihai protokol nesnesini yazar).
|
|
437
|
+
const objects = jsonObjectCandidates(raw).reverse();
|
|
438
|
+
candidates.push(...objects.filter((o) => PROTOCOL_KEYS.test(o)), ...objects.filter((o) => !PROTOCOL_KEYS.test(o)));
|
|
327
439
|
for (const candidate of candidates) {
|
|
328
|
-
|
|
440
|
+
for (const attempt of [candidate, repairJsonText(candidate)]) {
|
|
441
|
+
try {
|
|
442
|
+
const parsed = JSON.parse(attempt);
|
|
443
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
444
|
+
} catch {}
|
|
445
|
+
}
|
|
329
446
|
}
|
|
330
|
-
|
|
447
|
+
const error = new Error(`${label} gecerli JSON dondurmedi.`);
|
|
448
|
+
error.rawText = raw;
|
|
449
|
+
throw error;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Protokol hatasinda operatorun ham metnini kullaniciya gosterilebilir bir cevaba cevirir.
|
|
453
|
+
// Yarim kalmis/kirik JSON parcalarini ve kod bloklarini atar; geriye anlamli duz metin
|
|
454
|
+
// kalmiyorsa null doner (o zaman normal hata akisi calisir).
|
|
455
|
+
function conversationalAnswer(error) {
|
|
456
|
+
if (!/gecerli JSON dondurmedi/i.test(String(error?.message || ""))) return null;
|
|
457
|
+
const raw = String(error.rawText || "");
|
|
458
|
+
if (!raw.trim()) return null;
|
|
459
|
+
const stripped = raw
|
|
460
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
461
|
+
.replace(/\{[\s\S]*\}/g, " ")
|
|
462
|
+
.replace(/[\s\S]*?^\s*\{[\s\S]*$/m, (match) => match.split("{")[0])
|
|
463
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
464
|
+
.trim();
|
|
465
|
+
// Cok kisa artiklar (or. tek noktalama) cevap sayilmaz.
|
|
466
|
+
return stripped.length >= 8 ? stripped : null;
|
|
331
467
|
}
|
|
332
468
|
|
|
333
469
|
function capabilitiesFor(agent) {
|
|
@@ -555,6 +691,35 @@ class Engine extends EventEmitter {
|
|
|
555
691
|
defaultOperator: cfg.operator?.cli || "",
|
|
556
692
|
callsToday: store.getCallCount(),
|
|
557
693
|
budget: cfg.dailyCallBudget,
|
|
694
|
+
usageToday: store.getDailyUsage(),
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// KULLANIM TELEMETRISI: gorev suresince agent bazinda token/maliyet toplar. Tamamen
|
|
699
|
+
// additive ve hata toleranslidir — veri veremeyen CLI'lar (metin modunda calisan
|
|
700
|
+
// codex/claude/gemini) icin sayaclar bos kalir, hicbir akis bundan etkilenmez.
|
|
701
|
+
resetUsage() {
|
|
702
|
+
this._usage = { total: { ...EMPTY_USAGE }, byAgent: {}, calls: 0, reportingCalls: 0 };
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
recordUsage(agentName, usage) {
|
|
706
|
+
if (!this._usage) this.resetUsage();
|
|
707
|
+
this._usage.total = addUsage(this._usage.total, usage);
|
|
708
|
+
this._usage.byAgent[agentName] = addUsage(this._usage.byAgent[agentName], usage);
|
|
709
|
+
this._usage.reportingCalls++;
|
|
710
|
+
try { store.addDailyUsage(usage); } catch {}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Gorev kapanisinda saklanacak sekil. Hicbir cagri veri vermediyse null doner ki arayuz
|
|
714
|
+
// "0 token" gibi yaniltici bir sayi yerine "veri yok" gosterebilsin.
|
|
715
|
+
usageSummary() {
|
|
716
|
+
const usage = this._usage;
|
|
717
|
+
if (!usage || !usage.reportingCalls) return null;
|
|
718
|
+
return {
|
|
719
|
+
total: usage.total,
|
|
720
|
+
byAgent: usage.byAgent,
|
|
721
|
+
calls: usage.calls,
|
|
722
|
+
reportingCalls: usage.reportingCalls,
|
|
558
723
|
};
|
|
559
724
|
}
|
|
560
725
|
|
|
@@ -689,16 +854,22 @@ class Engine extends EventEmitter {
|
|
|
689
854
|
} catch (error) {
|
|
690
855
|
if (!this.salvage(task, error)) {
|
|
691
856
|
const found = store.findTask(task.id);
|
|
857
|
+
const failure = classifyCliError(error);
|
|
692
858
|
task.status = "failed";
|
|
693
859
|
task.error = error.message;
|
|
860
|
+
// Siniflandirilmis teshis gorevle birlikte saklanir; arayuz "ne oldu / ne yapmali"
|
|
861
|
+
// ayrimini ham hata metnini kirparak tahmin etmek yerine buradan okur.
|
|
862
|
+
task.failure = failure;
|
|
863
|
+
// Basarisiz gorev de para harcar; telemetri burada da saklanmali.
|
|
864
|
+
task.usage = this.usageSummary();
|
|
694
865
|
task.finishedAt = new Date().toISOString();
|
|
695
866
|
if (found) store.moveTask(found.state, "failed", task);
|
|
696
|
-
this.publish("log", { level: "error", msg: `HATA: ${
|
|
867
|
+
this.publish("log", { level: "error", msg: `HATA (${failure.code}): ${failure.summary}` }, task.id);
|
|
868
|
+
this.publish("log", { level: "warn", msg: `Ne yapmali: ${failure.action}` }, task.id);
|
|
697
869
|
if (task.kind === "operator-chat") {
|
|
698
|
-
|
|
699
|
-
this.publish("result", { id: task.id, kind: "operator-chat", parentTaskId: task.parentTaskId, status: "failed", error: failure.summary }, task.id);
|
|
870
|
+
this.publish("result", { id: task.id, kind: "operator-chat", parentTaskId: task.parentTaskId, status: "failed", error: failure.summary, failure }, task.id);
|
|
700
871
|
}
|
|
701
|
-
this.notifyOutcome(task, "failed", { error:
|
|
872
|
+
this.notifyOutcome(task, "failed", { error: failure.summary, failureCode: failure.code, action: failure.action });
|
|
702
873
|
this.emit("queue");
|
|
703
874
|
}
|
|
704
875
|
} finally {
|
|
@@ -734,6 +905,8 @@ class Engine extends EventEmitter {
|
|
|
734
905
|
if (count > (cfg.dailyCallBudget || Number.MAX_SAFE_INTEGER)) {
|
|
735
906
|
return reject(new Error(`Gunluk cagri butcesi asildi (${cfg.dailyCallBudget}).`));
|
|
736
907
|
}
|
|
908
|
+
if (!this._usage) this.resetUsage();
|
|
909
|
+
this._usage.calls++;
|
|
737
910
|
|
|
738
911
|
const callId = `${this.current?.id || "run"}-${++this.callSequence}`;
|
|
739
912
|
let promptFile = "";
|
|
@@ -844,14 +1017,21 @@ class Engine extends EventEmitter {
|
|
|
844
1017
|
this.activeChild = null;
|
|
845
1018
|
if (promptFile) { try { fs.rmSync(promptFile); } catch {} }
|
|
846
1019
|
const durationMs = Date.now() - started;
|
|
847
|
-
|
|
1020
|
+
// Cikti tek kez ayristirilir; hem kullanim telemetrisi hem metin ayni sonuctan okunur.
|
|
1021
|
+
const normalized = normalizeCliOutput(agent, stdout);
|
|
1022
|
+
if (normalized.usage) this.recordUsage(displayName, normalized.usage);
|
|
1023
|
+
this.publish("activity", { ...base, kind: "process.finished", exitCode: code, signal, durationMs, usage: normalized.usage, reason: silenceTimedOut ? "silence-timeout" : (timedOut ? "timeout" : null) });
|
|
848
1024
|
if (silenceTimedOut) return reject(new Error(`CLI_STALLED: ${agent.cmd} ${Math.round(silenceTimeoutMs / 1000)} saniye boyunca cikti uretmedi ve otomatik durduruldu.`));
|
|
849
1025
|
if (timedOut) return reject(new Error(`${agent.cmd} ${Math.round(timeoutMs / 1000)} saniyede zaman asimina ugradi${signal ? ` (signal=${signal})` : ""}. ${clip(stdout || stderr, 500)}`));
|
|
850
|
-
|
|
851
|
-
|
|
1026
|
+
// Teshis icin HER IKI akis da gerekir: bazi CLI'lar (Gemini dahil) kota/oturum hatasini
|
|
1027
|
+
// stdout'a, yigin izini stderr'e yazar. Yalnizca birini almak siniflandiriciyi kor birakiyordu.
|
|
1028
|
+
if (code !== 0) {
|
|
1029
|
+
const detail = [stderr, stdout].map((stream) => String(stream || "").trim()).filter(Boolean).join("\n");
|
|
1030
|
+
return reject(new Error(`${agent.cmd} cikis kodu ${code ?? "yok"}${signal ? ` (signal=${signal})` : ""}.\n${clip(detail, 2000)}`));
|
|
1031
|
+
}
|
|
852
1032
|
if (normalized.error) return reject(new Error(`OpenCode model/provider hatasi: ${clip(normalized.error, 500)}`));
|
|
853
1033
|
if (!normalized.text) return reject(new Error(`${agent.cmd} kullanilabilir cikti dondurmedi.${stderr ? ` ${clip(stderr, 300)}` : ""}`));
|
|
854
|
-
resolve({ text: normalized.text, stderr: stderr.trim(), exitCode: code, durationMs, callId });
|
|
1034
|
+
resolve({ text: normalized.text, stderr: stderr.trim(), exitCode: code, durationMs, callId, usage: normalized.usage });
|
|
855
1035
|
});
|
|
856
1036
|
|
|
857
1037
|
// Prompt argumanla/dosyayla verilse bile stdin MUTLAKA kapatilmali. OpenCode gibi CLI'lar
|
|
@@ -897,7 +1077,13 @@ class Engine extends EventEmitter {
|
|
|
897
1077
|
// auth/timeout veya proses hatasi ayni operatoru tekrar calistirmamalidir.
|
|
898
1078
|
if (!/gecerli JSON dondurmedi/i.test(String(error?.message || ""))) throw error;
|
|
899
1079
|
if (attempt >= retries) break;
|
|
900
|
-
|
|
1080
|
+
// Modelin ne dondurdugunu duzeltme istegine geri veriyoruz: "yalnizca JSON dondur"
|
|
1081
|
+
// talimatini tekrarlamak, hatayi goremeyen model icin cogu zaman ayni cevabi uretiyordu.
|
|
1082
|
+
correction = `\n\nONCEKI CEVAP PROTOKOLE UYMADI: ${error.message}\n` +
|
|
1083
|
+
`Senin onceki cevabin (kirpilmis):\n"""\n${clip(String(error.rawText || ""), 1200)}\n"""\n` +
|
|
1084
|
+
`Bu cevap gecerli bir JSON nesnesi degildi. Simdi SADECE tek bir JSON nesnesi dondur: ` +
|
|
1085
|
+
`ilk karakter { ve son karakter } olsun; oncesinde/sonrasinda selamlama, aciklama, dusunce, ` +
|
|
1086
|
+
`Markdown veya \`\`\` kod bloğu OLMASIN. Metin alanlarindaki cift tirnaklari \\" ile kacir.`;
|
|
901
1087
|
this.publish("log", { level: "warn", msg: `${label} protokol hatasi; operator yeniden deneniyor (${attempt + 2}/${retries + 1}).` });
|
|
902
1088
|
}
|
|
903
1089
|
}
|
|
@@ -959,7 +1145,8 @@ class Engine extends EventEmitter {
|
|
|
959
1145
|
: "";
|
|
960
1146
|
const protocol = phase === "plan"
|
|
961
1147
|
? `Yalnizca gecerli JSON dondur. Gorev bir is/degisiklik/arastirma gerektiriyorsa delege et: {"summary":"yaklasim", "completionCriteria":["..."], "assignments":[{"id":"benzersiz-id", "agent":"catalog-name", "kind":"implement|review|research|plan", "instruction":"net gorev ve teslimat", "dependsOn":[]${skillField}}]} (en az bir assignment). ` +
|
|
962
|
-
`Gorev yalnizca bir bilgi/soru
|
|
1148
|
+
`Gorev yalnizca bir bilgi/soru VEYA bir selamlasma/sohbet mesaji ise (or. "merhaba", "nasilsin", "kac beceri var") ve yaniti dogrudan verebiliyorsan, HICBIR delegasyon acmadan: {"status":"complete", "final":"kullaniciya gosterilecek CEVABIN TAM METNI", "verification":"cevabin dayandigi kaynak"}. ` +
|
|
1149
|
+
`Selamlasmalarda bile cevabi JSON DISINA yazma: kullaniciya gosterilen tek alan "final"dir, nesnenin disindaki her metin atilir. Gercek is gerektiren gorevi bu kestirmeyle atlatma.${skillProtocol}`
|
|
963
1150
|
: `Yalnizca gecerli JSON dondur. Is tamamlanmadiysa {"status":"continue", "reason":"...", "assignments":[{"id":"...", "agent":"...", "kind":"implement|review|research|plan", "instruction":"...", "dependsOn":[]${skillField}}]}; tum kabul kriterleri karsilandiysa {"status":"complete", "final":"en fazla 5 kisa maddeyle ne yapildi", "verification":"tek satir dogrulama"}. Dosya listesini motor ekleyecek; final icinde uzun log veya ham agent cevabi tekrarlama. Continue icin en az bir yeni assignment zorunlu.${skillProtocol}\n` +
|
|
964
1151
|
`INCELEME DISIPLINI: Bagimsiz denetim VERDICT: PASS verdiyse ayni teslimat icin YENI review delegasyonu acma; complete dondur ve dusuk/orta onemdeki kalan notlari final metninde kalan risk olarak belirt. Ekipte bulunmayan dogrulama yetenegini (or. canli tarayici) tamamlanma sarti yapma; NOT RUN kalan dusuk riskli kontroller teslimati engellemez.`;
|
|
965
1152
|
const strategy = task.executionMode === "fast"
|
|
@@ -968,6 +1155,11 @@ class Engine extends EventEmitter {
|
|
|
968
1155
|
? "DERIN MOD: Isi uygun uzmanliklar arasinda dagit — planlama, uygulama, test ve bagimsiz inceleme icin AYRI ve dogru uzmanlari kullan."
|
|
969
1156
|
: "DENGELI MOD: Katalogda planner, executor ve reviewer varsa ucunu da ILK planda kullan. PLANLAMA -> UYGULAMA -> BAGIMSIZ INCELEME delegasyonlarini dependsOn ile ayni turda zincirle ve isi tek turda bitirmeyi hedefle. Ayni rolde birden fazla esdeger agent varsa yalnizca en uygun olani sec.";
|
|
970
1157
|
return `${role}\n\n---\n## Operator protokolu\n${protocol}\n` +
|
|
1158
|
+
// OpenCode gibi sohbet odakli CLI'lar varsayilan olarak once bir cumle yazip JSON'u
|
|
1159
|
+
// arkasina ekliyor; motor bunu ayiklayabilse de belirsizlik protokol hatasi uretiyordu.
|
|
1160
|
+
`## Cikti bicimi (KATI)\nCevabin TAMAMI tek bir JSON nesnesi olmali: ilk karakter { , son karakter } . ` +
|
|
1161
|
+
`Oncesinde/sonrasinda selamlama, aciklama, dusunce notu, Markdown basligi veya \`\`\` kod bloğu YAZMA. ` +
|
|
1162
|
+
`Kullaniciya iletmek istedigin her seyi ilgili JSON alanina ("final" veya "summary") koy; nesnenin disindaki metin kullaniciya ULASMAZ.\n` +
|
|
971
1163
|
`Yalnizca katalogdaki agent adlarini kullan. Her assignment kind degeri secilen agentin allowedKinds listesinde olmali. Kendine gorev atama. Ayni delegasyon id'sini tekrar kullanma.\n` +
|
|
972
1164
|
`Bir agent basarisiz veya kullanilamaz raporlandiysa ayni isi ona tekrar verme; katalogdaki alternatif bir uzmani sec.\n` +
|
|
973
1165
|
`AJAN SECIMI: Her alt gorevi, katalogdaki YETENEKLERE ve role gore o ise EN UYGUN uzmana ata; tum isi tek bir CLI'ye yigma. Isin ihtiyac duydugu her uzmanlik icin dogru uzmani sec (plan/tasarim -> planlama yetenegi; uygulama -> implementation; inceleme/dogrulama -> review/test; arastirma -> research/web). Ayni KIND icin birden fazla eşdeğer agent varsa YALNIZCA birini (en uygun ve en dusuk maliyetli) kullan; ayni isi iki eşdeğer agente verme. Farkli roller (or. bir uygulayici + bir inceleyici) FARKLI islerdir, tekrar sayilmaz.\n` +
|
|
@@ -1088,6 +1280,8 @@ class Engine extends EventEmitter {
|
|
|
1088
1280
|
this._cwd = path.resolve(store.WORK_BASE, task.targetDir || cfg.workingDir || ".");
|
|
1089
1281
|
this._snapBefore = snapshotDir(this._cwd);
|
|
1090
1282
|
this.startLiveDiff(task);
|
|
1283
|
+
// Onay sonrasi devam eden gorevde sayaci sifirlamayiz; ilk girise ozgu.
|
|
1284
|
+
if (!task.approved || !this._usage) this.resetUsage();
|
|
1091
1285
|
this.publish("log", { level: "task", msg: `GOREV ${task.id}: ${task.prompt}` }, task.id);
|
|
1092
1286
|
// Otomatik surumleme: gorev CALISMADAN ONCE calisma klasorunun bir surumunu al ki ajan
|
|
1093
1287
|
// mevcut kodu bozarsa bu gorev oncesine tek tikla donulebilsin. Yalnizca ilk girus'te
|
|
@@ -1128,7 +1322,19 @@ class Engine extends EventEmitter {
|
|
|
1128
1322
|
const shortcutRetries = requiresWork ? Math.max(1, cfg.operator?.protocolRetries ?? 1) : 0;
|
|
1129
1323
|
let plan, shortcutAttempt = 0, planCorrection = "";
|
|
1130
1324
|
while (true) {
|
|
1131
|
-
|
|
1325
|
+
try {
|
|
1326
|
+
plan = await this.invokeOperatorJson(operatorCli, this.operatorPrompt(task, cfg, memory, state, "plan") + planCorrection, cfg, "operator-plan", "Operator plani");
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
// Sohbet kurtarma: gorev delegasyon gerektirmiyorsa (selamlasma, kisa soru) ve operator
|
|
1329
|
+
// protokol nesnesi yerine duz metinle cevap verdiyse, kullaniciyi "gecerli JSON
|
|
1330
|
+
// dondurmedi" hatasiyla karsilamak yanlistir — istenen sey zaten o metindi. Yalnizca
|
|
1331
|
+
// is gerektirmeyen gorevlerde ve elde gercek bir metin varken devreye girer.
|
|
1332
|
+
const prose = conversationalAnswer(error);
|
|
1333
|
+
if (requiresWork || !prose) throw error;
|
|
1334
|
+
this.publish("log", { level: "warn", msg: "Operator protokol nesnesi dondurmedi; gorev is gerektirmedigi icin duz metin cevabi teslimat sayildi." }, task.id);
|
|
1335
|
+
task.teamState = state;
|
|
1336
|
+
return this.complete(task, state, "done", prose, { verification: "Operatorun dogrudan yaniti (protokol nesnesi yerine duz metin)." });
|
|
1337
|
+
}
|
|
1132
1338
|
const isShortcut = plan && String(plan.status).toLowerCase() === "complete" && !Array.isArray(plan.assignments);
|
|
1133
1339
|
if (!isShortcut) break;
|
|
1134
1340
|
if (!requiresWork) {
|
|
@@ -1374,6 +1580,7 @@ class Engine extends EventEmitter {
|
|
|
1374
1580
|
task.summary = concise;
|
|
1375
1581
|
task.final = finalText;
|
|
1376
1582
|
task.changes = changes;
|
|
1583
|
+
task.usage = this.usageSummary();
|
|
1377
1584
|
const warnings = (Array.isArray(decision.warnings) ? decision.warnings : []).map(String).filter(Boolean);
|
|
1378
1585
|
task.delivery = {
|
|
1379
1586
|
summary: concise,
|
|
@@ -1389,7 +1596,7 @@ class Engine extends EventEmitter {
|
|
|
1389
1596
|
const found = store.findTask(task.id);
|
|
1390
1597
|
store.moveTask(found?.state || "pending", status, task);
|
|
1391
1598
|
store.appendMemory(`${task.id} [${status}] ${task.prompt}`, `${task.summary}\nDosyalar: ${[...changes.created, ...changes.modified, ...changes.deleted].join(", ")}`);
|
|
1392
|
-
this.publish("result", { id: task.id, prompt: task.prompt, status, dir: this._cwd, changes, summary: task.summary, delivery: task.delivery, operator: task.operatorCli, rounds: state.round }, task.id);
|
|
1599
|
+
this.publish("result", { id: task.id, prompt: task.prompt, status, dir: this._cwd, changes, summary: task.summary, delivery: task.delivery, usage: task.usage, operator: task.operatorCli, rounds: state.round }, task.id);
|
|
1393
1600
|
this.publish("log", { level: "ok", msg: `GOREV tamamlandi: ${task.id}` }, task.id);
|
|
1394
1601
|
this.notifyOutcome(task, status, { summary: task.summary, rounds: state.round, fileCount: files.length, verification: task.delivery.verification });
|
|
1395
1602
|
this.emit("queue");
|
|
@@ -1418,4 +1625,6 @@ module.exports = new Engine();
|
|
|
1418
1625
|
module.exports._internals = {
|
|
1419
1626
|
normalizeAssignments, ensureBalancedRoleChain, extractVerdict, clipMiddle, snapshotDir, diffSnapshots,
|
|
1420
1627
|
captureTextSnapshot, buildLineDiff, describeFileDiff, isSensitiveDiffPath, taskRequiresDelegation,
|
|
1628
|
+
parseJson, conversationalAnswer, classifyCliError, RECOVERABLE_CLI_ERRORS, QUARANTINE_CLI_ERRORS,
|
|
1629
|
+
normalizeCliOutput, addUsage, usageTotal,
|
|
1421
1630
|
};
|