@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7

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.
Files changed (145) hide show
  1. package/.env.example +2 -2
  2. package/.env.production.example +9 -1
  3. package/README.md +14 -38
  4. package/dist/config.js +36 -3
  5. package/dist/knowledge/backendConstraints.js +982 -0
  6. package/dist/knowledge/componentCatalog.js +549 -92
  7. package/dist/knowledge/componentEvents.js +120 -25
  8. package/dist/knowledge/coverage.js +43 -0
  9. package/dist/knowledge/flowGuards.js +30 -0
  10. package/dist/knowledge/functionGuards.js +134 -0
  11. package/dist/knowledge/guideSections.js +49 -0
  12. package/dist/knowledge/hydrate.js +97 -0
  13. package/dist/knowledge/languageCodes.js +36 -0
  14. package/dist/knowledge/languages.js +105 -0
  15. package/dist/knowledge/layoutSummary.js +10 -0
  16. package/dist/knowledge/localization.js +51 -0
  17. package/dist/knowledge/normalize.js +500 -16
  18. package/dist/knowledge/outlines.js +167 -12
  19. package/dist/knowledge/policy.js +14 -5
  20. package/dist/knowledge/propTypes.js +80 -0
  21. package/dist/knowledge/rules.js +961 -44
  22. package/dist/knowledge/schemaGuard.js +199 -0
  23. package/dist/knowledge/styleResolve.js +63 -4
  24. package/dist/observability/trace.js +27 -3
  25. package/dist/platform/csv.js +123 -0
  26. package/dist/platform/errors.js +107 -6
  27. package/dist/platform/http.js +34 -4
  28. package/dist/platform/patch.js +27 -0
  29. package/dist/platform/refs.js +104 -0
  30. package/dist/platform/responses.js +25 -6
  31. package/dist/platform/screenshot.js +544 -16
  32. package/dist/platform/secrets.js +40 -0
  33. package/dist/platform/tokenVault.js +89 -0
  34. package/dist/platform/writeDiff.js +281 -0
  35. package/dist/resources/examples/flow-call-function.json +66 -0
  36. package/dist/resources/examples/function-total.json +70 -0
  37. package/dist/resources/examples/layout-app-shell.json +1 -1
  38. package/dist/resources/examples/microfunction-node-sum.json +56 -0
  39. package/dist/resources/examples/microservice-table-list.json +2 -4
  40. package/dist/resources/examples/page-dynamic-form.json +3 -3
  41. package/dist/resources/examples/page-flex-sections.json +99 -0
  42. package/dist/resources/examples/page-sign-in.json +4 -3
  43. package/dist/resources/guides/component.md +81 -8
  44. package/dist/resources/guides/entity.md +712 -20
  45. package/dist/resources/guides/expressions.md +47 -2
  46. package/dist/resources/guides/external-component.md +9 -0
  47. package/dist/resources/guides/functions.md +262 -0
  48. package/dist/resources/guides/getting-started.md +82 -7
  49. package/dist/resources/guides/layout.md +53 -10
  50. package/dist/resources/guides/merge.md +231 -0
  51. package/dist/resources/guides/microfunction.md +166 -0
  52. package/dist/resources/guides/microservice.md +287 -15
  53. package/dist/resources/guides/middleware.md +89 -1
  54. package/dist/resources/guides/modify-existing.md +23 -0
  55. package/dist/resources/guides/page.md +402 -49
  56. package/dist/resources/guides/translation.md +150 -8
  57. package/dist/resources/guides/view.md +16 -1
  58. package/dist/resources/guides/workflow.md +199 -28
  59. package/dist/resources/knowledge.js +5 -0
  60. package/dist/resources/rules.js +27 -0
  61. package/dist/resources/schema-fingerprint.json +8 -0
  62. package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
  63. package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
  64. package/dist/resources/schemas/CreateBulkRowCommand.json +36 -0
  65. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  66. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  67. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  68. package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
  69. package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
  70. package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
  71. package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
  72. package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
  73. package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
  74. package/dist/resources/schemas/CreateMicroserviceCommand.json +145 -3
  75. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  76. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  77. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  78. package/dist/resources/schemas/CreateWorkflowCommand.json +106 -14
  79. package/dist/resources/schemas/DataJobDto.json +267 -0
  80. package/dist/resources/schemas/ExportRowsCommand.json +172 -0
  81. package/dist/resources/schemas/Filter.json +3 -1
  82. package/dist/resources/schemas/GetRowsQuery.json +6 -1
  83. package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
  84. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
  85. package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
  86. package/dist/resources/schemas/ImportPlanDto.json +260 -0
  87. package/dist/resources/schemas/ImportSampleDto.json +90 -0
  88. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
  89. package/dist/resources/schemas/PreviewMicroserviceCommand.json +135 -1
  90. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  91. package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
  92. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
  93. package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
  94. package/dist/resources/schemas/RunImportCommand.json +62 -0
  95. package/dist/resources/schemas/StepDto.json +34 -3
  96. package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
  97. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  98. package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
  99. package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
  100. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
  101. package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
  102. package/dist/resources/schemas/UpdateMicroserviceCommand.json +147 -4
  103. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  104. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  105. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  106. package/dist/resources/schemas/UpdateWorkflowCommand.json +106 -14
  107. package/dist/server.js +17 -1
  108. package/dist/tools/access.js +3 -3
  109. package/dist/tools/activities.js +40 -0
  110. package/dist/tools/applications.js +1 -1
  111. package/dist/tools/automations.js +76 -34
  112. package/dist/tools/common.js +55 -0
  113. package/dist/tools/components.js +16 -7
  114. package/dist/tools/datasources.js +83 -21
  115. package/dist/tools/dependencies.js +1 -1
  116. package/dist/tools/deployments.js +34 -2
  117. package/dist/tools/dtos.js +25 -12
  118. package/dist/tools/entities.js +113 -28
  119. package/dist/tools/environments.js +52 -0
  120. package/dist/tools/externalComponents.js +9 -3
  121. package/dist/tools/files.js +57 -42
  122. package/dist/tools/flows.js +205 -13
  123. package/dist/tools/folders.js +7 -2
  124. package/dist/tools/functions.js +331 -0
  125. package/dist/tools/globalFunctions.js +63 -0
  126. package/dist/tools/importSchemas.js +79 -0
  127. package/dist/tools/imports.js +241 -0
  128. package/dist/tools/layouts.js +103 -29
  129. package/dist/tools/merges.js +113 -0
  130. package/dist/tools/microfunctions.js +155 -0
  131. package/dist/tools/microservices.js +270 -40
  132. package/dist/tools/organizations.js +5 -6
  133. package/dist/tools/pages.js +421 -104
  134. package/dist/tools/platformInfo.js +155 -11
  135. package/dist/tools/projects.js +183 -33
  136. package/dist/tools/queries.js +32 -10
  137. package/dist/tools/revisions.js +94 -0
  138. package/dist/tools/rows.js +484 -50
  139. package/dist/tools/screenWrite.js +68 -0
  140. package/dist/tools/screens.js +28 -0
  141. package/dist/tools/templates.js +32 -5
  142. package/dist/tools/translations.js +24 -8
  143. package/dist/tools/views.js +20 -10
  144. package/dist/tools/workflows.js +56 -21
  145. package/package.json +7 -2
@@ -5,6 +5,17 @@ import { TokenProvider } from './token.js';
5
5
  import { randomUUID } from 'node:crypto';
6
6
  import { request } from 'undici';
7
7
  export const requestAuthStorage = new AsyncLocalStorage();
8
+ const hiddenResponseHeaders = new Set(['set-cookie', 'authorization', 'proxy-authorization']);
9
+ /**
10
+ * Sayfalı tablo akışları `Total-Count` header'ına bağlı ama execute ailesi yalnız gövdeyi
11
+ * döndürüyordu; sözleşme hiçbir tool'dan doğrulanamıyordu (İK koşusu M-8). Gizli header'lar
12
+ * her koşulda elenir.
13
+ */
14
+ export function responseHeadersForTool(raw) {
15
+ return Object.fromEntries(Object.entries(raw)
16
+ .map(([name, value]) => [name.toLowerCase(), Array.isArray(value) ? value.join(', ') : value])
17
+ .filter((entry) => typeof entry[1] === 'string' && !hiddenResponseHeaders.has(entry[0])));
18
+ }
8
19
  function buildMultipart(parts, boundary) {
9
20
  const chunks = [];
10
21
  for (const part of parts) {
@@ -70,6 +81,18 @@ export class Platform {
70
81
  });
71
82
  return outcome.body;
72
83
  }
84
+ /**
85
+ * `tenant` gibi davranır (4xx/5xx atar) ama yanıt başlıklarını da verir. `tenantRaw` bu iş için
86
+ * kullanılamaz: o hata durumunda atmaz, yani satır yazma yolunda 400'ü başarı gibi döndürürdü.
87
+ * Satır araçları başlığa bakmak zorunda — `X-Unmapped-Members` platformun `localizations`
88
+ * alanını tanımadığını söyleyen tek işaret (sözleşme D6, gateway eski platformda companion'a düşer).
89
+ */
90
+ async tenantWithHeaders(method, project, path, options = {}) {
91
+ return this.send('tenant', method, `/${this.resolveProject(project)}${path}`, {
92
+ ...options,
93
+ environment: this.resolveEnvironment(options.environment)
94
+ });
95
+ }
73
96
  async tenantRaw(method, project, path, options = {}) {
74
97
  return this.send('tenant', method, `/${this.resolveProject(project)}${path}`, {
75
98
  ...options,
@@ -95,12 +118,17 @@ export class Platform {
95
118
  const url = new URL(`${baseUrl}${path}`);
96
119
  appendQuery(url, options.query);
97
120
  const callerHeaders = Object.fromEntries(Object.entries(options.headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]));
121
+ const authorization = callerHeaders.authorization ?? (options.anonymous ? undefined : await this.authorizationHeader());
98
122
  const headers = {
99
123
  host: `${this.config.organization}.${this.config.hostSuffix}`,
100
124
  accept: 'application/json',
101
- ...(callerHeaders.authorization || options.anonymous ? {} : { authorization: await this.authorizationHeader() }),
125
+ ...(authorization === undefined ? {} : { authorization }),
102
126
  ...(options.environment ? { 'x-environment': options.environment } : {}),
103
- ...callerHeaders
127
+ ...callerHeaders,
128
+ 'x-client': 'mcp',
129
+ ...(options.mirrorAuthorizationCookie && authorization !== undefined && callerHeaders.cookie === undefined
130
+ ? { cookie: `NG_AccessToken=${authorization.replace(/^Bearer /, '')}` }
131
+ : {})
104
132
  };
105
133
  let body;
106
134
  if (options.multipart) {
@@ -127,6 +155,7 @@ export class Platform {
127
155
  let statusCode;
128
156
  let payload;
129
157
  let responseBytes = 0;
158
+ let responseHeaders = {};
130
159
  try {
131
160
  const response = await request(url, {
132
161
  method: method,
@@ -135,6 +164,7 @@ export class Platform {
135
164
  signal: AbortSignal.timeout(this.config.requestTimeoutMs)
136
165
  });
137
166
  statusCode = response.statusCode;
167
+ responseHeaders = responseHeadersForTool(response.headers);
138
168
  if (options.expectBinary && statusCode < 400) {
139
169
  const bytes = Buffer.from(await response.body.arrayBuffer());
140
170
  responseBytes = bytes.byteLength;
@@ -164,9 +194,9 @@ export class Platform {
164
194
  return this.send(api, method, path, options, raw, true);
165
195
  }
166
196
  if (raw)
167
- return { httpStatus: statusCode, body: payload ?? null };
197
+ return { httpStatus: statusCode, body: payload ?? null, headers: responseHeaders };
168
198
  if (statusCode >= 400)
169
199
  throw mapPlatformError(statusCode, payload, context);
170
- return { httpStatus: statusCode, body: payload ?? { success: true } };
200
+ return { httpStatus: statusCode, body: payload ?? { success: true }, headers: responseHeaders };
171
201
  }
172
202
  }
@@ -0,0 +1,27 @@
1
+ export function patchResult(response, fallback) {
2
+ const envelope = response && typeof response === 'object'
3
+ ? (response.data ?? response)
4
+ : {};
5
+ const written = envelope.data && typeof envelope.data === 'object'
6
+ ? envelope.data
7
+ : fallback;
8
+ return {
9
+ // Sayı olarak gelen bir newVersion JSON.parse sırasında ZATEN yuvarlanmıştır
10
+ // (6.4e16 > MAX_SAFE_INTEGER): geri gönderilirse her patch 409 verir, boş bırakmak dürüst.
11
+ newVersion: typeof envelope.newVersion === 'string' ? envelope.newVersion : '',
12
+ applied: typeof envelope.applied === 'number' ? envelope.applied : 0,
13
+ data: written
14
+ };
15
+ }
16
+ /**
17
+ * `baseVersion` PATCH gövdesinde ZORUNLU. Okunan kayıtta `version` yoksa platform 422 döndürür;
18
+ * ajanın hatayı gateway'e değil eksik alana bağlayabilmesi için burada erken ve açık patlıyoruz.
19
+ */
20
+ export function baseVersionOf(definition, readTool) {
21
+ const version = definition.version;
22
+ if (typeof version === 'string' && version.length > 0)
23
+ return version;
24
+ if (typeof version === 'number')
25
+ return String(version);
26
+ throw new Error(`Okunan kayıtta 'version' alanı yok; PATCH 'baseVersion' olmadan gönderilemez. ${readTool} çıktısında 'version' görünmüyorsa platform sürümü bu ucu desteklemiyordur.`);
27
+ }
@@ -0,0 +1,104 @@
1
+ import { PlatformApiError } from './errors.js';
2
+ const guidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3
+ export function isGuid(value) {
4
+ return guidPattern.test(value);
5
+ }
6
+ export class RefResolveError extends PlatformApiError {
7
+ }
8
+ /**
9
+ * Slug → id çözümü çağrı başına yapılır ve çağrılar arasında TAŞINMAZ: gateway stateless, N
10
+ * replika ile paylaşılan bir önbellek tutarsız olur. Belirsiz slug (aynı ad iki datasource'ta)
11
+ * sessizce "ilkini al" ile geçilmez — bu planın var oluş nedeni sessiz yanlışı azaltmaktı;
12
+ * adaylar listelenip `datasource/entity` biçimi istenir.
13
+ */
14
+ export class RefResolver {
15
+ platform;
16
+ project;
17
+ environment;
18
+ resolved = [];
19
+ tree;
20
+ fieldsByEntity = new Map();
21
+ constructor(platform, project, environment) {
22
+ this.platform = platform;
23
+ this.project = project;
24
+ this.environment = environment;
25
+ }
26
+ async applicationId(ref) {
27
+ if (isGuid(ref))
28
+ return ref;
29
+ const projectPayload = await this.platform.org('GET', `/p/${this.platform.resolveProject(this.project)}`);
30
+ const applicationsPayload = await this.platform.org('GET', '/applications', { query: { projectId: String(projectPayload.data?.id ?? ''), pageLength: 100 } });
31
+ const applications = applicationsPayload.data ?? [];
32
+ const found = applications.find(application => application.slug === ref);
33
+ if (!found) {
34
+ throw new RefResolveError(`'${ref}' diye bir application yok. Mevcut slug'lar: ${applications.map(application => application.slug).join(', ') || '(yok)'}`);
35
+ }
36
+ this.resolved.push(`applicationId '${ref}' → ${found.id}`);
37
+ return found.id;
38
+ }
39
+ async entity(ref) {
40
+ const tree = await this.loadTree();
41
+ const flat = tree
42
+ .filter((datasource) => Boolean(datasource) && typeof datasource === 'object')
43
+ .flatMap(datasource => (Array.isArray(datasource.entities) ? datasource.entities : [])
44
+ .filter((entity) => Boolean(entity) && typeof entity === 'object' && typeof entity.id === 'string')
45
+ .map(entity => ({ datasource, entity })));
46
+ if (isGuid(ref)) {
47
+ const byId = flat.find(({ entity }) => entity.id.toLowerCase() === ref.toLowerCase());
48
+ if (!byId)
49
+ throw new RefResolveError(`'${ref}' id'li entity ağaçta yok.`);
50
+ return { entityId: byId.entity.id, datasourceId: byId.datasource.id, slug: byId.entity.slug };
51
+ }
52
+ const separator = ref.indexOf('/');
53
+ const [datasourcePart, entityPart] = separator === -1 ? [undefined, ref] : [ref.slice(0, separator), ref.slice(separator + 1)];
54
+ const matches = flat.filter(({ datasource, entity }) => entity.slug === entityPart && (datasourcePart === undefined || datasource.slug === datasourcePart || datasource.name === datasourcePart));
55
+ if (matches.length === 0) {
56
+ throw new RefResolveError(`'${ref}' diye bir entity yok. Mevcut slug'lar: ${flat.map(({ entity }) => entity.slug).join(', ') || '(yok)'}`);
57
+ }
58
+ if (matches.length > 1) {
59
+ throw new RefResolveError(`'${ref}' birden çok datasource'ta var: ${matches.map(({ datasource, entity }) => `${datasource.slug}/${entity.slug}`).join(', ')}. 'datasource/entity' biçimiyle verin.`);
60
+ }
61
+ const [{ datasource, entity }] = matches;
62
+ this.resolved.push(`entityId '${ref}' → ${entity.id}`);
63
+ return { entityId: entity.id, datasourceId: datasource.id, slug: entity.slug };
64
+ }
65
+ /**
66
+ * Entity'nin ham kaydı (alan listesi DIŞINDAKİ kök alanlar için: `localizationEntityId`,
67
+ * `localizationParentId`, `type`). `entityFields` ile aynı ucu çağırır ve aynı önbelleği
68
+ * kullanır — iki tur atmaz.
69
+ */
70
+ async entityRecord(ref) {
71
+ const cached = this.recordByEntity.get(ref.entityId);
72
+ if (cached)
73
+ return cached;
74
+ const payload = await this.platform.tenant('GET', this.project, `/entities/${ref.datasourceId}/${ref.entityId}`, { environment: this.environment });
75
+ const record = payload.data ?? {};
76
+ this.recordByEntity.set(ref.entityId, record);
77
+ return record;
78
+ }
79
+ async entityFields(ref) {
80
+ const cached = this.fieldsByEntity.get(ref.entityId);
81
+ if (cached)
82
+ return cached;
83
+ const payload = await this.platform.tenant('GET', this.project, `/entities/${ref.datasourceId}/${ref.entityId}`, { environment: this.environment });
84
+ const fields = (payload.data?.fields ?? [])
85
+ .filter(field => typeof field?.id === 'string' && field.id !== '' && typeof field.slug === 'string' && field.slug !== '')
86
+ .map(field => ({
87
+ id: String(field.id),
88
+ slug: String(field.slug),
89
+ relationType: String(field.relationType ?? 'none'),
90
+ relationId: field.relationId ?? null,
91
+ primaryKey: field.primaryKey === true
92
+ }));
93
+ this.fieldsByEntity.set(ref.entityId, fields);
94
+ return fields;
95
+ }
96
+ recordByEntity = new Map();
97
+ async loadTree() {
98
+ if (this.tree)
99
+ return this.tree;
100
+ const payload = await this.platform.tenant('GET', this.project, '/datasources/tree', { environment: this.environment });
101
+ this.tree = Array.isArray(payload) ? payload : (payload?.data ?? []);
102
+ return this.tree;
103
+ }
104
+ }
@@ -1,5 +1,7 @@
1
1
  import { logError, logStep } from '../observability/trace.js';
2
2
  import { PlatformApiError } from './errors.js';
3
+ import { PolicyBlockError } from '../knowledge/policy.js';
4
+ import { maskSecrets } from './secrets.js';
3
5
  export function prune(value) {
4
6
  if (Array.isArray(value))
5
7
  return value.map(prune);
@@ -125,13 +127,20 @@ function asStructured(payload) {
125
127
  return payload;
126
128
  return { data: payload };
127
129
  }
128
- export function toolResult(payload, config) {
130
+ export function toolResult(payload, config, expectFailure) {
129
131
  const pruned = prune(payload);
130
- const runtimeErrors = runtimeErrorSummary(pruned);
132
+ const { payload: safe, masked, tokenRefs } = maskSecrets(pruned);
133
+ const runtimeErrors = runtimeErrorSummary(safe);
131
134
  if (runtimeErrors.length > 0) {
132
- 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`, runtimeErrors);
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);
133
139
  }
134
- const { payload: bounded, truncated } = shrink(pruned, config.maxResponseBytes);
140
+ if (masked.length > 0) {
141
+ logStep('maskeleme', `${masked.length} gizli alan yanıttan çıkarıldı — değer *** ile değiştirildi, yolu gatewayMasked'de`, masked);
142
+ }
143
+ const { payload: bounded, truncated } = shrink(safe, config.maxResponseBytes);
135
144
  const structured = asStructured(bounded);
136
145
  if (truncated) {
137
146
  logStep('kırpma ✂', `yanıt ${config.maxResponseBytes} bayt sınırını aştı — en uzun liste yarıya indirilerek küçültüldü (MAX_RESPONSE_BYTES)`);
@@ -139,6 +148,9 @@ export function toolResult(payload, config) {
139
148
  const enriched = {
140
149
  ...structured,
141
150
  ...(runtimeErrors.length > 0 ? { runtimeErrors } : {}),
151
+ ...(runtimeErrors.length > 0 && expectFailure === true ? { expectedFailure: true } : {}),
152
+ ...(masked.length > 0 ? { gatewayMasked: masked } : {}),
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." } : {}),
142
154
  ...(truncated ? { truncated: true, truncationNote: 'Liste, yanıt boyutu sınırı nedeniyle kırpıldı; sayfalama ya da daha dar filtre kullanın.' } : {})
143
155
  };
144
156
  return {
@@ -148,7 +160,8 @@ export function toolResult(payload, config) {
148
160
  }
149
161
  export function toolError(error) {
150
162
  logError(error);
151
- const message = error instanceof PlatformApiError
163
+ // Politika durdurması bilinçli bir karardır, "beklenmeyen" değil (33/S-5).
164
+ const message = error instanceof PlatformApiError || error instanceof PolicyBlockError
152
165
  ? error.message
153
166
  : error instanceof Error
154
167
  ? `Beklenmeyen gateway hatası: ${error.message}`
@@ -161,7 +174,13 @@ export function toolError(error) {
161
174
  export function handle(config, run) {
162
175
  return async (args) => {
163
176
  try {
164
- return toolResult(await run(args), config);
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);
165
184
  }
166
185
  catch (error) {
167
186
  return toolError(error);