@appaflytech/zeyrek-ai-mcp 0.0.52 → 0.0.54
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/dist/knowledge/componentCatalog.js +3 -1
- package/dist/knowledge/componentEvents.js +13 -1
- package/dist/knowledge/mobileComponentCatalog.js +4 -1
- package/dist/knowledge/normalize.js +4 -3
- package/dist/platform/argumentAliases.js +13 -4
- package/dist/platform/bodyFile.js +195 -38
- package/dist/platform/capture/actions.js +24 -5
- package/dist/platform/capture/capture.js +80 -11
- package/dist/platform/capture/persistent-browser.js +23 -1
- package/dist/platform/capture/readiness.js +32 -1
- package/dist/platform/errors.js +45 -5
- package/dist/platform/http.js +17 -9
- package/dist/platform/responses.js +7 -1
- package/dist/platform/secrets.js +8 -0
- package/dist/platform/token.js +32 -21
- package/dist/resources/examples/mobile-offline-list.json +1 -1
- package/dist/resources/examples/mobile-offline-queue.json +1 -1
- package/dist/resources/examples/mobile-offline-seamless.json +12 -3
- package/dist/resources/guides/deployment.md +9 -0
- package/dist/resources/guides/merge.md +5 -0
- package/dist/resources/guides/microservice.md +13 -1
- package/dist/resources/guides/mobile-publish.md +7 -4
- package/dist/resources/guides/mobile-steps.md +40 -3
- package/dist/resources/guides/mobile.md +39 -8
- package/dist/resources/guides/page.md +39 -8
- package/dist/resources/guides/translation.md +3 -1
- package/dist/resources/prompts/mobile-publish-prepare.md +3 -2
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/tools/applications.js +9 -4
- package/dist/tools/connectData.js +13 -4
- package/dist/tools/deployments.js +1 -1
- package/dist/tools/designSchemaPatch.js +7 -2
- package/dist/tools/environments.js +40 -0
- package/dist/tools/flows.js +30 -3
- package/dist/tools/microservices.js +25 -7
- package/dist/tools/mobile.js +56 -0
- package/dist/tools/mobileStore.js +2 -2
- package/dist/tools/pages.js +69 -16
- package/dist/tools/platformInfo.js +1 -1
- package/dist/tools/screenWrite.js +107 -1
- package/dist/tools/variables.js +1 -1
- package/package.json +1 -1
- package/dist/resources/guides/mobile-flow.md +0 -64
package/dist/tools/flows.js
CHANGED
|
@@ -64,6 +64,23 @@ function hostNodes(record) {
|
|
|
64
64
|
return record.schema ?? record.views ?? [];
|
|
65
65
|
}
|
|
66
66
|
const BINDINGS_UNAVAILABLE = 'Barındırıcı kaydı okunamadığı için olay bağları hesaplanamadı; akış listesi yine de günceldir.';
|
|
67
|
+
/**
|
|
68
|
+
* g-21 (D-21, plans/defter-saha-bulgulari): `flow_get` `host` olmadan da çalışır. Akış kimliği sahibini söylemez ve
|
|
69
|
+
* platformda kimlikten sahibe giden uç yoktur; kimlik sırayla sayfa → layout → komponent kaydı olarak okunur. Yalnız
|
|
70
|
+
* 404 sonrakine geçer — başka hata (yetki, 500) sahibi hakkında bir şey söylemez ve olduğu gibi döner.
|
|
71
|
+
*/
|
|
72
|
+
async function resolveFlowHost(platform, project, environment, applicationId, hostId) {
|
|
73
|
+
for (const host of ['page', 'layout', 'component']) {
|
|
74
|
+
try {
|
|
75
|
+
return { host, record: await fetchHost(platform, host, project, environment, applicationId, hostId) };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (!(error instanceof PlatformApiError) || error.status !== 404)
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new PlatformApiError(`flow_get: bu kimlikte sayfa/layout/komponent yok (${hostId}, uygulama ${applicationId}). hostId akışın barındırıcısının id'sidir, akışın kendi id'si değil — page_list / layout_list / component_list ile doğrulayın ya da host'u açıkça verin.`, 404);
|
|
83
|
+
}
|
|
67
84
|
/**
|
|
68
85
|
* TB-126 / S2-36 — satır aksiyonu taşıyabilen komponentler. Bu tiplerin `actions` prop'unu çoğu
|
|
69
86
|
* zaman YÜKLEME AKIŞI çalışma zamanında yazar (aksiyon role göre eklenip çıkarılıyorsa başka yolu
|
|
@@ -529,16 +546,26 @@ export function registerFlowTools(server, platform) {
|
|
|
529
546
|
server.registerTool('flow_get', {
|
|
530
547
|
title: 'Akış getir',
|
|
531
548
|
description: "Tek akışı adımlarıyla getirir; yeni akış yazarken en iyi şablon budur. bindings bu akışa bağlı olaylardır ([{ target: elementId | 'page', event }]; boşsa akış sahipsizdir). Hedefi silinmiş (element'siz) setter iteratee'si varsa yanıttaki flowWarnings söyler — o adım koşar ama hiçbir şeye yazmaz.",
|
|
532
|
-
inputSchema: {
|
|
549
|
+
inputSchema: {
|
|
550
|
+
project: projectParam,
|
|
551
|
+
environment: environmentParam,
|
|
552
|
+
host: hostParam.optional().describe("Akışın barındırıcısı: page | layout | component. Verilmezse gateway hostId'yi sırayla sayfa → layout → komponent olarak okur (en çok üç okuma, 404 sonrakine geçer) ve yanıtın hostResolvedAs alanı bulduğunu söyler; biliyorsanız verin, tek okuma yapılır"),
|
|
553
|
+
applicationId: guid,
|
|
554
|
+
hostId: guid,
|
|
555
|
+
flowId: flowIdParam
|
|
556
|
+
},
|
|
533
557
|
annotations: readOnly
|
|
534
558
|
}, handle(config, async ({ project, environment, host, applicationId, hostId, flowId }) => {
|
|
535
|
-
const
|
|
536
|
-
const
|
|
559
|
+
const resolved = host === undefined ? await resolveFlowHost(platform, project, environment, applicationId, hostId) : undefined;
|
|
560
|
+
const owner = resolved?.host ?? host;
|
|
561
|
+
const response = await platform.tenant('GET', project, flowPath(owner, applicationId, hostId, flowId), { environment });
|
|
562
|
+
const record = resolved?.record ?? await fetchHost(platform, owner, project, environment, applicationId, hostId).catch(() => undefined);
|
|
537
563
|
const warnings = targetlessSetterWarnings(response);
|
|
538
564
|
if (!response || typeof response !== 'object' || Array.isArray(response))
|
|
539
565
|
return response;
|
|
540
566
|
return {
|
|
541
567
|
...response,
|
|
568
|
+
...(resolved === undefined ? {} : { hostResolvedAs: resolved.host }),
|
|
542
569
|
...(record === undefined
|
|
543
570
|
? { bindingsNote: BINDINGS_UNAVAILABLE }
|
|
544
571
|
: { bindings: flowBindings(hostNodes(record), record.events).get(flowId.toLowerCase()) ?? [] }),
|
|
@@ -214,25 +214,36 @@ function executePath(prefix, application, microservice, routeParams) {
|
|
|
214
214
|
/**
|
|
215
215
|
* Slug application_update ile değişebildiği için önbellek kısa ömürlüdür (yalnız aynı turdaki tekrar okumaları ucuzlatır);
|
|
216
216
|
* runtime yolu 404 dönen çağıran `fresh: true` ile önbelleği atlar. Org API'nin 404 dışı hatası (yetki, ağ) çağırana gider.
|
|
217
|
+
*
|
|
218
|
+
* Uygulama kaydı (ad, slug, ayar) ve varlığı production'a yalnız dağıtımla gider, dal açıldığı andaki kopyayı tutar
|
|
219
|
+
* (backend ADR 0073, g-24): `GET /applications/{id}` Core'dur = development. Başka ortamın slug'ı o ortamın kendi
|
|
220
|
+
* kaydından (tenant `GET /{project}/applications/{id}`, X-Environment) okunur; yoksa uygulama oraya dağıtılmamıştır (APP-002).
|
|
217
221
|
*/
|
|
218
222
|
const APPLICATION_SLUG_TTL_MS = 60_000;
|
|
219
223
|
const applicationSlugCache = new Map();
|
|
220
224
|
export async function applicationSlug(platform, applicationId, options = {}) {
|
|
221
|
-
const
|
|
225
|
+
const environment = platform.resolveEnvironment(options.environment);
|
|
226
|
+
const core = environment === 'development';
|
|
227
|
+
const key = `${core ? '' : platform.resolveProject(options.project)}:${environment}:${applicationId}`;
|
|
228
|
+
const cached = options.fresh ? undefined : applicationSlugCache.get(key);
|
|
222
229
|
if (cached && cached.expiresAt > Date.now())
|
|
223
230
|
return cached.slug;
|
|
224
231
|
try {
|
|
225
|
-
const payload =
|
|
232
|
+
const payload = (core
|
|
233
|
+
? await platform.org('GET', `/applications/${applicationId}`)
|
|
234
|
+
: await platform.tenant('GET', options.project, `/applications/${applicationId}`, { environment }));
|
|
226
235
|
const application = (payload?.data ?? payload);
|
|
227
236
|
const slug = typeof application.slug === 'string' && application.slug !== '' ? application.slug : undefined;
|
|
228
237
|
if (slug)
|
|
229
|
-
applicationSlugCache.set(
|
|
238
|
+
applicationSlugCache.set(key, { slug, expiresAt: Date.now() + APPLICATION_SLUG_TTL_MS });
|
|
230
239
|
return slug;
|
|
231
240
|
}
|
|
232
241
|
catch (error) {
|
|
233
|
-
if (error instanceof PlatformApiError
|
|
242
|
+
if (!(error instanceof PlatformApiError) || error.status !== 404)
|
|
243
|
+
throw error;
|
|
244
|
+
if (core)
|
|
234
245
|
return undefined;
|
|
235
|
-
throw
|
|
246
|
+
throw new PlatformApiError(`Uygulama ${applicationId} '${environment}' ortamında yok: uygulama kaydı (ad, slug, ayar) ve varlığı production'a yalnız dağıtımla gider, dal açıldığı andaki kopyayı tutar (APP-002). Production için deployment_start ile dağıtın; dalda yalnız dal açılmadan önce var olan uygulama bulunur — development'ta deneyin ya da dalı yeniden açın.`, 404);
|
|
236
247
|
}
|
|
237
248
|
}
|
|
238
249
|
/**
|
|
@@ -247,7 +258,14 @@ async function resolveRuntimeUrl(platform, project, environment, applicationId,
|
|
|
247
258
|
const method = String(definition.httpMethod ?? 'get').toUpperCase();
|
|
248
259
|
const resolvedProject = platform.resolveProject(project);
|
|
249
260
|
const resolvedEnvironment = platform.resolveEnvironment(environment);
|
|
250
|
-
|
|
261
|
+
// Dağıtılmamış uygulamada tanım yine döner; tarif yer tutucu slug ve nedeni taşır.
|
|
262
|
+
let notDeployed;
|
|
263
|
+
const application = await applicationSlug(platform, applicationId, { project, environment }).catch((error) => {
|
|
264
|
+
if (!(error instanceof PlatformApiError) || error.status !== 404)
|
|
265
|
+
throw error;
|
|
266
|
+
notDeployed = error.message;
|
|
267
|
+
return undefined;
|
|
268
|
+
});
|
|
251
269
|
const path = `/${resolvedProject}/m/${application ?? '<application slug>'}/${slug}`;
|
|
252
270
|
return {
|
|
253
271
|
method,
|
|
@@ -257,7 +275,7 @@ async function resolveRuntimeUrl(platform, project, environment, applicationId,
|
|
|
257
275
|
Host: `${platform.config.organization}.${platform.config.hostSuffix}`,
|
|
258
276
|
'X-Environment': resolvedEnvironment
|
|
259
277
|
},
|
|
260
|
-
note: `Yolu SLUG kurar, 'url' alanı ('${String(definition.url ?? '')}') rotaya girmez. Route parametreleri slug'ın ardına eklenir. Anonim erişim testi için microservice_execute'u anonymous: true ile çağırın; korumalı uçlarda 401, anonim uçlarda 200 beklenir
|
|
278
|
+
note: `Yolu SLUG kurar, 'url' alanı ('${String(definition.url ?? '')}') rotaya girmez. Route parametreleri slug'ın ardına eklenir. Anonim erişim testi için microservice_execute'u anonymous: true ile çağırın; korumalı uçlarda 401, anonim uçlarda 200 beklenir.${notDeployed ? ` ${notDeployed}` : ''}`
|
|
261
279
|
};
|
|
262
280
|
}
|
|
263
281
|
export function registerMicroserviceTools(server, platform) {
|
package/dist/tools/mobile.js
CHANGED
|
@@ -114,6 +114,57 @@ export function registerMobileTools(server, platform) {
|
|
|
114
114
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid.describe('Mobil uygulama (slug da verilebilir, gateway GUID\'e çevirir)'), ...pushAudienceFields, pageIndex: pagingParams.pageIndex, pageLength: pagingParams.pageLength.describe('Sayfa boyu; varsayılan 25') },
|
|
115
115
|
annotations: readOnly
|
|
116
116
|
}, handle(config, ({ project, environment, applicationId, ...query }) => platform.tenant('GET', project, `/push/devices/${applicationId}`, { environment, query })));
|
|
117
|
+
server.registerTool('mobile_error_list', {
|
|
118
|
+
title: 'Telefonda düşen akış hataları',
|
|
119
|
+
description: "Mobil uygulamanın TELEFONDA düşen akış hatalarını okur (kabuk gönderir, sunucu saklar — defter-saha-bulgulari b-10/f-10). Kayıt biçimi page_screenshot.runtimeErrors ile aynıdır (flowId · step{id,name,type} · code · message · element · property · kind) ve şunları ekler: pageSlug, platform, appVersion, devices (kaç kurulumda), count, lastAt. Aynı akış/adım/mesaj üçlüsü tek satıra katlanır, en yeni önce. Önizlemede görülmeyen cihaz hatasını (ağsız yazma, sayfa kapanınca atlanan adım `page-unmounted`, geri gidilemeyen `back-unavailable`) kullanıcının anlatmasını beklemeden okumak için. Kabuk hatayı ağ gelince gönderir: uçak kipinde düşen hata ağ dönene kadar görünmez; occurredAt telefondaki saattir. Sunucu kurulum başına son 200 kaydı ve 7 günü tutar; mesaj gateway maskesinden geçer. Uygulama ayarı properties.mobile.reportErrors: false gönderimi kapatır. Web önizlemesinin hataları burada YOK — onları page_screenshot.runtimeErrors verir.",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
project: projectParam,
|
|
122
|
+
environment: environmentParam,
|
|
123
|
+
applicationId: guid.describe('Mobil uygulama (slug da verilebilir, gateway GUID\'e çevirir)'),
|
|
124
|
+
pageSlug: z.string().min(1).optional().describe('Yalnız bu sayfada düşenler'),
|
|
125
|
+
flowId: z.string().min(1).optional().describe('Yalnız bu akış'),
|
|
126
|
+
installationId: z.string().min(1).optional().describe('Yalnız bu kurulum (notification_device_list\'teki installationId ile aynı kimlik)'),
|
|
127
|
+
since: z.string().min(1).optional().describe('ISO zaman; yalnız bundan sonra görülenler'),
|
|
128
|
+
limit: z.number().int().min(1).max(100).optional().describe('Katlanmış satır sayısı; varsayılan 20')
|
|
129
|
+
},
|
|
130
|
+
annotations: readOnly
|
|
131
|
+
}, handle(config, async ({ project, environment, applicationId, limit = 20, ...query }) => {
|
|
132
|
+
let response;
|
|
133
|
+
try {
|
|
134
|
+
response = await platform.tenant('GET', project, `/runtime-errors/${applicationId}`, { environment, query: { ...query, limit: 200 } });
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof PlatformApiError && error.status === 404) {
|
|
138
|
+
throw new PlatformApiError('mobile_error_list: bu backend istemci akış hatası kaydetmiyor (runtime-errors ucu yok) — backend imajı güncel değil ya da uç bu kurulumda kapalı. Önizleme hatalarını page_screenshot.runtimeErrors verir.', 404);
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
const rows = unwrap(response) ?? [];
|
|
143
|
+
// Aynı akış/adım/mesaj tek satır: sayılar toplanır, en yeni kaydın bağlamı (sayfa, sürüm, platform) taşınır.
|
|
144
|
+
const folded = new Map();
|
|
145
|
+
for (const row of rows) {
|
|
146
|
+
const key = `${row.flowId ?? ''}|${row.step?.id ?? ''}|${row.message ?? ''}`;
|
|
147
|
+
const lastAt = row.lastAt ?? row.occurredAt ?? '';
|
|
148
|
+
const existing = folded.get(key);
|
|
149
|
+
if (!existing) {
|
|
150
|
+
folded.set(key, { ...row, lastAt, count: row.count ?? 1, devices: new Set(row.installationId ? [row.installationId] : []) });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
existing.count = (existing.count ?? 1) + (row.count ?? 1);
|
|
154
|
+
if (row.installationId)
|
|
155
|
+
existing.devices.add(row.installationId);
|
|
156
|
+
if (lastAt > (existing.lastAt ?? ''))
|
|
157
|
+
Object.assign(existing, { ...row, lastAt, count: existing.count, devices: existing.devices });
|
|
158
|
+
}
|
|
159
|
+
const errors = [...folded.values()]
|
|
160
|
+
.sort((left, right) => (right.lastAt ?? '').localeCompare(left.lastAt ?? ''))
|
|
161
|
+
.map(({ devices, installationId: _installation, ...error }) => ({ ...error, devices: devices.size }));
|
|
162
|
+
return {
|
|
163
|
+
errors: errors.slice(0, limit),
|
|
164
|
+
...(errors.length > limit ? { truncated: true, total: errors.length } : {}),
|
|
165
|
+
...(errors.length === 0 ? { note: 'Bu süzgeçle kayıt yok. Telefon hatayı ağ gelince gönderir; uçak kipindeki hata ağ dönene kadar görünmez. Uygulama ayarı properties.mobile.reportErrors: false ise kabuk göndermez.' } : {})
|
|
166
|
+
};
|
|
167
|
+
}));
|
|
117
168
|
server.registerTool('notification_create', {
|
|
118
169
|
title: 'Push bildirimi gönder (varsayılan kuru koşu)',
|
|
119
170
|
description: "Kitleye dile göre içerikle push bildirimi gönderir — `SendPushNotificationCommand: { dryRun, audience, content, defaultLanguage, path?, query?, androidChannel?, badge?, testInstallationId? }`. dryRun VARSAYILAN TRUE: yalnız kitleyi sayar ({ total, byLanguage }), hiçbir şey yazmaz; göndermek için dryRun: false AÇIKÇA verilir. Gerçek gönderim kuyruğa girer ve yanıt id döner; sonucu notification_history { id } ile okuyun (birkaç saniye sonra sent/failed, teslim sayıları ve hatalı cihazlar). Kitle her zaman aktif ve izinli cihazlara daraltılır; testInstallationId verilirse yalnız o kurulum (notification_device_list'teki installationId). İçerik dil → { title, body }; başlığı boş dil düşer ve o cihaz varsayılan dili alır; varsayılan dilde başlık zorunlu (NTF-002, platform 400). Dil anahtarları küçük harfe indirilir. path dokunuşta açılacak sayfanın slug'ı, query onun sorgusu. Ret: production'da dryRun: false istek gitmeden reddedilir (NTF-001).",
|
|
@@ -169,6 +220,11 @@ export function registerMobileTools(server, platform) {
|
|
|
169
220
|
const plan = buildMobileAppPlan(input);
|
|
170
221
|
if (dryRun)
|
|
171
222
|
return { dryRun: true, plan };
|
|
223
|
+
// APP-001 (K5, b-15): sekme ve ana sayfa ayarı yalnız development'a yazılır. Dalda kurulan sayfalara bağlı ayar
|
|
224
|
+
// development'ta olmayan sayfaları gösterir, dal sekmesiz kalırdı.
|
|
225
|
+
if (platform.resolveEnvironment(environment) !== 'development') {
|
|
226
|
+
throw new PlatformApiError(`mobile_app_create [APP-001]: uygulama ayarı (sekmeler, ana sayfa) yalnız development'a yazılır; '${platform.resolveEnvironment(environment)}' ortamında kurulan sayfalara bağlanamaz. Mobil uygulamayı development'ta kurun, production'a deployment_start ile taşıyın. Hiçbir şey yazılmadı.`, 400);
|
|
227
|
+
}
|
|
172
228
|
const completed = [];
|
|
173
229
|
const skipped = new Set();
|
|
174
230
|
const warnings = [];
|
|
@@ -467,7 +467,7 @@ export function registerMobileStoreTools(server, platform, storeArt = {}) {
|
|
|
467
467
|
}));
|
|
468
468
|
server.registerTool('mobile_build_start', {
|
|
469
469
|
title: 'Mobil derleme başlat',
|
|
470
|
-
description: "Mobil uygulama için derleme işi açar (POST mobile-builds { platform, target }). Önce ön kontrolü okur (GET mobile-store/{appId}/preflight — Studio'nun Yayımla düğmesiyle aynı liste: ayar, görsel, gizli bilgi, App Store Connect / Play erişimi, production sayfası, etkin iş, çevrimiçi runner, beyanlar; iOS yükleme hedeflerinde App Review satırları P-15…P-21, yayın hedeflerinde kaynak derleme P-22); bir satır bile 'fail' ise iş BAŞLATILMAZ ve fail satırları kod + öneriyle hata olarak döner ('manual' satırlar başlatmayı engellemez, kullanıcıya söylenir). dryRun:true yalnız ön kontrolü döner, iş açmaz — yayından önce bununla bakın. Hedef–platform: build her ikisinde, testflight ve app-store-review/app-store-release yalnız ios, internal ve play-promote yalnız android. Mağaza yayın hedefleri (app-store-review, app-store-release, play-promote + releaseTrack closed|production) geri dönüşsüz ya da insan onaylıdır: YALNIZ dryRun ile kabul edilir, gerçek gönderim Studio › Yayınla'dadır; mağaza durumu ve sıradaki eylem mobile_store_get { detail: ['status'] }. packageType: aab (varsayılan) | apk — APK yalnız android build'de (mağazaya gitmez, telefona kurulur). Runner: runnerScope verilmezse backend seçer — kurumun kendi Mac'i çevrimiçiyse dedicated, değilse Zeyrek'in ortak runner'ı (shared): kurumun kendi Mac'i yoksa iş Zeyrek'in runner'ına düşer; yanıtta runnerKind own | zeyrek. runnerId işi kullanıcının o Mac'ine ayırır (runnerScope gerekmez — iş dedicated kapsamında o runner'ı bekler; kullanıcının birden çok çevrimiçi Mac'i varken işin hangisine düşeceğini sabitler): runner kurumun kendi runner'ı değilse, platformu desteklemiyorsa ya da isteyene derleyemiyorsa 404 döner, runnerScope: 'shared' ile birlikte 400 döner — ikisinde de numara ayrılmaz; çevrimdışı Mac reddedilmez, iş onu bekler. runnerId verilmezse iş eskisi gibi ayrılmaz (kapsamdaki ilk yoklayan runner alır). Bu araç Cihazda dene önizlemesini başlatmaz (önizleme görevi, kod ve davet kullanıcının Studio'daki işidir). Tuzak: her başlatma derleme numarasını AYIRIR ve ilk başlatma kimlik alanlarını kilitler; iptal ya da hata numarayı geri vermez. Aynı platformda etkin iş varsa 409. İş asenkrondur: mobile_build_status ile izleyin. Ortam: environment (ad ya da slug; boş = production) derlemenin içeriğini o ortamdan alır — development, production ya da hazır bir dal; yalnız build | testflight | internal'da verilir, yükseltme hedefinde gateway reddeder: mağazaya yalnız production işinin derlemesi gider, development/dal test derlemesi asla yükseltilemez. Ön kontrolün kapsamı scope: 'store' (varsayılan; mağazaya giden işin bütün kuralları) | 'test' (Yayınla 3 · Test sürümü: yalnız TestFlight / Play iç testin istedikleri — kimlik, hesap ve imza, konsolda uygulama kaydı, seçilen ortamda açılış sayfası, runner; metin, görsel, beyan ve App Review satırları sorulmaz); environment verilince scope 'test' olur, 'store' ile birlikte reddedilir; scope işe yazılmaz. Derlenen uygulama işin ortamını okur — yükleme hedeflerinde açılış sayfası o ortamda olmalı (P-06 fail; production'da dağıtılmış olmalı); yalın build'de P-06 yalnız uyarıdır. Yanıt işin environment'ını taşır.",
|
|
470
|
+
description: "Mobil uygulama için derleme işi açar (POST mobile-builds { platform, target }). Önce ön kontrolü okur (GET mobile-store/{appId}/preflight — Studio'nun Yayımla düğmesiyle aynı liste: ayar, görsel, gizli bilgi, App Store Connect / Play erişimi, production sayfası, etkin iş, çevrimiçi runner, beyanlar; iOS yükleme hedeflerinde App Review satırları P-15…P-21, yayın hedeflerinde kaynak derleme P-22); bir satır bile 'fail' ise iş BAŞLATILMAZ ve fail satırları kod + öneriyle hata olarak döner ('manual' satırlar başlatmayı engellemez, kullanıcıya söylenir). dryRun:true yalnız ön kontrolü döner, iş açmaz — yayından önce bununla bakın. Hedef–platform: build her ikisinde, testflight ve app-store-review/app-store-release yalnız ios, internal ve play-promote yalnız android. Mağaza yayın hedefleri (app-store-review, app-store-release, play-promote + releaseTrack closed|production) geri dönüşsüz ya da insan onaylıdır: YALNIZ dryRun ile kabul edilir, gerçek gönderim Studio › Yayınla'dadır; mağaza durumu ve sıradaki eylem mobile_store_get { detail: ['status'] }. packageType: aab (varsayılan) | apk — APK yalnız android build'de (mağazaya gitmez, telefona kurulur). Runner: runnerScope verilmezse backend seçer — kurumun kendi Mac'i çevrimiçiyse dedicated, değilse Zeyrek'in ortak runner'ı (shared): kurumun kendi Mac'i yoksa iş Zeyrek'in runner'ına düşer; yanıtta runnerKind own | zeyrek. runnerId işi kullanıcının o Mac'ine ayırır (runnerScope gerekmez — iş dedicated kapsamında o runner'ı bekler; kullanıcının birden çok çevrimiçi Mac'i varken işin hangisine düşeceğini sabitler): runner kurumun kendi runner'ı değilse, platformu desteklemiyorsa ya da isteyene derleyemiyorsa 404 döner, runnerScope: 'shared' ile birlikte 400 döner — ikisinde de numara ayrılmaz; çevrimdışı Mac reddedilmez, iş onu bekler. runnerId verilmezse iş eskisi gibi ayrılmaz (kapsamdaki ilk yoklayan runner alır). Bu araç Cihazda dene önizlemesini başlatmaz (önizleme görevi, kod ve davet kullanıcının Studio'daki işidir). Sayfa değişikliğini yerelde görmek için derleme gerekmez: platform_guide('mobile') §Cihazda dene → Senin Mac'inde; iOS target 'build' imzalama kimliği yokken imzasız simülatör .app'i derler (kimlik tamsa imzalı .ipa — simülatörde açılmaz). Tuzak: her başlatma derleme numarasını AYIRIR ve ilk başlatma kimlik alanlarını kilitler; iptal ya da hata numarayı geri vermez. Aynı platformda etkin iş varsa 409. İş asenkrondur: mobile_build_status ile izleyin. Ortam: environment (ad ya da slug; boş = production) derlemenin içeriğini o ortamdan alır — development, production ya da hazır bir dal; yalnız build | testflight | internal'da verilir, yükseltme hedefinde gateway reddeder: mağazaya yalnız production işinin derlemesi gider, development/dal test derlemesi asla yükseltilemez. Ön kontrolün kapsamı scope: 'store' (varsayılan; mağazaya giden işin bütün kuralları) | 'test' (Yayınla 3 · Test sürümü: yalnız TestFlight / Play iç testin istedikleri — kimlik, hesap ve imza, konsolda uygulama kaydı, seçilen ortamda açılış sayfası, runner; metin, görsel, beyan ve App Review satırları sorulmaz); environment verilince scope 'test' olur, 'store' ile birlikte reddedilir; scope işe yazılmaz. Derlenen uygulama işin ortamını okur — yükleme hedeflerinde açılış sayfası o ortamda olmalı (P-06 fail; production'da dağıtılmış olmalı); yalın build'de P-06 yalnız uyarıdır. Yanıt işin environment'ını taşır.",
|
|
471
471
|
inputSchema: {
|
|
472
472
|
applicationId,
|
|
473
473
|
platform: mobilePlatform,
|
|
@@ -590,7 +590,7 @@ export function registerMobileStoreTools(server, platform, storeArt = {}) {
|
|
|
590
590
|
}, handle(config, async ({ buildId: id }) => buildSummary(unwrap(await platform.org('POST', `/mobile-builds/${id}/cancel`)), new Date().toISOString())));
|
|
591
591
|
server.registerTool('mobile_asset_create', {
|
|
592
592
|
title: 'Mağaza görseli yükle',
|
|
593
|
-
description: `Mağaza görselini yükler (POST mobile-store/{appId}/assets, multipart). Kaynak path (gateway'in okuyabildiği yerel dosya) ya da base64 content'tir — dosya ajanın bağlamına girmez. Yuvalar: derleme görselleri icon (1024×1024 PNG, alfasız), adaptiveForeground / adaptiveBackground / adaptiveMonochrome (1024×1024 PNG), splash (≥ 1242×2436 PNG) — dilsizdir, locale verilmez; mağaza görselleri featureGraphic (1024×500, alfasız), promo, screenshot (deviceClass zorunlu, locale önerilir: 'screenshot/tr/iphone69'); reviewAttachment (dilsiz, PNG/JPEG) App Review'a ek görseldir — yalnız app-store-review gönderimine girer. Tuzak: screenshot ve promo dışındaki türler TEKLİDİR — yükleme yuvadaki önceki görselin YERİNE geçer ve eskisi silinir. Yuva başına en çok 10 ekran görüntüsü, dosya ≤ 10 MB, PNG ya da JPEG (derleme görseli yalnız PNG). Ölçü, biçim ve alfa backend'de denetlenir; 422 hangi ölçünün beklendiğini söyler. EKRAN GÖRÜNTÜSÜ uygulamanın kendi sayfasından üretilir: önce page_screenshot { environment: 'production', applicationSlug, pageSlug, device, format: 'jpeg', quality: 92, language, colorScheme: 'light', outPath }, sonra bu araç { path: <outPath>, kind: 'screenshot', deviceClass, locale }. Cihaz sınıfı → device: ${captureTable}. JPEG şarttır: iPhone ve iPad ekran görüntüsünde alfa kanalı reddedilir. Giriş/splash ekranını çekmeyin (App Review 2.3.3: uygulama kullanımda gösterilir). Sıra: mobile_asset_update; silme: mobile_asset_delete; liste: mobile_store_get { detail: ['assets'] }.`,
|
|
593
|
+
description: `Mağaza görselini yükler (POST mobile-store/{appId}/assets, multipart). Kaynak path (gateway'in okuyabildiği yerel dosya) ya da base64 content'tir — dosya ajanın bağlamına girmez. Yuvalar: derleme görselleri icon (1024×1024 PNG, alfasız), adaptiveForeground / adaptiveBackground / adaptiveMonochrome (1024×1024 PNG), splash (≥ 1242×2436 PNG) — dilsizdir, locale verilmez; mağaza görselleri featureGraphic (1024×500, alfasız), promo, screenshot (deviceClass zorunlu, locale önerilir: 'screenshot/tr/iphone69'); reviewAttachment (dilsiz, PNG/JPEG) App Review'a ek görseldir — yalnız app-store-review gönderimine girer. Tuzak: screenshot ve promo dışındaki türler TEKLİDİR — yükleme yuvadaki önceki görselin YERİNE geçer ve eskisi silinir. Yuva başına en çok 10 ekran görüntüsü, dosya ≤ 10 MB, PNG ya da JPEG (derleme görseli yalnız PNG). Ölçü, biçim ve alfa backend'de denetlenir; 422 hangi ölçünün beklendiğini söyler. EKRAN GÖRÜNTÜSÜ uygulamanın kendi sayfasından üretilir: önce page_screenshot { environment: 'production', applicationSlug: <uygulama id'si — production'ın kendi slug'ına çözülür, APP-002>, pageSlug, device, format: 'jpeg', quality: 92, language, colorScheme: 'light', outPath }, sonra bu araç { path: <outPath>, kind: 'screenshot', deviceClass, locale }. Cihaz sınıfı → device: ${captureTable}. JPEG şarttır: iPhone ve iPad ekran görüntüsünde alfa kanalı reddedilir. Giriş/splash ekranını çekmeyin (App Review 2.3.3: uygulama kullanımda gösterilir). Sıra: mobile_asset_update; silme: mobile_asset_delete; liste: mobile_store_get { detail: ['assets'] }.`,
|
|
594
594
|
inputSchema: {
|
|
595
595
|
applicationId,
|
|
596
596
|
path: z.string().min(1).optional().describe("Gateway sürecinin okuyabildiği yerel PNG/JPEG yolu (page_screenshot outPath'i); content ile birlikte verilmez"),
|
package/dist/tools/pages.js
CHANGED
|
@@ -4,14 +4,14 @@ import { functionSignature } from '../knowledge/functionGuards.js';
|
|
|
4
4
|
import { attachFindings } from '../knowledge/policy.js';
|
|
5
5
|
import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath, countSchemaNodes, missingElementIds, readFullSchema } from './screens.js';
|
|
6
6
|
import { fetchComponentEventNames } from './components.js';
|
|
7
|
-
import { assertScreenWriteGuards, screenCatalogNotes, assertScreenCatalog } from './screenWrite.js';
|
|
7
|
+
import { assertScreenWriteGuards, screenCatalogNotes, assertScreenCatalog, unknownPropOps } from './screenWrite.js';
|
|
8
8
|
import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, applySkeletonOps, reshapesSkeleton, planEventRemovals, callerSlots, planEmulatedOps, recordsWithEventRemovals, serverOpsInOrder, rejectedOpsMessage } from './schemaOps.js';
|
|
9
9
|
import { formBindingWarnings, formSubmitButtonWarnings } from '../knowledge/coverage.js';
|
|
10
10
|
import { emptyWriteDiff, schemaOpDrops, unwrapProps, withWriteDiff, writeDiff } from '../platform/writeDiff.js';
|
|
11
11
|
import { PlatformApiError } from '../platform/errors.js';
|
|
12
12
|
import { existingMatch, ifExistsParam, listAll, withOutcome } from '../platform/ifExists.js';
|
|
13
13
|
import { handle, toolResult, toolError } from '../platform/responses.js';
|
|
14
|
-
import { expressionErrorsNote, runtimeErrorsNote, StudioScreenshotter } from '../platform/capture/index.js';
|
|
14
|
+
import { awaitingUserNote, expressionErrorsNote, runtimeErrorsNote, StudioScreenshotter } from '../platform/capture/index.js';
|
|
15
15
|
import { captureTargetFor } from '../platform/capture/target.js';
|
|
16
16
|
import { diffSummaries, summaryLines, summaryStats } from '../platform/domSummary.js';
|
|
17
17
|
import { captureKey, imageHash, sharedCaptureMemory } from '../platform/screenshotMemory.js';
|
|
@@ -30,6 +30,8 @@ import { boxSectionNote, collectIds, ensureBoxSections, expandSectionToggles } f
|
|
|
30
30
|
import { designSnapshotInput } from '../knowledge/designSnapshot.js';
|
|
31
31
|
import { discoverPage } from '../knowledge/pageDiscover.js';
|
|
32
32
|
import { applicationTypeField, resolveProfile, resolveProfileBySlug } from '../knowledge/platform/resolve.js';
|
|
33
|
+
import { applicationSlug as environmentApplicationSlug } from './microservices.js';
|
|
34
|
+
import { isGuid } from '../platform/refs.js';
|
|
33
35
|
import { batchEnvelopeNote, batchIndexHint, openBatchEnvelopes } from '../knowledge/platform/batchOps.js';
|
|
34
36
|
import { heroTags } from '../knowledge/platform/heroTag.js';
|
|
35
37
|
import { profileLintReadsSavedNodes } from '../knowledge/platform/lint.js';
|
|
@@ -58,6 +60,20 @@ function runtimeFunctions(value) {
|
|
|
58
60
|
.map(parameter => ({ name: parameter.name, required: parameter.required === true, defaultValue: parameter.defaultValue ?? null }))
|
|
59
61
|
}));
|
|
60
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* g-24 / APP-002: uygulama kaydı production'a yalnız dağıtımla gider, dal açıldığı andaki kopyayı tutar; application_list
|
|
65
|
+
* ve application_get Core'u (development) döner. Yeniden adlanan uygulamanın production/dal adresi eski slug'la çalışır,
|
|
66
|
+
* Core slug'ı orada 404 verir — id verilirse slug'ı o ortamın kaydından gateway okur.
|
|
67
|
+
*/
|
|
68
|
+
const runtimeApplicationParam = z.string().min(1).describe("Uygulamanın slug'ı ya da id'si (GUID). application_list / application_get DEVELOPMENT slug'ını döner; production ve dal uygulama kaydını yalnız dağıtımla/açılışla alır, yeniden adlanan uygulama orada dağıtıma kadar eski slug'la açılır. production ya da dalda id verin: gateway o ortamın kendi slug'ını okur, uygulama o ortamda yoksa (dağıtılmamış) istek gitmeden hata döner (APP-002)");
|
|
69
|
+
async function runtimeApplicationSlug(platform, project, ref, environment) {
|
|
70
|
+
if (!isGuid(ref))
|
|
71
|
+
return ref;
|
|
72
|
+
const slug = await environmentApplicationSlug(platform, ref, { project, environment });
|
|
73
|
+
if (!slug)
|
|
74
|
+
throw new PlatformApiError(`Uygulama ${ref} bulunamadı: id'yi application_list ile doğrulayın.`, 404);
|
|
75
|
+
return slug;
|
|
76
|
+
}
|
|
61
77
|
/**
|
|
62
78
|
* G-10: `index` taşıyan add/move op'u varsa sayfa okunur ve `flexIndexOrder` flex kapta görünen
|
|
63
79
|
* sırayı konumlara çevirir. Okuma başarısız olursa op'lar olduğu gibi gider — bugünkü davranış.
|
|
@@ -181,7 +197,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
181
197
|
inputSchema: {
|
|
182
198
|
project: projectParam,
|
|
183
199
|
environment: environmentParam,
|
|
184
|
-
applicationSlug:
|
|
200
|
+
applicationSlug: runtimeApplicationParam,
|
|
185
201
|
pageSlug: z.string().min(1).describe("Sayfanın slug'ı (page_list)"),
|
|
186
202
|
view: z.enum(['summary', 'full']).optional().describe('summary (varsayılan): şema/layout ağaçları id/name/type özetine indirgenir. full: ham gövde — büyük olabilir'),
|
|
187
203
|
resolveStyles: z.boolean().optional().describe("summary görünümünde her düğüme EFEKTİF stilini ekler: tema componentStyles → preset → düğümün kendi stili kaskadı çözülür (aibox'ta tipli stil grupları da derlenir ve TABAN katman olur). styleLayers hangi katmanın konuştuğunu söyler — 'bu kutu neden böyle görünüyor' sorusunun cevabı budur."),
|
|
@@ -190,7 +206,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
190
206
|
asUserId: asUserIdParam
|
|
191
207
|
},
|
|
192
208
|
annotations: readOnly
|
|
193
|
-
}, handle(config, async ({ project, environment, applicationSlug, pageSlug, view, resolveStyles, anonymous, asUser, asUserId }) => {
|
|
209
|
+
}, handle(config, async ({ project, environment, applicationSlug: applicationRef, pageSlug, view, resolveStyles, anonymous, asUser, asUserId }) => {
|
|
194
210
|
let response;
|
|
195
211
|
if (anonymous === true && asUser !== undefined) {
|
|
196
212
|
throw new PlatformApiError('page_runtime_get: anonymous:true ile asUser birlikte verilemez — biri kimliksiz isteği, diğeri belirli bir üyenin kimliğiyle atılan isteği ister.');
|
|
@@ -199,6 +215,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
199
215
|
// yapısal olarak geçemiyordu. Kimlik artık iki kanalda birden gider (Y-02).
|
|
200
216
|
const eye = await impersonationHeaders(platform, 'page_runtime_get', project, environment, { asUserId, asUser, anonymous }, { runtime: true });
|
|
201
217
|
const identity = asUser === undefined ? (eye ? { headers: eye } : {}) : { headers: { authorization: `Bearer ${await resolveIdentity(asUser, platform, project, environment)}` } };
|
|
218
|
+
const applicationSlug = await runtimeApplicationSlug(platform, project, applicationRef, environment);
|
|
202
219
|
try {
|
|
203
220
|
response = await platform.tenant('GET', project, `/${applicationSlug}/p/${pageSlug}`, { environment, anonymous, mirrorAuthorizationCookie: true, ...identity });
|
|
204
221
|
}
|
|
@@ -210,7 +227,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
210
227
|
status: error.status,
|
|
211
228
|
message: error.message,
|
|
212
229
|
...(decision ? { accessDecision: decision } : {}),
|
|
213
|
-
note: "Sayfaya bağlı middleware isteği reddetti ya da kayıt bulunamadı. Korumalı sayfada kimliksiz/yetkisiz isteğin reddedilmesi BEKLENEN davranıştır. Sayfanın bağlarını page_get ile, middleware tarifini platform_guide('middleware') ile kontrol edin; slug'ları page_list/application_list doğrular."
|
|
230
|
+
note: "Sayfaya bağlı middleware isteği reddetti ya da kayıt bulunamadı. Korumalı sayfada kimliksiz/yetkisiz isteğin reddedilmesi BEKLENEN davranıştır. Sayfanın bağlarını page_get ile, middleware tarifini platform_guide('middleware') ile kontrol edin; slug'ları page_list/application_list doğrular — application_list development slug'ını verir; production ya da dalda applicationSlug yerine uygulama id'sini verin (APP-002)."
|
|
214
231
|
};
|
|
215
232
|
}
|
|
216
233
|
throw error;
|
|
@@ -272,11 +289,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
272
289
|
const maxInlinePngBytes = 5 * 1024 * 1024;
|
|
273
290
|
server.registerTool('page_screenshot', {
|
|
274
291
|
title: 'Sayfanın ekran görüntüsünü al',
|
|
275
|
-
description: "ÖNCE KARESİZ DENE: 'kart göründü mü, tablo doldu mu, hata var mı' gibi olgu sorularında image:'none' verin — kare çekilmez, yanıttaki summaryText her kök düğümün görünürlüğünü, metnini, satır sayısını, açık sekmesini ve hata mesajlarını söyler (bir tam sayfa karesinin yüzlerce katı küçük). Kareyi yalnız görsel soru (renk, hizalama, referansa sadakat) için alın ve küçültün: clip ile bölge, scale:0.5 ile yarı çözünürlük; aynı sayfayı yeniden çekerken image:'ifChanged'. Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Bir komponent prop'unun İFADESİ patlarsa (Studio'da 'Expression error' toast'ı) yanıt expressionErrors taşır: {elementId, prop, expression, message, count}, öğe başına en çok iki; aynı satırın konsol kopyası consoleErrors'a düşmez. Sayfa ya da bir bileşen ÇİZİLEMEZSE neden yanıttadır: mobilde çöken düğüm renderErrors ({elementId, message}; sayfanın geri kalanı çizilir), yakalanmamış istisna pageErrors, konsol hatası consoleErrors — kare alınamadıysa bütün nedenler hata metninde. KARE DONUKTUR: tarayıcı bağlamı 'hareketi azalt' tercihiyle açılır; mobil runtime bileşen hareketini (giriş, döngü, geçiş, iskelet parıltısı, yükleme göstergesi) durdurur ve aynı sayfanın iki karesi tekrarlanabilir. Web sayfasında yalnız CSS'i prefers-reduced-motion'a uyan animasyonlar durur. Hareketin kendisini görmek için motion:'play'. Kare DİLİ ve TEMASI seçilebilir: 'language' adrese ?lang ekler (çeviriye bağlı prop'lar da çok dilli alanların satır değerleri de o dilde çözülür), 'colorScheme' ziyaretçinin tema tercihini sayfa açılmadan eker — ikisi de sayfaya dil değiştirici ya da theme-toggle koymayı GEREKTİRMEZ, yani referansa sadık bir tasarımın ikinci dili ve koyu teması bozulmadan kanıtlanır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir. DAL ORTAMI DA GÖRÜNTÜLENİR (2026-09-09'dan beri): 'development' /dev, 'production' /view, dal ise /branch/{dal} şeridinden açılır ve sayfanın BÜTÜN istekleri (şema da akışlar da) o dala gider — dalda kurulan sayfa artık merge'i beklemeden gözle doğrulanır (platform_guide('merge') §Dalda görsel doğrulama).",
|
|
292
|
+
description: "Telefonda (mağaza kabuğu ya da oynatıcı) düşen akış hatasını bu araç görmez — onu mobile_error_list okur. ÖNCE KARESİZ DENE: 'kart göründü mü, tablo doldu mu, hata var mı' gibi olgu sorularında image:'none' verin — kare çekilmez, yanıttaki summaryText her kök düğümün görünürlüğünü, metnini, satır sayısını, açık sekmesini ve hata mesajlarını söyler (bir tam sayfa karesinin yüzlerce katı küçük). Kareyi yalnız görsel soru (renk, hizalama, referansa sadakat) için alın ve küçültün: clip ile bölge, scale:0.5 ile yarı çözünürlük; aynı sayfayı yeniden çekerken image:'ifChanged'. Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Bir komponent prop'unun İFADESİ patlarsa (Studio'da 'Expression error' toast'ı) yanıt expressionErrors taşır: {elementId, prop, expression, message, count}, öğe başına en çok iki; aynı satırın konsol kopyası consoleErrors'a düşmez. Sayfa ya da bir bileşen ÇİZİLEMEZSE neden yanıttadır: mobilde çöken düğüm renderErrors ({elementId, message}; sayfanın geri kalanı çizilir), yakalanmamış istisna pageErrors, konsol hatası consoleErrors — kare alınamadıysa bütün nedenler hata metninde. KARE DONUKTUR: tarayıcı bağlamı 'hareketi azalt' tercihiyle açılır; mobil runtime bileşen hareketini (giriş, döngü, geçiş, iskelet parıltısı, yükleme göstergesi) durdurur ve aynı sayfanın iki karesi tekrarlanabilir. Web sayfasında yalnız CSS'i prefers-reduced-motion'a uyan animasyonlar durur. Hareketin kendisini görmek için motion:'play'. Kare DİLİ ve TEMASI seçilebilir: 'language' adrese ?lang ekler (çeviriye bağlı prop'lar da çok dilli alanların satır değerleri de o dilde çözülür), 'colorScheme' ziyaretçinin tema tercihini sayfa açılmadan eker — ikisi de sayfaya dil değiştirici ya da theme-toggle koymayı GEREKTİRMEZ, yani referansa sadık bir tasarımın ikinci dili ve koyu teması bozulmadan kanıtlanır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir. DAL ORTAMI DA GÖRÜNTÜLENİR (2026-09-09'dan beri): 'development' /dev, 'production' /view, dal ise /branch/{dal} şeridinden açılır ve sayfanın BÜTÜN istekleri (şema da akışlar da) o dala gider — dalda kurulan sayfa artık merge'i beklemeden gözle doğrulanır (platform_guide('merge') §Dalda görsel doğrulama). Production ve dalda uygulamanın slug'ı o ortamın kendi kaydıdır (dağıtıma/açılışa kadar eski slug; application_list development'ı döner): applicationSlug'a uygulama id'sini verin, gateway ortamın slug'ını okur (APP-002).",
|
|
276
293
|
inputSchema: {
|
|
277
294
|
project: projectParam,
|
|
278
295
|
environment: environmentParam,
|
|
279
|
-
applicationSlug:
|
|
296
|
+
applicationSlug: runtimeApplicationParam,
|
|
280
297
|
pageSlug: z.string().min(1).describe("Sayfanın slug'ı (page_list); sayfa parametresi sorguyla verilebilir: 'ilac-detay?id=7' (language '&lang=' olarak eklenir)"),
|
|
281
298
|
viewport: z.object({
|
|
282
299
|
width: z.number().int().min(320).max(3840),
|
|
@@ -300,14 +317,16 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
300
317
|
layoutReport: z.boolean().optional().describe("true: kareyle birlikte her runtime düğümünün efektif piksel kutusunu ve özetini (taşan / viewport dışı / sıfır boyutlu elementId'ler) döner — taşma ve çakışmayı görüntüsüz, sayısal yakalar. Mobil uygulamada özet ayrıca layoutSummary.alignment taşır: gutter (ekran kenarı boşluğu), leftEdges (aynı kapsamdaki geniş içeriğin sol kenar kümeleri — sözleşmede tek küme), surfaces (iç hizası bozuk yüzey kapları), widthOutliers (sağ kenarı sapan geniş öğeler, içeri itilme dp'si), offScaleGaps (0/4/8/16/24/32 dışı dikey aralıklar). Yan yana dizilen, ekran dışında kalan ve akış dışı (app-bar, fab, modal…) öğeler kıyasa girmez. Düğümler data-element-id taşımıyorsa layoutReportUnavailable döner."),
|
|
301
318
|
cookies: z.record(z.string()).optional().describe("Tarayıcıya enjekte edilecek EK çerezler (ad → değer): uygulamanın kendi oturum çerezi buraya verilir. Aynı ada sahip çerez gateway jetonunun yerine geçer. anonymous ile birlikte verilemez."),
|
|
302
319
|
actions: z.array(z.object({
|
|
303
|
-
action: z.enum(['set', 'click', 'wait', 'drag', 'swipe', 'tab', 'select', 'submit']),
|
|
304
|
-
target: z.string().min(1).optional().describe("Hedefin elementId'si (page_schema_get); runtime düğümü data-element-id ile bulunur. 'drag' için sürüklenen öğe (ör. 'kb_board__card-42', 'cal_plan__event-7'); 'swipe' için jestin basıldığı öğe (ör. tutamaç 'lst__row-2__handle', satır 'lst__row-3'); 'tab' için sekme GRUBUNUN kendisi ('tabs_settings'), 'select' için açılır kutu, 'submit' için form komponenti"),
|
|
320
|
+
action: z.enum(['set', 'click', 'wait', 'drag', 'swipe', 'tab', 'select', 'submit', 'network']),
|
|
321
|
+
target: z.string().min(1).optional().describe("Hedefin elementId'si (page_schema_get); runtime düğümü data-element-id ile bulunur. 'drag' için sürüklenen öğe (ör. 'kb_board__card-42', 'cal_plan__event-7'); 'swipe' için jestin basıldığı öğe (ör. tutamaç 'lst__row-2__handle', satır 'lst__row-3'); 'tab' için sekme GRUBUNUN kendisi ('tabs_settings'), 'select' için açılır kutu, 'submit' için form komponenti. Önizlemenin kendi pencereleri de hedeftir: confirm/alert adımı 'preview-dialog__confirm' / 'preview-dialog__cancel', biometric-auth 'preview-biometric__verify' / '__cancel' — pencereyi açan eylemin raporu pageState 'awaiting-user' der"),
|
|
305
322
|
to: z.string().min(1).optional().describe("'drag' ve 'swipe': bırakma hedefinin elementId'si ('swipe'ta jest hedefin içinde, hareket yönündeki kenarına 2 px kala biter — sıralamada satır tam hedefin yerine iner; dx/dy ile birlikte verilmez) — kanban kolonu '<elementId>__col-<value>', takvim günü '<elementId>__day-<yyyy-MM-dd>', tüm gün hücresi '<elementId>__allday-<yyyy-MM-dd>', saat dilimi '<elementId>__slot-<yyyy-MM-dd>T<HH>'"),
|
|
306
323
|
value: z.unknown().optional().describe("'set' için yazılacak değer. Tarih kutularına ISO yazın ('2026-08-01'); çok alanlı tarih aralığında kök DEĞİL kutu hedeflenir: '<elementId>__start' / '<elementId>__end'. 'tab' için sekmenin ANAHTARI ('iot') ya da görünen metni; 'select' için seçeneğin value'su ya da görünen metni"),
|
|
307
324
|
ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi"),
|
|
308
325
|
dx: z.number().optional().describe("Yalnız 'swipe': jestin yatay kayması (CSS pikseli; sola kaydırma eksi). to ile birlikte verilmez"),
|
|
309
|
-
dy: z.number().optional().describe("Yalnız 'swipe': jestin dikey kayması (CSS pikseli; yukarı eksi). to ile birlikte verilmez")
|
|
310
|
-
|
|
326
|
+
dy: z.number().optional().describe("Yalnız 'swipe': jestin dikey kayması (CSS pikseli; yukarı eksi). to ile birlikte verilmez"),
|
|
327
|
+
blur: z.boolean().optional().describe("Yalnız 'set': false verilirse odak alanda KALIR (telefonda klavye açıkken yazılan hâl) — input+change atılır, blur/focusout atılmaz; odak kaybına bağlı akış ('changed') bu adımda KOŞMAZ. Varsayılan true: gerçek kullanıcı gibi yazıp alandan çıkar")
|
|
328
|
+
})).max(20).optional().describe("Kare ALINMADAN ÖNCE sayfada koşturulacak etkileşim adımları: [{action:'set', target:'flt_month', value:'2022-10'}, {action:'click', target:'btn_sign_in'}, {action:'tab', target:'tabs_settings', value:'iot'}, {action:'select', target:'dd_city', value:'34'}, {action:'submit', target:'frm_note'}, {action:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}]. 'tab' SEKME SEÇER: sekme grubunun köküne 'click' göndermek DOM sırasındaki İLK sekmeye gider ve ikinci sekme hiç açılamıyordu — tablist köküne click artık reddedilir ve bu fiile yönlendirir. Eşleme önce '<elementId>__tab-<sekme anahtarı>' kimliğiyle, o yoksa görünen metinle yapılır; actionsReport.matchedBy hangisi olduğunu, actionsReport.activeTab adımdan SONRA hangi sekmenin açık olduğunu söyler. Etkin olmayan sekmenin içeriği DOM'da YOKTUR: o sekmedeki bir düğüme yazmadan önce 'tab' adımı gerekir. 'select' açılır kutudan seçer — kutu native alan değildir, değeri yazmak hiçbir şey yapmaz, seçeneğe tıklanması gerekir; liste 3 sn'ye kadar YOKLANIR (sorgudan beslenen kutuda seçenekler geç basılır) ve actionsReport.optionsSeen + waitedMs neyin görüldüğünü söyler. Autocomplete'te 'set' metni alana yazar, 'select' metni yazıp öneri satırını GÖRÜNEN METİNLE seçer (value ile değil; eşleşme yoksa yazılan metin alanda kalır ve note bunu söyler). 'submit' formu gönder butonu OLMADAN gönderir (form.requestSubmit); actionsReport.submitVia hangi yolun kullanıldığını yazar. 'drag' HTML5 sürükle-bırak olaylarını (dragstart→dragover→drop→dragend) hedefin merkezinde atar; kanban cardMove ve takvim eventMove bununla tetiklenir. 'swipe' DOKUNMATİK jesttir (mobil): hedefin merkezine basar, 12 adımda 16 ms arayla taşır ve bırakır (pointer + mouse olayları; sıfır kayma reddedilir — dokunuş 'click'tir) — tutamaçtan sıralama {action:'swipe', target:'lst__row-2__handle', to:'lst__row-0'}, satır kaydırma eylemi {action:'swipe', target:'lst__row-1', dx:-160, dy:0}; 'drag' web'in HTML5 sürüklemesi, 'swipe' dokunmatik jest. 'network' bağlamın AĞINI keser ya da açar ({action:'network', value:'offline'} → ağsız yaz/kaydet → {action:'network', value:'online'}): sayfanın tamamı etkilenir, hedef almaz; actionsReport.online sayfanın gördüğü navigator.onLine'dır, ağ açılınca koşan eşitleme akışları flowRuns'a yazılır. Çağrının sonunda ağ yeniden açılır; ağsız davranışı iki çağrı arasında sürdürmek için session ile depo taşınır, ağ taşınmaz. 'set' + blur:false odağı alanda bırakır (telefonda klavye açıkken). 'set' native setter kullanır ve input+change olaylarını bubbles ile atar (düz DOM ataması framework'ün duyduğu olayı üretmez); ardından kök düğümün runtime prop'larını yeniden okur: actionsReport.propWritten değerin prop'a ULAŞIP ulaşmadığıdır ve false ise applied da false döner — DOM'a yazılmış ama bileşenin duymadığı değer 'uygulandı' sayılmaz (G-14). Her adımdan sonra yerleşme beklenir ve yanıttaki actionsReport hedefin bulunup bulunmadığını ve adımdan sonra KAÇ AĞ İSTEĞİ çıktığını söyler — 'bağ kuruldu' ile 'çalışıyor' arasındaki farkı ancak bu ayırır (G-24)."),
|
|
329
|
+
session: z.string().regex(/^[a-z0-9-]{1,40}$/).optional().describe("Çağrılar arası tarayıcı oturumu (kısa ad, [a-z0-9-]{1,40}): verilirse çekim sonunda çerezler, localStorage ve IndexedDB gateway BELLEĞİNDE saklanır ve aynı adla gelen sonraki çekim o depoyla açılır — kalıcı state.app ve çevrimdışı servis kuyruğu ikinci çağrıda okunur. Yanıttaki sessionRestored önceki depo yüklendi mi söyler (ilk çağrıda false). Kimlik ve adres anahtara dahildir: aynı adla farklı kullanıcı (jetonun sub'ı; yenilenen jeton aynı oturumdur), asUserId, cookies, uygulama ya da ortam ayrı oturumdur. Depo okunamazsa oturum silinir ve yanıt sessionSaved: false der. Ömür 30 dk, en çok 8 oturum (en eskisi düşer); gateway yeniden başlayınca silinir. sessionStorage (güvenli depo) taşınmaz. Ağ durumu taşınmaz: her çekim ağ AÇIK başlar. Verilmezse her çekim boş depoyla başlar (bugünkü yalıtım)."),
|
|
311
330
|
language: z.string().min(1).optional().describe("Kareyi BU DİLDE çeker: adrese '?lang=<kod>' eklenir ve runtime dili oradan seçer (öncelik: ?lang → tarayıcıda kayıtlı seçim → tarayıcı dilleri → projenin varsayılanı). Seçim hem çeviriye bağlı prop'ları hem çok dilli alanların satır değerlerini kapsar, yani ikinci dilin karesi için sayfaya dil değiştirici KOYMAK GEREKMEZ — referans tasarımı bozulmadan ikinci dil kanıtlanır. ISO 639-1 iki harfli kod ('tr', 'en'); bölgeli kod ('tr-TR') indirgenir ve indirgeme yanıtta bildirilir. Kod projenin languages listesinde yoksa çağrı REDDEDİLİR: runtime tanımadığı kodu yok sayıp kareyi varsayılan dilde çizerdi ve 'çevrildi' sanılan yanlış bir kare raporlanırdı. MOBİL uygulamada da geçerlidir (/preview aynı ?lang'i okur); cihazın kendisinde ?lang yoktur (platform_guide('mobile') §Çok dil)."),
|
|
312
331
|
colorScheme: z.enum(['light', 'dark']).optional().describe("Kareyi açık ya da koyu temada çeker. Ziyaretçi tema tercihi çerezde değil localStorage'da yaşar ve tema store'u onu sayfa açılırken bir kez okur; gateway tercihi sayfa script'lerinden ÖNCE eker, bu yüzden sayfada 'theme-toggle' komponenti OLMASA DA koyu tema karesi alınabilir. Verilmezse projenin kendi modu çizilir."),
|
|
313
332
|
motion: z.enum(['frozen', 'play']).optional().describe("Varsayılan 'frozen': bağlam 'hareketi azalt' (prefers-reduced-motion: reduce) ile açılır; mobil runtime hareketi dondurur ve kare tekrarlanabilir olur — diff'in imageUnchanged'ı ancak böyle anlamlıdır. Web sayfasında yalnız bu tercihe uyan CSS animasyonları durur. 'play': hareket oynar (giriş animasyonu, dönen gösterge, iskelet parıltısı); kare çekildiği ana bağlıdır, iki kare bayt bayt aynı çıkmayabilir ve yanıt bunu motionNote ile söyler. diff belleği iki kipi ayrı tutar."),
|
|
@@ -324,7 +343,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
324
343
|
outPath: z.string().min(1).optional().describe('Verilirse PNG bu yerel yola yazılır ve inline görüntü DÖNMEZ; yalnız özet (yol, boyut) döner — büyük fullPage kareleri için')
|
|
325
344
|
},
|
|
326
345
|
annotations: readOnly
|
|
327
|
-
}, async ({ project, environment, applicationSlug, pageSlug, viewport, device, fullPage, delayMs, captureUnready, anonymous, asUser, asUserId, cookies, layoutReport, actions, outPath, expectFailure, language, colorScheme, motion, summary, image, format, quality, scale, clip, diff }) => {
|
|
346
|
+
}, async ({ project, environment, applicationSlug: applicationRef, pageSlug, viewport, device, fullPage, delayMs, captureUnready, anonymous, asUser, asUserId, cookies, layoutReport, actions, outPath, expectFailure, language, colorScheme, motion, summary, image, format, quality, scale, clip, diff, session }) => {
|
|
328
347
|
try {
|
|
329
348
|
if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
|
|
330
349
|
throw new PlatformApiError("page_screenshot: anonymous:true ile asUser/cookies birlikte verilemez — biri kimliksiz kareyi, diğeri belirli bir kimlikle çekilen kareyi ister. Anonim kare için yalnız anonymous:true, üye kimliğiyle kare için yalnız asUser (ve gerekiyorsa cookies) gönderin.");
|
|
@@ -353,6 +372,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
353
372
|
if (stillSwipe >= 0) {
|
|
354
373
|
throw new PlatformApiError(`page_screenshot: actions[${stillSwipe}] 'swipe' adımının kayması sıfır — yerinde jest bir basıştır; dokunuş için {action:'click', target:'${actions?.[stillSwipe]?.target}'} kullanın.`);
|
|
355
374
|
}
|
|
375
|
+
const badNetwork = actions?.findIndex(step => step.action === 'network' && (step.value !== 'offline' && step.value !== 'online' || step.target !== undefined || step.to !== undefined)) ?? -1;
|
|
376
|
+
if (badNetwork >= 0) {
|
|
377
|
+
throw new PlatformApiError(`page_screenshot: actions[${badNetwork}] 'network' adımı yalnız value: 'offline' | 'online' alır, hedef almaz — bağlamın tamamının ağını keser/açar. Ör. {action:'network', value:'offline'}.`);
|
|
378
|
+
}
|
|
379
|
+
const misplacedBlur = actions?.findIndex(step => step.blur !== undefined && step.action !== 'set') ?? -1;
|
|
380
|
+
if (misplacedBlur >= 0) {
|
|
381
|
+
throw new PlatformApiError(`page_screenshot: actions[${misplacedBlur}] '${actions?.[misplacedBlur]?.action}' adımı blur almaz — odakta kalma yalnız 'set'in seçeneğidir.`);
|
|
382
|
+
}
|
|
356
383
|
if (!config.studioBaseUrl) {
|
|
357
384
|
throw new PlatformApiError("page_screenshot için STUDIO_BASE_URL tanımlı değil. Studio arayüzünün kök adresini .env'e ekleyin (yerel geliştirmede http://localhost:3000, dağıtımda nginx adresi).");
|
|
358
385
|
}
|
|
@@ -388,6 +415,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
388
415
|
// 37 S-17 / 38 S-13: sorgulu slug (`ilac-detay?id=7`) dizeyle birleşince dil ikinci bir '?' ile
|
|
389
416
|
// ekleniyordu ve runtime `id`'yi '7?lang=tr' okudu; sayfa isteği işareti de sorguyu taşıdığı
|
|
390
417
|
// için sayfanın kendi veri isteğiyle hiç eşleşmiyordu.
|
|
418
|
+
const applicationSlug = await runtimeApplicationSlug(platform, project, applicationRef, environment);
|
|
391
419
|
const address = new URL(`${config.studioBaseUrl}/${lane}/${projectSlug}/${applicationSlug}/${pageSlug}`);
|
|
392
420
|
if (languageChoice)
|
|
393
421
|
address.searchParams.set('lang', languageChoice.code);
|
|
@@ -395,7 +423,8 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
395
423
|
const pagePath = pageSlug.split(/[?#]/, 1)[0];
|
|
396
424
|
// Hedef ve cihaz uygulamanın platform profilinden (G3): mobil uygulama react-native-web
|
|
397
425
|
// çizimidir, varsayılan kare telefon ön ayarıdır. Tip okunamazsa web davranışı aynen.
|
|
398
|
-
|
|
426
|
+
// Eski production slug'ı Core'da yoktur: id verildiyse tip id'den okunur, slug'la arama web'e düşerdi (APP-002).
|
|
427
|
+
const resolved = isGuid(applicationRef) ? await resolveProfile(platform, applicationRef) : await resolveProfileBySlug(platform, projectSlug, applicationSlug);
|
|
399
428
|
const target = captureTargetFor(resolved.profile.capture.target);
|
|
400
429
|
const mobile = resolved.profile.platform === 'mobile';
|
|
401
430
|
if (device !== undefined && !mobile) {
|
|
@@ -439,7 +468,27 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
439
468
|
...(clip === undefined ? {} : { clip: clip }),
|
|
440
469
|
...(actions === undefined ? {} : { actions: actions }),
|
|
441
470
|
...(mobile ? { target } : {}),
|
|
442
|
-
...(dpr === undefined ? {} : { dpr })
|
|
471
|
+
...(dpr === undefined ? {} : { dpr }),
|
|
472
|
+
// Oturum kimlikle ve adresle ayrılır: jetonun öznesi (`sub`; yenilenen gateway jetonu oturumu düşürmesin, yoksa
|
|
473
|
+
// jetonun kendisi), asUserId, çerez değerleri, proje, uygulama ve ortam anahtara hash'lenerek girer.
|
|
474
|
+
...(session === undefined ? {} : {
|
|
475
|
+
session: `${session}|${captureKey({
|
|
476
|
+
identity,
|
|
477
|
+
principal: `${(() => {
|
|
478
|
+
try {
|
|
479
|
+
const claims = JSON.parse(Buffer.from(String(identityToken ?? '').split('.')[1] ?? '', 'base64url').toString('utf8'));
|
|
480
|
+
return typeof claims.sub === 'string' ? `sub:${claims.sub}` : identityToken ?? '';
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
return identityToken ?? '';
|
|
484
|
+
}
|
|
485
|
+
})()}|${asUserId ?? ''}`,
|
|
486
|
+
cookieValues: extraCookies.map(cookie => `${cookie.name}=${cookie.value}`),
|
|
487
|
+
project: projectSlug,
|
|
488
|
+
applicationSlug,
|
|
489
|
+
environment: resolvedEnvironment
|
|
490
|
+
})}`
|
|
491
|
+
})
|
|
443
492
|
});
|
|
444
493
|
// Fark ÖZETTEN çıkar; `summary:false` verilse de fark için toplanmıştır (yanıta girmez).
|
|
445
494
|
// Anahtar KİMLİĞİ ayırır (K8): 'asUser' etiketi yetmez — iki farklı üyenin jetonu aynı
|
|
@@ -494,7 +543,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
494
543
|
const emptyOptions = actionsOf(outcome => outcome.action === 'select' && outcome.optionsSeen === 0);
|
|
495
544
|
const unwritten = actionsOf(outcome => outcome.propWritten === false);
|
|
496
545
|
// 'wait' bir eylem değil bekleyiştir: akış sayacı ona bağlanmaz, notu tetiklemez (33/S-1).
|
|
497
|
-
const silent = actionsOf(outcome => outcome.action !== 'wait' && outcome.applied && outcome.requests === 0 && (outcome.flowRuns ?? 0) === 0);
|
|
546
|
+
const silent = actionsOf(outcome => outcome.action !== 'wait' && outcome.action !== 'network' && outcome.applied && outcome.requests === 0 && (outcome.flowRuns ?? 0) === 0);
|
|
498
547
|
if (missing.length > 0)
|
|
499
548
|
actionNotes.push(`Bulunamayan hedef var: ${missing.map(outcome => outcome.target ?? '?').join(' · ')}. elementId'leri page_schema_get ile doğrulayın; runtime düğümü data-element-id ile bulunuyor.`);
|
|
500
549
|
if (textMatchedTabs.length > 0)
|
|
@@ -521,6 +570,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
521
570
|
fullPage: fullPage === true,
|
|
522
571
|
identity,
|
|
523
572
|
cookieNames,
|
|
573
|
+
...(session === undefined ? {} : { session, sessionRestored: shot.sessionRestored === true, ...(shot.sessionSaved === false ? { sessionSaved: false, sessionNote: 'Depo durumu okunamadı: oturum SİLİNDİ, aynı adla sonraki çekim boş depoyla başlar (sessionRestored: false).' } : {}) }),
|
|
524
574
|
...(shot.png === undefined
|
|
525
575
|
? { image: 'none' }
|
|
526
576
|
: {
|
|
@@ -582,7 +632,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
582
632
|
...(shot.pageState === 'ready-no-flows'
|
|
583
633
|
? { pageStateNote: 'Runtime çizildi ama hiç mount akışı koşmadı. Sayfa akışsızsa (giriş ekranı) ya da verisi veri kaynağı bağlantısından geliyorsa (connectData prop akış koşturmadan okunur; summaryText satırları gösterir) normaldir; veri bekliyor ve göremiyorsanız page_schema_get ile mount olayına bağlı akışa, bağlı prop için connect_data_get / connect_data_execute (op list) ile bağlantıya bakın.' }
|
|
584
634
|
: shot.pageState === 'awaiting-user'
|
|
585
|
-
? { pageStateNote:
|
|
635
|
+
? { pageStateNote: awaitingUserNote(shot.awaitingWindow) }
|
|
586
636
|
: shot.pageState === 'ready-no-counter'
|
|
587
637
|
? { pageStateNote: 'Runtime koşan akış sayacı yayınlamıyor (eski runtime); hazırlık DOM durulmasından çıkarıldı, akışların bittiği kanıtlanamadı.' }
|
|
588
638
|
: shot.pageState === 'error'
|
|
@@ -816,6 +866,9 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
816
866
|
for (const entry of opRecords)
|
|
817
867
|
reject('PAGE-008', entry, unknownEventOps([entry], typesByElementId, eventsBySlug));
|
|
818
868
|
}
|
|
869
|
+
// g-21: tanınmayan prop platformda bütün yamayı `unknown-prop` ile düşürür; op'u adıyla burada söyleriz.
|
|
870
|
+
for (const issue of await unknownPropOps(platform, project, environment, applicationId, async () => (await readPage()).schema, allOps))
|
|
871
|
+
reject('unknown-prop', issue.op, [issue.message]);
|
|
819
872
|
if (rejected.length > 0)
|
|
820
873
|
throw new PlatformApiError(rejectedOpsMessage(rejected, batchIndexHint(opened)), 400);
|
|
821
874
|
// TR-005 / TR-006: yamanın yükü op'ların EZDİĞİ kayıtlı değerle karşılaştırılır — orada zaten duran kanca eskidir.
|
|
@@ -102,7 +102,7 @@ export function registerPlatformTools(server, platform) {
|
|
|
102
102
|
'Modülün iş kuralları rehberini döner. Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun:',
|
|
103
103
|
...guides.map(name => `- ${name}${guideRouting[name] ? `: ${guideRouting[name]}` : ''}`),
|
|
104
104
|
"Büyük rehberler (page ~120 KB) section'sız çağrıda TAM METİN DÖNMEZ: giriş + bölüm listesi (outline) döner, truncated: true olur. Gövdeyi section ile bölüm bölüm alın.",
|
|
105
|
-
"Büyük gövdeyi (sayfa şeması, akış adımları, uç/workflow tanımı, patch op'ları) dosyaya yazıp bodyFile ile verin: gövde bağlamınızdan geçmez, satır içiyle aynı doğrulamadan geçer (page_*/flow_*/microservice_*/workflow_* create/update/patch). microservice_execute yanıt başlıkları varsayılan kısadır; ucun kendi başlığı için includeHeaders:'all'.",
|
|
105
|
+
"Büyük gövdeyi (sayfa şeması, akış adımları, uç/workflow tanımı, patch op'ları) dosyaya yazıp bodyFile ile verin: gövde bağlamınızdan geçmez, satır içiyle aynı doğrulamadan geçer (page_*/flow_*/microservice_*/workflow_* create/update/patch). Uzun kod alanı ayrı dosyada durabilir: gövdede { \"$file\": \"./adim.js\" } o dosyanın METNİYLE değişir (çalıştırılmaz; yol bodyFile'ın klasörüne göre, GW-013). microservice_execute yanıt başlıkları varsayılan kısadır; ucun kendi başlığı için includeHeaders:'all'.",
|
|
106
106
|
"Örnek gövdeler ve kural katalogları (ngapps://examples/<ad>, ngapps://rules/<ad>) istemci MCP resource okuyamıyorsa da bu araçla okunur: platform_guide({ resource: 'ngapps://rules/connect-data' }) — guide verilmez; dizin için 'ngapps://examples/index'."
|
|
107
107
|
].join('\n');
|
|
108
108
|
server.registerTool('platform_whoami', {
|