@appaflytech/zeyrek-ai-mcp 0.0.6 → 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.
- 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 +85 -0
- package/dist/knowledge/componentCatalog.js +357 -54
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/functionGuards.js +134 -0
- package/dist/knowledge/normalize.js +53 -2
- package/dist/knowledge/outlines.js +31 -0
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/rules.js +343 -1
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +62 -3
- package/dist/platform/http.js +1 -0
- package/dist/platform/responses.js +16 -4
- package/dist/platform/screenshot.js +18 -1
- 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 +1 -1
- package/dist/resources/guides/component.md +13 -1
- package/dist/resources/guides/entity.md +321 -17
- package/dist/resources/guides/expressions.md +3 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +5 -0
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +61 -2
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +20 -4
- package/dist/resources/guides/translation.md +24 -0
- package/dist/resources/guides/workflow.md +1 -1
- 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 +119 -1
- package/dist/resources/schemas/CreateWorkflowCommand.json +13 -2
- 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 +119 -1
- package/dist/resources/schemas/PreviewWorkflowCommand.json +13 -2
- 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 +13 -2
- 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 +119 -1
- package/dist/resources/schemas/UpdateWorkflowCommand.json +13 -2
- package/dist/server.js +15 -3
- 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 +95 -2
- package/dist/tools/functions.js +331 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +42 -3
- package/dist/tools/pages.js +62 -10
- package/dist/tools/platformInfo.js +4 -1
- 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 +24 -6
- package/package.json +3 -1
|
@@ -2,7 +2,7 @@ import { guid, entityRef, resolveEntityId, withResolved, projectParam, environme
|
|
|
2
2
|
import { RefResolver } from '../platform/refs.js';
|
|
3
3
|
import { normalizeWorkflowDefinition, withNormalization } from '../knowledge/normalize.js';
|
|
4
4
|
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
5
|
-
import { policyRules } from '../knowledge/rules.js';
|
|
5
|
+
import { policyRules, microfunctionStepFindings } from '../knowledge/rules.js';
|
|
6
6
|
import { languageHeader } from '../knowledge/languageCodes.js';
|
|
7
7
|
import { handle } from '../platform/responses.js';
|
|
8
8
|
import { z } from 'zod';
|
|
@@ -38,7 +38,10 @@ export function registerAutomationTools(server, platform) {
|
|
|
38
38
|
annotations: writes
|
|
39
39
|
}, handle(config, async ({ project, environment, entityId, definition, policyOverride }) => {
|
|
40
40
|
const normalized = normalizeWorkflowDefinition(definition);
|
|
41
|
-
const findings = enforcePolicy(
|
|
41
|
+
const findings = enforcePolicy([
|
|
42
|
+
...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'create', workflowKind: 'automation' }, policyOverride),
|
|
43
|
+
...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride)
|
|
44
|
+
]);
|
|
42
45
|
const resolver = new RefResolver(platform, project, environment);
|
|
43
46
|
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
44
47
|
const payload = await platform.tenant('POST', project, `/entities/${resolvedEntityId}/automation`, { environment, body: definition });
|
|
@@ -51,7 +54,10 @@ export function registerAutomationTools(server, platform) {
|
|
|
51
54
|
annotations: updates
|
|
52
55
|
}, handle(config, async ({ project, environment, entityId, id, definition, policyOverride }) => {
|
|
53
56
|
const normalized = normalizeWorkflowDefinition(definition);
|
|
54
|
-
const findings = enforcePolicy(
|
|
57
|
+
const findings = enforcePolicy([
|
|
58
|
+
...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'update', workflowKind: 'automation' }, policyOverride),
|
|
59
|
+
...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride)
|
|
60
|
+
]);
|
|
55
61
|
const resolver = new RefResolver(platform, project, environment);
|
|
56
62
|
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
57
63
|
const payload = await platform.tenant('PUT', project, `/entities/${resolvedEntityId}/automation/${id}`, { environment, body: definition });
|
|
@@ -123,11 +129,14 @@ export function registerAutomationTools(server, platform) {
|
|
|
123
129
|
server.registerTool('automation_preview', {
|
|
124
130
|
title: 'Automation önizle',
|
|
125
131
|
description: "Kaydedilmemiş automation tanımını çalıştırır; kaydetmeden önce preview ile doğrulayın. definition = PreviewWorkflowCommand gövdesi. Preview tek transaction'da koşar ve sonunda ROLLBACK edilir — kalıcı satır yazmaz (ölçüldü, İK koşusu W-1); code step'teki dış HTTP çağrıları istisnadır. Davranış kuralları: platform_guide('workflow').",
|
|
126
|
-
inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, definition: definitionParam, policyOverride: policyOverrideParam, language: languageParam },
|
|
132
|
+
inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, definition: definitionParam, policyOverride: policyOverrideParam, language: languageParam, expectFailure: z.boolean().optional().describe("true: bu önizlemede bir adımın/dalın düşmesi BEKLENİYOR (kasıtlı negatif test — geçersiz gövde, iş kuralı reddi, revert). Yanıt yine executionSuccess:false/runtimeErrors taşır, ama ölçüm aracı bu satırı plansız hata saymaz (page_screenshot'taki expectFailure deseniyle aynı).") },
|
|
127
133
|
annotations: writes
|
|
128
134
|
}, handle(config, async ({ project, environment, entityId, definition, policyOverride, language }) => {
|
|
129
135
|
const normalized = normalizeWorkflowDefinition(definition);
|
|
130
|
-
const findings = enforcePolicy(
|
|
136
|
+
const findings = enforcePolicy([
|
|
137
|
+
...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'preview', workflowKind: 'automation' }, policyOverride),
|
|
138
|
+
...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride)
|
|
139
|
+
]);
|
|
131
140
|
const resolver = new RefResolver(platform, project, environment);
|
|
132
141
|
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
133
142
|
const payload = await platform.tenant('POST', project, `/entities/${resolvedEntityId}/automation/preview`, { environment, body: definition, headers: languageHeader(language, normalized) });
|
package/dist/tools/common.js
CHANGED
|
@@ -51,6 +51,22 @@ export const pagingParams = {
|
|
|
51
51
|
order: z.string().optional().describe('Sıralanacak alan adı'),
|
|
52
52
|
isDescending: z.boolean().optional()
|
|
53
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Dal adı kuralı: `Environment` ctor'unun aynası, gerçek koruma backend'dedir. Ad yalnız
|
|
56
|
+
* `environment_create`'i değil, `page_screenshot`'ın `/branch/{env}` şeridini de ilgilendirdiği
|
|
57
|
+
* için tek yerde durur — iki kopya birbirinden ayrılırsa gateway kabul ettiği bir adı
|
|
58
|
+
* görüntüleyemez hâle gelir.
|
|
59
|
+
*/
|
|
60
|
+
export const branchNamePattern = /^[a-z][a-z0-9_]{2,30}$/;
|
|
61
|
+
const reservedEnvironmentNames = new Set(['development', 'production', 'public', 'information_schema', 'dev', 'view', 'api', 'files', 'hubs']);
|
|
62
|
+
/** Sabit ortamlar, url sözcükleri ve postgres şemaları: dal adı olamazlar */
|
|
63
|
+
export function isReservedEnvironmentName(name) {
|
|
64
|
+
return name.startsWith('pg_') || reservedEnvironmentNames.has(name);
|
|
65
|
+
}
|
|
66
|
+
/** Hem biçimi tutan hem rezerve olmayan ad — platformun dal diye kabul edeceği tek küme */
|
|
67
|
+
export function isBranchName(name) {
|
|
68
|
+
return branchNamePattern.test(name) && !isReservedEnvironmentName(name);
|
|
69
|
+
}
|
|
54
70
|
export const readOnly = { readOnlyHint: true, openWorldHint: false };
|
|
55
71
|
export const writes = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
56
72
|
export const updates = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
@@ -9,7 +9,7 @@ export function registerDependencyTools(server, platform) {
|
|
|
9
9
|
inputSchema: {
|
|
10
10
|
project: projectParam,
|
|
11
11
|
environment: environmentParam,
|
|
12
|
-
module: z.string().describe('Modül adı: entity | field | microservice | workflow | query | view | dataTransferObject vb. (platform Module enum değeri)'),
|
|
12
|
+
module: z.string().describe('Modül adı: entity | field | microservice | workflow | query | view | dataTransferObject | global-function (fonksiyon, iki kaynak: onu çağıran flow/workflow listesi; flow düğümü sahibini hostModule/hostId/hostName, bağlandığı komponenti ve olayı componentName/eventName/eventTitle ile taşır) vb. (platform Module enum değeri)'),
|
|
13
13
|
id: guid
|
|
14
14
|
},
|
|
15
15
|
annotations: readOnly
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { branchNamePattern, destructive, guid, isReservedEnvironmentName, readOnly, writes } from './common.js';
|
|
2
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
3
|
+
import { handle } from '../platform/responses.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
/** Aggregate kuralının aynası (`Environment` ctor); gerçek koruma backend'dedir. Kural `common.ts`'te. */
|
|
6
|
+
const branchName = z.string().regex(branchNamePattern)
|
|
7
|
+
.describe("Dal adı: küçük harfle başlar, yalnız küçük harf/rakam/alt çizgi, 3-31 karakter. Bu ad PG şema adı olur.");
|
|
8
|
+
function assertBranchName(name) {
|
|
9
|
+
if (isReservedEnvironmentName(name)) {
|
|
10
|
+
throw new PlatformApiError(`environment_create: '${name}' rezerve bir şema adı — dal adı olarak kullanılamaz. Başka bir ad seçin.`, 400);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function registerEnvironmentTools(server, platform) {
|
|
14
|
+
const config = platform.config;
|
|
15
|
+
server.registerTool('environment_list', {
|
|
16
|
+
title: 'Environment listesi',
|
|
17
|
+
description: "Projenin aktif environment'larını döner: önce sabitler (development, production), sonra dallar. Her satırda 'state' vardır (creating | ready | merging | deleting | merged | error); yalnız 'ready' bir dalda çalışılabilir. Dalda çalışmak için tenant tool'larına environment: '<dal adı>' geçin.",
|
|
18
|
+
inputSchema: { projectId: guid },
|
|
19
|
+
annotations: readOnly
|
|
20
|
+
}, handle(config, ({ projectId }) => platform.org('GET', `/environments/${projectId}`)));
|
|
21
|
+
server.registerTool('environment_create', {
|
|
22
|
+
title: 'Dal environment\'ı aç',
|
|
23
|
+
description: "Projede yeni bir dal environment'ı açar: kendi PG şeması kurulur ve development'ın tanımları oraya kopyalanır. Asenkron bir saga'dır — yanıt 202 döner, dal hemen kullanılabilir DEĞİLDİR. includeRecords true ise projenin KENDİ veritabanındaki bütün veri tablolarının satırları da kopyalanır (çok dilli companion tablolar ve junction'lar dahil; files/folders ve audit trail tabloları hariç, dış datasource'lara dokunulmaz) — entity'de kayıt taşıması ayarı açık olmasa bile. Satırlar merge'de geri taşınmaz.",
|
|
24
|
+
inputSchema: { projectId: guid, name: branchName, includeRecords: z.boolean().optional().describe('Projenin kendi veritabanındaki bütün tabloların satırları da kopyalansın mı (varsayılan false)') },
|
|
25
|
+
annotations: writes
|
|
26
|
+
}, handle(config, async ({ projectId, name, includeRecords }) => {
|
|
27
|
+
assertBranchName(name);
|
|
28
|
+
const payload = await platform.org('POST', `/environments/${projectId}`, { body: { name, includeRecords: includeRecords === true } });
|
|
29
|
+
return {
|
|
30
|
+
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
31
|
+
note: "dal asenkron açılıyor; environment_list ile state=ready bekleyin"
|
|
32
|
+
};
|
|
33
|
+
}));
|
|
34
|
+
server.registerTool('environment_delete', {
|
|
35
|
+
title: 'Dal environment\'ını sil',
|
|
36
|
+
description: "Bir dal environment'ını ve PG şemasını siler. Sabit environment'lar (development, production) silinemez — gateway bunu HTTP'den önce reddeder. Yalnız 'ready' ya da 'error' durumundaki dal silinebilir; silme asenkrondur.",
|
|
37
|
+
inputSchema: { projectId: guid, environmentId: guid },
|
|
38
|
+
annotations: destructive
|
|
39
|
+
}, handle(config, async ({ projectId, environmentId }) => {
|
|
40
|
+
const environments = await platform.org('GET', `/environments/${projectId}`);
|
|
41
|
+
const rows = environments?.data ?? [];
|
|
42
|
+
const target = rows.find(row => row.id === environmentId);
|
|
43
|
+
if (target && target.isBranch !== true) {
|
|
44
|
+
throw new PlatformApiError(`environment_delete: '${String(target.name)}' sabit bir environment — silinemez. Yalnız dallar silinir (environment_list çıktısında isBranch: true olanlar).`, 400);
|
|
45
|
+
}
|
|
46
|
+
const payload = await platform.org('DELETE', `/environments/${projectId}/${environmentId}`);
|
|
47
|
+
return {
|
|
48
|
+
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
49
|
+
note: "şema asenkron düşürülüyor; environment_list ile dalın listeden çıkmasını bekleyin"
|
|
50
|
+
};
|
|
51
|
+
}));
|
|
52
|
+
}
|
package/dist/tools/files.js
CHANGED
|
@@ -38,6 +38,56 @@ const mimeTypes = {
|
|
|
38
38
|
'.xml': 'application/xml',
|
|
39
39
|
'.zip': 'application/zip'
|
|
40
40
|
};
|
|
41
|
+
export async function uploadFile(platform, { project, environment, path, content, text, filename, contentType, folderId }) {
|
|
42
|
+
const given = [path, content, text].filter(value => value !== undefined);
|
|
43
|
+
if (given.length !== 1) {
|
|
44
|
+
throw new PlatformApiError("file_upload: `path`, `content` ya da `text` parametrelerinden tam olarak biri verilmeli — `path` gateway'in okuyabildiği yerel dosya, `content` base64 içerik, `text` düz metin (ikisi de `filename` ile).");
|
|
45
|
+
}
|
|
46
|
+
let bytes;
|
|
47
|
+
let name;
|
|
48
|
+
if (path) {
|
|
49
|
+
const absolute = resolve(path);
|
|
50
|
+
try {
|
|
51
|
+
bytes = await readFile(absolute);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new PlatformApiError(`file_upload: dosya okunamadı — ${absolute} (${error instanceof Error ? error.message : String(error)}). Yol gateway sürecinin çalıştığı makineye göre çözülür.`);
|
|
55
|
+
}
|
|
56
|
+
name = filename ?? basename(absolute);
|
|
57
|
+
}
|
|
58
|
+
else if (text !== undefined) {
|
|
59
|
+
if (!filename)
|
|
60
|
+
throw new PlatformApiError('file_upload: `text` verildiğinde `filename` zorunludur.');
|
|
61
|
+
bytes = Buffer.from(text, 'utf8');
|
|
62
|
+
if (bytes.byteLength > maxTextBytes) {
|
|
63
|
+
throw new PlatformApiError(`file_upload: \`text\` içeriği ${maxTextBytes} bayt sınırını aşıyor (${bytes.byteLength} B). Büyük dosyalar için \`path\` ya da \`content\` kullanın.`);
|
|
64
|
+
}
|
|
65
|
+
name = filename;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (!filename)
|
|
69
|
+
throw new PlatformApiError('file_upload: `content` verildiğinde `filename` zorunludur.');
|
|
70
|
+
bytes = Buffer.from(content, 'base64');
|
|
71
|
+
if (bytes.byteLength === 0)
|
|
72
|
+
throw new PlatformApiError('file_upload: `content` geçerli base64 değil ya da boş.');
|
|
73
|
+
if (bytes.byteLength > maxInlineBytes) {
|
|
74
|
+
throw new PlatformApiError(`file_upload: base64 içerik ${maxInlineBytes} bayt sınırını aşıyor (${bytes.byteLength} B). Büyük dosyalar için \`path\` kullanın.`);
|
|
75
|
+
}
|
|
76
|
+
name = filename;
|
|
77
|
+
}
|
|
78
|
+
if (bytes.byteLength > maxUploadBytes) {
|
|
79
|
+
throw new PlatformApiError(`file_upload: dosya ${maxUploadBytes} bayt platform sınırını aşıyor (${bytes.byteLength} B).`);
|
|
80
|
+
}
|
|
81
|
+
const offendingChar = [...forbiddenNameChars].find(character => name.includes(character));
|
|
82
|
+
if (offendingChar) {
|
|
83
|
+
throw new PlatformApiError(`file_upload: dosya adı '${offendingChar}' karakterini içeriyor; platform doğrulaması bunu 422 ile reddeder (FILE-001, UploadFileCommandValidator). Yasak karakterler: ${forbiddenNameChars}`);
|
|
84
|
+
}
|
|
85
|
+
const resolvedType = contentType ?? mimeTypes[extname(name).toLowerCase()] ?? 'application/octet-stream';
|
|
86
|
+
const multipart = folderId
|
|
87
|
+
? [{ name: 'FolderId', value: folderId }, { name: 'File', filename: name, contentType: resolvedType, content: bytes }]
|
|
88
|
+
: [{ name: 'File', filename: name, contentType: resolvedType, content: bytes }];
|
|
89
|
+
return platform.tenant('POST', project, '/files/upload', { environment, multipart });
|
|
90
|
+
}
|
|
41
91
|
export function registerFileTools(server, platform) {
|
|
42
92
|
const config = platform.config;
|
|
43
93
|
server.registerTool('file_list', {
|
|
@@ -66,56 +116,7 @@ export function registerFileTools(server, platform) {
|
|
|
66
116
|
folderId: guid.optional().describe('Hedef klasör (folder_list); boşsa kök')
|
|
67
117
|
},
|
|
68
118
|
annotations: writes
|
|
69
|
-
}, handle(config,
|
|
70
|
-
const given = [path, content, text].filter(value => value !== undefined);
|
|
71
|
-
if (given.length !== 1) {
|
|
72
|
-
throw new PlatformApiError("file_upload: `path`, `content` ya da `text` parametrelerinden tam olarak biri verilmeli — `path` gateway'in okuyabildiği yerel dosya, `content` base64 içerik, `text` düz metin (ikisi de `filename` ile).");
|
|
73
|
-
}
|
|
74
|
-
let bytes;
|
|
75
|
-
let name;
|
|
76
|
-
if (path) {
|
|
77
|
-
const absolute = resolve(path);
|
|
78
|
-
try {
|
|
79
|
-
bytes = await readFile(absolute);
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
throw new PlatformApiError(`file_upload: dosya okunamadı — ${absolute} (${error instanceof Error ? error.message : String(error)}). Yol gateway sürecinin çalıştığı makineye göre çözülür.`);
|
|
83
|
-
}
|
|
84
|
-
name = filename ?? basename(absolute);
|
|
85
|
-
}
|
|
86
|
-
else if (text !== undefined) {
|
|
87
|
-
if (!filename)
|
|
88
|
-
throw new PlatformApiError('file_upload: `text` verildiğinde `filename` zorunludur.');
|
|
89
|
-
bytes = Buffer.from(text, 'utf8');
|
|
90
|
-
if (bytes.byteLength > maxTextBytes) {
|
|
91
|
-
throw new PlatformApiError(`file_upload: \`text\` içeriği ${maxTextBytes} bayt sınırını aşıyor (${bytes.byteLength} B). Büyük dosyalar için \`path\` ya da \`content\` kullanın.`);
|
|
92
|
-
}
|
|
93
|
-
name = filename;
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
if (!filename)
|
|
97
|
-
throw new PlatformApiError('file_upload: `content` verildiğinde `filename` zorunludur.');
|
|
98
|
-
bytes = Buffer.from(content, 'base64');
|
|
99
|
-
if (bytes.byteLength === 0)
|
|
100
|
-
throw new PlatformApiError('file_upload: `content` geçerli base64 değil ya da boş.');
|
|
101
|
-
if (bytes.byteLength > maxInlineBytes) {
|
|
102
|
-
throw new PlatformApiError(`file_upload: base64 içerik ${maxInlineBytes} bayt sınırını aşıyor (${bytes.byteLength} B). Büyük dosyalar için \`path\` kullanın.`);
|
|
103
|
-
}
|
|
104
|
-
name = filename;
|
|
105
|
-
}
|
|
106
|
-
if (bytes.byteLength > maxUploadBytes) {
|
|
107
|
-
throw new PlatformApiError(`file_upload: dosya ${maxUploadBytes} bayt platform sınırını aşıyor (${bytes.byteLength} B).`);
|
|
108
|
-
}
|
|
109
|
-
const offendingChar = [...forbiddenNameChars].find(character => name.includes(character));
|
|
110
|
-
if (offendingChar) {
|
|
111
|
-
throw new PlatformApiError(`file_upload: dosya adı '${offendingChar}' karakterini içeriyor; platform doğrulaması bunu 422 ile reddeder (FILE-001, UploadFileCommandValidator). Yasak karakterler: ${forbiddenNameChars}`);
|
|
112
|
-
}
|
|
113
|
-
const resolvedType = contentType ?? mimeTypes[extname(name).toLowerCase()] ?? 'application/octet-stream';
|
|
114
|
-
const multipart = folderId
|
|
115
|
-
? [{ name: 'FolderId', value: folderId }, { name: 'File', filename: name, contentType: resolvedType, content: bytes }]
|
|
116
|
-
: [{ name: 'File', filename: name, contentType: resolvedType, content: bytes }];
|
|
117
|
-
return platform.tenant('POST', project, '/files/upload', { environment, multipart });
|
|
118
|
-
}));
|
|
119
|
+
}, handle(config, (input) => uploadFile(platform, input)));
|
|
119
120
|
server.registerTool('file_download', {
|
|
120
121
|
title: 'Dosya indir',
|
|
121
122
|
description: "Dosyanın içeriğini indirir ve `outPath` ile verilen yerel yola yazar; dönen gövde yalnız özet (yol, boyut, tip) — ikili içerik model bağlamına girmez. Yol gateway sürecinin çalıştığı makineye göre çözülür.",
|
package/dist/tools/flows.js
CHANGED
|
@@ -9,7 +9,8 @@ import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
|
|
|
9
9
|
import { resolvePatchOperations } from '../knowledge/outlines.js';
|
|
10
10
|
import { patchResult, baseVersionOf } from '../platform/patch.js';
|
|
11
11
|
import fastJsonPatch from 'fast-json-patch';
|
|
12
|
-
import { policyRules } from '../knowledge/rules.js';
|
|
12
|
+
import { policyRules, flowExpressionSites } from '../knowledge/rules.js';
|
|
13
|
+
import { functionCallRefs } from '../knowledge/functionGuards.js';
|
|
13
14
|
import { randomUUID } from 'node:crypto';
|
|
14
15
|
import { z } from 'zod';
|
|
15
16
|
/**
|
|
@@ -28,7 +29,7 @@ async function flowWriteDrops(platform, host, project, environment, applicationI
|
|
|
28
29
|
return { dropped: [], normalized: [] };
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
|
-
const stepsParam = z.array(z.record(z.unknown())).describe("Adım listesi (StepManagementSchema: { stepId, name, type, kind, args, branches }). DİKKAT: branches[].nextStepId bir GUID değil, hedef adımın stepId STRING'idir; dal ADI motorun anahtarıdır ve start/setter/code adımlarında 'always' olmak zorundadır. Tam şablon: ngapps://examples/flow-load-table (ya da flow_get çıktısı); adım tipleri ve setter/parameters şekli platform_guide('page') Kural 1c.");
|
|
32
|
+
const stepsParam = z.array(z.record(z.unknown())).describe("Adım listesi (StepManagementSchema: { stepId, name, type, kind, args, branches }). DİKKAT: branches[].nextStepId bir GUID değil, hedef adımın stepId STRING'idir; dal ADI motorun anahtarıdır ve start/setter/code/function adımlarında 'always' olmak zorundadır. Tam şablon: ngapps://examples/flow-load-table (ya da flow_get çıktısı); adım tipleri ve setter/parameters şekli platform_guide('page') Kural 1c. Kullanıcı fonksiyonu çağırmak için 'function' adımı: { type:'function', args:{ functionId:'<function_list id>', functionName:'<ad>', parameters:[{ name:'<bildirimdeki ad>', value:{ type:'static'|'property'|'storage'|'state'|'code', ref } }] } } → çıktı Steps.<adımAdı>.data (ngapps://examples/flow-call-function, platform_guide('functions')).");
|
|
32
33
|
const policyOverrideParam = z.boolean().optional();
|
|
33
34
|
const patchOperationsParam = z.array(z.record(z.unknown())).min(1).describe("RFC 6902 JSON Patch işlemleri; yolları flow_get çıktısıyla keşfedin. Dizi öğelerinde indeks yerine seçici kullanılabilir: '/steps[stepId=paint]/args/iteratees[element=tbl_recruiters]/mapping/source' — seçici gövde platforma gitmeden indekse çevrilir, çözülemezse istek hiç gönderilmez.");
|
|
34
35
|
const flowIdParam = guid.describe("Akış id'si; şemadaki events[].value bu id'dir");
|
|
@@ -136,6 +137,95 @@ function rowsTargetsByStepName(steps) {
|
|
|
136
137
|
}
|
|
137
138
|
return targets;
|
|
138
139
|
}
|
|
140
|
+
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
141
|
+
function toUiFunctionRecord(entry) {
|
|
142
|
+
return {
|
|
143
|
+
id: String(entry.id ?? ''),
|
|
144
|
+
name: String(entry.name ?? ''),
|
|
145
|
+
scope: String(entry.scope ?? 'application'),
|
|
146
|
+
pageId: typeof entry.pageId === 'string' ? entry.pageId : null,
|
|
147
|
+
isAsync: entry.isAsync === true,
|
|
148
|
+
parameters: (Array.isArray(entry.parameters) ? entry.parameters : [])
|
|
149
|
+
.filter(isRecord)
|
|
150
|
+
.map(parameter => ({ name: String(parameter.name ?? ''), required: parameter.required === true, defaultValue: parameter.defaultValue ?? null }))
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Uç okunamazsa (eski backend, yetki) `undefined`: uyarı üretilmez — lint yazmanın sonucu değil, eklentisidir. */
|
|
154
|
+
async function loadUiFunctions(platform, project, environment, applicationId) {
|
|
155
|
+
try {
|
|
156
|
+
const payload = await platform.tenant('GET', project, '/global-functions', { environment, query: { applicationId } });
|
|
157
|
+
return (Array.isArray(payload?.data) ? payload.data : []).filter(isRecord).map(toUiFunctionRecord);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* FLOW-024 / FLOW-025 (G-04 · G-05): `function` adımının bildirimle eşleşmesi ve ifadelerdeki
|
|
165
|
+
* `Functions.<ad>(…)` çağrılarının gerçek bir fonksiyona çözülmesi. Küme dinamiktir (uygulama
|
|
166
|
+
* fonksiyonları + barındırıcı SAYFANIN fonksiyonları, K2) — bu yüzden politika kuralı değil,
|
|
167
|
+
* ucu okuyan uyarıdır (`requiredParameterWarnings` deseni). docs/16 §5'in "lint yok" kararı
|
|
168
|
+
* fonksiyon kümesi dinamikleştiği için burada revize edildi (G-13).
|
|
169
|
+
*/
|
|
170
|
+
async function functionWarnings(platform, host, project, environment, applicationId, hostId, steps) {
|
|
171
|
+
const functionSteps = steps.filter(step => step.type === 'function');
|
|
172
|
+
const callSites = steps.flatMap(step => flowExpressionSites(step)
|
|
173
|
+
.flatMap(site => functionCallRefs(site.expression).map(ref => ({ step, origin: site.origin, ref }))));
|
|
174
|
+
if (functionSteps.length === 0 && callSites.length === 0)
|
|
175
|
+
return [];
|
|
176
|
+
const functions = await loadUiFunctions(platform, project, environment, applicationId);
|
|
177
|
+
if (functions === undefined)
|
|
178
|
+
return [];
|
|
179
|
+
const byId = new Map(functions.map(entry => [entry.id, entry]));
|
|
180
|
+
const byName = new Map(functions.map(entry => [entry.name, entry]));
|
|
181
|
+
const known = functions.map(entry => entry.name).sort().join(', ') || '(hiç fonksiyon yok)';
|
|
182
|
+
const visible = (entry) => entry.scope !== 'page' || (host === 'page' && entry.pageId === hostId);
|
|
183
|
+
const label = (step) => String(step.stepId ?? step.name ?? '?');
|
|
184
|
+
const warnings = new Set();
|
|
185
|
+
for (const step of functionSteps) {
|
|
186
|
+
const args = (step.args ?? {});
|
|
187
|
+
const id = typeof args.functionId === 'string' ? args.functionId : '';
|
|
188
|
+
const fn = byId.get(id);
|
|
189
|
+
if (fn === undefined) {
|
|
190
|
+
if (id !== '')
|
|
191
|
+
warnings.add(`FLOW-024: '${label(step)}' adımının functionId'si (${id}) uygulamada yok — platform 422 GlobalFunctionNotFound verir. Bilinen fonksiyonlar: ${known}. Gerçek id'yi function_list ile alın.`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (typeof args.functionName === 'string' && args.functionName !== '' && args.functionName !== fn.name) {
|
|
195
|
+
warnings.add(`FLOW-024: '${label(step)}' adımının functionName'i '${args.functionName}' ama functionId '${fn.name}' fonksiyonuna ait — Studio özeti ve ad yedeği yanlış fonksiyonu gösterir; functionName: '${fn.name}' yazın.`);
|
|
196
|
+
}
|
|
197
|
+
if (!visible(fn)) {
|
|
198
|
+
warnings.add(`FLOW-024: '${label(step)}' adımı '${fn.name}' SAYFA fonksiyonunu çağırıyor ama fonksiyon başka bir sayfaya (${fn.pageId}) ait — bu ${host} bağlamının runtime paketi onu taşımaz, adım 'Functions.${fn.name} is not a function' ile düşer (K2). Fonksiyonu uygulama kapsamına alın ya da bu sayfada tanımlayın.`);
|
|
199
|
+
}
|
|
200
|
+
const given = (Array.isArray(args.parameters) ? args.parameters : []).filter(isRecord).map(parameter => String(parameter.name ?? ''));
|
|
201
|
+
const declared = fn.parameters.map(parameter => parameter.name);
|
|
202
|
+
const unknown = given.filter(name => name !== '' && !declared.includes(name));
|
|
203
|
+
if (unknown.length > 0) {
|
|
204
|
+
warnings.add(`FLOW-024: '${label(step)}' adımı '${fn.name}' fonksiyonunda olmayan parametre geçiyor: ${unknown.join(', ')} — bildirilenler: ${declared.join(', ') || '(parametresiz)'}. Eşleme ada göredir; tanınmayan parametre sessizce düşer.`);
|
|
205
|
+
}
|
|
206
|
+
const missing = fn.parameters.filter(parameter => parameter.required && !given.includes(parameter.name)).map(parameter => parameter.name);
|
|
207
|
+
if (missing.length > 0) {
|
|
208
|
+
warnings.add(`FLOW-024: '${label(step)}' adımında '${fn.name}' fonksiyonunun zorunlu parametresi eksik: ${missing.join(', ')} — çağrı 'Functions.${fn.name}: "${missing[0]}" is required' ile düşer, Steps.${String(step.name ?? label(step))}.error dolar ve akış hata gövdesiyle 'always' dalından devam eder.`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (const { step, origin, ref } of callSites) {
|
|
212
|
+
const fn = byName.get(ref.name);
|
|
213
|
+
if (fn === undefined) {
|
|
214
|
+
warnings.add(`FLOW-025: '${label(step)}' adımının ${origin} ifadesi Functions.${ref.name}(…) çağırıyor ama uygulamada bu adda fonksiyon yok — runtime 'Functions.${ref.name} is not a function' verir; runCode hatayı yutar, hedef prop yazılmaz. Bilinen fonksiyonlar: ${known}.`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (!visible(fn)) {
|
|
218
|
+
warnings.add(`FLOW-025: '${label(step)}' adımının ${origin} ifadesi Functions.${ref.name}(…) çağırıyor ama bu bir SAYFA fonksiyonu ve başka sayfaya (${fn.pageId}) ait — bu ${host} bağlamında tanımsızdır (K2).`);
|
|
219
|
+
}
|
|
220
|
+
if (ref.argCount > fn.parameters.length) {
|
|
221
|
+
warnings.add(`FLOW-025: '${label(step)}' adımının ${origin} ifadesi Functions.${ref.name} için ${ref.argCount} argüman geçiyor, fonksiyon ${fn.parameters.length} parametre bildiriyor (${fn.parameters.map(parameter => parameter.name).join(', ') || 'parametresiz'}) — fazlası yok sayılır (konumsal eşleme, K5).`);
|
|
222
|
+
}
|
|
223
|
+
if (fn.isAsync && !(step.type === 'code' && origin === 'args.value')) {
|
|
224
|
+
warnings.add(`FLOW-025: '${label(step)}' adımının ${origin} ifadesi async fonksiyon Functions.${ref.name} çağırıyor — dönüş bir Promise'dir; setter/parametre/condition ifadesinde değer olarak '[object Promise]' kalır. async fonksiyonu code adımında 'await Functions.${ref.name}(…)' ile ya da function adımıyla çağırın.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return [...warnings];
|
|
228
|
+
}
|
|
139
229
|
export function registerFlowTools(server, platform) {
|
|
140
230
|
const config = platform.config;
|
|
141
231
|
server.registerTool('flow_list', {
|
|
@@ -189,6 +279,7 @@ export function registerFlowTools(server, platform) {
|
|
|
189
279
|
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'create' }, policyOverride));
|
|
190
280
|
const parameterWarnings = [
|
|
191
281
|
...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
|
|
282
|
+
...await functionWarnings(platform, host, project, environment, applicationId, hostId, steps),
|
|
192
283
|
...flowBodyWarnings(steps),
|
|
193
284
|
...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord))
|
|
194
285
|
];
|
|
@@ -241,6 +332,7 @@ export function registerFlowTools(server, platform) {
|
|
|
241
332
|
: [];
|
|
242
333
|
const parameterWarnings = [
|
|
243
334
|
...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
|
|
335
|
+
...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
|
|
244
336
|
...flowBodyWarnings(flow.steps),
|
|
245
337
|
...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
|
|
246
338
|
];
|
|
@@ -290,6 +382,7 @@ export function registerFlowTools(server, platform) {
|
|
|
290
382
|
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'update' }, policyOverride));
|
|
291
383
|
const parameterWarnings = [
|
|
292
384
|
...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
|
|
385
|
+
...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
|
|
293
386
|
...flowBodyWarnings(flow.steps),
|
|
294
387
|
...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
|
|
295
388
|
];
|