@nocobase/plugin-flow-engine 2.3.0-alpha.1 → 3.0.0-alpha.1
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/ai/ai-employees/nathan/prompt.md +2 -0
- package/dist/ai/ai-employees/nathan/skills/frontend-developer/SKILLS.md +1 -0
- package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/patchJSCode.js +2 -2
- package/dist/ai/docs/runjs/context/ai.md +194 -4
- package/dist/externalVersion.js +9 -9
- package/dist/locale/en-US.json +2 -0
- package/dist/locale/index.d.ts +4 -0
- package/dist/locale/zh-CN.json +2 -0
- package/dist/node_modules/@ant-design/icons-svg/package.json +1 -1
- package/dist/node_modules/acorn/package.json +1 -1
- package/dist/node_modules/acorn-jsx/package.json +1 -1
- package/dist/node_modules/acorn-walk/package.json +1 -1
- package/dist/node_modules/ses/package.json +1 -1
- package/dist/node_modules/zod/package.json +1 -1
- package/dist/server/flow-surfaces/navigation-targets.d.ts +7 -0
- package/dist/server/flow-surfaces/navigation-targets.js +161 -54
- package/dist/server/flow-surfaces/service.d.ts +7 -0
- package/dist/server/flow-surfaces/service.js +186 -53
- package/dist/swagger/flow-surfaces.js +5 -5
- package/package.json +2 -2
|
@@ -38,7 +38,6 @@ const DEFAULT_ADMIN_UI_LAYOUT_UID = "admin-layout-model";
|
|
|
38
38
|
const DEFAULT_MOBILE_UI_LAYOUT_UID = "mobile-layout-model";
|
|
39
39
|
const DEFAULT_ADMIN_MULTI_PORTAL_UID = "__default_admin__";
|
|
40
40
|
const DEFAULT_MOBILE_MULTI_PORTAL_UID = "__default_mobile__";
|
|
41
|
-
const RESERVED_MULTI_PORTAL_UIDS = /* @__PURE__ */ new Set([DEFAULT_ADMIN_MULTI_PORTAL_UID, DEFAULT_MOBILE_MULTI_PORTAL_UID]);
|
|
42
41
|
function readRecordField(record, field) {
|
|
43
42
|
if (!record || typeof record !== "object") {
|
|
44
43
|
return void 0;
|
|
@@ -60,6 +59,9 @@ function normalizeRoles(currentRoles) {
|
|
|
60
59
|
function normalizeRelationRouteId(routeId) {
|
|
61
60
|
return routeId === null || typeof routeId === "undefined" ? void 0 : routeId;
|
|
62
61
|
}
|
|
62
|
+
function isDefaultLayoutMultiPortalUid(uid) {
|
|
63
|
+
return uid === DEFAULT_ADMIN_MULTI_PORTAL_UID || uid === DEFAULT_MOBILE_MULTI_PORTAL_UID;
|
|
64
|
+
}
|
|
63
65
|
class FlowSurfaceNavigationTargetsService {
|
|
64
66
|
constructor(db) {
|
|
65
67
|
this.db = db;
|
|
@@ -85,9 +87,17 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
85
87
|
return portalUid || void 0;
|
|
86
88
|
}
|
|
87
89
|
async listNavigationTargets(currentRoles, transaction) {
|
|
88
|
-
|
|
90
|
+
var _a;
|
|
91
|
+
let layoutTargets = await this.listLayoutTargets(transaction);
|
|
89
92
|
const multiPortal = this.hasMultiPortalCapability();
|
|
90
|
-
const
|
|
93
|
+
const portalInventory = multiPortal ? await this.getEnabledPortalInventory(currentRoles, transaction) : { accessible: [] };
|
|
94
|
+
const portalTargets = multiPortal ? await this.listAccessiblePortalTargets(portalInventory.accessible, transaction) : [];
|
|
95
|
+
if (multiPortal) {
|
|
96
|
+
layoutTargets = layoutTargets.map(({ default: _default, ...target }) => target);
|
|
97
|
+
if (portalInventory.accessible.length === 1 && ((_a = portalTargets[0]) == null ? void 0 : _a.uid) === readStringField(portalInventory.accessible[0], "uid")) {
|
|
98
|
+
portalTargets[0] = { ...portalTargets[0], default: true };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
91
101
|
return {
|
|
92
102
|
version: "1",
|
|
93
103
|
capabilities: {
|
|
@@ -96,6 +106,60 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
96
106
|
targets: [...layoutTargets, ...portalTargets]
|
|
97
107
|
};
|
|
98
108
|
}
|
|
109
|
+
async resolveDefaultPortal(options) {
|
|
110
|
+
if (!this.hasMultiPortalCapability()) {
|
|
111
|
+
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} requires the Multi-portal capability`, {
|
|
112
|
+
ruleId: "navigation-portal-unsupported",
|
|
113
|
+
path: "navigation"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const inventory = await this.getEnabledPortalInventory(options.currentRoles, options.transaction);
|
|
117
|
+
if (!inventory.enabled.length) {
|
|
118
|
+
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} requires an enabled portal`, {
|
|
119
|
+
ruleId: "navigation-portal-not-found",
|
|
120
|
+
path: "navigation",
|
|
121
|
+
details: {
|
|
122
|
+
uiBuilderAllowed: false,
|
|
123
|
+
adminLayoutFallbackAllowed: false,
|
|
124
|
+
agentInstruction: "Stop this Flow Surfaces write. Ask the user to create and enable a Portal before retrying; do not fall back to the Admin layout."
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (!inventory.accessible.length) {
|
|
129
|
+
(0, import_errors.throwForbidden)(
|
|
130
|
+
`flowSurfaces ${options.actionName} current roles cannot access an enabled portal`,
|
|
131
|
+
"FLOW_SURFACE_NAVIGATION_PORTAL_FORBIDDEN",
|
|
132
|
+
{
|
|
133
|
+
ruleId: "navigation-portal-forbidden",
|
|
134
|
+
path: "navigation"
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (inventory.accessible.length > 1) {
|
|
139
|
+
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} requires an explicit portal selection`, {
|
|
140
|
+
ruleId: "navigation-portal-selection-required",
|
|
141
|
+
path: "navigation",
|
|
142
|
+
details: {
|
|
143
|
+
candidates: inventory.accessible.map((portal) => {
|
|
144
|
+
const uid = readStringField(portal, "uid");
|
|
145
|
+
return {
|
|
146
|
+
uid,
|
|
147
|
+
portalName: readStringField(portal, "portalName") || null,
|
|
148
|
+
title: readStringField(portal, "title") || uid,
|
|
149
|
+
portalType: readStringField(portal, "portalType") || null
|
|
150
|
+
};
|
|
151
|
+
}),
|
|
152
|
+
uiBuilderAllowed: false,
|
|
153
|
+
adminLayoutFallbackAllowed: false,
|
|
154
|
+
agentInstruction: "Stop this Flow Surfaces write and ask the user to select one candidate before retrying; do not choose by Portal type or fall back to the Admin layout."
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return this.resolvePortal(readStringField(inventory.accessible[0], "uid"), {
|
|
159
|
+
...options,
|
|
160
|
+
path: "navigation"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
99
163
|
async resolvePortal(portalUidValue, options) {
|
|
100
164
|
const portalUid = this.normalizePortalUid(portalUidValue);
|
|
101
165
|
if (!portalUid) {
|
|
@@ -111,19 +175,9 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
111
175
|
details: { portalUid }
|
|
112
176
|
});
|
|
113
177
|
}
|
|
114
|
-
if (RESERVED_MULTI_PORTAL_UIDS.has(portalUid)) {
|
|
115
|
-
(0, import_errors.throwBadRequest)(
|
|
116
|
-
`flowSurfaces ${options.actionName} ${options.path} cannot use reserved default portal uid '${portalUid}'; use layoutUid semantics instead`,
|
|
117
|
-
{
|
|
118
|
-
ruleId: "navigation-portal-reserved",
|
|
119
|
-
path: options.path,
|
|
120
|
-
details: { portalUid }
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
178
|
const portal = await this.db.getRepository("multiPortals").findOne({
|
|
125
179
|
filter: { uid: portalUid },
|
|
126
|
-
fields: ["uid", "title", "icon", "
|
|
180
|
+
fields: ["uid", "title", "icon", "portalType", "portalName", "routePath", "authCheck", "enabled", "uiLayoutUid"],
|
|
127
181
|
transaction: options.transaction
|
|
128
182
|
});
|
|
129
183
|
if (!portal) {
|
|
@@ -133,6 +187,18 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
133
187
|
details: { portalUid }
|
|
134
188
|
});
|
|
135
189
|
}
|
|
190
|
+
const routeScopeKind = isDefaultLayoutMultiPortalUid(portalUid) ? "layout" : "portal";
|
|
191
|
+
if (routeScopeKind === "portal" && !await this.canAccessPortal(portalUid, options.currentRoles, options.transaction)) {
|
|
192
|
+
(0, import_errors.throwForbidden)(
|
|
193
|
+
`flowSurfaces ${options.actionName} current roles cannot access portal '${portalUid}'`,
|
|
194
|
+
"FLOW_SURFACE_NAVIGATION_PORTAL_FORBIDDEN",
|
|
195
|
+
{
|
|
196
|
+
ruleId: "navigation-portal-forbidden",
|
|
197
|
+
path: options.path,
|
|
198
|
+
details: { portalUid }
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
}
|
|
136
202
|
if (readRecordField(portal, "enabled") !== true) {
|
|
137
203
|
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} portal '${portalUid}' is disabled`, {
|
|
138
204
|
ruleId: "navigation-portal-disabled",
|
|
@@ -140,6 +206,33 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
140
206
|
details: { portalUid }
|
|
141
207
|
});
|
|
142
208
|
}
|
|
209
|
+
const portalType = readStringField(portal, "portalType");
|
|
210
|
+
if (portalType !== "no-code") {
|
|
211
|
+
if (portalType === "ai") {
|
|
212
|
+
(0, import_errors.throwBadRequest)(
|
|
213
|
+
`flowSurfaces ${options.actionName} cannot use UI Builder or Flow Surfaces for AI Portal '${portalUid}'; stop and implement the request in the Portal source code`,
|
|
214
|
+
{
|
|
215
|
+
ruleId: "navigation-portal-type-unsupported",
|
|
216
|
+
path: options.path,
|
|
217
|
+
details: {
|
|
218
|
+
portalUid,
|
|
219
|
+
portalType,
|
|
220
|
+
portalName: readStringField(portal, "portalName") || null,
|
|
221
|
+
expectedPortalType: "no-code",
|
|
222
|
+
uiBuilderAllowed: false,
|
|
223
|
+
adminLayoutFallbackAllowed: false,
|
|
224
|
+
implementationPath: "ai-portal-source",
|
|
225
|
+
agentInstruction: "Stop this Flow Surfaces write. Run nb portal info <portalName> -j to resolve localPath, then implement the request in that AI Portal source code. Do not retry with an Admin layout."
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} portal '${portalUid}' does not support no-code routes`, {
|
|
231
|
+
ruleId: "navigation-portal-type-unsupported",
|
|
232
|
+
path: options.path,
|
|
233
|
+
details: { portalUid, portalType: portalType || null }
|
|
234
|
+
});
|
|
235
|
+
}
|
|
143
236
|
const layoutUid = readStringField(portal, "uiLayoutUid");
|
|
144
237
|
if (!layoutUid || !this.db.getCollection("uiLayouts")) {
|
|
145
238
|
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} portal '${portalUid}' has no available backing UI layout`, {
|
|
@@ -173,27 +266,18 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
173
266
|
}
|
|
174
267
|
);
|
|
175
268
|
}
|
|
176
|
-
if (!await this.canAccessPortal(portalUid, options.currentRoles, options.transaction)) {
|
|
177
|
-
(0, import_errors.throwForbidden)(
|
|
178
|
-
`flowSurfaces ${options.actionName} current roles cannot access portal '${portalUid}'`,
|
|
179
|
-
"FLOW_SURFACE_NAVIGATION_PORTAL_FORBIDDEN",
|
|
180
|
-
{
|
|
181
|
-
ruleId: "navigation-portal-forbidden",
|
|
182
|
-
path: options.path,
|
|
183
|
-
details: { portalUid }
|
|
184
|
-
}
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
269
|
return {
|
|
188
270
|
uid: portalUid,
|
|
189
271
|
title: readStringField(portal, "title") || portalUid,
|
|
190
272
|
icon: readStringField(portal, "icon") || null,
|
|
191
|
-
routeName: readStringField(portal, "
|
|
273
|
+
routeName: readStringField(portal, "portalName"),
|
|
192
274
|
routePath: readStringField(portal, "routePath"),
|
|
193
275
|
authCheck: readRecordField(portal, "authCheck") === true,
|
|
194
276
|
enabled: true,
|
|
195
277
|
layoutUid,
|
|
196
|
-
layoutType: readStringField(layout, "layoutType")
|
|
278
|
+
layoutType: readStringField(layout, "layoutType"),
|
|
279
|
+
portalType,
|
|
280
|
+
routeScopeKind
|
|
197
281
|
};
|
|
198
282
|
}
|
|
199
283
|
async readRoutePortalUids(routeId, transaction) {
|
|
@@ -293,43 +377,48 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
293
377
|
return left.uid.localeCompare(right.uid);
|
|
294
378
|
});
|
|
295
379
|
}
|
|
296
|
-
async
|
|
380
|
+
async getEnabledPortalInventory(currentRoles, transaction) {
|
|
381
|
+
const enabled = await this.db.getRepository("multiPortals").find({
|
|
382
|
+
filter: { enabled: true },
|
|
383
|
+
fields: ["uid", "title", "icon", "portalType", "portalName", "routePath", "authCheck", "enabled", "uiLayoutUid"],
|
|
384
|
+
sort: ["uid"],
|
|
385
|
+
transaction
|
|
386
|
+
});
|
|
387
|
+
enabled.sort(
|
|
388
|
+
(left, right) => String(readStringField(left, "uid") || "").localeCompare(String(readStringField(right, "uid") || ""))
|
|
389
|
+
);
|
|
297
390
|
const roles = normalizeRoles(currentRoles);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
return [];
|
|
302
|
-
}
|
|
391
|
+
const isRoot = roles.includes("root");
|
|
392
|
+
const accessiblePortalUids = /* @__PURE__ */ new Set();
|
|
393
|
+
if (!isRoot && roles.length && this.db.getCollection("rolesMultiPortals")) {
|
|
303
394
|
const grants = await this.db.getRepository("rolesMultiPortals").find({
|
|
304
|
-
fields: ["multiPortalUid"],
|
|
305
395
|
filter: { roleName: roles },
|
|
396
|
+
fields: ["multiPortalUid"],
|
|
306
397
|
transaction
|
|
307
398
|
});
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (!accessiblePortalUids.length) {
|
|
314
|
-
return [];
|
|
399
|
+
for (const grant of grants) {
|
|
400
|
+
const portalUid = readStringField(grant, "multiPortalUid");
|
|
401
|
+
if (portalUid) {
|
|
402
|
+
accessiblePortalUids.add(portalUid);
|
|
403
|
+
}
|
|
315
404
|
}
|
|
316
405
|
}
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
"uid.$notIn": Array.from(RESERVED_MULTI_PORTAL_UIDS)
|
|
321
|
-
};
|
|
322
|
-
const portals = await this.db.getRepository("multiPortals").find({
|
|
323
|
-
filter,
|
|
324
|
-
fields: ["uid", "title", "icon", "routeName", "routePath", "authCheck", "enabled", "uiLayoutUid"],
|
|
325
|
-
sort: ["uid"],
|
|
326
|
-
transaction
|
|
406
|
+
const accessible = enabled.filter((portal) => {
|
|
407
|
+
const portalUid = readStringField(portal, "uid");
|
|
408
|
+
return !!portalUid && (isRoot || isDefaultLayoutMultiPortalUid(portalUid) || accessiblePortalUids.has(portalUid));
|
|
327
409
|
});
|
|
410
|
+
return { enabled, accessible };
|
|
411
|
+
}
|
|
412
|
+
async listAccessiblePortalTargets(portals, transaction) {
|
|
328
413
|
const targets = [];
|
|
329
414
|
for (const portal of portals) {
|
|
330
415
|
const portalUid = readStringField(portal, "uid");
|
|
331
416
|
const layoutUid = readStringField(portal, "uiLayoutUid");
|
|
332
|
-
|
|
417
|
+
const portalType = readStringField(portal, "portalType");
|
|
418
|
+
if (!portalUid || portalType !== "no-code") {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (!layoutUid || !this.db.getCollection("uiLayouts")) {
|
|
333
422
|
continue;
|
|
334
423
|
}
|
|
335
424
|
const layout = await this.db.getRepository("uiLayouts").findOne({
|
|
@@ -348,12 +437,30 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
348
437
|
icon: readStringField(portal, "icon") || null,
|
|
349
438
|
layoutUid,
|
|
350
439
|
layoutType: readStringField(layout, "layoutType"),
|
|
351
|
-
routeName: readStringField(portal, "
|
|
440
|
+
routeName: readStringField(portal, "portalName"),
|
|
352
441
|
routePath: readStringField(portal, "routePath"),
|
|
353
442
|
authCheck: readRecordField(portal, "authCheck") === true
|
|
354
443
|
});
|
|
355
444
|
}
|
|
356
|
-
return targets;
|
|
445
|
+
return targets.sort((left, right) => this.comparePortalPriority(left, right));
|
|
446
|
+
}
|
|
447
|
+
comparePortalPriority(left, right) {
|
|
448
|
+
const priorityDelta = this.getPortalPriority(left) - this.getPortalPriority(right);
|
|
449
|
+
if (priorityDelta) {
|
|
450
|
+
return priorityDelta;
|
|
451
|
+
}
|
|
452
|
+
return String(readStringField(left, "uid") || "").localeCompare(String(readStringField(right, "uid") || ""));
|
|
453
|
+
}
|
|
454
|
+
getPortalPriority(portal) {
|
|
455
|
+
const uiLayout = readRecordField(portal, "uiLayout");
|
|
456
|
+
const layoutType = readStringField(portal, "layoutType") || readStringField(uiLayout, "layoutType");
|
|
457
|
+
if (readStringField(portal, "uid") === DEFAULT_ADMIN_MULTI_PORTAL_UID) {
|
|
458
|
+
return 0;
|
|
459
|
+
}
|
|
460
|
+
if (layoutType !== "mobile") {
|
|
461
|
+
return 1;
|
|
462
|
+
}
|
|
463
|
+
return 2;
|
|
357
464
|
}
|
|
358
465
|
async canAccessPortal(portalUid, currentRoles, transaction) {
|
|
359
466
|
const roles = normalizeRoles(currentRoles);
|
|
@@ -96,6 +96,12 @@ export declare class FlowSurfacesService {
|
|
|
96
96
|
private resolveDesktopRouteLayoutUidFilter;
|
|
97
97
|
private assertNavigationTargetIsExclusive;
|
|
98
98
|
private readDesktopRouteScope;
|
|
99
|
+
private buildDesktopRouteScopeForPortal;
|
|
100
|
+
private assertAdminLayoutCreateScope;
|
|
101
|
+
private getPortalRouteFilterUid;
|
|
102
|
+
private assertRouteBelongsToResolvedPortal;
|
|
103
|
+
private resolveDefaultDesktopRouteScope;
|
|
104
|
+
private resolveDesktopRouteMatchScope;
|
|
99
105
|
private resolveRequestedDesktopRouteScope;
|
|
100
106
|
private resolveExistingDesktopRouteScope;
|
|
101
107
|
private attachDesktopRouteScopeToRouteAndChildren;
|
|
@@ -227,6 +233,7 @@ export declare class FlowSurfacesService {
|
|
|
227
233
|
private dispatchPlanOnlyAction;
|
|
228
234
|
private resolveApplyBlueprintReplaceTargetInfo;
|
|
229
235
|
private resolveApplyBlueprintCreateNavigationGroup;
|
|
236
|
+
private resolveApplyBlueprintDefaultNavigationTarget;
|
|
230
237
|
private normalizeApplyBlueprintCreateMobileNavigation;
|
|
231
238
|
private assertApplyBlueprintCreateNavigationTarget;
|
|
232
239
|
private resolveApplyBlueprintCreatePageIdentity;
|