@appaflytech/zeyrek-ai-mcp 0.0.18 → 0.0.20
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/README.md +13 -14
- package/dist/config.js +2 -0
- package/dist/knowledge/componentCatalog.js +204 -836
- package/dist/knowledge/componentEvents.js +75 -368
- package/dist/knowledge/mobileComponentCatalog.js +331 -0
- package/dist/knowledge/mobileComponentEvents.js +50 -0
- package/dist/knowledge/normalize.js +63 -66
- package/dist/knowledge/platform/lint.js +205 -0
- package/dist/knowledge/platform/mobile.js +78 -0
- package/dist/knowledge/platform/mobileRules.js +121 -0
- package/dist/knowledge/platform/mobileTabs.js +108 -0
- package/dist/knowledge/platform/profile.js +20 -0
- package/dist/knowledge/platform/resolve.js +122 -0
- package/dist/knowledge/platform/web.js +38 -0
- package/dist/knowledge/rules.js +6 -29
- package/dist/knowledge/styleResolve.js +1 -1
- package/dist/knowledge/styleResolveNative.js +410 -0
- package/dist/knowledge/themeResolve.js +3 -3
- package/dist/platform/capture/actions.js +333 -0
- package/dist/platform/capture/browser.js +1 -0
- package/dist/platform/{screenshot.js → capture/capture.js} +23 -509
- package/dist/platform/capture/index.js +10 -0
- package/dist/platform/capture/readiness.js +103 -0
- package/dist/platform/capture/summary.js +77 -0
- package/dist/platform/capture/target.js +5 -0
- package/dist/platform/capture/targets/studio-mobile.js +15 -0
- package/dist/platform/capture/targets/studio-web.js +5 -0
- package/dist/platform/domSummary.js +1 -1
- package/dist/platform/responses.js +1 -2
- package/dist/platform/strictArguments.js +2 -22
- package/dist/platform/writeDiff.js +31 -58
- package/dist/resources/examples/mobile-app-shell-drawer.json +209 -0
- package/dist/resources/examples/mobile-app-shell-stack.json +123 -0
- package/dist/resources/examples/mobile-app-shell-tab.json +174 -0
- package/dist/resources/examples/mobile-list-detail-form.json +656 -0
- package/dist/resources/guides/component.md +6 -9
- package/dist/resources/guides/functions.md +3 -32
- package/dist/resources/guides/getting-started.md +0 -35
- package/dist/resources/guides/layout.md +11 -7
- package/dist/resources/guides/mobile.md +215 -0
- package/dist/resources/guides/page.md +71 -49
- package/dist/resources/guides/translation.md +6 -5
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +0 -18
- package/dist/server.js +2 -0
- package/dist/tools/applications.js +60 -3
- package/dist/tools/components.js +50 -10
- package/dist/tools/designSchemaPatch.js +18 -13
- package/dist/tools/externalComponents.js +25 -4
- package/dist/tools/flows.js +51 -38
- package/dist/tools/functions.js +10 -20
- package/dist/tools/layouts.js +41 -9
- package/dist/tools/microservices.js +11 -39
- package/dist/tools/mobile.js +203 -0
- package/dist/tools/pages.js +127 -95
- package/dist/tools/platformInfo.js +2 -1
- package/dist/tools/profileWrite.js +45 -0
- package/dist/tools/schemaOps.js +2 -2
- package/dist/tools/screenWrite.js +2 -2
- package/dist/tools/screens.js +9 -3
- package/dist/tools/workflows.js +12 -39
- package/package.json +2 -7
- package/dist/platform/diagnostics.js +0 -85
- package/dist/platform/ifExists.js +0 -129
- package/dist/platform/writeView.js +0 -17
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kare ne zaman hazır? (G-30 → B-6, 2026-09-07)
|
|
3
|
+
*
|
|
4
|
+
* `delayMs` TAHMİNDİ; 09-05'te runtime koşan akış sayacını yayınlamaya başladı (`@ng-apps/flow`
|
|
5
|
+
* → flow-runtime-errors.ts) ve bekleme "running sıfır, completed > 0" olunca bitiyordu. Bu şart
|
|
6
|
+
* mount akışı OLMAYAN sayfada (giriş ekranı) hiç sağlanmaz; akış tetiklemeyen `set` eylemi de
|
|
7
|
+
* `completed`'ı artırmaz. 09-06·2 koşusunda üç eylemli giriş denemesi bu yüzden 47 sn sürdü;
|
|
8
|
+
* sentetik ölçümde sayfa 0.3 sn'de hazırken aynı çağrı 48 sn'ydi — süre sayfanın değil, bu
|
|
9
|
+
* döngünün. Hazırlık artık TEK anlık görüntüden okunur: akış sayacı + Studio'nun yükleme
|
|
10
|
+
* iskeleti (`ViewDetail.svelte` → `.view-detail > .bx--skeleton__placeholder`) + runtime
|
|
11
|
+
* düğümleri (`[data-element-id]`) + hata/bulunamadı hâli + adres. Karar `decideReadiness`'ta.
|
|
12
|
+
*
|
|
13
|
+
* Yüklenmemiş kare ASLA dönmez: bütçe dolduğunda sayfa hâlâ hazır değilse çağrı hatayla düşer
|
|
14
|
+
* (`captureUnready: true` teşhis kipi dışında). Bozuk sayfayı görmek istiyorsan hata mesajı
|
|
15
|
+
* neyin görüldüğünü zaten söyler.
|
|
16
|
+
*/
|
|
17
|
+
export const readinessScript = `(function () { /* readinessSnapshot */
|
|
18
|
+
var activity = window.__ngAppsFlowActivity;
|
|
19
|
+
return {
|
|
20
|
+
activity: activity && typeof activity.running === 'number' && typeof activity.completed === 'number' ? { running: activity.running, completed: activity.completed } : null,
|
|
21
|
+
runtimeNodes: document.querySelectorAll('[data-element-id]').length,
|
|
22
|
+
skeleton: !!document.querySelector('.view-detail > .bx--skeleton__placeholder'),
|
|
23
|
+
viewDetail: !!document.querySelector('.view-detail'),
|
|
24
|
+
errorState: !!document.querySelector('.view-detail__error, .view-detail .error-page'),
|
|
25
|
+
readyState: String(document.readyState),
|
|
26
|
+
href: String(location.href)
|
|
27
|
+
};
|
|
28
|
+
})()`;
|
|
29
|
+
/** Okunan değer gerçekten sayaç mı; değilse sayfa sayaç yayınlamıyordur (eski runtime). */
|
|
30
|
+
export function isFlowActivity(activity) {
|
|
31
|
+
if (!activity || typeof activity !== 'object')
|
|
32
|
+
return false;
|
|
33
|
+
const { running, completed } = activity;
|
|
34
|
+
return typeof running === 'number' && typeof completed === 'number';
|
|
35
|
+
}
|
|
36
|
+
/** Tarayıcıdan dönen değer anlık görüntü mü; yönlendirme sırasında evaluate düşer ve undefined kalır. */
|
|
37
|
+
export function parseReadiness(raw) {
|
|
38
|
+
if (!raw || typeof raw !== 'object')
|
|
39
|
+
return undefined;
|
|
40
|
+
const value = raw;
|
|
41
|
+
if (typeof value.runtimeNodes !== 'number' || typeof value.href !== 'string')
|
|
42
|
+
return undefined;
|
|
43
|
+
return {
|
|
44
|
+
activity: isFlowActivity(value.activity) ? { running: value.activity.running, completed: value.activity.completed } : null,
|
|
45
|
+
runtimeNodes: value.runtimeNodes,
|
|
46
|
+
skeleton: value.skeleton === true,
|
|
47
|
+
viewDetail: value.viewDetail === true,
|
|
48
|
+
errorState: value.errorState === true,
|
|
49
|
+
readyState: String(value.readyState ?? ''),
|
|
50
|
+
href: value.href
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** `/dev` ve `/view` aynı sayfadır (Studio `hooks.ts` reroute); yönlendirme kararı yalnız yoldan verilir. */
|
|
54
|
+
export function runtimePath(url) {
|
|
55
|
+
try {
|
|
56
|
+
return new URL(url).pathname.replace(/^\/dev\//, '/view/').replace(/\/+$/, '');
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return url;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** "Hiç akış koşmadı" kararı için DOM'un kaç yoklama değişmemesi gerekir (~600 ms = eski settleMs). */
|
|
63
|
+
const quietPolls = 5;
|
|
64
|
+
/** Sayaç yayınlanmayan runtime'da tek kanıt DOM durulmasıdır; daha uzun beklenir (~1 sn). */
|
|
65
|
+
const legacyQuietPolls = 8;
|
|
66
|
+
/** Runtime dışına yönlendirme: hedef sayfanın kendi JS'i çizmeyi bitirsin (~1.5 sn). */
|
|
67
|
+
const redirectQuietPolls = 12;
|
|
68
|
+
/**
|
|
69
|
+
* Tek anlık görüntüden "kare alınabilir" kararı; iki uçta da yanlış beklememek için tek yerde.
|
|
70
|
+
* `quiet` görüntünün kaç yoklamadır değişmediğidir, `completedBefore` bu beklemenin başındaki
|
|
71
|
+
* bitmiş akış sayısı (sayfa değiştiyse çağıran sıfırlar). İskelet görünüyorsa hiçbir karar yok.
|
|
72
|
+
*/
|
|
73
|
+
export function decideReadiness(snapshot, quiet, completedBefore, baselineUrl) {
|
|
74
|
+
if (snapshot.skeleton)
|
|
75
|
+
return undefined;
|
|
76
|
+
const { activity } = snapshot;
|
|
77
|
+
const idle = activity !== null && activity.running === 0;
|
|
78
|
+
if (idle && activity.completed > completedBefore && snapshot.runtimeNodes > 0)
|
|
79
|
+
return 'ready';
|
|
80
|
+
if (idle && snapshot.runtimeNodes > 0 && quiet >= quietPolls)
|
|
81
|
+
return 'ready-no-flows';
|
|
82
|
+
if (activity === null && snapshot.runtimeNodes > 0 && quiet >= legacyQuietPolls)
|
|
83
|
+
return 'ready-no-counter';
|
|
84
|
+
if (idle && snapshot.errorState && quiet >= quietPolls)
|
|
85
|
+
return 'error';
|
|
86
|
+
if (!snapshot.viewDetail && snapshot.readyState === 'complete' && baselineUrl !== undefined && runtimePath(snapshot.href) !== runtimePath(baselineUrl) && quiet >= redirectQuietPolls)
|
|
87
|
+
return 'redirected';
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
/** Hata mesajı için: bütçe dolduğunda sayfada NE görüldü. */
|
|
91
|
+
export function describeUnready(readiness, apiNote) {
|
|
92
|
+
const snapshot = readiness.snapshot;
|
|
93
|
+
const suffix = apiNote === undefined ? '' : ` · ${apiNote}`;
|
|
94
|
+
if (!snapshot)
|
|
95
|
+
return `tarayıcı sayfayı hiç değerlendiremedi (sürekli yönlendirme ya da bağlam düştü)${suffix}`;
|
|
96
|
+
if (snapshot.skeleton)
|
|
97
|
+
return `sayfa hâlâ yükleme iskeletinde${suffix}`;
|
|
98
|
+
if (snapshot.activity && snapshot.activity.running > 0)
|
|
99
|
+
return `${snapshot.activity.running} akış hâlâ koşuyor (${snapshot.activity.completed} bitti)${suffix}`;
|
|
100
|
+
if (snapshot.runtimeNodes === 0)
|
|
101
|
+
return `runtime hiç render olmadı — data-element-id taşıyan düğüm yok${snapshot.viewDetail ? '' : ', .view-detail kabı da yok'}${suffix}`;
|
|
102
|
+
return `sayfa durulmadı (${snapshot.runtimeNodes} düğüm, sayaç ${snapshot.activity ? `${snapshot.activity.running} koşan / ${snapshot.activity.completed} biten` : 'yok'})${suffix}`;
|
|
103
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NE GÖRÜNÜYOR — resimsiz (S-5). Aynı sayfa yüklemesinden çıkan üçüncü çıktı: her KÖK runtime
|
|
3
|
+
* düğümünün görünürlüğü, metni, satır sayısı, açık sekmesi, değeri ve hata metinleri.
|
|
4
|
+
*
|
|
5
|
+
* Kök düğüm = kimliğinde `__` OLMAYAN düğüm; iç roller (`tbl__row-3`) satır sayısına dönüşür,
|
|
6
|
+
* listeye girmez. Tavan 300 düğüm, metin 80 karakter (K3) — özet bir kareyi değiştirmeli, onun
|
|
7
|
+
* kadar pahalı olmamalı. Tip `data-component-type`'tan gelir (frontend f-02); runtime basmıyorsa
|
|
8
|
+
* alan YAZILMAZ ve `typed: false` döner — `layoutReportUnavailable` ile aynı dürüstlük kuralı.
|
|
9
|
+
*/
|
|
10
|
+
export function domSummaryScript(viewportHeight) {
|
|
11
|
+
return `(function () { /* domSummary */
|
|
12
|
+
var MAX = 300;
|
|
13
|
+
var CUT = 80;
|
|
14
|
+
// Görünür alanın GERÇEK boyu: fullPage ve clip yolları viewport'u içerik boyuna büyütür ve
|
|
15
|
+
// o anda window.innerHeight = içerik yüksekliği olur — her düğüm "ekran içinde" görünürdü ve
|
|
16
|
+
// "kart katlanmanın altında mı" sorusu HER ZAMAN 'visible' cevabı alırdı (B-10).
|
|
17
|
+
var VH = ${Math.max(Math.round(viewportHeight), 1)};
|
|
18
|
+
var clean = function (value) { return String(value || '').replace(/\\s+/g, ' ').trim(); };
|
|
19
|
+
// Mobil sekme çubuğu '__tabs' bir KÖK satırdır (öğeleri '__tabs__item-<slug>' iç roldür, K14);
|
|
20
|
+
// web runtime bu kimliği hiç basmaz.
|
|
21
|
+
var ROOT_SELECTOR = '[data-element-id]:not([data-element-id*="__"]), [data-element-id="__tabs"]';
|
|
22
|
+
var isRoot = function (element) { var id = String(element.getAttribute('data-element-id') || ''); return id.indexOf('__') < 0 || id === '__tabs'; };
|
|
23
|
+
var all = Array.prototype.slice.call(document.querySelectorAll('[data-element-id]'));
|
|
24
|
+
var roots = all.filter(isRoot);
|
|
25
|
+
var typed = false;
|
|
26
|
+
var nodes = roots.slice(0, MAX).map(function (node) {
|
|
27
|
+
var id = node.getAttribute('data-element-id');
|
|
28
|
+
var type = node.getAttribute('data-component-type');
|
|
29
|
+
if (type) typed = true;
|
|
30
|
+
// Bir olgu, ONU TAŞIYAN düğüme yazılır: en yakın kök ata bu düğüm değilse o olgu
|
|
31
|
+
// çocuğundur ve orada zaten listelenecektir (B-8). Aksi hâlde aynı değer üç satırda çıkar.
|
|
32
|
+
var owned = function (element) { return !!element && element.closest(ROOT_SELECTOR) === node; };
|
|
33
|
+
var rect = node.getBoundingClientRect();
|
|
34
|
+
var visible = typeof node.checkVisibility === 'function'
|
|
35
|
+
? !!node.checkVisibility({ checkVisibilityCSS: true, checkOpacity: true })
|
|
36
|
+
: (rect.width > 0 && rect.height > 0);
|
|
37
|
+
var inViewport = rect.bottom > 0 && rect.right > 0 && rect.top < VH && rect.left < (window.innerWidth || 0);
|
|
38
|
+
// Kap düğümün innerText'i ÇOCUKLARININ metnidir; metin yalnız YAPRAK köklerde yazılır.
|
|
39
|
+
var nested = Array.prototype.slice.call(node.querySelectorAll('[data-element-id]'))
|
|
40
|
+
.some(isRoot);
|
|
41
|
+
var text = nested ? '' : clean(node.innerText);
|
|
42
|
+
if (text.length > CUT) text = text.slice(0, CUT) + '…';
|
|
43
|
+
var rows = document.querySelectorAll('[data-element-id^="' + id + '__row-"]').length;
|
|
44
|
+
var tab = node.querySelector('[role="tab"][aria-selected="true"]');
|
|
45
|
+
var field = node.querySelector('input, select, textarea');
|
|
46
|
+
var chosen = node.querySelector('.selected-item');
|
|
47
|
+
var holder = field || chosen;
|
|
48
|
+
var value = !owned(holder) ? '' : (field ? (field.type === 'password' ? '***' : clean(field.value)) : clean(chosen.textContent));
|
|
49
|
+
var media = node.querySelector('img, svg, canvas, video, iframe');
|
|
50
|
+
// İç içe <li> hem grubu hem alt mesajları sayıyordu: tek grup + 2 alt hata 3 görünüyordu (B-7).
|
|
51
|
+
var errorItems = Array.prototype.slice.call(node.querySelectorAll('.form-validation-errors li:not(:has(li))'))
|
|
52
|
+
.filter(owned).map(function (item) { return clean(item.textContent); });
|
|
53
|
+
// aria-invalid hem köke hem input'a yazılabiliyor (Password.svelte); yalnız ALANLAR sayılır.
|
|
54
|
+
var invalid = Array.prototype.slice.call(node.querySelectorAll('input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"]')).filter(owned).length;
|
|
55
|
+
var summary = { elementId: id, visible: !!visible, inViewport: !!inViewport };
|
|
56
|
+
if (type) summary.type = type;
|
|
57
|
+
if (text) summary.text = text;
|
|
58
|
+
if (rows > 0) summary.rows = rows;
|
|
59
|
+
if (owned(tab)) summary.activeTab = clean(tab.textContent);
|
|
60
|
+
if (value) summary.value = value;
|
|
61
|
+
if (visible && !text && !media && !nested) summary.empty = true;
|
|
62
|
+
if (errorItems.length > 0) summary.errors = errorItems;
|
|
63
|
+
else if (invalid > 0) summary.errors = ['aria-invalid: ' + invalid];
|
|
64
|
+
return summary;
|
|
65
|
+
});
|
|
66
|
+
var messages = [];
|
|
67
|
+
Array.prototype.slice.call(document.querySelectorAll('.ng-toast')).forEach(function (toast) {
|
|
68
|
+
var name = String(toast.className || '');
|
|
69
|
+
var kind = name.indexOf('ng-toast--error') >= 0 ? 'toast/error' : (name.indexOf('ng-toast--success') >= 0 ? 'toast/success' : 'toast');
|
|
70
|
+
messages.push({ kind: kind, text: clean(toast.innerText).slice(0, 160) });
|
|
71
|
+
});
|
|
72
|
+
Array.prototype.slice.call(document.querySelectorAll('.view-detail__error, .error-page')).forEach(function (node) {
|
|
73
|
+
messages.push({ kind: 'error', text: clean(node.innerText).slice(0, 160) });
|
|
74
|
+
});
|
|
75
|
+
return { nodes: nodes, messages: messages, truncated: roots.length > MAX, typed: typed };
|
|
76
|
+
})()`;
|
|
77
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mobil uygulama: `@ng-apps/ui-native` `NativeRuntime`, Studio'da react-native-web ile çizilir
|
|
3
|
+
* (plans/mobil-uygulama-expo F5). Ağaçta `<form>` yoktur — `submit` runtime'ın yayımladığı köprüyle
|
|
4
|
+
* form düğümünün `submit` olayını makineye iletir (`web-runtime-host.tsx` `__ngAppsSubmitForm`).
|
|
5
|
+
* Sekme çubuğu `__tabs`, öğeleri `__tabs__item-<sayfa slug'ı>` kimliğini taşır (K14). Açılır
|
|
6
|
+
* listenin tetikleyicisi `role="combobox"`, seçenekleri `role="option"` + `data-value` taşır ve liste
|
|
7
|
+
* `Modal` katmanında açılır (`ng-apps-ui-native` `dropdown`).
|
|
8
|
+
*/
|
|
9
|
+
export const studioMobileTarget = Object.freeze({
|
|
10
|
+
name: 'studio-mobile',
|
|
11
|
+
submitBridge: '__ngAppsSubmitForm',
|
|
12
|
+
optionSelector: '[role="option"][data-value], [role="treeitem"][data-value]',
|
|
13
|
+
optionsOutsideNode: true,
|
|
14
|
+
platformNote: 'Mobil uygulama: kare Studio\'daki react-native-web çizimidir (/dev|/view) — cihazdaki native çizimle yapısal olarak (düğüm, sıra, ölçü modu, renk) aynı, piksel olarak değil.'
|
|
15
|
+
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (card) · GİZLİ" 200 karakterdir.
|
|
8
8
|
*
|
|
9
9
|
* Katman saftır: tarayıcıdan gelen anlık görüntüyü doğrular ve biçimler. Toplama işi
|
|
10
|
-
* `
|
|
10
|
+
* `capture/summary.ts` `domSummaryScript`'te, sunum `pages.ts`'te.
|
|
11
11
|
*/
|
|
12
12
|
function text(value) {
|
|
13
13
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { compactDiagnostics } from './diagnostics.js';
|
|
2
1
|
import { logError, logStep } from '../observability/trace.js';
|
|
3
2
|
import { PlatformApiError, describeErrorBody } from './errors.js';
|
|
4
3
|
import { PolicyBlockError } from '../knowledge/policy.js';
|
|
@@ -140,7 +139,7 @@ function asStructured(payload) {
|
|
|
140
139
|
return { data: payload };
|
|
141
140
|
}
|
|
142
141
|
export function toolResult(payload, config, expectFailure) {
|
|
143
|
-
const pruned = prune(
|
|
142
|
+
const pruned = prune(payload);
|
|
144
143
|
const { payload: safe, masked, tokenRefs } = maskSecrets(pruned);
|
|
145
144
|
const runtimeErrors = runtimeErrorSummary(safe);
|
|
146
145
|
if (runtimeErrors.length > 0) {
|
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { diagnosticsParam, runWithDiagnostics } from './diagnostics.js';
|
|
3
|
-
/**
|
|
4
|
-
* `writes`/`updates` açıklamalı ama KAYIT yazmayan tool'lar: çalıştırma, önizleme, dışa aktarma, indirme,
|
|
5
|
-
* bağlantı testi. Yanıtlarında yazma tanılaması (silentlyDropped) yoktur; parametre yalnız tool listesini
|
|
6
|
-
* büyütür. Yeni böyle bir tool eklenirse buraya yazılır — `test/diagnostics.test.ts` listeyi kilitler.
|
|
7
|
-
*/
|
|
8
|
-
const NON_WRITING_TOOLS = new Set([
|
|
9
|
-
'microservice_execute', 'microservice_preview', 'microservice_debug', 'query_preview', 'workflow_preview',
|
|
10
|
-
'automation_preview', 'automation_trigger', 'row_export', 'file_download', 'microfunction_invoke',
|
|
11
|
-
'microfunction_runtime_test_connection'
|
|
12
|
-
]);
|
|
13
2
|
function isRawShape(value) {
|
|
14
3
|
if (!value || typeof value !== 'object' || '_def' in value)
|
|
15
4
|
return false;
|
|
@@ -30,23 +19,14 @@ export function rejectUnknownArguments(server) {
|
|
|
30
19
|
server.registerTool = ((name, config, callback) => {
|
|
31
20
|
if (!isRawShape(config.inputSchema))
|
|
32
21
|
return registerTool(name, config, callback);
|
|
33
|
-
|
|
34
|
-
// platforma geçiren tool'larda sızardı), yalnız toolResult'ın okuduğu bağlama konur.
|
|
35
|
-
const writesDiagnostics = config.annotations?.readOnlyHint === false && config.annotations.destructiveHint !== true && !NON_WRITING_TOOLS.has(name);
|
|
36
|
-
const shape = writesDiagnostics ? { ...config.inputSchema, diagnostics: diagnosticsParam } : config.inputSchema;
|
|
37
|
-
const run = writesDiagnostics
|
|
38
|
-
? ((args, extra) => {
|
|
39
|
-
const { diagnostics, ...rest } = args ?? {};
|
|
40
|
-
return runWithDiagnostics(diagnostics === 'full' ? 'full' : 'compact', () => callback(rest, extra));
|
|
41
|
-
})
|
|
42
|
-
: callback;
|
|
22
|
+
const shape = config.inputSchema;
|
|
43
23
|
const accepted = Object.keys(shape).join(', ');
|
|
44
24
|
const inputSchema = z.object(shape, {
|
|
45
25
|
errorMap: (issue, context) => issue.code === 'unrecognized_keys'
|
|
46
26
|
? { message: `${name} şu üst düzey argümanları tanımıyor: ${issue.keys.map(key => `'${key}'`).join(', ')}. Tanınmayan alan sessizce yok sayılmaz — çağrı hiç gönderilmedi. Bu tool'un okuduğu alanlar: ${accepted}.` }
|
|
47
27
|
: { message: context.defaultError }
|
|
48
28
|
}).strict();
|
|
49
|
-
return registerTool(name, { ...config, inputSchema },
|
|
29
|
+
return registerTool(name, { ...config, inputSchema }, callback);
|
|
50
30
|
});
|
|
51
31
|
return server;
|
|
52
32
|
}
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { PROP_VALUE_TYPES } from '../knowledge/normalize.js';
|
|
2
2
|
import { logStep } from '../observability/trace.js';
|
|
3
3
|
const maxDrops = 20;
|
|
4
|
-
const maxCollected = 1000;
|
|
5
4
|
const maxDepth = 12;
|
|
6
5
|
const missing = Symbol('missing');
|
|
7
6
|
/**
|
|
8
7
|
* Platformun kendi doldurduğu alanlar. Yalnız gönderilen tarafta da varsalar (patch yolunda gövde
|
|
9
8
|
* okunup geri yazılır) fark üretirlerdi ve bu fark hep gürültüdür: `audit.updatedAt` her yazmada
|
|
10
|
-
* değişir. 09-05 koşusunda `microservice_patch` yanıtındaki iki satırdan biri buydu.
|
|
11
|
-
* eşzamanlılık sayacıdır: okunan belge geri yazılınca platform onu artırır, fark her zaman gürültüdür.
|
|
9
|
+
* değişir. 09-05 koşusunda `microservice_patch` yanıtındaki iki satırdan biri buydu.
|
|
12
10
|
*/
|
|
13
11
|
const serverManagedKeys = new Set([
|
|
14
|
-
'audit', 'createdAt', 'updatedAt', 'createdUserId', 'createdUserName', 'updatedUserId', 'updatedUserName'
|
|
12
|
+
'audit', 'createdAt', 'updatedAt', 'createdUserId', 'createdUserName', 'updatedUserId', 'updatedUserName'
|
|
15
13
|
]);
|
|
16
14
|
/**
|
|
17
15
|
* Platformun KENDİ ürettiği kimlik. Gönderilen id kaydedilenden farklıysa bu bir kayıp değildir:
|
|
@@ -52,29 +50,6 @@ export const unstoredKeys = new Set(['fieldSlug']);
|
|
|
52
50
|
const canonicalValues = {
|
|
53
51
|
type: [['string', 'text']]
|
|
54
52
|
};
|
|
55
|
-
export function emptyWriteDiff() {
|
|
56
|
-
return { dropped: [], normalized: [], droppedCount: 0, droppedCountCapped: false, notes: [] };
|
|
57
|
-
}
|
|
58
|
-
function addDrop(sink, line) {
|
|
59
|
-
if (sink.droppedCount >= maxCollected) {
|
|
60
|
-
sink.droppedCountCapped = true;
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
sink.dropped.push(line);
|
|
64
|
-
sink.droppedCount++;
|
|
65
|
-
}
|
|
66
|
-
/** Listeyi `maxDrops` satırda keser; sayı ve tavan bilgisi notlara yazılır. */
|
|
67
|
-
function finishReport(sink) {
|
|
68
|
-
if (sink.dropped.length > maxDrops) {
|
|
69
|
-
const total = sink.droppedCountCapped ? `en az ${sink.droppedCount}` : `toplam ${sink.droppedCount}`;
|
|
70
|
-
sink.dropped = sink.dropped.slice(0, maxDrops);
|
|
71
|
-
sink.notes.push(`ilk ${maxDrops} fark listelendi, ${total} — gövdeyi *_get ile okuyup karşılaştırın.`);
|
|
72
|
-
}
|
|
73
|
-
else if (sink.droppedCountCapped) {
|
|
74
|
-
sink.notes.push(`sayım en az ${sink.droppedCount} farkta durdu.`);
|
|
75
|
-
}
|
|
76
|
-
return sink;
|
|
77
|
-
}
|
|
78
53
|
/**
|
|
79
54
|
* Yolun son alan adı; `tags[0]` ya da `fields[fieldId=x]` gibi indeks/anahtar ekini atar.
|
|
80
55
|
*/
|
|
@@ -106,11 +81,11 @@ function lookup(saved, key) {
|
|
|
106
81
|
return Object.hasOwn(record, key) ? record[key] : missing;
|
|
107
82
|
}
|
|
108
83
|
function compare(sent, saved, path, sink, depth) {
|
|
109
|
-
if (sink.
|
|
84
|
+
if (sink.dropped.length >= maxDrops || depth > maxDepth)
|
|
110
85
|
return;
|
|
111
86
|
if (Array.isArray(sent)) {
|
|
112
87
|
if (saved === missing) {
|
|
113
|
-
|
|
88
|
+
sink.dropped.push(`${path}: gönderilen ${render(sent)} ↔ kaydedilen yok`);
|
|
114
89
|
return;
|
|
115
90
|
}
|
|
116
91
|
const key = Array.isArray(saved) ? identityKeyFor(sent) : undefined;
|
|
@@ -128,18 +103,18 @@ function compare(sent, saved, path, sink, depth) {
|
|
|
128
103
|
}
|
|
129
104
|
return;
|
|
130
105
|
}
|
|
131
|
-
const before = sink.
|
|
106
|
+
const before = sink.dropped.length;
|
|
132
107
|
sent.forEach((entry, index) => compare(entry, lookup(saved, index), `${path}[${index}]`, sink, depth + 1));
|
|
133
108
|
// Sıra tabanlı karşılaştırmada "değişmiş" görünen öğe aslında yer değiştirmiş olabilir. Tek
|
|
134
109
|
// elemanlı dizide sıra farkı imkânsız olduğu için not düşülmez (2026-09-06 · S-1).
|
|
135
|
-
if (Array.isArray(saved) && sent.length > 1 && saved.length > 1 && sink.
|
|
136
|
-
sink.
|
|
110
|
+
if (Array.isArray(saved) && sent.length > 1 && saved.length > 1 && sink.dropped.length > before) {
|
|
111
|
+
sink.dropped.splice(before, 0, `${path}: (sıra tabanlı karşılaştırma — anahtar yok) aşağıdaki farklar platform diziyi başka sırada sakladıysa yanıltıcıdır`);
|
|
137
112
|
}
|
|
138
113
|
return;
|
|
139
114
|
}
|
|
140
115
|
if (sent && typeof sent === 'object') {
|
|
141
116
|
if (saved === missing) {
|
|
142
|
-
|
|
117
|
+
sink.dropped.push(`${path}: gönderilen ${render(sent)} ↔ kaydedilen yok`);
|
|
143
118
|
return;
|
|
144
119
|
}
|
|
145
120
|
for (const [key, entry] of Object.entries(sent)) {
|
|
@@ -155,7 +130,7 @@ function compare(sent, saved, path, sink, depth) {
|
|
|
155
130
|
if (unstoredKeys.has(field) && (saved === missing || saved === null))
|
|
156
131
|
return;
|
|
157
132
|
if (saved === missing) {
|
|
158
|
-
|
|
133
|
+
sink.dropped.push(`${path}: gönderilen ${render(sent)} ↔ kaydedilen yok`);
|
|
159
134
|
return;
|
|
160
135
|
}
|
|
161
136
|
if (sent === saved)
|
|
@@ -166,7 +141,7 @@ function compare(sent, saved, path, sink, depth) {
|
|
|
166
141
|
sink.normalized.push(`${path}: gönderilen ${render(sent)} platformun kanonik adıyla ${render(saved)} olarak saklandı — eşdeğer, kayıp değil`);
|
|
167
142
|
return;
|
|
168
143
|
}
|
|
169
|
-
|
|
144
|
+
sink.dropped.push(`${path}: gönderilen ${render(sent)} ↔ kaydedilen ${render(saved)}`);
|
|
170
145
|
}
|
|
171
146
|
/**
|
|
172
147
|
* Sol taraf platforma GİDEN gövdedir (gateway normalizasyonu uygulandıktan sonrası), sağ taraf
|
|
@@ -177,8 +152,8 @@ function compare(sent, saved, path, sink, depth) {
|
|
|
177
152
|
* İki liste döner: `dropped` gerçekten kaybolan alanlar, `normalized` platformun kanonik adıyla
|
|
178
153
|
* sakladığı eşdeğerler. İkincisi `gatewayNormalized`a gider, uyarıya değil.
|
|
179
154
|
*/
|
|
180
|
-
export function writeDiff(sent, saved, aliases = {}
|
|
181
|
-
const sink =
|
|
155
|
+
export function writeDiff(sent, saved, aliases = {}) {
|
|
156
|
+
const sink = { dropped: [], normalized: [] };
|
|
182
157
|
if (!sent || typeof sent !== 'object')
|
|
183
158
|
return sink;
|
|
184
159
|
const wrapper = saved && typeof saved === 'object' && !Array.isArray(saved)
|
|
@@ -196,16 +171,19 @@ export function writeDiff(sent, saved, aliases = {}, finish = true) {
|
|
|
196
171
|
if (keys.length > 0 && shared.length === 0)
|
|
197
172
|
return sink;
|
|
198
173
|
compare(comparable, root, '', sink, 0);
|
|
199
|
-
|
|
174
|
+
if (sink.dropped.length >= maxDrops) {
|
|
175
|
+
sink.dropped.push(`… ilk ${maxDrops} fark listelendi, daha fazla fark var — gövdeyi *_get ile okuyup karşılaştırın.`);
|
|
176
|
+
}
|
|
177
|
+
return sink;
|
|
200
178
|
}
|
|
201
179
|
export function silentlyDropped(sent, saved, aliases = {}) {
|
|
202
180
|
return writeDiff(sent, saved, aliases).dropped;
|
|
203
181
|
}
|
|
204
182
|
export function withWriteDiff(payload, diff) {
|
|
205
|
-
const drops = diff.dropped;
|
|
206
|
-
const normalized = diff
|
|
183
|
+
const drops = Array.isArray(diff) ? diff : diff.dropped;
|
|
184
|
+
const normalized = Array.isArray(diff) ? [] : diff.normalized;
|
|
207
185
|
if (drops.length > 0) {
|
|
208
|
-
logStep('sessiz düşme ⚠', `${
|
|
186
|
+
logStep('sessiz düşme ⚠', `${drops.length} alan gönderildi ama kayda geçmedi — platform hata vermeden yok saydı, yanıta silentlyDropped olarak eklenir`, drops);
|
|
209
187
|
}
|
|
210
188
|
if (normalized.length > 0) {
|
|
211
189
|
logStep('düzeltme', `platform ${normalized.length} alanı kanonik adıyla sakladı — eşdeğer, yanıta gatewayNormalized olarak eklenir`, normalized);
|
|
@@ -217,9 +195,7 @@ export function withWriteDiff(payload, diff) {
|
|
|
217
195
|
const existing = payload.gatewayNormalized;
|
|
218
196
|
return {
|
|
219
197
|
...payload,
|
|
220
|
-
...(drops.length > 0 ? { silentlyDropped: drops
|
|
221
|
-
...(drops.length > 0 && diff.droppedCountCapped ? { droppedCountCapped: true } : {}),
|
|
222
|
-
...(drops.length > 0 && diff.notes.length > 0 ? { silentlyDroppedNotes: diff.notes } : {}),
|
|
198
|
+
...(drops.length > 0 ? { silentlyDropped: drops } : {}),
|
|
223
199
|
...(normalized.length > 0
|
|
224
200
|
? { gatewayNormalized: [...(Array.isArray(existing) ? existing : []), ...normalized] }
|
|
225
201
|
: {})
|
|
@@ -271,9 +247,9 @@ function findByElementId(nodes, elementId) {
|
|
|
271
247
|
* yanıt yalnız `appliedPatch` olduğu için karşılaştırma yamadan SONRA okunan şemaya dayanır.
|
|
272
248
|
*/
|
|
273
249
|
export function schemaOpDrops(schema, ops) {
|
|
274
|
-
const drops = { dropped: [], droppedCount: 0, droppedCountCapped: false, notes: [] };
|
|
275
250
|
if (!Array.isArray(ops))
|
|
276
|
-
return
|
|
251
|
+
return [];
|
|
252
|
+
const drops = [];
|
|
277
253
|
for (const op of ops) {
|
|
278
254
|
if (op.op === 'add_component') {
|
|
279
255
|
const component = (op.component ?? {});
|
|
@@ -282,10 +258,12 @@ export function schemaOpDrops(schema, ops) {
|
|
|
282
258
|
if (!elementId)
|
|
283
259
|
continue;
|
|
284
260
|
if (!saved) {
|
|
285
|
-
|
|
261
|
+
drops.push(`add_component '${elementId}': komponent şemada bulunamadı — yama 200 döndü ama düğüm kaydedilmemiş.`);
|
|
286
262
|
continue;
|
|
287
263
|
}
|
|
288
|
-
|
|
264
|
+
for (const entry of silentlyDropped(unwrapProps(component), unwrapProps(saved))) {
|
|
265
|
+
drops.push(`add_component '${elementId}' → ${entry}`);
|
|
266
|
+
}
|
|
289
267
|
continue;
|
|
290
268
|
}
|
|
291
269
|
if (op.op === 'replace_prop' && typeof op.target === 'string' && typeof op.prop === 'string') {
|
|
@@ -294,18 +272,13 @@ export function schemaOpDrops(schema, ops) {
|
|
|
294
272
|
continue;
|
|
295
273
|
const props = (saved.props ?? {});
|
|
296
274
|
if (!Object.hasOwn(props, op.prop)) {
|
|
297
|
-
|
|
275
|
+
drops.push(`replace_prop '${op.target}.${op.prop}': prop şemada yok — değer kaydedilmemiş.`);
|
|
298
276
|
continue;
|
|
299
277
|
}
|
|
300
|
-
|
|
278
|
+
for (const entry of silentlyDropped({ [op.prop]: unwrapProps(op.value) }, { [op.prop]: unwrapProps(props[op.prop]) })) {
|
|
279
|
+
drops.push(`replace_prop '${op.target}' → ${entry}`);
|
|
280
|
+
}
|
|
301
281
|
}
|
|
302
282
|
}
|
|
303
|
-
return
|
|
304
|
-
}
|
|
305
|
-
/** İç karşılaştırmanın satırlarını, sayısını ve notlarını dış rapora önekle taşır; kesme en dışta yapılır. */
|
|
306
|
-
function mergeNested(target, nested, prefix) {
|
|
307
|
-
for (const line of nested.dropped)
|
|
308
|
-
addDrop(target, `${prefix}${line}`);
|
|
309
|
-
target.droppedCountCapped = target.droppedCountCapped || nested.droppedCountCapped;
|
|
310
|
-
target.notes.push(...nested.notes.map(note => `${prefix}${note}`));
|
|
283
|
+
return drops.slice(0, maxDrops);
|
|
311
284
|
}
|