@appaflytech/zeyrek-ai-mcp 0.0.6 → 0.0.8
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/.env.example +2 -2
- package/.env.production.example +8 -0
- package/README.md +9 -8
- package/dist/config.js +18 -3
- package/dist/knowledge/backendConstraints.js +88 -0
- package/dist/knowledge/componentCatalog.js +415 -68
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/functionGuards.js +134 -0
- package/dist/knowledge/normalize.js +512 -25
- package/dist/knowledge/outlines.js +31 -0
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/propTypes.js +5 -4
- package/dist/knowledge/rules.js +453 -6
- package/dist/knowledge/styleResolve.js +6 -1
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +62 -3
- package/dist/platform/http.js +11 -2
- package/dist/platform/responses.js +16 -4
- package/dist/platform/screenshot.js +98 -9
- package/dist/platform/strictArguments.js +32 -0
- package/dist/platform/writeDiff.js +6 -3
- package/dist/resources/examples/flow-call-function.json +66 -0
- package/dist/resources/examples/function-total.json +70 -0
- package/dist/resources/examples/microfunction-node-sum.json +56 -0
- package/dist/resources/examples/page-dynamic-form.json +1 -1
- package/dist/resources/examples/page-sign-in.json +2 -2
- package/dist/resources/guides/component.md +14 -2
- package/dist/resources/guides/entity.md +321 -17
- package/dist/resources/guides/expressions.md +9 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +7 -0
- package/dist/resources/guides/layout.md +6 -0
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +166 -7
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +101 -22
- package/dist/resources/guides/translation.md +35 -10
- package/dist/resources/guides/workflow.md +51 -2
- package/dist/resources/schema-fingerprint.json +4 -4
- package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
- package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +3 -0
- package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
- package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
- package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
- package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
- package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
- package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +123 -2
- package/dist/resources/schemas/CreateWorkflowCommand.json +17 -3
- package/dist/resources/schemas/DataJobDto.json +267 -0
- package/dist/resources/schemas/ExportRowsCommand.json +172 -0
- package/dist/resources/schemas/Filter.json +3 -1
- package/dist/resources/schemas/GetRowsQuery.json +3 -1
- package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
- package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
- package/dist/resources/schemas/ImportPlanDto.json +260 -0
- package/dist/resources/schemas/ImportSampleDto.json +90 -0
- package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +123 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +17 -3
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
- package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
- package/dist/resources/schemas/RunImportCommand.json +62 -0
- package/dist/resources/schemas/StepDto.json +17 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +3 -0
- package/dist/resources/schemas/UpdateEntityCommand.json +3 -1
- package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
- package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
- package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +123 -2
- package/dist/resources/schemas/UpdateWorkflowCommand.json +17 -3
- package/dist/server.js +17 -4
- package/dist/tools/activities.js +40 -0
- package/dist/tools/automations.js +14 -5
- package/dist/tools/common.js +16 -0
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/environments.js +52 -0
- package/dist/tools/files.js +51 -50
- package/dist/tools/flows.js +104 -5
- package/dist/tools/functions.js +331 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/layouts.js +4 -4
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +112 -9
- package/dist/tools/pages.js +188 -66
- package/dist/tools/platformInfo.js +4 -1
- package/dist/tools/queries.js +72 -6
- package/dist/tools/revisions.js +94 -0
- package/dist/tools/rows.js +342 -22
- package/dist/tools/screenWrite.js +11 -2
- package/dist/tools/templates.js +32 -5
- package/dist/tools/workflows.js +28 -6
- package/package.json +3 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway'in kendi minimal RFC 4180 okuyucusu. `packages/ng-apps-ui`'daki `csv-parse.ts`
|
|
3
|
+
* kopyalanmaz: o dosya tarayıcı için yazılmıştır ve bu proje frontend workspace'ine dahil
|
|
4
|
+
* değildir (sözleşme D5) — kopya iki yerde birden bozulurdu.
|
|
5
|
+
*
|
|
6
|
+
* Değerler HEP string döner. Tip dönüşümü backend'in işidir: `1.234,56` ile `1,234.56` arasındaki
|
|
7
|
+
* fark kültüre bağlıdır ve gateway istek kültürünü bilmez.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Ayraç ilk satırdan tahmin edilir. Türkçe Excel `;` ile yazar ve o dosyalarda virgül ONDALIK
|
|
11
|
+
* AYRACIDIR — yanlış tahmin `1,5` değerini iki sütuna böler, hem de sessizce.
|
|
12
|
+
*/
|
|
13
|
+
export function detectDelimiter(firstLine) {
|
|
14
|
+
let semicolons = 0;
|
|
15
|
+
let commas = 0;
|
|
16
|
+
let quoted = false;
|
|
17
|
+
for (const character of firstLine) {
|
|
18
|
+
if (character === '"')
|
|
19
|
+
quoted = !quoted;
|
|
20
|
+
else if (quoted)
|
|
21
|
+
continue;
|
|
22
|
+
else if (character === ';')
|
|
23
|
+
semicolons += 1;
|
|
24
|
+
else if (character === ',')
|
|
25
|
+
commas += 1;
|
|
26
|
+
}
|
|
27
|
+
return semicolons > commas ? ';' : ',';
|
|
28
|
+
}
|
|
29
|
+
function stripBom(text) {
|
|
30
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
31
|
+
}
|
|
32
|
+
function firstLineOf(text) {
|
|
33
|
+
let quoted = false;
|
|
34
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
35
|
+
const character = text[index];
|
|
36
|
+
if (character === '"')
|
|
37
|
+
quoted = !quoted;
|
|
38
|
+
else if (!quoted && (character === '\n' || character === '\r'))
|
|
39
|
+
return text.slice(0, index);
|
|
40
|
+
}
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Satır satır hücre dizisi. Tırnaklı hücre ayraç ve satır sonu taşıyabilir, `""` tek tırnaktır;
|
|
45
|
+
* tamamen boş satırlar atılır — Excel'in dosya sonuna bıraktığı boş satır aksi hâlde her alanı
|
|
46
|
+
* boş bir kayıt üretirdi.
|
|
47
|
+
*/
|
|
48
|
+
export function parseCsv(text, delimiter) {
|
|
49
|
+
const content = stripBom(text);
|
|
50
|
+
const separator = delimiter ?? detectDelimiter(firstLineOf(content));
|
|
51
|
+
const rows = [];
|
|
52
|
+
let row = [];
|
|
53
|
+
let value = '';
|
|
54
|
+
let quoted = false;
|
|
55
|
+
let index = 0;
|
|
56
|
+
const endValue = () => {
|
|
57
|
+
row.push(value);
|
|
58
|
+
value = '';
|
|
59
|
+
};
|
|
60
|
+
const endRow = () => {
|
|
61
|
+
endValue();
|
|
62
|
+
if (row.some(cell => cell.length > 0))
|
|
63
|
+
rows.push(row);
|
|
64
|
+
row = [];
|
|
65
|
+
};
|
|
66
|
+
while (index < content.length) {
|
|
67
|
+
const character = content[index];
|
|
68
|
+
if (quoted) {
|
|
69
|
+
if (character === '"') {
|
|
70
|
+
if (content[index + 1] === '"') {
|
|
71
|
+
value += '"';
|
|
72
|
+
index += 2;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
quoted = false;
|
|
76
|
+
index += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
value += character;
|
|
80
|
+
index += 1;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (character === '"' && value.length === 0) {
|
|
84
|
+
quoted = true;
|
|
85
|
+
index += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (character === separator) {
|
|
89
|
+
endValue();
|
|
90
|
+
index += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (character === '\r' || character === '\n') {
|
|
94
|
+
endRow();
|
|
95
|
+
index += character === '\r' && content[index + 1] === '\n' ? 2 : 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
value += character;
|
|
99
|
+
index += 1;
|
|
100
|
+
}
|
|
101
|
+
if (value.length > 0 || row.length > 0)
|
|
102
|
+
endRow();
|
|
103
|
+
return rows;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Başlık satırını anahtar yapan satır sözlükleri. Başlık boş olan sütun atlanır (Excel'in sağa
|
|
107
|
+
* doğru uzattığı boş sütunlar aksi hâlde `''` anahtarlı bir alan üretirdi) ve fazladan hücre
|
|
108
|
+
* taşıyan satır kırpılır.
|
|
109
|
+
*/
|
|
110
|
+
export function csvRows(text, delimiter) {
|
|
111
|
+
const grid = parseCsv(text, delimiter);
|
|
112
|
+
if (grid.length === 0)
|
|
113
|
+
return [];
|
|
114
|
+
const headers = (grid[0] ?? []).map(header => header.trim());
|
|
115
|
+
return grid.slice(1).map(cells => {
|
|
116
|
+
const row = {};
|
|
117
|
+
headers.forEach((header, column) => {
|
|
118
|
+
if (header.length > 0)
|
|
119
|
+
row[header] = cells[column] ?? '';
|
|
120
|
+
});
|
|
121
|
+
return row;
|
|
122
|
+
});
|
|
123
|
+
}
|
package/dist/platform/errors.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export class PlatformApiError extends Error {
|
|
2
2
|
status;
|
|
3
3
|
code;
|
|
4
|
+
/**
|
|
5
|
+
* Toplu satır yazmasında düşen satırın parti içi indeksi (`BulkRowErrorResponse.rowIndex`).
|
|
6
|
+
* Mesajda da geçer ama metinden ayıklanamaz: parti döngüsü dosya satır numarasını hesaplamak
|
|
7
|
+
* için sayıya ihtiyaç duyar, çağıran hangi satırı düzelteceğini ancak öyle bilir.
|
|
8
|
+
*/
|
|
9
|
+
rowIndex;
|
|
4
10
|
constructor(message, status, code) {
|
|
5
11
|
super(message);
|
|
6
12
|
this.status = status;
|
|
@@ -50,6 +56,56 @@ function collectValidationMessages(payload) {
|
|
|
50
56
|
* hatalı prop'ları sessizce kaydediyordu). Orada op listesi yoktur ve `page_schema_validate` de
|
|
51
57
|
* uygulanmaz — ipucu bu yüzden yola göre ayrışır.
|
|
52
58
|
*/
|
|
59
|
+
/**
|
|
60
|
+
* Merge apply'ın iki 409'u da aynı şeyi söyler: plan artık uygulanabilir değil. Ajan bunu
|
|
61
|
+
* 'geçici hata, tekrar dene' diye okuyup aynı çağrıyı yollamasın diye yol açıkça yazılır.
|
|
62
|
+
*/
|
|
63
|
+
function mergeHint(path) {
|
|
64
|
+
if (!/\/merges\//.test(path))
|
|
65
|
+
return '';
|
|
66
|
+
return " Üç sebepten biri: çözülmemiş çakışma, ya da plan alındığından beri DALIN veya development’ın değişmesi (MergePlanStaleException) — ikincisinde planın taşıdığı liste eskidir ve uygulansaydı sonradan yapılan iş sessizce düşerdi. merge_get ile planı yeniden okuyun (isStale), bayatsa merge_refresh ile tazeleyin — yeni plan açmanız gerekmez. Aynı merge_apply çağrısını tekrarlamak sonucu değiştirmez.";
|
|
67
|
+
}
|
|
68
|
+
function mergeRevertHint(code) {
|
|
69
|
+
if (code === 'MergeRevertWindowExpiredException') {
|
|
70
|
+
return " Geri alma penceresi doldu: pencere kapanınca merge öncesi anlık görüntü de silinir, bu yüzden plan artık geri alınamaz. merge_list status:'applied' ile pencere içindeki bir plan seçin.";
|
|
71
|
+
}
|
|
72
|
+
if (code === 'MergePlanNotRevertableException') {
|
|
73
|
+
return ' Bu plan bir GERİ ALMA planı (kind=revert) ve geri almanın geri alması yoktur. Geri almayı iptal etmek istiyorsanız dalı yeniden merge edin.';
|
|
74
|
+
}
|
|
75
|
+
if (code === 'MergePlanNotAppliedException') {
|
|
76
|
+
return " Plan uygulanmış DEĞİL — durumu mesajda yazıyor (failed / discarded / reverted / planned…). Yalnız 'applied' ve 'applied-with-drift' geri alınabilir; merge_list status:'applied' ile uygun planı seçin. Zaten geri alınmış bir planın durumu 'reverted'dır ve ikinci kez geri alınamaz.";
|
|
77
|
+
}
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* İçe aktarma zinciri dört çağrıdır ve her hata farklı bir adıma geri gönderir: plan süresi
|
|
82
|
+
* dolduysa analizden, anahtar benzersiz değilse plan gövdesinden, hata oranı yüksekse dosyanın
|
|
83
|
+
* kendisinden. Kod olmadan mesajlar "422" diye okunur ve ajan aynı çağrıyı tekrarlar.
|
|
84
|
+
*/
|
|
85
|
+
function importHint(code, path) {
|
|
86
|
+
if (code === 'ImportPlanExpiredException') {
|
|
87
|
+
return ' Plan bir saat geçerlidir ve süresi doldu (ya da başka bir kullanıcıya ait). import_analyze ile dosyayı yeniden okuyup import_plan ile yeni bir plan üretin — aynı planId bir daha çalışmaz.';
|
|
88
|
+
}
|
|
89
|
+
if (code === 'ImportTooManyErrorsException') {
|
|
90
|
+
return " Satırların yarısından fazlası kuru koşuda düştü. Önce planın `errors` listesine bakın: çoğu zaman tek bir yanlış `locale` ya da yanlış sütun tipi bütün satırları düşürür. Dosya gerçekten böyleyse import_run'ı `force: true` ile çağırın.";
|
|
91
|
+
}
|
|
92
|
+
if (code === 'ImportKeyFieldNotUniqueException') {
|
|
93
|
+
return " Upsert/update anahtarı benzersiz olmalı: alanın kendisi unique/primary key olmalı, ya da tek alanlı bir unique index taşımalı, ya da 'id'/'code' olmalı. entity_get ile alanın `unique` bayrağını doğrulayın.";
|
|
94
|
+
}
|
|
95
|
+
if (code === 'ImportRelationMatchFieldNotUniqueException') {
|
|
96
|
+
return ' Ad → id eşlemesi yalnız hedef tablonun display alanı ya da benzersiz bir alanıyla yapılır; benzersiz olmayan bir alanda aynı ad iki satıra gidebilir ve hangisinin yazılacağı belirsiz olur.';
|
|
97
|
+
}
|
|
98
|
+
if (code === 'ImportSheetEmptyException' || code === 'ImportRowLimitExceededException') {
|
|
99
|
+
return ' `headerRow` doğru mu kontrol edin (0 = başlık yok); satır sınırı aşıldıysa dosyayı sayfalara bölün.';
|
|
100
|
+
}
|
|
101
|
+
if (code === 'DataJobNotUndoableException' && path.endsWith('/cancel')) {
|
|
102
|
+
return ' İş zaten bitmiş (completed / failed / undone / cancelled); iptal edilecek bir şey yok. Tamamlanmış işi geri almak için import_undo.';
|
|
103
|
+
}
|
|
104
|
+
if (code === 'DataJobNotUndoableException' && path.endsWith('/undo')) {
|
|
105
|
+
return ' Yalnız tamamlanmış ve undoAvailableUntil geçmemiş bir içe aktarma geri alınır; mevcut tabloya yalnız ekleme yapan işin penceresi hiç açılmaz. Süren işi durdurmak için import_cancel.';
|
|
106
|
+
}
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
53
109
|
function pageSchemaHint(code, path) {
|
|
54
110
|
if (code === 'PageSchemaVersionConflictException') {
|
|
55
111
|
return ' Sayfa siz okuduktan sonra değişti ve HİÇBİR op uygulanmadı. page_schema_get ile yeniden okuyup planı tazeleyin, yeni `version` değerini `baseVersion` olarak gönderin.';
|
|
@@ -133,7 +189,7 @@ export function mapPlatformError(status, payload, context) {
|
|
|
133
189
|
if (validationMessages.length > 0) {
|
|
134
190
|
return new PlatformApiError(`Doğrulama hatası (${location}): ${validationMessages.join(' ; ')}. Gövde şeması için ilgili ngapps://schemas kaynağına ya da mevcut bir kaydın *_get çıktısına bakın.`, status, code);
|
|
135
191
|
}
|
|
136
|
-
return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}`, status, code);
|
|
192
|
+
return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}${mergeRevertHint(code)}${importHint(code, context.path)}`, status, code);
|
|
137
193
|
}
|
|
138
194
|
if (status === 401) {
|
|
139
195
|
const hint = context.authMode === 'local'
|
|
@@ -151,7 +207,7 @@ export function mapPlatformError(status, payload, context) {
|
|
|
151
207
|
if (context.method !== 'GET' && /\/flows(\/|$)/.test(context.path)) {
|
|
152
208
|
return new PlatformApiError(`Adımda referans verilen kayıt çözümlenemedi (${location}). Bu uçta 404 akışın kendisini DEĞİL bir adım argümanını kastediyor: flow-trigger adımının args'ında ne flowId ne triggerElementId var (ya da triggerElementId sayfadaki hiçbir elementId ile eşleşmiyor), setter adımının iteratees[].element'i bulunamıyor, ya da bir adım silinmiş bir komponente bakıyor. Adım argümanlarını page_schema_get'teki elementId'lerle karşılaştırın; flow-trigger'ın iki geçerli şekli için platform_guide('page') Kural 1c.`, status, code);
|
|
153
209
|
}
|
|
154
|
-
return new PlatformApiError(`Kayıt bulunamadı (${location}).${environmentHint} id/slug değerini ilgili *_list tool'uyla doğrulayın
|
|
210
|
+
return new PlatformApiError(`Kayıt bulunamadı (${location}).${environmentHint} id/slug değerini ilgili *_list tool'uyla doğrulayın.${importHint(code, context.path)}`, status, code);
|
|
155
211
|
}
|
|
156
212
|
if (status === 409 || status === 422) {
|
|
157
213
|
const projectConflictHint = status === 409 && context.method === 'POST' && /\/projects\/?$/.test(context.path)
|
|
@@ -159,7 +215,10 @@ export function mapPlatformError(status, payload, context) {
|
|
|
159
215
|
: '';
|
|
160
216
|
const validationMessages = collectValidationMessages(body);
|
|
161
217
|
const detail = validationMessages.length > 0 ? ` → ${validationMessages.join(' ; ')}` : '';
|
|
162
|
-
|
|
218
|
+
const rejected = new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code, context.path)}${bulkRowHint(code)}${importHint(code, context.path)}${versionConflictHint(code, context.path)}${mergeHint(context.path)}`, status, code);
|
|
219
|
+
if (typeof body.rowIndex === 'number')
|
|
220
|
+
rejected.rowIndex = body.rowIndex;
|
|
221
|
+
return rejected;
|
|
163
222
|
}
|
|
164
223
|
const fallbackValidationMessages = collectValidationMessages(body);
|
|
165
224
|
const fallbackDetail = fallbackValidationMessages.length > 0 ? ` → ${fallbackValidationMessages.join(' ; ')}` : '';
|
package/dist/platform/http.js
CHANGED
|
@@ -42,8 +42,16 @@ function appendQuery(url, query) {
|
|
|
42
42
|
if (value === undefined || value === null || value === '')
|
|
43
43
|
continue;
|
|
44
44
|
if (Array.isArray(value)) {
|
|
45
|
-
|
|
46
|
-
url.searchParams.append(key,
|
|
45
|
+
if (value.some(entry => entry !== null && typeof entry === 'object')) {
|
|
46
|
+
url.searchParams.append(key, JSON.stringify(value));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
for (const entry of value)
|
|
50
|
+
url.searchParams.append(key, String(entry));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (typeof value === 'object') {
|
|
54
|
+
url.searchParams.append(key, JSON.stringify(value));
|
|
47
55
|
}
|
|
48
56
|
else {
|
|
49
57
|
url.searchParams.append(key, String(value));
|
|
@@ -125,6 +133,7 @@ export class Platform {
|
|
|
125
133
|
...(authorization === undefined ? {} : { authorization }),
|
|
126
134
|
...(options.environment ? { 'x-environment': options.environment } : {}),
|
|
127
135
|
...callerHeaders,
|
|
136
|
+
'x-client': 'mcp',
|
|
128
137
|
...(options.mirrorAuthorizationCookie && authorization !== undefined && callerHeaders.cookie === undefined
|
|
129
138
|
? { cookie: `NG_AccessToken=${authorization.replace(/^Bearer /, '')}` }
|
|
130
139
|
: {})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logError, logStep } from '../observability/trace.js';
|
|
2
2
|
import { PlatformApiError } from './errors.js';
|
|
3
|
+
import { PolicyBlockError } from '../knowledge/policy.js';
|
|
3
4
|
import { maskSecrets } from './secrets.js';
|
|
4
5
|
export function prune(value) {
|
|
5
6
|
if (Array.isArray(value))
|
|
@@ -126,12 +127,15 @@ function asStructured(payload) {
|
|
|
126
127
|
return payload;
|
|
127
128
|
return { data: payload };
|
|
128
129
|
}
|
|
129
|
-
export function toolResult(payload, config) {
|
|
130
|
+
export function toolResult(payload, config, expectFailure) {
|
|
130
131
|
const pruned = prune(payload);
|
|
131
132
|
const { payload: safe, masked, tokenRefs } = maskSecrets(pruned);
|
|
132
133
|
const runtimeErrors = runtimeErrorSummary(safe);
|
|
133
134
|
if (runtimeErrors.length > 0) {
|
|
134
|
-
|
|
135
|
+
// Kasıtlı negatif test (expectFailure:true) bir kusur değil bir KANIT: çağıran beklediğini
|
|
136
|
+
// söylediyse SONUÇ satırı 'başarılı' yazılır ve ölçüm aracının plansız hata oranını şişirmez
|
|
137
|
+
// (pages.ts'teki page_screenshot expectFailure deseniyle aynı; artık her handle()'lı tool'da geçerli).
|
|
138
|
+
logStep('akış hatası ✕', `${runtimeErrors.length} adım hata ile bitti — platform HTTP 200 döndürdüğü için çağrı başarılı görünüyordu${expectFailure === true ? ' (expectFailure:true — kasıtlı negatif test)' : ''}`, runtimeErrors);
|
|
135
139
|
}
|
|
136
140
|
if (masked.length > 0) {
|
|
137
141
|
logStep('maskeleme', `${masked.length} gizli alan yanıttan çıkarıldı — değer *** ile değiştirildi, yolu gatewayMasked'de`, masked);
|
|
@@ -144,6 +148,7 @@ export function toolResult(payload, config) {
|
|
|
144
148
|
const enriched = {
|
|
145
149
|
...structured,
|
|
146
150
|
...(runtimeErrors.length > 0 ? { runtimeErrors } : {}),
|
|
151
|
+
...(runtimeErrors.length > 0 && expectFailure === true ? { expectedFailure: true } : {}),
|
|
147
152
|
...(masked.length > 0 ? { gatewayMasked: masked } : {}),
|
|
148
153
|
...(Object.keys(tokenRefs).length > 0 ? { tokenRefs, tokenRefNote: "Maskelenen JWT'ler gateway içinde tutuldu; bu referansları page_screenshot / page_runtime_get 'asUser' alanına verebilirsiniz (15 dk geçerli). Jetonun kendisi yanıta ve günlüğe hiç girmez." } : {}),
|
|
149
154
|
...(truncated ? { truncated: true, truncationNote: 'Liste, yanıt boyutu sınırı nedeniyle kırpıldı; sayfalama ya da daha dar filtre kullanın.' } : {})
|
|
@@ -155,7 +160,8 @@ export function toolResult(payload, config) {
|
|
|
155
160
|
}
|
|
156
161
|
export function toolError(error) {
|
|
157
162
|
logError(error);
|
|
158
|
-
|
|
163
|
+
// Politika durdurması bilinçli bir karardır, "beklenmeyen" değil (33/S-5).
|
|
164
|
+
const message = error instanceof PlatformApiError || error instanceof PolicyBlockError
|
|
159
165
|
? error.message
|
|
160
166
|
: error instanceof Error
|
|
161
167
|
? `Beklenmeyen gateway hatası: ${error.message}`
|
|
@@ -168,7 +174,13 @@ export function toolError(error) {
|
|
|
168
174
|
export function handle(config, run) {
|
|
169
175
|
return async (args) => {
|
|
170
176
|
try {
|
|
171
|
-
|
|
177
|
+
// Tool'un kendi input şeması `expectFailure: z.boolean().optional()` taşıyorsa (ör.
|
|
178
|
+
// microservice_execute, automation_preview) burada otomatik okunur ve toolResult'a geçirilir
|
|
179
|
+
// — kasıtlı negatif testler (yanlış parola, yetkisiz erişim, iş kuralı ihlali) ölçüm
|
|
180
|
+
// aracının plansız hata oranını şişirmez. Şemasında bu alan olmayan tool'larda args'ta
|
|
181
|
+
// anahtar hiç bulunmaz, davranış değişmez.
|
|
182
|
+
const expectFailure = args?.expectFailure === true;
|
|
183
|
+
return toolResult(await run(args), config, expectFailure);
|
|
172
184
|
}
|
|
173
185
|
catch (error) {
|
|
174
186
|
return toolError(error);
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { PlatformApiError } from './errors.js';
|
|
2
2
|
import { INTERNAL_ROLE_SEPARATOR, knownInternalRoles } from '../knowledge/componentCatalog.js';
|
|
3
3
|
import { logStep } from '../observability/trace.js';
|
|
4
|
+
/**
|
|
5
|
+
* Ziyaretçinin tema tercihinin yaşadığı localStorage anahtarı — `theme-toggle` bunu yazar,
|
|
6
|
+
* `Theme` okur ve projenin kendi modunun önüne geçirir
|
|
7
|
+
* (`zeyrek-frontend/packages/ng-apps-ui/src/shared/theme/stores.ts`). Anahtar uygulama başına
|
|
8
|
+
* DEĞİL tarayıcı başına tektir.
|
|
9
|
+
*/
|
|
10
|
+
const VIEWER_MODE_STORAGE_KEY = 'zeyrek-viewer-theme-mode';
|
|
4
11
|
/**
|
|
5
12
|
* G-24 — gateway sayfayı kurabiliyor, okuyabiliyor ve çizdirebiliyordu ama üzerinde OLAY
|
|
6
13
|
* TETİKLEYEMİYORDU: "filtre değişince veri yenileniyor" iddiası yalnız şema okumasıyla
|
|
@@ -26,11 +33,54 @@ export function actionScript(action) {
|
|
|
26
33
|
return { found: true, applied: true, requests: count };
|
|
27
34
|
})()`;
|
|
28
35
|
}
|
|
36
|
+
// G-09 — kanban kartı ve takvim etkinliği HTML5 DnD ile taşınır. Olaylar sentetik atılır ve
|
|
37
|
+
// TEK DataTransfer'i paylaşır; koordinat bırakma hedefinin merkezidir, çünkü takvimin saat
|
|
38
|
+
// kolonu bırakılan saati `clientY`'den hesaplar (slot hedefi o saatin ortasına düşer).
|
|
39
|
+
// Playwright `dragTo` gerçek fare sürer ama ScreenshotPage bu yeteneği taşımaz; betik yolu
|
|
40
|
+
// öteki eylemlerle aynı ölçüm döngüsünden (requests · flowRuns) geçer.
|
|
41
|
+
if (action.action === 'drag') {
|
|
42
|
+
const destination = JSON.stringify(`[data-element-id="${String(action.to ?? '')}"]`);
|
|
43
|
+
const destinationName = JSON.stringify(String(action.to ?? ''));
|
|
44
|
+
return `(function () {
|
|
45
|
+
var count = performance.getEntriesByType('resource').length;
|
|
46
|
+
var node = document.querySelector(${selector});
|
|
47
|
+
if (!node) return { found: false, applied: false, requests: count };
|
|
48
|
+
var drop = document.querySelector(${destination});
|
|
49
|
+
if (!drop) return { found: false, applied: false, note: "sürükleme kaynağı bulundu ama bırakma hedefi ('to') sayfada yok: " + ${destinationName} + " — kanban kolonu <elementId>__col-<value>, takvim günü <elementId>__day-<yyyy-MM-dd>, saat dilimi <elementId>__slot-<yyyy-MM-dd>T<HH>", requests: count };
|
|
50
|
+
if (node.getAttribute('draggable') !== 'true') return { found: true, applied: false, note: "kaynak sürüklenebilir değil (draggable != true) — kanban isDraggable ya da eventcalendar isEditable kapalı olabilir", requests: count };
|
|
51
|
+
var transfer = new DataTransfer();
|
|
52
|
+
var center = function (element) { var rect = element.getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; };
|
|
53
|
+
var fire = function (element, type, at) { element.dispatchEvent(new DragEvent(type, { bubbles: true, cancelable: true, composed: true, dataTransfer: transfer, clientX: at.x, clientY: at.y })); };
|
|
54
|
+
var from = center(node);
|
|
55
|
+
var to = center(drop);
|
|
56
|
+
fire(node, 'dragstart', from);
|
|
57
|
+
fire(drop, 'dragenter', to);
|
|
58
|
+
fire(drop, 'dragover', to);
|
|
59
|
+
fire(drop, 'drop', to);
|
|
60
|
+
fire(node, 'dragend', to);
|
|
61
|
+
return { found: true, applied: true, requests: count };
|
|
62
|
+
})()`;
|
|
63
|
+
}
|
|
64
|
+
const target = String(action.target ?? '');
|
|
65
|
+
const root = rootElementId(target);
|
|
66
|
+
// G-14 — `applied` yalnız "DOM'a yazıldı" demekti: tarih seçici olayı dinlemiyordu, kutu dolu
|
|
67
|
+
// görünüyor, prop boş kalıyordu. Kökün prop parmak izi eylemden ÖNCE alınır; runActions
|
|
68
|
+
// yerleşmeden sonra yeniden okuyup `propWritten`'ı çıkarır. Çok alanlı bileşenin kökünde tek
|
|
69
|
+
// değer yoktur — ilk kutuya yazmak yarım değeri "uygulandı" diye raporluyordu, kök reddedilir.
|
|
29
70
|
return `(function () {
|
|
30
71
|
var count = performance.getEntriesByType('resource').length;
|
|
31
72
|
var node = document.querySelector(${selector});
|
|
32
73
|
if (!node) return { found: false, applied: false, requests: count };
|
|
33
74
|
var wanted = String(${value});
|
|
75
|
+
var propsBefore = ${runtimePropsScript(root)};
|
|
76
|
+
var finish = function (result) { if (propsBefore !== undefined) result.propsBefore = propsBefore; return result; };
|
|
77
|
+
|
|
78
|
+
if (${JSON.stringify(root === target)}) {
|
|
79
|
+
var parts = Array.prototype.slice.call(node.querySelectorAll(${JSON.stringify(`[data-element-id^="${root}${INTERNAL_ROLE_SEPARATOR}"]`)}))
|
|
80
|
+
.filter(function (part) { return part.querySelector('input, select, textarea'); })
|
|
81
|
+
.map(function (part) { return part.getAttribute('data-element-id'); });
|
|
82
|
+
if (parts.length > 1) return finish({ found: true, applied: false, note: "çok alanlı bileşen: kökte tek değer yok, değeri kutulardan birine yazın — " + parts.join(' · '), requests: count });
|
|
83
|
+
}
|
|
34
84
|
|
|
35
85
|
// Açılır kutu (dropdown / multi-select) NATIVE alan değildir: değeri yazmak
|
|
36
86
|
// hiçbir şey yapmaz, seçeneğe TIKLANMASI gerekir. Liste ancak kutu açıkken
|
|
@@ -48,29 +98,46 @@ export function actionScript(action) {
|
|
|
48
98
|
var match = options.filter(function (option) { return option.getAttribute('data-value') === wanted; })[0]
|
|
49
99
|
|| options.filter(function (option) { return (option.textContent || '').trim() === wanted; })[0];
|
|
50
100
|
if (!match) {
|
|
51
|
-
resolve({ found: true, applied: false, note: "açılır kutu açıldı ama bu değere karşılık gelen seçenek yok: " + wanted + " — options listesini page_runtime_get ile doğrulayın; seçenek sayısı: " + options.length, requests: count });
|
|
101
|
+
resolve(finish({ found: true, applied: false, note: "açılır kutu açıldı ama bu değere karşılık gelen seçenek yok: " + wanted + " — options listesini page_runtime_get ile doğrulayın; seçenek sayısı: " + options.length, requests: count }));
|
|
52
102
|
return;
|
|
53
103
|
}
|
|
54
104
|
(match.querySelector('button') || match).click();
|
|
55
|
-
resolve({ found: true, applied: true, requests: count });
|
|
105
|
+
resolve(finish({ found: true, applied: true, requests: count }));
|
|
56
106
|
}, 300);
|
|
57
107
|
});
|
|
58
108
|
}
|
|
59
109
|
|
|
60
110
|
var field = node.matches('input, select, textarea') ? node : node.querySelector('input, select, textarea');
|
|
61
|
-
if (!field) return { found: true, applied: false, note: 'düğümde input/select/textarea yok — bu komponent native alan kullanmıyor olabilir, click ile deneyin', requests: count };
|
|
111
|
+
if (!field) return finish({ found: true, applied: false, note: 'düğümde input/select/textarea yok — bu komponent native alan kullanmıyor olabilir, click ile deneyin', requests: count });
|
|
62
112
|
var proto = field instanceof HTMLSelectElement ? HTMLSelectElement.prototype
|
|
63
113
|
: (field instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype);
|
|
64
114
|
if (field instanceof HTMLInputElement && (field.type === 'checkbox' || field.type === 'radio')) {
|
|
65
115
|
var shouldCheck = wanted === 'true' || wanted === '1' || wanted === 'on';
|
|
66
116
|
if (field.checked !== shouldCheck) field.click();
|
|
67
|
-
return { found: true, applied: true, requests: count };
|
|
117
|
+
return finish({ found: true, applied: true, requests: count });
|
|
68
118
|
}
|
|
69
119
|
var setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
|
|
70
120
|
setter.call(field, ${value});
|
|
71
121
|
field.dispatchEvent(new Event('input', { bubbles: true }));
|
|
72
122
|
field.dispatchEvent(new Event('change', { bubbles: true }));
|
|
73
|
-
return { found: true, applied: true, requests: count };
|
|
123
|
+
return finish({ found: true, applied: true, requests: count });
|
|
124
|
+
})()`;
|
|
125
|
+
}
|
|
126
|
+
/** `rf_range__start` → `rf_range`: iç rol hedefinin prop'ları kök düğümde yaşar. */
|
|
127
|
+
export function rootElementId(target) {
|
|
128
|
+
const separator = target.indexOf(INTERNAL_ROLE_SEPARATOR);
|
|
129
|
+
return separator > 0 ? target.slice(0, separator) : target;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Düğümün runtime prop'larının parmak izi (`@ng-apps/flow` → flow-runtime-props.ts,
|
|
133
|
+
* `window.__ngAppsReadProps`). `undefined` okuyucu yayınlanmıyor (eski runtime) ya da prop'lar
|
|
134
|
+
* serileşmiyor; `null` hiçbir makine düğümü tanımıyor; dize karşılaştırılabilir iz.
|
|
135
|
+
*/
|
|
136
|
+
export function runtimePropsScript(elementId) {
|
|
137
|
+
return `(function () { /* runtimePropsSnapshot */
|
|
138
|
+
var read = window.__ngAppsReadProps;
|
|
139
|
+
if (typeof read !== 'function') return undefined;
|
|
140
|
+
try { var props = read(${JSON.stringify(elementId)}); return props === null ? null : JSON.stringify(props); } catch (error) { return undefined; }
|
|
74
141
|
})()`;
|
|
75
142
|
}
|
|
76
143
|
/**
|
|
@@ -421,19 +488,31 @@ export class StudioScreenshotter {
|
|
|
421
488
|
: navigated
|
|
422
489
|
? activityAfter.completed
|
|
423
490
|
: activityBefore === null ? undefined : Math.max(activityAfter.completed - activityBefore.completed, 0);
|
|
491
|
+
// G-14 — DOM'a yazmak değerin prop'a ulaştığını kanıtlamaz. İz yalnız okuyucu yayınlanıyor ve
|
|
492
|
+
// makine düğümü tanıyorsa (dize) karşılaştırılır; aksi hâlde propWritten bilinmez kalır.
|
|
493
|
+
const propsAfter = typeof before.propsBefore === 'string' && before.applied === true && !navigated
|
|
494
|
+
? await page.evaluate(runtimePropsScript(rootElementId(String(action.target ?? '')))).catch(() => undefined)
|
|
495
|
+
: undefined;
|
|
496
|
+
const propWritten = typeof propsAfter === 'string' ? propsAfter !== before.propsBefore : undefined;
|
|
497
|
+
const applied = before.applied === true && propWritten !== false;
|
|
498
|
+
const note = propWritten === false
|
|
499
|
+
? `değer DOM'a yazıldı ama '${rootElementId(String(action.target ?? ''))}' düğümünün runtime prop'ları DEĞİŞMEDİ — bileşen olayı kendi change yoluna çevirmedi ya da değer zaten aynıydı. Çok alanlı bileşende <elementId>__<rol> kutusunu hedefleyin (component_get → internalRoles).`
|
|
500
|
+
: before.note;
|
|
424
501
|
outcomes.push({
|
|
425
502
|
index,
|
|
426
503
|
action: String(action.action),
|
|
427
504
|
...(action.target === undefined ? {} : { target: action.target }),
|
|
505
|
+
...(action.to === undefined ? {} : { to: action.to }),
|
|
428
506
|
found: before.found === true,
|
|
429
|
-
applied
|
|
430
|
-
...(
|
|
507
|
+
applied,
|
|
508
|
+
...(propWritten === undefined ? {} : { propWritten }),
|
|
509
|
+
...(note === undefined ? {} : { note }),
|
|
431
510
|
requests,
|
|
432
511
|
...(flowRuns === undefined ? {} : { flowRuns }),
|
|
433
512
|
pageState: readiness.state
|
|
434
513
|
});
|
|
435
514
|
}
|
|
436
|
-
logStep('tarayıcı ⤵', `${outcomes.length} etkileşim adımı koştu`, outcomes.map(outcome => `${outcome.index}. ${outcome.action}${outcome.target ? ` '${outcome.target}'` : ''} → ${outcome.found ? (outcome.applied ? 'uygulandı' : 'bulundu ama uygulanamadı') : 'hedef BULUNAMADI'} · ${outcome.requests} ağ isteği`));
|
|
515
|
+
logStep('tarayıcı ⤵', `${outcomes.length} etkileşim adımı koştu`, outcomes.map(outcome => `${outcome.index}. ${outcome.action}${outcome.target ? ` '${outcome.target}'` : ''}${outcome.to ? ` ⇒ '${outcome.to}'` : ''} → ${outcome.found ? (outcome.applied ? 'uygulandı' : 'bulundu ama uygulanamadı') : 'hedef BULUNAMADI'} · ${outcome.requests} ağ isteği`));
|
|
437
516
|
return outcomes;
|
|
438
517
|
}
|
|
439
518
|
/**
|
|
@@ -505,7 +584,7 @@ export class StudioScreenshotter {
|
|
|
505
584
|
});
|
|
506
585
|
context = await browser.newContext({ viewport: request.viewport, deviceScaleFactor: 1 });
|
|
507
586
|
}
|
|
508
|
-
logStep('tarayıcı →', `GET ${request.url} · viewport ${request.viewport.width}x${request.viewport.height}${request.fullPage ? ' · fullPage' : ''}${request.token ? '' : ' · gateway jetonu YOK'}${(request.cookies ?? []).length > 0 ? ` · caller çerezleri: ${(request.cookies ?? []).map(cookie => cookie.name).join(', ')}` : ''}${request.token || (request.cookies ?? []).length > 0 ? '' : ' · ANONİM (çerez YOK)'}`);
|
|
587
|
+
logStep('tarayıcı →', `GET ${request.url} · viewport ${request.viewport.width}x${request.viewport.height}${request.fullPage ? ' · fullPage' : ''}${request.token ? '' : ' · gateway jetonu YOK'}${(request.cookies ?? []).length > 0 ? ` · caller çerezleri: ${(request.cookies ?? []).map(cookie => cookie.name).join(', ')}` : ''}${request.token || (request.cookies ?? []).length > 0 ? '' : ' · ANONİM (çerez YOK)'}${request.colorScheme ? ` · tema ${request.colorScheme}` : ''}`);
|
|
509
588
|
try {
|
|
510
589
|
const cookies = new Map();
|
|
511
590
|
if (request.token)
|
|
@@ -515,6 +594,16 @@ export class StudioScreenshotter {
|
|
|
515
594
|
if (cookies.size > 0) {
|
|
516
595
|
await context.addCookies([...cookies].map(([name, value]) => ({ name, value, url: request.cookieUrl, httpOnly: false })));
|
|
517
596
|
}
|
|
597
|
+
// Tema tercihi çerezde değil localStorage'da yaşar ve tema store'u değerini modül İLK
|
|
598
|
+
// değerlendirildiğinde okur: sayfa açıldıktan sonra yazmak bir sonraki yüklemeye kadar
|
|
599
|
+
// etkisizdir. Bu yüzden tercih, sayfanın script'lerinden önce koşan bağlam script'iyle
|
|
600
|
+
// ekilir. Yeteneği olmayan bağlamda çağrı SESSİZCE açık temada kare almaz, hata verir.
|
|
601
|
+
if (request.colorScheme) {
|
|
602
|
+
if (!context.addInitScript) {
|
|
603
|
+
throw new PlatformApiError(`colorScheme='${request.colorScheme}' uygulanamadı: tarayıcı bağlamı addInitScript desteklemiyor. Tema tercihi sayfa açılmadan ekilmek zorunda; desteksiz bağlamda kare projenin varsayılan modunda çıkardı ve bu sessizce yanlış olurdu.`);
|
|
604
|
+
}
|
|
605
|
+
await context.addInitScript(`try { localStorage.setItem(${JSON.stringify(VIEWER_MODE_STORAGE_KEY)}, ${JSON.stringify(request.colorScheme)}) } catch (error) { /* localStorage kapalıysa proje varsayılanı çizilir */ }`);
|
|
606
|
+
}
|
|
518
607
|
const page = await context.newPage();
|
|
519
608
|
// Soğuk Vite derlemesinde sayfanın kendi veri isteği 15 sn'yi aşabiliyor; delayMs bu
|
|
520
609
|
// bütçeyi de uzatır (09-02 E-2). Yanıt hazırlık döngüsünü BLOKLAMAZ: runtime dışına
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
function isRawShape(value) {
|
|
3
|
+
if (!value || typeof value !== 'object' || '_def' in value)
|
|
4
|
+
return false;
|
|
5
|
+
return Object.values(value).every(entry => entry instanceof z.ZodType);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* GW-005 — tanınmayan üst düzey argüman REDDEDİLİR.
|
|
9
|
+
*
|
|
10
|
+
* SDK ham şekli `z.object(shape)` ile sarar ve zod'un varsayılanı bilinmeyen anahtarı hata vermeden
|
|
11
|
+
* ATMAKTIR: şemada olmayan bir alan doğrulamadan geçer, handler'a hiç ulaşmaz ve çağrı başarıyla
|
|
12
|
+
* biter. 2026-09-15 G-13'te `microservice_execute`/`microservice_debug`/`query_preview`'a verilen
|
|
13
|
+
* `parameters` böyle kayboldu — dört farklı filtre aynı gövdeyi döndü ve ölçüm aracı çağıranı
|
|
14
|
+
* olmayan bir sorgu kusuruna yönlendirdi. Tek tek tool'lara alan eklemek sınıfı kapatmaz; sessizlik
|
|
15
|
+
* burada, SDK'ya şema verilmeden önce kaldırılır.
|
|
16
|
+
*/
|
|
17
|
+
export function rejectUnknownArguments(server) {
|
|
18
|
+
const registerTool = server.registerTool.bind(server);
|
|
19
|
+
server.registerTool = ((name, config, callback) => {
|
|
20
|
+
if (!isRawShape(config.inputSchema))
|
|
21
|
+
return registerTool(name, config, callback);
|
|
22
|
+
const shape = config.inputSchema;
|
|
23
|
+
const accepted = Object.keys(shape).join(', ');
|
|
24
|
+
const inputSchema = z.object(shape, {
|
|
25
|
+
errorMap: (issue, context) => issue.code === 'unrecognized_keys'
|
|
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}.` }
|
|
27
|
+
: { message: context.defaultError }
|
|
28
|
+
}).strict();
|
|
29
|
+
return registerTool(name, { ...config, inputSchema }, callback);
|
|
30
|
+
});
|
|
31
|
+
return server;
|
|
32
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PROP_VALUE_TYPES } from '../knowledge/normalize.js';
|
|
1
2
|
import { logStep } from '../observability/trace.js';
|
|
2
3
|
const maxDrops = 20;
|
|
3
4
|
const maxDepth = 12;
|
|
@@ -214,9 +215,11 @@ export function unwrapProps(value) {
|
|
|
214
215
|
const record = value;
|
|
215
216
|
const isProp = typeof record.type === 'string' && (Object.hasOwn(record, 'data') || Object.hasOwn(record, 'ref'));
|
|
216
217
|
if (isProp) {
|
|
217
|
-
// `
|
|
218
|
-
//
|
|
219
|
-
|
|
218
|
+
// Platform `static` dışındaki her tipte değeri `ref`'e yazar (Prop ctor'u,
|
|
219
|
+
// DesignComponentManagementSchema.cs:666); `data` ya boştur ya türetilmiştir (file URL'i, page
|
|
220
|
+
// slug'ı, translation tasarım yedeği). `data`'yı taşıyıcı saymak bağlı akışı her yazmada
|
|
221
|
+
// "kaydedilen null" gösteriyordu (S-7).
|
|
222
|
+
const carrier = record.type !== 'static' && PROP_VALUE_TYPES.has(record.type)
|
|
220
223
|
? (Object.hasOwn(record, 'ref') ? record.ref : record.data)
|
|
221
224
|
: (Object.hasOwn(record, 'data') ? record.data : record.ref);
|
|
222
225
|
return unwrapProps(carrier);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": true,
|
|
4
|
+
"note": "Bir butonun click olayına bağlanan akış: 'function' adımı kullanıcı fonksiyonunu çağırır (fiyat komponent prop'undan, adet sabit), sonraki setter sonucu Steps.<adımAdı>.data'dan okuyup metne yazar. Canlı kanıt: 2026-09-08/09 fonksiyon-demo sayfası (kabul senaryosu 2 → 30). Şekil backend StepManagementSchema + ng-apps-flow tasks/function/function.ts okunarak kuruldu.",
|
|
5
|
+
"harvestedFrom": "kod + canlı doğrulama: zeyrek-frontend packages/ng-apps-flow/src/tasks/function, zeyrek-backend Contexts/Studio/Flows/StepArgument (UiFunctionId/UiFunctionName) (2026-09-09)"
|
|
6
|
+
},
|
|
7
|
+
"_rules": {
|
|
8
|
+
"functionId": "args.functionId ZORUNLU ve uygulamada var olmalı — backend 422 UiFunctionNotFound, gateway FLOW-024 (block). Id'yi function_list'ten alın. Runtime fonksiyonu ÖNCE id ile çözer (K9); functionName gösterim ve yedektir.",
|
|
9
|
+
"parameters": "parameters[].name bildirimdeki parametre adıyla eşleşir; value şekli microservice adımıyla aynı (static · property · storage · state · variable · code · page · item). Eksik zorunlu parametre adımı 'Functions.<ad>: \"<param>\" is required' ile düşürür; tanınmayan ad sessizce düşer — FLOW-024 ikisini de uyarır.",
|
|
10
|
+
"output": "Çıktı Steps.<adım ADI>.data (stepId değil — machine.setStorage(task.name, …)); hata Steps.<ad>.error.message (nesne). Setter'ın storage okuması mapping.source: 'hesapla.data' ile yapılır (setter.ts:161 mapping.source doluysa value.ref okunmaz).",
|
|
11
|
+
"branchName": "Dal adı 'always' (motor anahtarı, FLOW-004). Son adımın dalı nextStepId: null ile biter; 'end' adımı koyma (FLOW-023).",
|
|
12
|
+
"binding": "Akışı olaya bağlamak ayrı adımdır: page_schema_patch set_event { target: 'btn_hesapla', event: 'click', eventValue: '<flow id>' }.",
|
|
13
|
+
"async": "isAsync=true fonksiyon bu adımdan güvenle çağrılır (Promise beklenir); {{ }} bağlamasında ise '[object Promise]' olur."
|
|
14
|
+
},
|
|
15
|
+
"flowCreateArgs": {
|
|
16
|
+
"host": "page",
|
|
17
|
+
"hostId": "<page id>",
|
|
18
|
+
"name": "btn_hesapla - click",
|
|
19
|
+
"slug": "btn-hesapla-click",
|
|
20
|
+
"steps": [
|
|
21
|
+
{
|
|
22
|
+
"stepId": "start",
|
|
23
|
+
"name": "start",
|
|
24
|
+
"type": "start",
|
|
25
|
+
"kind": "normal",
|
|
26
|
+
"args": {},
|
|
27
|
+
"branches": [ { "name": "always", "nextStepId": "hesapla" } ]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"stepId": "hesapla",
|
|
31
|
+
"name": "hesapla",
|
|
32
|
+
"type": "function",
|
|
33
|
+
"kind": "normal",
|
|
34
|
+
"args": {
|
|
35
|
+
"functionId": "<function_list id — toplamHesapla>",
|
|
36
|
+
"functionName": "toplamHesapla",
|
|
37
|
+
"parameters": [
|
|
38
|
+
{ "name": "fiyat", "value": { "type": "property", "ref": { "source": "component", "element": "num_fiyat", "property": "value" } } },
|
|
39
|
+
{ "name": "adet", "value": { "type": "static", "ref": 3 } }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"branches": [ { "name": "always", "nextStepId": "yaz" } ]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"stepId": "yaz",
|
|
46
|
+
"name": "yaz",
|
|
47
|
+
"type": "setter",
|
|
48
|
+
"kind": "normal",
|
|
49
|
+
"args": {
|
|
50
|
+
"iteratees": [
|
|
51
|
+
{
|
|
52
|
+
"type": "change",
|
|
53
|
+
"element": "txt_sonuc",
|
|
54
|
+
"property": "content",
|
|
55
|
+
"source": "component",
|
|
56
|
+
"mapping": { "source": "hesapla.data", "mappings": {} },
|
|
57
|
+
"value": { "type": "storage", "ref": "" }
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"branches": [ { "name": "always", "nextStepId": null } ]
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"bindEvent": { "op": "set_event", "target": "btn_hesapla", "event": "click", "eventValue": "<flow id>" }
|
|
66
|
+
}
|