@nocobase/plugin-flow-engine 2.1.0-beta.37 → 2.1.0-beta.38

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 (31) hide show
  1. package/dist/externalVersion.js +9 -9
  2. package/dist/node_modules/@ant-design/icons-svg/package.json +1 -1
  3. package/dist/node_modules/acorn/package.json +1 -1
  4. package/dist/node_modules/acorn-jsx/package.json +1 -1
  5. package/dist/node_modules/acorn-walk/package.json +1 -1
  6. package/dist/node_modules/ses/package.json +1 -1
  7. package/dist/node_modules/zod/package.json +1 -1
  8. package/dist/server/flow-surfaces/apply/compiler.js +18 -3
  9. package/dist/server/flow-surfaces/apply/matching.js +2 -0
  10. package/dist/server/flow-surfaces/authoring-validation.js +621 -66
  11. package/dist/server/flow-surfaces/blueprint/compile-blocks.js +21 -3
  12. package/dist/server/flow-surfaces/blueprint/compile-plan.js +9 -9
  13. package/dist/server/flow-surfaces/catalog.js +3 -0
  14. package/dist/server/flow-surfaces/chart-config.js +14 -1
  15. package/dist/server/flow-surfaces/compose-compiler.d.ts +2 -0
  16. package/dist/server/flow-surfaces/compose-compiler.js +2 -0
  17. package/dist/server/flow-surfaces/compose-runtime.js +4 -7
  18. package/dist/server/flow-surfaces/configure-options.js +18 -7
  19. package/dist/server/flow-surfaces/locator.js +16 -2
  20. package/dist/server/flow-surfaces/runjs-authoring/index.js +5909 -2214
  21. package/dist/server/flow-surfaces/service-utils.d.ts +2 -0
  22. package/dist/server/flow-surfaces/service-utils.js +8 -0
  23. package/dist/server/flow-surfaces/service.d.ts +22 -0
  24. package/dist/server/flow-surfaces/service.js +722 -59
  25. package/dist/server/flow-surfaces/template-service-utils.d.ts +1 -0
  26. package/dist/swagger/flow-surfaces.d.ts +4 -2
  27. package/dist/swagger/flow-surfaces.examples.d.ts +6 -17
  28. package/dist/swagger/flow-surfaces.examples.js +5 -5
  29. package/dist/swagger/flow-surfaces.js +5 -3
  30. package/dist/swagger/index.d.ts +4 -2
  31. package/package.json +2 -2
@@ -42,6 +42,7 @@ module.exports = __toCommonJS(service_exports);
42
42
  var import_crypto = require("crypto");
43
43
  var import_utils = require("@nocobase/utils");
44
44
  var import_lodash = __toESM(require("lodash"));
45
+ var antDesignIconAsn = __toESM(require("@ant-design/icons-svg"));
45
46
  var import_repository = __toESM(require("../repository"));
46
47
  var import_catalog = require("./catalog");
47
48
  var import_action_scope = require("./action-scope");
@@ -102,6 +103,56 @@ var import_template_service_utils = require("./template-service-utils");
102
103
  var import_hidden_popup_contract = require("./hidden-popup-contract");
103
104
  var import_hidden_popup_calendar = require("./hidden-popup-calendar");
104
105
  var import_hidden_popup_kanban = require("./hidden-popup-kanban");
106
+ const FLOW_SURFACE_CHART_REPAIR_HINT = "This is a chart payload shape problem. Repair the current chart block payload using assets.charts.<key>.query/visual plus block.chart, or localized settings.query/settings.visual. Do not change this block type to table, jsBlock, actionPanel, gridCard, or another block type. Do not drop or defer the chart. KPI / summary numbers should use jsBlock; charts are for trends, distributions, rankings, and visual analysis.";
107
+ const FLOW_SURFACE_CHART_REPAIR_STEPS = [
108
+ "Keep the block type as chart.",
109
+ "Define assets.charts.<key>.query and assets.charts.<key>.visual, or repair the localized settings.query/settings.visual on the existing chart block.",
110
+ "Reference the asset from the chart block with block.chart = <key> when using assets.charts.",
111
+ "Retry the chart payload instead of replacing the chart with another block type, omitting it, or deferring it."
112
+ ];
113
+ const FLOW_SURFACE_CHART_EXPECTED_SHAPE = {
114
+ assets: {
115
+ charts: {
116
+ chartKey: {
117
+ query: "builder/sql query configuration",
118
+ visual: "basic/custom visual configuration"
119
+ }
120
+ }
121
+ },
122
+ block: {
123
+ type: "chart",
124
+ chart: "chartKey"
125
+ }
126
+ };
127
+ const FLOW_SURFACE_CHART_FORBIDDEN_FALLBACKS = [
128
+ "table",
129
+ "jsBlock",
130
+ "actionPanel",
131
+ "gridCard",
132
+ "drop chart",
133
+ "defer chart"
134
+ ];
135
+ function withChartRepairMessage(message) {
136
+ return `${message}. ${FLOW_SURFACE_CHART_REPAIR_HINT}`;
137
+ }
138
+ function withFlowSurfaceChartRepairDetails(details = {}) {
139
+ return {
140
+ ...details,
141
+ repairHint: FLOW_SURFACE_CHART_REPAIR_HINT,
142
+ repairSteps: FLOW_SURFACE_CHART_REPAIR_STEPS,
143
+ expectedShape: FLOW_SURFACE_CHART_EXPECTED_SHAPE,
144
+ forbiddenFallbacks: FLOW_SURFACE_CHART_FORBIDDEN_FALLBACKS
145
+ };
146
+ }
147
+ function throwChartRepairBadRequest(message, details = {}) {
148
+ (0, import_errors.throwBadRequest)(withChartRepairMessage(message), {
149
+ details: withFlowSurfaceChartRepairDetails(details)
150
+ });
151
+ }
152
+ function isFlowSurfaceChartRepairError(error) {
153
+ var _a, _b;
154
+ return error instanceof import_errors.FlowSurfaceBadRequestError && ((_b = (_a = error.options) == null ? void 0 : _a.details) == null ? void 0 : _b.repairHint) === FLOW_SURFACE_CHART_REPAIR_HINT;
155
+ }
105
156
  const FORM_BLOCK_USES = /* @__PURE__ */ new Set(["FormBlockModel", "CreateFormModel", "EditFormModel", ...import_approval.APPROVAL_FORM_BLOCK_USES]);
106
157
  const AUTO_SUBMIT_FORM_BLOCK_USES = /* @__PURE__ */ new Set(["CreateFormModel", "EditFormModel"]);
107
158
  const FLOW_SURFACE_DEFAULT_ACTION_SETTINGS_KEYS = /* @__PURE__ */ new Set(["filter"]);
@@ -251,6 +302,17 @@ const JS_ACTION_USES = /* @__PURE__ */ new Set([
251
302
  "JSActionModel"
252
303
  ]);
253
304
  const JS_ITEM_ACTION_USES = /* @__PURE__ */ new Set(["JSItemActionModel"]);
305
+ const JS_POPUP_GUIDANCE_MESSAGE = "should use ctx.openView to open popup";
306
+ const JS_POPUP_GUIDANCE_USES = /* @__PURE__ */ new Set([
307
+ ...JS_ACTION_USES,
308
+ ...JS_ITEM_ACTION_USES,
309
+ "JSColumnModel",
310
+ "JSItemModel",
311
+ "JSFieldModel",
312
+ "JSEditableFieldModel",
313
+ "FormJSFieldItemModel"
314
+ ]);
315
+ const JS_POPUP_GUIDANCE_PUBLIC_KEYS = /* @__PURE__ */ new Set(["js", "jsColumn", "jsItem"]);
254
316
  const POPUP_ACTION_USES = /* @__PURE__ */ new Set([
255
317
  "AddNewActionModel",
256
318
  "ViewActionModel",
@@ -264,6 +326,24 @@ const POPUP_ACTION_USES = /* @__PURE__ */ new Set([
264
326
  "AddChildActionModel",
265
327
  "MailSendActionModel"
266
328
  ]);
329
+ function withJsPopupGuidance(message, value) {
330
+ const normalizedValue = String(value || "").trim();
331
+ if (JS_POPUP_GUIDANCE_USES.has(normalizedValue) || JS_POPUP_GUIDANCE_PUBLIC_KEYS.has(normalizedValue)) {
332
+ return `${message}; ${JS_POPUP_GUIDANCE_MESSAGE}`;
333
+ }
334
+ return message;
335
+ }
336
+ function assertNoJsDeclarativeOpenView(context, changes, use) {
337
+ const normalizedUse = String(use || "").trim();
338
+ if ((0, import_service_utils.hasOwnDefined)(changes, "openView") && JS_POPUP_GUIDANCE_USES.has(normalizedUse)) {
339
+ (0, import_errors.throwBadRequest)(
340
+ withJsPopupGuidance(
341
+ `flowSurfaces configure ${context} '${normalizedUse}' does not support openView`,
342
+ normalizedUse
343
+ )
344
+ );
345
+ }
346
+ }
267
347
  const POPUP_HOST_DEFAULT_RECORD_CONTEXT_ACTION_USES = /* @__PURE__ */ new Set([
268
348
  "ViewActionModel",
269
349
  "EditActionModel",
@@ -457,6 +537,7 @@ const UPDATE_SETTINGS_STEP_PARAM_MIRRORS_BY_USE = {
457
537
  DividerItemModel: DIVIDER_ITEM_STEP_PARAM_MIRRORS
458
538
  };
459
539
  const FLOW_SURFACE_MENU_BINDABLE_OPTION_KEY = "flowSurfaceMenuBindable";
540
+ const ANT_DESIGN_ICON_NAMES = new Set(Object.keys(antDesignIconAsn || {}));
460
541
  const AI_EMPLOYEE_ACTION_USE = "AIEmployeeButtonModel";
461
542
  const AI_EMPLOYEE_OWNER_PLUGIN = "@nocobase/plugin-ai";
462
543
  const AI_EMPLOYEE_PUBLIC_SETTING_KEYS = ["username", "auto", "workContext", "tasks", "style"];
@@ -464,10 +545,13 @@ const AI_EMPLOYEE_INTERNAL_PROP_KEYS = ["aiEmployee", "context", "auto", "tasks"
464
545
  const AI_EMPLOYEE_TASK_STEP_PARAMS_PATH = ["shortcutSettings", "editTasks", "tasks"];
465
546
  const AI_EMPLOYEE_WORK_CONTEXT_PUBLIC_KEYS = ["type", "uid", "target"];
466
547
  const AI_EMPLOYEE_TASK_PUBLIC_SETTING_KEYS = ["title", "message", "autoSend", "skillSettings", "model", "webSearch"];
548
+ const AI_EMPLOYEE_TASK_PATCH_PUBLIC_SETTING_KEYS = [...AI_EMPLOYEE_TASK_PUBLIC_SETTING_KEYS, "prompt"];
467
549
  const AI_EMPLOYEE_TASK_MESSAGE_PUBLIC_KEYS = ["system", "user", "workContext"];
468
550
  const AI_EMPLOYEE_TASK_MODEL_PUBLIC_KEYS = ["llmService", "model"];
469
551
  const AI_EMPLOYEE_SKILL_SETTINGS_PUBLIC_KEYS = ["skills", "tools", "skillsVersion", "toolsVersion"];
470
552
  const AI_EMPLOYEE_STYLE_PUBLIC_KEYS = ["size", "mask"];
553
+ const AI_EMPLOYEE_WORK_CONTEXT_REPAIR_HINT = 'Use workContext entries like { "target": "self" } or { "uid": "<flow-model-uid>" }. The only supported type is flow-model, so type is optional and defaults to flow-model.';
554
+ const AI_EMPLOYEE_TASK_REPAIR_HINT = "Use task keys title, message, prompt, autoSend, skillSettings, model, and webSearch. Put user instructions in tasks[n].message.user or the prompt alias; do not write raw props, stepParams, or prompt beside message.user.";
471
555
  const AI_EMPLOYEE_PROMPT_CONTEXT_MAX_DEPTH = 8;
472
556
  const AI_EMPLOYEE_PROMPT_VARIABLE_INVALID = "FLOW_SURFACE_AI_EMPLOYEE_PROMPT_VARIABLE_INVALID";
473
557
  const AI_EMPLOYEE_CURRENT_RECORD_PROMPT_VARIABLE = "{{ ctx.record }}";
@@ -773,6 +857,18 @@ class FlowSurfacesService {
773
857
  })
774
858
  );
775
859
  }
860
+ routeParentIdMatches(routeParentId, parentId) {
861
+ if (import_lodash.default.isNil(routeParentId) && import_lodash.default.isNil(parentId)) {
862
+ return true;
863
+ }
864
+ return String(routeParentId ?? "") === String(parentId ?? "");
865
+ }
866
+ async findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction) {
867
+ const routes = await this.findMenuGroupRoutesByTitle(title, transaction);
868
+ return routes.filter(
869
+ (route) => this.routeParentIdMatches(this.readRouteField(route, "parentId") ?? null, parentId)
870
+ );
871
+ }
776
872
  async findFlowPageRoutesByParentIdAndTitle(parentId, title, transaction) {
777
873
  const normalizedTitle = String(title || "").trim();
778
874
  const normalizedParentId = String(parentId ?? "").trim();
@@ -854,20 +950,97 @@ class FlowSurfacesService {
854
950
  ...extras
855
951
  };
856
952
  }
953
+ isValidMenuIconName(value) {
954
+ const normalized = String(value || "").trim();
955
+ return !!normalized && ANT_DESIGN_ICON_NAMES.has(normalized);
956
+ }
957
+ assertVisibleNavigationIcon(actionName, path, values) {
958
+ if (values.hideInMenu === true) {
959
+ return;
960
+ }
961
+ const icon = String(values.icon || "").trim();
962
+ if (!icon) {
963
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path}.icon is required when creating a visible menu route`, {
964
+ ruleId: "navigation-icon-required",
965
+ path: `${path}.icon`,
966
+ details: {
967
+ repairHint: "Pass a valid Ant Design icon name such as AppstoreOutlined, DashboardOutlined, or FolderOpenOutlined."
968
+ }
969
+ });
970
+ }
971
+ if (!this.isValidMenuIconName(icon)) {
972
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path}.icon must be a valid Ant Design icon name`, {
973
+ ruleId: "navigation-icon-unknown",
974
+ path: `${path}.icon`,
975
+ details: {
976
+ icon
977
+ }
978
+ });
979
+ }
980
+ }
981
+ assertVisibleNavigationRouteIconUpdate(actionName, path, route, values) {
982
+ if (!Object.prototype.hasOwnProperty.call(values, "icon") || this.readRouteField(route, "hideInMenu") === true) {
983
+ return;
984
+ }
985
+ const icon = String(values.icon || "").trim();
986
+ if (!icon) {
987
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path}.icon cannot be empty for a visible menu route`, {
988
+ ruleId: "navigation-icon-required",
989
+ path: `${path}.icon`,
990
+ details: {
991
+ repairHint: "Pass a valid Ant Design icon name such as AppstoreOutlined, DashboardOutlined, or FolderOpenOutlined."
992
+ }
993
+ });
994
+ }
995
+ if (!this.isValidMenuIconName(icon)) {
996
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path}.icon must be a valid Ant Design icon name`, {
997
+ ruleId: "navigation-icon-unknown",
998
+ path: `${path}.icon`,
999
+ details: {
1000
+ icon
1001
+ }
1002
+ });
1003
+ }
1004
+ }
857
1005
  async createFlowMenuGroup(values, transaction) {
858
1006
  const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, transaction);
1007
+ const parentId = this.readRouteField(parentRoute, "id") ?? null;
1008
+ const title = String(values.title || "").trim();
1009
+ const existingGroups = await this.findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction);
1010
+ if (existingGroups.length === 1) {
1011
+ return this.buildMenuResult(existingGroups[0]);
1012
+ }
1013
+ if (existingGroups.length > 1) {
1014
+ (0, import_errors.throwBadRequest)(
1015
+ `flowSurfaces createMenu group title '${title}' is ambiguous under parentMenuRouteId '${values.parentMenuRouteId ?? "root"}'; use an explicit existing navigation.group.routeId or clean up duplicate menu groups before retrying`,
1016
+ {
1017
+ ruleId: "menu-group-title-ambiguous-under-parent",
1018
+ details: {
1019
+ title,
1020
+ parentMenuRouteId: values.parentMenuRouteId ?? null,
1021
+ matches: existingGroups.map((route2) => ({
1022
+ routeId: this.readRouteField(route2, "id"),
1023
+ parentMenuRouteId: this.readRouteField(route2, "parentId") ?? null,
1024
+ type: this.readRouteField(route2, "type"),
1025
+ schemaUid: this.readRouteField(route2, "schemaUid") ?? null
1026
+ }))
1027
+ }
1028
+ }
1029
+ );
1030
+ }
1031
+ this.assertVisibleNavigationIcon("createMenu", "values", values);
859
1032
  const schemaUid = values.schemaUid || (0, import_utils.uid)();
860
1033
  const desktopRoutes = this.db.getRepository("desktopRoutes");
861
1034
  await desktopRoutes.create({
862
1035
  values: {
863
1036
  type: "group",
864
1037
  sort: this.allocateRouteSortValue(),
865
- title: values.title,
1038
+ title,
866
1039
  icon: values.icon,
867
1040
  tooltip: values.tooltip,
868
1041
  schemaUid,
869
1042
  hideInMenu: !!values.hideInMenu,
870
- parentId: this.readRouteField(parentRoute, "id") ?? null
1043
+ parentId
871
1044
  },
872
1045
  transaction
873
1046
  });
@@ -880,6 +1053,7 @@ class FlowSurfacesService {
880
1053
  async createFlowMenuItem(values, transaction) {
881
1054
  var _a;
882
1055
  const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, transaction);
1056
+ this.assertVisibleNavigationIcon("createMenu", "values", values);
883
1057
  const pageSchemaUid = values.pageSchemaUid || (0, import_utils.uid)();
884
1058
  const pageUid = values.pageUid || (0, import_utils.uid)();
885
1059
  const tabSchemaUid = values.tabSchemaUid || (0, import_utils.uid)();
@@ -2607,6 +2781,10 @@ class FlowSurfacesService {
2607
2781
  ),
2608
2782
  options.transaction
2609
2783
  );
2784
+ if (!(rawNode == null ? void 0 : rawNode.uid)) {
2785
+ const resolvedUid = String((resolved == null ? void 0 : resolved.uid) || "").trim() || String(target.uid || target.tabSchemaUid || target.pageSchemaUid || target.routeId || "").trim() || "unknown";
2786
+ (0, import_errors.throwBadRequest)(`flowSurfaces:get target '${resolvedUid}' could not resolve a readable surface tree`);
2787
+ }
2610
2788
  const publicNode = this.stripInternalSurfaceMetaFromNodeTree(import_lodash.default.cloneDeep(rawNode));
2611
2789
  return this.buildSurfaceReadPayload(target, resolved, publicNode, options);
2612
2790
  }
@@ -2778,13 +2956,13 @@ class FlowSurfacesService {
2778
2956
  if (!groupTitle) {
2779
2957
  return document;
2780
2958
  }
2781
- const matchedRoutes = await this.findMenuGroupRoutesByTitle(groupTitle, transaction);
2959
+ const matchedRoutes = await this.findMenuGroupRoutesByParentIdAndTitle(null, groupTitle, transaction);
2782
2960
  if (!matchedRoutes.length) {
2783
2961
  return document;
2784
2962
  }
2785
2963
  if (matchedRoutes.length > 1) {
2786
2964
  (0, import_errors.throwBadRequest)(
2787
- `flowSurfaces applyBlueprint navigation.group.title '${groupTitle}' matches ${matchedRoutes.length} existing menu groups; pass navigation.group.routeId explicitly`
2965
+ `flowSurfaces applyBlueprint navigation.group.title '${groupTitle}' matches ${matchedRoutes.length} existing root menu groups; pass navigation.group.routeId explicitly`
2788
2966
  );
2789
2967
  }
2790
2968
  const routeId = this.readRouteField(matchedRoutes[0], "id");
@@ -3332,6 +3510,12 @@ class FlowSurfacesService {
3332
3510
  options
3333
3511
  );
3334
3512
  const pageLocator = (0, import_blueprint.resolveApplyBlueprintPageLocator)(prepared, result);
3513
+ await this.ensureSurfaceTableDefaultActionIntegrity(pageLocator, {
3514
+ ...options,
3515
+ enabledPackages: await this.resolveEnabledPluginPackages(options),
3516
+ popupTemplateAliasSession,
3517
+ popupTemplateTreeCache
3518
+ });
3335
3519
  if (resultOptions.readSurface === false) {
3336
3520
  return {
3337
3521
  version: "1",
@@ -4008,8 +4192,8 @@ class FlowSurfacesService {
4008
4192
  stepParams: (0, import_template_service_utils.buildFlowTemplateReferenceBlockStepParams)(template, templateTargetUid, init)
4009
4193
  };
4010
4194
  }
4011
- buildPopupTemplateReferenceOpenView(template, templateTargetUid) {
4012
- return this.buildPopupTemplateOpenView(template, "reference", templateTargetUid);
4195
+ buildPopupTemplateReferenceOpenView(template, templateTargetUid, options = {}) {
4196
+ return this.buildPopupTemplateOpenView(template, "reference", templateTargetUid, options);
4013
4197
  }
4014
4198
  shouldHydrateDetachedPopupTemplateBlockResourceContext(blockNode, context) {
4015
4199
  if (!this.isCollectionBlockUse(blockNode == null ? void 0 : blockNode.use) || !context.associationName || !context.sourceId) {
@@ -4359,15 +4543,35 @@ class FlowSurfacesService {
4359
4543
  }
4360
4544
  const nextStepParams = import_lodash.default.cloneDeep(sourceNode.stepParams || {});
4361
4545
  const currentGroup = import_lodash.default.isPlainObject(nextStepParams[openViewStep.flowKey]) ? import_lodash.default.cloneDeep(nextStepParams[openViewStep.flowKey]) : {};
4546
+ const popupProfile = await this.resolvePopupBlockProfile(sourceNode.uid, null, sourceNode, transaction).catch(
4547
+ () => null
4548
+ );
4549
+ const useRuntimeRecordFilterByTk = (popupProfile == null ? void 0 : popupProfile.hasCurrentRecord) === true;
4550
+ const filterTargetKey = useRuntimeRecordFilterByTk ? await this.resolveOpenViewRuntimeRecordFilterTargetKey(openViewStep.openView, template, {
4551
+ transaction,
4552
+ popupTemplateHostUid: sourceNode.uid,
4553
+ popupActionContext: {
4554
+ hasCurrentRecord: true
4555
+ }
4556
+ }) : void 0;
4557
+ const currentOpenView = import_lodash.default.omit(openViewStep.openView || {}, [
4558
+ "popupTemplateUid",
4559
+ "popupTemplateMode",
4560
+ "popupTemplateContext",
4561
+ "popupTemplateHasFilterByTk",
4562
+ "popupTemplateHasSourceId"
4563
+ ]);
4564
+ if (useRuntimeRecordFilterByTk && this.shouldOmitRuntimeRecordFilterByTk(currentOpenView.filterByTk, {
4565
+ filterTargetKey
4566
+ })) {
4567
+ delete currentOpenView.filterByTk;
4568
+ }
4362
4569
  let nextOpenView = {
4363
- ...import_lodash.default.omit(openViewStep.openView || {}, [
4364
- "popupTemplateUid",
4365
- "popupTemplateMode",
4366
- "popupTemplateContext",
4367
- "popupTemplateHasFilterByTk",
4368
- "popupTemplateHasSourceId"
4369
- ]),
4370
- ...this.buildPopupTemplateReferenceOpenView(template, templateTargetUid)
4570
+ ...currentOpenView,
4571
+ ...this.buildPopupTemplateReferenceOpenView(template, templateTargetUid, {
4572
+ filterTargetKey,
4573
+ useRuntimeRecordFilterByTk
4574
+ })
4371
4575
  };
4372
4576
  if (String(sourceNode.use || "").trim() === "AddChildActionModel") {
4373
4577
  nextOpenView = await this.normalizeAddChildOpenViewForAction(sourceNode, nextOpenView, transaction);
@@ -5109,6 +5313,13 @@ class FlowSurfacesService {
5109
5313
  });
5110
5314
  for (const [blockIndex, block] of result.blocks.entries()) {
5111
5315
  const blockSpec = normalizedBlocks.find((item) => item.key === block.key);
5316
+ if ((blockSpec == null ? void 0 : blockSpec.type) === "table") {
5317
+ await this.ensureTableDefaultActionIntegrity(block.uid, {
5318
+ ...runtimeOptions,
5319
+ enabledPackages,
5320
+ popupTemplateAliasSession
5321
+ });
5322
+ }
5112
5323
  if ((blockSpec == null ? void 0 : blockSpec.type) === "table") {
5113
5324
  const appliedTreeTableDefaults = await this.applyTreeTableCreatedBlockDefaults(
5114
5325
  {
@@ -5242,7 +5453,7 @@ class FlowSurfacesService {
5242
5453
  if ((current == null ? void 0 : current.use) === "DividerItemModel") {
5243
5454
  return this.configureDividerItem(target, changes, options);
5244
5455
  }
5245
- return this.configureJSItem(target, changes, options);
5456
+ return this.configureJSItem(target, changes, { ...options, currentUse: (current == null ? void 0 : current.use) || "JSItemModel" });
5246
5457
  }
5247
5458
  if ((0, import_service_utils.isFieldNodeUse)(current == null ? void 0 : current.use)) {
5248
5459
  return this.configureFieldNode(target, changes, configureOptions);
@@ -5317,6 +5528,7 @@ class FlowSurfacesService {
5317
5528
  if (!this.isBindableMenuRoutePendingInitialization(route, structure)) {
5318
5529
  (0, import_errors.throwBadRequest)(`flowSurfaces createPage does not allow re-initializing menu route '${routeId}'`);
5319
5530
  }
5531
+ this.assertVisibleNavigationRouteIconUpdate("createPage", "values", route, values);
5320
5532
  const existingPage = structure.pageModel;
5321
5533
  const enableTabs = !!values.enableTabs;
5322
5534
  const displayTitle = values.displayTitle !== false;
@@ -6396,7 +6608,7 @@ class FlowSurfacesService {
6396
6608
  key: String((values == null ? void 0 : values.key) || catalogItem.key || (values == null ? void 0 : values.type) || (values == null ? void 0 : values.use) || "addBlock_inline").trim() || "addBlock_inline",
6397
6609
  type: catalogItem.key || values.type,
6398
6610
  fields: values.fields,
6399
- fieldsLayout: values.fieldsLayout
6611
+ ...Object.prototype.hasOwnProperty.call(values || {}, "fieldsLayout") ? { fieldsLayout: values.fieldsLayout } : {}
6400
6612
  },
6401
6613
  0,
6402
6614
  enabledPackages,
@@ -6590,6 +6802,8 @@ class FlowSurfacesService {
6590
6802
  ...fieldSpec.popupSize ? { popupSize: fieldSpec.popupSize } : {},
6591
6803
  ...!import_lodash.default.isUndefined(fieldSpec.pageSize) ? { pageSize: fieldSpec.pageSize } : {},
6592
6804
  ...!import_lodash.default.isUndefined(fieldSpec.showIndex) ? { showIndex: fieldSpec.showIndex } : {},
6805
+ ...fieldSpec.defaultTargetUid ? { defaultTargetUid: fieldSpec.defaultTargetUid } : {},
6806
+ ...fieldSpec.targetBlockUid ? { targetBlockUid: fieldSpec.targetBlockUid } : {},
6593
6807
  ...fieldSpec.popup ? { popup: fieldSpec.popup } : {},
6594
6808
  ...fieldSpec.__autoPopupForRelationField ? { __autoPopupForRelationField: true } : {},
6595
6809
  ...fieldSpec[import_defaults.FLOW_SURFACE_APPLY_BLUEPRINT_POPUP_DEFAULTS_KEY] ? {
@@ -6664,6 +6878,13 @@ class FlowSurfacesService {
6664
6878
  enabledPackages
6665
6879
  }
6666
6880
  );
6881
+ if (catalogItem.use === "TableBlockModel" && !options.skipDefaultBlockActions) {
6882
+ await this.ensureTableDefaultActionIntegrity(created, {
6883
+ ...options,
6884
+ enabledPackages,
6885
+ popupTemplateTreeCache: options.popupTemplateTreeCache
6886
+ });
6887
+ }
6667
6888
  if (!options.deferAutoLayout && (initialGrid == null ? void 0 : initialGrid.uid)) {
6668
6889
  const finalGrid = await this.repository.findModelById(parentUid, {
6669
6890
  transaction: options.transaction,
@@ -6731,7 +6952,9 @@ class FlowSurfacesService {
6731
6952
  (0, import_errors.throwBadRequest)("flowSurfaces fieldType is only supported for relation fields");
6732
6953
  }
6733
6954
  if (inlinePopup) {
6734
- (0, import_errors.throwBadRequest)(`flowSurfaces addField type '${values.type}' does not support popup`);
6955
+ (0, import_errors.throwBadRequest)(
6956
+ withJsPopupGuidance(`flowSurfaces addField type '${values.type}' does not support popup`, values.type)
6957
+ );
6735
6958
  }
6736
6959
  if (isFilterFormItem) {
6737
6960
  (0, import_errors.throwBadRequest)(`flowSurfaces addField type '${values.type}' is not allowed under filter-form`);
@@ -6926,9 +7149,18 @@ class FlowSurfacesService {
6926
7149
  }
6927
7150
  }
6928
7151
  if (inlinePopup && !this.isPopupFieldHostUse(boundFieldCapability.fieldUse)) {
6929
- (0, import_errors.throwBadRequest)(`flowSurfaces addField field '${boundFieldCapability.fieldUse}' does not support popup`);
7152
+ (0, import_errors.throwBadRequest)(
7153
+ withJsPopupGuidance(
7154
+ `flowSurfaces addField field '${boundFieldCapability.fieldUse}' does not support popup`,
7155
+ boundFieldCapability.fieldUse
7156
+ )
7157
+ );
6930
7158
  }
6931
- if (values.__autoPopupForRelationField === true && !inlinePopup && this.isPopupFieldHostUse(boundFieldCapability.fieldUse) && ((0, import_service_helpers.isAssociationField)(resolvedField.field) || !!normalizedFieldBinding.associationPathName) && !this.peekInlineFieldSettingsOpenView(inlineSettings, boundFieldCapability.wrapperUse)) {
7159
+ const inlineSettingsOpenView = this.peekInlineFieldSettingsOpenView(
7160
+ inlineSettings,
7161
+ boundFieldCapability.wrapperUse
7162
+ );
7163
+ if (values.__autoPopupForRelationField === true && !inlinePopup && this.isPopupFieldHostUse(boundFieldCapability.fieldUse) && ((0, import_service_helpers.isAssociationField)(resolvedField.field) || !!normalizedFieldBinding.associationPathName) && !inlineSettingsOpenView) {
6932
7164
  const popupDefaultsMetadata = values[import_defaults.FLOW_SURFACE_APPLY_BLUEPRINT_POPUP_DEFAULTS_KEY];
6933
7165
  inlinePopup = this.normalizeInlinePopup("addField", {
6934
7166
  tryTemplate: true,
@@ -6938,9 +7170,19 @@ class FlowSurfacesService {
6938
7170
  } : {}
6939
7171
  });
6940
7172
  }
6941
- if (inlinePopup && this.isExternalPopupOpenView(
6942
- this.peekInlineFieldSettingsOpenView(inlineSettings, boundFieldCapability.wrapperUse)
6943
- )) {
7173
+ const hasExternalInlineSettingsOpenView = this.isExternalPopupOpenView(inlineSettingsOpenView);
7174
+ if (!inlinePopup && this.isPopupFieldHostUse(boundFieldCapability.fieldUse) && !hasExternalInlineSettingsOpenView && (this.isInlineFieldClickToOpenEnabled(inlineSettings, boundFieldCapability.wrapperUse) || import_lodash.default.isPlainObject(inlineSettingsOpenView))) {
7175
+ const popupDefaultsMetadata = values[import_defaults.FLOW_SURFACE_APPLY_BLUEPRINT_POPUP_DEFAULTS_KEY];
7176
+ inlinePopup = this.normalizeInlinePopup("addField", {
7177
+ tryTemplate: true,
7178
+ defaultType: "view",
7179
+ ...import_lodash.default.isPlainObject(inlineSettingsOpenView) ? { openView: import_lodash.default.cloneDeep(inlineSettingsOpenView) } : {},
7180
+ ...popupDefaultsMetadata ? {
7181
+ [import_defaults.FLOW_SURFACE_APPLY_BLUEPRINT_POPUP_DEFAULTS_KEY]: popupDefaultsMetadata
7182
+ } : {}
7183
+ });
7184
+ }
7185
+ if (inlinePopup && hasExternalInlineSettingsOpenView) {
6944
7186
  (0, import_errors.throwBadRequest)(`flowSurfaces addField popup cannot be combined with external openView.uid`);
6945
7187
  }
6946
7188
  const defaultFieldState = (0, import_service_utils.buildDefaultFieldState)(
@@ -7079,7 +7321,12 @@ class FlowSurfacesService {
7079
7321
  );
7080
7322
  }
7081
7323
  if (inlinePopup && !POPUP_ACTION_USES.has(actionCatalogItem.use)) {
7082
- (0, import_errors.throwBadRequest)(`flowSurfaces addAction type '${actionCatalogItem.key}' does not support popup`);
7324
+ (0, import_errors.throwBadRequest)(
7325
+ withJsPopupGuidance(
7326
+ `flowSurfaces addAction type '${actionCatalogItem.key}' does not support popup`,
7327
+ actionCatalogItem.key
7328
+ )
7329
+ );
7083
7330
  }
7084
7331
  await this.assertApprovalActionSingleton(container.parentUid, actionCatalogItem.use, options.transaction);
7085
7332
  (0, import_action_scope.assertRequestedActionScope)({
@@ -7129,6 +7376,12 @@ class FlowSurfacesService {
7129
7376
  actionSettingsPayload,
7130
7377
  aiEmployeeSettingsPayload
7131
7378
  );
7379
+ await this.assertNoDuplicateAIEmployeeAction(
7380
+ "addAction",
7381
+ container.parentUid,
7382
+ actionSettingsPayload,
7383
+ options.transaction
7384
+ );
7132
7385
  }
7133
7386
  const action = (0, import_builder.buildActionTree)({
7134
7387
  use: actionCatalogItem.use,
@@ -7191,7 +7444,12 @@ class FlowSurfacesService {
7191
7444
  );
7192
7445
  const resolvedScope = actionCatalogItem.scope;
7193
7446
  if (inlinePopup && !POPUP_ACTION_USES.has(actionCatalogItem.use)) {
7194
- (0, import_errors.throwBadRequest)(`flowSurfaces addRecordAction type '${actionCatalogItem.key}' does not support popup`);
7447
+ (0, import_errors.throwBadRequest)(
7448
+ withJsPopupGuidance(
7449
+ `flowSurfaces addRecordAction type '${actionCatalogItem.key}' does not support popup`,
7450
+ actionCatalogItem.key
7451
+ )
7452
+ );
7195
7453
  }
7196
7454
  if (this.isAddChildCatalogItem(actionCatalogItem)) {
7197
7455
  await this.assertAddChildSupportedForOwnerNode(container.ownerNode, "addRecordAction", options.transaction);
@@ -7245,6 +7503,12 @@ class FlowSurfacesService {
7245
7503
  actionSettingsPayload,
7246
7504
  aiEmployeeSettingsPayload
7247
7505
  );
7506
+ await this.assertNoDuplicateAIEmployeeAction(
7507
+ "addRecordAction",
7508
+ materializedContainer.parentUid,
7509
+ actionSettingsPayload,
7510
+ options.transaction
7511
+ );
7248
7512
  }
7249
7513
  const action = (0, import_builder.buildActionTree)({
7250
7514
  use: actionCatalogItem.use,
@@ -8181,6 +8445,17 @@ class FlowSurfacesService {
8181
8445
  const { fieldChanges } = (0, import_service_utils.splitComposeFieldChanges)(settings, wrapperUse);
8182
8446
  return fieldChanges.openView;
8183
8447
  }
8448
+ isInlineFieldClickToOpenEnabled(settings, wrapperUse) {
8449
+ if (!settings || !Object.keys(settings).length) {
8450
+ return false;
8451
+ }
8452
+ const { fieldChanges } = (0, import_service_utils.splitComposeFieldChanges)(settings, wrapperUse);
8453
+ if (!Object.prototype.hasOwnProperty.call(fieldChanges, "clickToOpen")) {
8454
+ return false;
8455
+ }
8456
+ const value = fieldChanges.clickToOpen;
8457
+ return value === true || import_lodash.default.isPlainObject(value) && value.clickToOpen === true;
8458
+ }
8184
8459
  async assertOpenViewUidTarget(actionName, uid2, options = {}) {
8185
8460
  const targetNode = await this.repository.findModelById(uid2, {
8186
8461
  transaction: options.transaction,
@@ -8711,15 +8986,71 @@ class FlowSurfacesService {
8711
8986
  sourceId: String(template.sourceId || "").trim() || void 0
8712
8987
  };
8713
8988
  }
8714
- buildPopupTemplateOpenView(template, mode, uidValue) {
8989
+ shouldUseRuntimeRecordFilterByTkForOpenView(openView, options = {}) {
8990
+ var _a;
8991
+ return !!String((openView == null ? void 0 : openView.popupTemplateUid) || "").trim() && ((_a = options.popupActionContext) == null ? void 0 : _a.hasCurrentRecord) === true;
8992
+ }
8993
+ resolveOpenViewFilterTargetKey(openView, template) {
8994
+ const dataSourceKey = String((openView == null ? void 0 : openView.dataSourceKey) || (template == null ? void 0 : template.dataSourceKey) || "main").trim() || "main";
8995
+ const collectionName = String((openView == null ? void 0 : openView.collectionName) || (template == null ? void 0 : template.collectionName) || "").trim();
8996
+ const collection = collectionName ? this.getCollection(dataSourceKey, collectionName) : null;
8997
+ return this.getCollectionFilterTargetKey(collection);
8998
+ }
8999
+ async resolvePopupHostCurrentRecordFilterTargetKey(hostUid, transaction) {
9000
+ var _a, _b, _c;
9001
+ const normalizedHostUid = String(hostUid || "").trim();
9002
+ if (!normalizedHostUid) {
9003
+ return void 0;
9004
+ }
9005
+ const hostNode = await this.repository.findModelById(normalizedHostUid, {
9006
+ transaction,
9007
+ includeAsyncNode: true
9008
+ }).catch(() => null);
9009
+ if (!(hostNode == null ? void 0 : hostNode.uid)) {
9010
+ return void 0;
9011
+ }
9012
+ const hostContext = await this.resolvePopupHostProfileContext(hostNode, transaction).catch(() => null);
9013
+ const ownerResourceInit = ((_a = hostContext == null ? void 0 : hostContext.resourceContext) == null ? void 0 : _a.resourceInit) || {};
9014
+ const dataSourceKey = String(((_b = hostContext == null ? void 0 : hostContext.associationContext) == null ? void 0 : _b.dataSourceKey) || ownerResourceInit.dataSourceKey || "main").trim() || "main";
9015
+ const collectionName = String(
9016
+ ((_c = hostContext == null ? void 0 : hostContext.associationContext) == null ? void 0 : _c.collectionName) || ownerResourceInit.collectionName || ""
9017
+ ).trim();
9018
+ const collection = collectionName ? this.getCollection(dataSourceKey, collectionName) : null;
9019
+ return collection ? this.getCollectionFilterTargetKey(collection) : void 0;
9020
+ }
9021
+ async resolveOpenViewRuntimeRecordFilterTargetKey(openView, template, options = {}) {
9022
+ var _a;
9023
+ const contextKey = String(((_a = options.popupActionContext) == null ? void 0 : _a.currentRecordFilterTargetKey) || "").trim();
9024
+ if (contextKey) {
9025
+ return contextKey;
9026
+ }
9027
+ const hostKey = await this.resolvePopupHostCurrentRecordFilterTargetKey(
9028
+ options.popupTemplateHostUid,
9029
+ options.transaction
9030
+ );
9031
+ return hostKey || this.resolveOpenViewFilterTargetKey(openView, template);
9032
+ }
9033
+ shouldOmitRuntimeRecordFilterByTk(value, options = {}) {
9034
+ const normalized = this.normalizeFlowContextTemplateValue(value);
9035
+ if (!normalized) {
9036
+ return false;
9037
+ }
9038
+ if (normalized === "{{ctx.view.inputArgs.filterByTk}}") {
9039
+ return true;
9040
+ }
9041
+ const filterTargetKey = String(options.filterTargetKey || "").trim();
9042
+ return !!filterTargetKey && normalized === `{{ctx.record.${filterTargetKey}}}`;
9043
+ }
9044
+ buildPopupTemplateOpenView(template, mode, uidValue, options = {}) {
8715
9045
  const popupTemplateHasFilterByTk = !!String(template.filterByTk || "").trim();
8716
9046
  const popupTemplateHasSourceId = !!String(template.sourceId || "").trim() || !!String(template.associationName || "").trim();
9047
+ const omitRuntimeRecordFilterByTk = options.useRuntimeRecordFilterByTk === true && this.shouldOmitRuntimeRecordFilterByTk(template.filterByTk, { filterTargetKey: options.filterTargetKey });
8717
9048
  const base = (0, import_service_utils.buildDefinedPayload)({
8718
9049
  uid: uidValue,
8719
9050
  dataSourceKey: template.dataSourceKey,
8720
9051
  collectionName: template.collectionName,
8721
9052
  associationName: template.associationName,
8722
- ...popupTemplateHasFilterByTk ? { filterByTk: template.filterByTk } : {},
9053
+ ...popupTemplateHasFilterByTk && !omitRuntimeRecordFilterByTk ? { filterByTk: template.filterByTk } : {},
8723
9054
  ...popupTemplateHasSourceId && template.sourceId ? { sourceId: template.sourceId } : {},
8724
9055
  popupTemplateHasFilterByTk,
8725
9056
  popupTemplateHasSourceId,
@@ -8792,7 +9123,7 @@ class FlowSurfacesService {
8792
9123
  }
8793
9124
  }
8794
9125
  async normalizeOpenView(actionName, openView, options = {}) {
8795
- var _a;
9126
+ var _a, _b, _c, _d;
8796
9127
  if (import_lodash.default.isUndefined(openView) || import_lodash.default.isNull(openView)) {
8797
9128
  return openView;
8798
9129
  }
@@ -8859,17 +9190,39 @@ class FlowSurfacesService {
8859
9190
  if (templateRef.mode === "copy") {
8860
9191
  await this.ensurePopupSurface(resolvedUid, options.transaction);
8861
9192
  }
8862
- let nextTemplateOpenView = this.buildPopupTemplateOpenView(template, templateRef.mode, resolvedUid);
9193
+ let runtimeRecordFilterTargetKey;
9194
+ if (((_b = options.popupActionContext) == null ? void 0 : _b.hasCurrentRecord) === true) {
9195
+ runtimeRecordFilterTargetKey = await this.resolveOpenViewRuntimeRecordFilterTargetKey(
9196
+ normalizedOpenView,
9197
+ template,
9198
+ options
9199
+ );
9200
+ }
9201
+ const filterTargetKey = runtimeRecordFilterTargetKey || this.resolveOpenViewFilterTargetKey(normalizedOpenView, template);
9202
+ let nextTemplateOpenView = this.buildPopupTemplateOpenView(template, templateRef.mode, resolvedUid, {
9203
+ filterTargetKey,
9204
+ useRuntimeRecordFilterByTk: ((_c = options.popupActionContext) == null ? void 0 : _c.hasCurrentRecord) === true
9205
+ });
8863
9206
  Object.keys(normalizedOpenView).forEach((key) => {
8864
9207
  if (key === "template" || key === "popupTemplateUid" || key === "popupTemplateMode" || key === "popupTemplateContext" || key === "popupTemplateHasFilterByTk" || key === "popupTemplateHasSourceId") {
8865
9208
  delete normalizedOpenView[key];
8866
9209
  }
8867
9210
  });
9211
+ if (((_d = options.popupActionContext) == null ? void 0 : _d.hasCurrentRecord) === true && this.shouldOmitRuntimeRecordFilterByTk(normalizedOpenView.filterByTk, {
9212
+ filterTargetKey
9213
+ })) {
9214
+ delete normalizedOpenView.filterByTk;
9215
+ }
8868
9216
  if (String(options.popupTemplateHostUse || "").trim() === "AddChildActionModel") {
8869
9217
  nextTemplateOpenView = import_lodash.default.omit(nextTemplateOpenView, ["sourceId"]);
8870
9218
  }
8871
9219
  Object.assign(normalizedOpenView, nextTemplateOpenView);
8872
9220
  }
9221
+ if (this.shouldUseRuntimeRecordFilterByTkForOpenView(normalizedOpenView, options) && this.shouldOmitRuntimeRecordFilterByTk(normalizedOpenView.filterByTk, {
9222
+ filterTargetKey: await this.resolveOpenViewRuntimeRecordFilterTargetKey(normalizedOpenView, void 0, options)
9223
+ })) {
9224
+ delete normalizedOpenView.filterByTk;
9225
+ }
8873
9226
  if (!import_lodash.default.isUndefined(normalizedOpenView.uid)) {
8874
9227
  const normalizedUid = String(normalizedOpenView.uid || "").trim();
8875
9228
  if (!normalizedUid) {
@@ -8910,7 +9263,8 @@ class FlowSurfacesService {
8910
9263
  return void 0;
8911
9264
  }
8912
9265
  return {
8913
- hasCurrentRecord: true
9266
+ hasCurrentRecord: true,
9267
+ currentRecordFilterTargetKey: await this.resolvePopupHostCurrentRecordFilterTargetKey(current.uid, transaction)
8914
9268
  };
8915
9269
  }
8916
9270
  unsetPayloadPathAndPruneEmptyParents(payload, path) {
@@ -9068,7 +9422,12 @@ class FlowSurfacesService {
9068
9422
  return {};
9069
9423
  }
9070
9424
  if (input.popup && !this.isPopupFieldHostUse(fieldNode.use)) {
9071
- (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} field '${fieldNode.use}' does not support popup`);
9425
+ (0, import_errors.throwBadRequest)(
9426
+ withJsPopupGuidance(
9427
+ `flowSurfaces ${actionName} field '${fieldNode.use}' does not support popup`,
9428
+ fieldNode.use
9429
+ )
9430
+ );
9072
9431
  }
9073
9432
  let openView = this.resolvePopupHostOpenView(fieldNode);
9074
9433
  if (input.popup && this.isExternalPopupOpenView(openView, fieldHostUid)) {
@@ -9394,6 +9753,20 @@ class FlowSurfacesService {
9394
9753
  isEmptyInlinePopupPayload(popup) {
9395
9754
  return import_lodash.default.isPlainObject(popup) && Object.keys(popup).length === 0;
9396
9755
  }
9756
+ isInlineFieldPopupDefaultShell(popup) {
9757
+ if (!import_lodash.default.isPlainObject(popup)) {
9758
+ return false;
9759
+ }
9760
+ const shellOnlyKeys = /* @__PURE__ */ new Set([
9761
+ "title",
9762
+ "mode",
9763
+ "openView",
9764
+ "tryTemplate",
9765
+ "defaultType",
9766
+ import_defaults.FLOW_SURFACE_APPLY_BLUEPRINT_POPUP_DEFAULTS_KEY
9767
+ ]);
9768
+ return Object.keys(popup).every((key) => shellOnlyKeys.has(key));
9769
+ }
9397
9770
  shouldAutoCompleteDefaultFieldPopup(fieldNode, popup, wrapperNode) {
9398
9771
  if (!import_lodash.default.isPlainObject(popup)) {
9399
9772
  return false;
@@ -9408,7 +9781,7 @@ class FlowSurfacesService {
9408
9781
  if (!String(fieldContext.collectionName || "").trim()) {
9409
9782
  return false;
9410
9783
  }
9411
- return popup.tryTemplate === true || !import_lodash.default.isUndefined(popup.defaultType) || Object.keys(popup).length === 0;
9784
+ return popup.tryTemplate === true || !import_lodash.default.isUndefined(popup.defaultType) || this.isInlineFieldPopupDefaultShell(popup);
9412
9785
  }
9413
9786
  isInlineFieldPopupBlockMissingResource(block) {
9414
9787
  if (!import_lodash.default.isPlainObject(block) || !import_lodash.default.isUndefined(block.template)) {
@@ -10470,14 +10843,21 @@ class FlowSurfacesService {
10470
10843
  actionNode,
10471
10844
  options.transaction
10472
10845
  ).catch(() => null);
10473
- const filterTargetKey = (popupProfile == null ? void 0 : popupProfile.currentCollection) ? this.getCollectionFilterTargetKey(popupProfile.currentCollection) : null;
10846
+ const useRuntimeRecordFilterByTk = this.isReferencedPopupTemplateOpenView(currentOpenView, actionNode.uid);
10847
+ const filterTargetKey = useRuntimeRecordFilterByTk ? await this.resolveOpenViewRuntimeRecordFilterTargetKey(currentOpenView, void 0, {
10848
+ transaction: options.transaction,
10849
+ popupTemplateHostUid: actionNode.uid,
10850
+ popupActionContext: {
10851
+ hasCurrentRecord: true
10852
+ }
10853
+ }) : (popupProfile == null ? void 0 : popupProfile.currentCollection) ? this.getCollectionFilterTargetKey(popupProfile.currentCollection) : null;
10474
10854
  const defaultFilterByTk = this.resolvePopupCurrentRecordResourceFilterByTk(popupProfile);
10475
10855
  const currentFilterByTk = import_lodash.default.isString(currentOpenView.filterByTk) ? currentOpenView.filterByTk.trim() : "";
10476
- const preserveCustomFilterByTk = currentFilterByTk && currentFilterByTk !== "{{ctx.view.inputArgs.filterByTk}}" && currentFilterByTk !== (filterTargetKey ? `{{ctx.record.${filterTargetKey}}}` : "");
10856
+ const preserveCustomFilterByTk = currentFilterByTk && !this.shouldOmitRuntimeRecordFilterByTk(currentOpenView.filterByTk, { filterTargetKey: filterTargetKey || "" });
10477
10857
  const nextOpenView = (0, import_service_utils.buildDefinedPayload)({
10478
- ...currentOpenView,
10858
+ ...useRuntimeRecordFilterByTk && !preserveCustomFilterByTk ? import_lodash.default.omit(currentOpenView, ["filterByTk"]) : currentOpenView,
10479
10859
  title: openViewTitle,
10480
- filterByTk: preserveCustomFilterByTk ? currentOpenView.filterByTk : defaultFilterByTk || currentOpenView.filterByTk
10860
+ filterByTk: useRuntimeRecordFilterByTk ? preserveCustomFilterByTk ? currentOpenView.filterByTk : void 0 : preserveCustomFilterByTk ? currentOpenView.filterByTk : defaultFilterByTk || currentOpenView.filterByTk
10481
10861
  });
10482
10862
  if (import_lodash.default.isEqual(nextOpenView, currentOpenView)) {
10483
10863
  return;
@@ -11700,7 +12080,12 @@ class FlowSurfacesService {
11700
12080
  }
11701
12081
  if (!((_c = sqlPreview.queryOutputs) == null ? void 0 : _c.length)) {
11702
12082
  (0, import_errors.throwBadRequest)(
11703
- "chart visual.mode='basic' requires previewable SQL query outputs; write query first, then read flowSurfaces:context(path='chart'), or use visual.mode='custom' after browser verification"
12083
+ withChartRepairMessage(
12084
+ "chart visual.mode='basic' requires previewable SQL query outputs; write query first, then read flowSurfaces:context(path='chart'), or use visual.mode='custom' after browser verification"
12085
+ ),
12086
+ {
12087
+ details: withFlowSurfaceChartRepairDetails()
12088
+ }
11704
12089
  );
11705
12090
  }
11706
12091
  const supportedOutputs = new Set(
@@ -11709,7 +12094,14 @@ class FlowSurfacesService {
11709
12094
  for (const mappingField of (0, import_chart_config.getChartVisualMappingAliases)(state.visual)) {
11710
12095
  if (!supportedOutputs.has(mappingField)) {
11711
12096
  (0, import_errors.throwBadRequest)(
11712
- `chart visual mappings only support SQL query output fields: ${Array.from(supportedOutputs).join(", ")}`
12097
+ withChartRepairMessage(
12098
+ `chart visual mappings only support SQL query output fields: ${Array.from(supportedOutputs).join(", ")}`
12099
+ ),
12100
+ {
12101
+ details: withFlowSurfaceChartRepairDetails({
12102
+ supportedOutputs: Array.from(supportedOutputs)
12103
+ })
12104
+ }
11713
12105
  );
11714
12106
  }
11715
12107
  }
@@ -11758,13 +12150,32 @@ class FlowSurfacesService {
11758
12150
  continue;
11759
12151
  }
11760
12152
  (0, import_errors.throwBadRequest)(
11761
- `flowSurfaces ${actionName} ${item.path} '${fieldPath}' does not exist on collection '${dataSourceKey}.${collectionName}'`
12153
+ withChartRepairMessage(
12154
+ `flowSurfaces ${actionName} ${item.path} '${fieldPath}' does not exist on collection '${dataSourceKey}.${collectionName}'`
12155
+ ),
12156
+ {
12157
+ details: withFlowSurfaceChartRepairDetails({
12158
+ fieldPath,
12159
+ dataSourceKey,
12160
+ collectionName
12161
+ })
12162
+ }
11762
12163
  );
11763
12164
  }
11764
12165
  if (!fieldPath.includes(".") && (0, import_service_helpers.isAssociationField)(field)) {
11765
12166
  const suggestion = this.resolveBuilderChartAssociationSubfieldSuggestion(fieldPath, field, dataSourceKey);
11766
12167
  (0, import_errors.throwBadRequest)(
11767
- `flowSurfaces ${actionName} ${item.path} '${fieldPath}' references an association field directly; use scalar subfield '${suggestion.suggestedFieldPath}' for builder charts`
12168
+ withChartRepairMessage(
12169
+ `flowSurfaces ${actionName} ${item.path} '${fieldPath}' references an association field directly; use scalar subfield '${suggestion.suggestedFieldPath}' for builder charts`
12170
+ ),
12171
+ {
12172
+ details: withFlowSurfaceChartRepairDetails({
12173
+ fieldPath,
12174
+ dataSourceKey,
12175
+ collectionName,
12176
+ ...suggestion
12177
+ })
12178
+ }
11768
12179
  );
11769
12180
  }
11770
12181
  }
@@ -11805,7 +12216,12 @@ class FlowSurfacesService {
11805
12216
  }
11806
12217
  if (!((_c = sqlPreview.queryOutputs) == null ? void 0 : _c.length)) {
11807
12218
  (0, import_errors.throwBadRequest)(
11808
- "chart visual.mode='basic' requires previewable SQL query outputs; write query first, then read flowSurfaces:context(path='chart'), or use visual.mode='custom' after browser verification"
12219
+ withChartRepairMessage(
12220
+ "chart visual.mode='basic' requires previewable SQL query outputs; write query first, then read flowSurfaces:context(path='chart'), or use visual.mode='custom' after browser verification"
12221
+ ),
12222
+ {
12223
+ details: withFlowSurfaceChartRepairDetails()
12224
+ }
11809
12225
  );
11810
12226
  }
11811
12227
  const supportedOutputs = new Set(
@@ -11814,7 +12230,14 @@ class FlowSurfacesService {
11814
12230
  for (const mappingField of (0, import_chart_config.getChartVisualMappingAliases)(state.visual)) {
11815
12231
  if (!supportedOutputs.has(mappingField)) {
11816
12232
  (0, import_errors.throwBadRequest)(
11817
- `chart visual mappings only support SQL query output fields: ${Array.from(supportedOutputs).join(", ")}`
12233
+ withChartRepairMessage(
12234
+ `chart visual mappings only support SQL query output fields: ${Array.from(supportedOutputs).join(", ")}`
12235
+ ),
12236
+ {
12237
+ details: withFlowSurfaceChartRepairDetails({
12238
+ supportedOutputs: Array.from(supportedOutputs)
12239
+ })
12240
+ }
11818
12241
  );
11819
12242
  }
11820
12243
  }
@@ -11826,16 +12249,16 @@ class FlowSurfacesService {
11826
12249
  const normalized = (typeof sql === "string" ? sql : "").trim();
11827
12250
  const inspected = this.stripChartSqlForInspection(normalized).replace(/;+\s*$/, "").trim();
11828
12251
  if (!inspected) {
11829
- (0, import_errors.throwBadRequest)("chart query.sql cannot be empty");
12252
+ throwChartRepairBadRequest("chart query.sql cannot be empty");
11830
12253
  }
11831
12254
  if (inspected.includes(";")) {
11832
- (0, import_errors.throwBadRequest)("chart query.sql must be a single read-only SELECT statement");
12255
+ throwChartRepairBadRequest("chart query.sql must be a single read-only SELECT statement");
11833
12256
  }
11834
12257
  if (!/^(with|select)\b/i.test(inspected)) {
11835
- (0, import_errors.throwBadRequest)("chart query.sql must start with SELECT or WITH");
12258
+ throwChartRepairBadRequest("chart query.sql must start with SELECT or WITH");
11836
12259
  }
11837
12260
  if (/\b(insert|update|delete|drop|alter|truncate|create|replace|grant|revoke|merge|call|execute)\b/i.test(inspected)) {
11838
- (0, import_errors.throwBadRequest)("chart query.sql must be read-only");
12261
+ throwChartRepairBadRequest("chart query.sql must be read-only");
11839
12262
  }
11840
12263
  return normalized.replace(/;+\s*$/, "").trim();
11841
12264
  }
@@ -11853,7 +12276,7 @@ class FlowSurfacesService {
11853
12276
  if ((_a = db == null ? void 0 : db.sequelize) == null ? void 0 : _a.query) {
11854
12277
  return db.sequelize.query(sql, { bind, transaction });
11855
12278
  }
11856
- (0, import_errors.throwBadRequest)("chart SQL preview is unavailable for the target data source");
12279
+ throwChartRepairBadRequest("chart SQL preview is unavailable for the target data source");
11857
12280
  }
11858
12281
  extractSqlChartPreviewAliases(metadata) {
11859
12282
  const pickName = (field) => {
@@ -11891,7 +12314,13 @@ class FlowSurfacesService {
11891
12314
  async resolveSqlChartPreview(query, _transaction) {
11892
12315
  var _a, _b;
11893
12316
  const normalizedSql = this.normalizeReadOnlyChartSql(query == null ? void 0 : query.sql);
11894
- const transformed = await (0, import_utils.transformSQL)(normalizedSql);
12317
+ let transformed;
12318
+ try {
12319
+ transformed = await (0, import_utils.transformSQL)(normalizedSql);
12320
+ } catch (error) {
12321
+ const message = (error == null ? void 0 : error.message) || String(error);
12322
+ throwChartRepairBadRequest(`chart query.sql is invalid: ${message}`);
12323
+ }
11895
12324
  const riskyHints = [];
11896
12325
  const hasRuntimeContext = Object.keys((transformed == null ? void 0 : transformed.bind) || {}).length > 0 || Object.keys((transformed == null ? void 0 : transformed.liquidContext) || {}).length > 0;
11897
12326
  if (hasRuntimeContext) {
@@ -11947,7 +12376,7 @@ class FlowSurfacesService {
11947
12376
  }
11948
12377
  const outputAliases = Object.keys(firstRow);
11949
12378
  if (!outputAliases.length) {
11950
- (0, import_errors.throwBadRequest)("chart query.sql must expose at least one output column");
12379
+ throwChartRepairBadRequest("chart query.sql must expose at least one output column");
11951
12380
  }
11952
12381
  return {
11953
12382
  queryOutputs: outputAliases.map((alias) => ({
@@ -11958,8 +12387,11 @@ class FlowSurfacesService {
11958
12387
  riskyHints
11959
12388
  };
11960
12389
  } catch (error) {
12390
+ if (isFlowSurfaceChartRepairError(error)) {
12391
+ throw error;
12392
+ }
11961
12393
  const message = (error == null ? void 0 : error.message) || String(error);
11962
- (0, import_errors.throwBadRequest)(`chart query.sql is invalid: ${message}`);
12394
+ throwChartRepairBadRequest(`chart query.sql is invalid: ${message}`);
11963
12395
  }
11964
12396
  }
11965
12397
  syncChartCardRuntimeStepParamsForUpdateSettings(current, nextPayload, replacementCardSettings) {
@@ -13967,6 +14399,97 @@ class FlowSurfacesService {
13967
14399
  }
13968
14400
  }
13969
14401
  }
14402
+ hasActionWithPublicType(actions, type) {
14403
+ return actions.some((action) => import_catalog.ACTION_KEY_BY_USE.get(String((action == null ? void 0 : action.use) || "").trim()) === type);
14404
+ }
14405
+ async ensureTableDefaultActionIntegrity(tableUid, options = {}) {
14406
+ var _a, _b, _c, _d;
14407
+ let table = await this.repository.findModelById(tableUid, {
14408
+ transaction: options.transaction,
14409
+ includeAsyncNode: true
14410
+ });
14411
+ if ((table == null ? void 0 : table.use) !== "TableBlockModel" || this.resolveTreeTableCreationContext(table)) {
14412
+ return;
14413
+ }
14414
+ const descriptors = (0, import_default_block_actions.getFlowSurfaceDefaultBlockActions)({ blockType: "table" });
14415
+ const blockActionDescriptors = descriptors.filter((descriptor) => descriptor.scope === "actions");
14416
+ const recordActionDescriptors = descriptors.filter((descriptor) => descriptor.scope === "recordActions");
14417
+ let blockActions = import_lodash.default.castArray(((_a = table == null ? void 0 : table.subModels) == null ? void 0 : _a.actions) || []);
14418
+ for (const descriptor of blockActionDescriptors) {
14419
+ if (this.hasActionWithPublicType(blockActions, descriptor.type)) {
14420
+ continue;
14421
+ }
14422
+ await this.addAction(
14423
+ {
14424
+ target: { uid: tableUid },
14425
+ type: descriptor.type,
14426
+ ...descriptor.popup ? { popup: import_lodash.default.cloneDeep(descriptor.popup) } : {}
14427
+ },
14428
+ options
14429
+ );
14430
+ table = await this.repository.findModelById(tableUid, {
14431
+ transaction: options.transaction,
14432
+ includeAsyncNode: true
14433
+ });
14434
+ blockActions = import_lodash.default.castArray(((_b = table == null ? void 0 : table.subModels) == null ? void 0 : _b.actions) || []);
14435
+ }
14436
+ const actionsColumnUid = await this.ensureTableActionsColumn(tableUid, options.transaction);
14437
+ let actionsColumn = await this.repository.findModelById(actionsColumnUid, {
14438
+ transaction: options.transaction,
14439
+ includeAsyncNode: true
14440
+ });
14441
+ let recordActions = import_lodash.default.castArray(((_c = actionsColumn == null ? void 0 : actionsColumn.subModels) == null ? void 0 : _c.actions) || []);
14442
+ for (const descriptor of recordActionDescriptors) {
14443
+ if (this.hasActionWithPublicType(recordActions, descriptor.type)) {
14444
+ continue;
14445
+ }
14446
+ await this.addRecordAction(
14447
+ {
14448
+ target: { uid: tableUid },
14449
+ type: descriptor.type,
14450
+ ...descriptor.popup ? { popup: import_lodash.default.cloneDeep(descriptor.popup) } : {}
14451
+ },
14452
+ options
14453
+ );
14454
+ actionsColumn = await this.repository.findModelById(actionsColumnUid, {
14455
+ transaction: options.transaction,
14456
+ includeAsyncNode: true
14457
+ });
14458
+ recordActions = import_lodash.default.castArray(((_d = actionsColumn == null ? void 0 : actionsColumn.subModels) == null ? void 0 : _d.actions) || []);
14459
+ }
14460
+ const missingRecordActions = recordActionDescriptors.map((descriptor) => descriptor.type).filter((type) => !this.hasActionWithPublicType(recordActions, type));
14461
+ if (missingRecordActions.length) {
14462
+ (0, import_errors.throwBadRequest)(`flowSurfaces table '${tableUid}' is missing required default record actions`, {
14463
+ ruleId: "table-record-actions-required",
14464
+ details: {
14465
+ tableUid,
14466
+ missingRecordActions,
14467
+ repairHint: "Put row actions under recordActions, or omit/pass an empty recordActions array so Flow Surfaces can create the default View/Edit/Delete row actions."
14468
+ }
14469
+ });
14470
+ }
14471
+ }
14472
+ collectNodeTreeDescendants(node, predicate, bucket = []) {
14473
+ if (!node || typeof node !== "object") {
14474
+ return bucket;
14475
+ }
14476
+ if (predicate(node)) {
14477
+ bucket.push(node);
14478
+ }
14479
+ Object.values(import_lodash.default.isPlainObject(node.subModels) ? node.subModels : {}).forEach((subModel) => {
14480
+ import_lodash.default.castArray(subModel).forEach((child) => this.collectNodeTreeDescendants(child, predicate, bucket));
14481
+ });
14482
+ return bucket;
14483
+ }
14484
+ async ensureSurfaceTableDefaultActionIntegrity(pageLocator, options = {}) {
14485
+ const surface = await this.get(pageLocator, options);
14486
+ const tables = this.collectNodeTreeDescendants(surface == null ? void 0 : surface.tree, (item) => (item == null ? void 0 : item.use) === "TableBlockModel");
14487
+ for (const table of tables) {
14488
+ if (table == null ? void 0 : table.uid) {
14489
+ await this.ensureTableDefaultActionIntegrity(table.uid, options);
14490
+ }
14491
+ }
14492
+ }
13970
14493
  async resolveReusableSingletonAction(input) {
13971
14494
  var _a, _b, _c;
13972
14495
  if (AUTO_SUBMIT_FORM_BLOCK_USES.has(input.ownerUse || "") && input.actionUse === "FormSubmitActionModel") {
@@ -14130,8 +14653,10 @@ class FlowSurfacesService {
14130
14653
  if (blockCatalogItem) {
14131
14654
  this.validateComposeActionGroups(blockCatalogItem.use, actions, recordActions, enabledPackages);
14132
14655
  }
14133
- const actionKeys = new Set(actions.map((item) => item.key).filter(Boolean));
14134
- const recordActionKeys = new Set(recordActions.map((item) => item.key).filter(Boolean));
14656
+ const actionKeys = new Set(actions.map((item) => String(item.key || "").trim()).filter(Boolean));
14657
+ const recordActionKeys = new Set(
14658
+ recordActions.map((item) => String(item.key || "").trim()).filter(Boolean)
14659
+ );
14135
14660
  const blockDescriptor = this.describeComposeBlock({
14136
14661
  index: index + 1,
14137
14662
  key,
@@ -14562,6 +15087,7 @@ class FlowSurfacesService {
14562
15087
  ...(0, import_service_utils.hasDefinedValue)(changes, [
14563
15088
  "pageSize",
14564
15089
  "density",
15090
+ "enableRowSelection",
14565
15091
  "showRowNumbers",
14566
15092
  "sorting",
14567
15093
  "dataScope",
@@ -14575,6 +15101,7 @@ class FlowSurfacesService {
14575
15101
  ...(0, import_service_utils.hasOwnDefined)(changes, "pageSize") ? { pageSize: { pageSize: changes.pageSize } } : {},
14576
15102
  ...(0, import_service_utils.hasOwnDefined)(changes, "density") ? { tableDensity: { size: changes.density } } : {},
14577
15103
  ...(0, import_service_utils.hasOwnDefined)(changes, "quickEdit") ? { quickEdit: { editable: changes.quickEdit } } : {},
15104
+ ...(0, import_service_utils.hasOwnDefined)(changes, "enableRowSelection") ? { enableRowSelection: { enableRowSelection: changes.enableRowSelection } } : {},
14578
15105
  ...(0, import_service_utils.hasOwnDefined)(changes, "showRowNumbers") ? { showRowNumbers: { showIndex: changes.showRowNumbers } } : {},
14579
15106
  ...(0, import_service_utils.hasOwnDefined)(changes, "sorting") ? {
14580
15107
  defaultSorting: {
@@ -15952,6 +16479,7 @@ class FlowSurfacesService {
15952
16479
  );
15953
16480
  }
15954
16481
  async configureJSColumn(target, changes, options) {
16482
+ assertNoJsDeclarativeOpenView("jsColumn", changes, "JSColumnModel");
15955
16483
  const allowedKeys = (0, import_configure_options.getConfigureOptionKeysForUse)("JSColumnModel");
15956
16484
  (0, import_service_utils.assertSupportedSimpleChanges)("jsColumn", changes, allowedKeys);
15957
16485
  return this.updateSettings(
@@ -15985,6 +16513,8 @@ class FlowSurfacesService {
15985
16513
  );
15986
16514
  }
15987
16515
  async configureJSItem(target, changes, options) {
16516
+ const { currentUse = "JSItemModel", ...updateOptions } = options;
16517
+ assertNoJsDeclarativeOpenView("jsItem", changes, currentUse);
15988
16518
  const allowedKeys = (0, import_configure_options.getConfigureOptionKeysForUse)("JSItemModel");
15989
16519
  (0, import_service_utils.assertSupportedSimpleChanges)("jsItem", changes, allowedKeys);
15990
16520
  return this.updateSettings(
@@ -16009,7 +16539,7 @@ class FlowSurfacesService {
16009
16539
  }
16010
16540
  } : void 0
16011
16541
  },
16012
- options
16542
+ updateOptions
16013
16543
  );
16014
16544
  }
16015
16545
  async configureDividerItem(target, changes, options) {
@@ -16060,6 +16590,7 @@ class FlowSurfacesService {
16060
16590
  transaction: options.transaction,
16061
16591
  includeAsyncNode: true
16062
16592
  }) : null;
16593
+ assertNoJsDeclarativeOpenView("field", changes, innerField == null ? void 0 : innerField.use);
16063
16594
  const currentFieldInit = ((_e = (_d = current == null ? void 0 : current.stepParams) == null ? void 0 : _d.fieldSettings) == null ? void 0 : _e.init) || ((_g = (_f = innerField == null ? void 0 : innerField.stepParams) == null ? void 0 : _f.fieldSettings) == null ? void 0 : _g.init) || {};
16064
16595
  const bindingChange = (0, import_service_utils.hasDefinedValue)(wrapperChanges, ["fieldPath", "associationPathName"]);
16065
16596
  const normalizedBinding = bindingChange ? this.normalizeDisplayFieldBinding({
@@ -16271,6 +16802,8 @@ class FlowSurfacesService {
16271
16802
  const enabledPackages = await this.resolveEnabledPluginPackages(options);
16272
16803
  const resolved = await this.locator.resolve(target, options);
16273
16804
  const current = await this.loadResolvedNode(resolved, options.transaction);
16805
+ const isJsFieldNode = ["JSFieldModel", "JSEditableFieldModel"].includes((current == null ? void 0 : current.use) || "");
16806
+ assertNoJsDeclarativeOpenView("field", changes, current == null ? void 0 : current.use);
16274
16807
  (0, import_service_utils.assertSupportedSimpleChanges)(
16275
16808
  "field",
16276
16809
  changes,
@@ -16281,7 +16814,6 @@ class FlowSurfacesService {
16281
16814
  transaction: options.transaction,
16282
16815
  includeAsyncNode: true
16283
16816
  }) : null;
16284
- const isJsFieldNode = ["JSFieldModel", "JSEditableFieldModel"].includes((current == null ? void 0 : current.use) || "");
16285
16817
  if ((0, import_service_utils.hasDefinedValue)(changes, ["code", "version"]) && !isJsFieldNode) {
16286
16818
  (0, import_errors.throwBadRequest)(`flowSurfaces configure field '${current == null ? void 0 : current.use}' does not support code/version`);
16287
16819
  }
@@ -16486,6 +17018,93 @@ class FlowSurfacesService {
16486
17018
  isAIEmployeeActionUse(use) {
16487
17019
  return String(use || "").trim() === AI_EMPLOYEE_ACTION_USE;
16488
17020
  }
17021
+ stableSerializeAIEmployeeValue(value) {
17022
+ if (Array.isArray(value)) {
17023
+ return `[${value.map((item) => this.stableSerializeAIEmployeeValue(item)).join(",")}]`;
17024
+ }
17025
+ if (import_lodash.default.isPlainObject(value)) {
17026
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${this.stableSerializeAIEmployeeValue(value[key])}`).join(",")}}`;
17027
+ }
17028
+ return JSON.stringify(value);
17029
+ }
17030
+ buildAIEmployeeActionDedupIdentity(values) {
17031
+ var _a, _b, _c, _d, _e, _f;
17032
+ const username = String(((_b = (_a = values == null ? void 0 : values.props) == null ? void 0 : _a.aiEmployee) == null ? void 0 : _b.username) || "").trim();
17033
+ if (!username) {
17034
+ return "";
17035
+ }
17036
+ return this.stableSerializeAIEmployeeValue({
17037
+ username,
17038
+ auto: typeof ((_c = values == null ? void 0 : values.props) == null ? void 0 : _c.auto) === "boolean" ? values.props.auto : false,
17039
+ workContext: this.normalizeAIEmployeeWorkContextForDedupIdentity((_e = (_d = values == null ? void 0 : values.props) == null ? void 0 : _d.context) == null ? void 0 : _e.workContext),
17040
+ tasks: this.normalizeAIEmployeeTasksForDedupIdentity(
17041
+ import_lodash.default.get(values, ["stepParams", ...AI_EMPLOYEE_TASK_STEP_PARAMS_PATH])
17042
+ ),
17043
+ style: {
17044
+ ...AI_EMPLOYEE_DEFAULT_STYLE,
17045
+ ...import_lodash.default.isPlainObject((_f = values == null ? void 0 : values.props) == null ? void 0 : _f.style) ? import_lodash.default.pick(values.props.style, AI_EMPLOYEE_STYLE_PUBLIC_KEYS) : {}
17046
+ }
17047
+ });
17048
+ }
17049
+ normalizeAIEmployeeWorkContextForDedupIdentity(value) {
17050
+ return import_lodash.default.castArray(value || []).map(
17051
+ (item) => import_lodash.default.isPlainObject(item) ? import_lodash.default.pick(item, AI_EMPLOYEE_WORK_CONTEXT_PUBLIC_KEYS) : item
17052
+ );
17053
+ }
17054
+ normalizeAIEmployeeTasksForDedupIdentity(value) {
17055
+ return import_lodash.default.castArray(value || []).map((task) => {
17056
+ if (!import_lodash.default.isPlainObject(task)) {
17057
+ return task;
17058
+ }
17059
+ const output = import_lodash.default.pick(task, AI_EMPLOYEE_TASK_PUBLIC_SETTING_KEYS);
17060
+ if (import_lodash.default.isPlainObject(output.message)) {
17061
+ output.message = import_lodash.default.pick(output.message, AI_EMPLOYEE_TASK_MESSAGE_PUBLIC_KEYS);
17062
+ if (Object.prototype.hasOwnProperty.call(output.message, "workContext")) {
17063
+ output.message.workContext = this.normalizeAIEmployeeWorkContextForDedupIdentity(output.message.workContext);
17064
+ }
17065
+ }
17066
+ if (import_lodash.default.isPlainObject(output.model)) {
17067
+ output.model = import_lodash.default.pick(output.model, AI_EMPLOYEE_TASK_MODEL_PUBLIC_KEYS);
17068
+ }
17069
+ if (import_lodash.default.isPlainObject(output.skillSettings)) {
17070
+ output.skillSettings = import_lodash.default.pick(output.skillSettings, AI_EMPLOYEE_SKILL_SETTINGS_PUBLIC_KEYS);
17071
+ }
17072
+ return output;
17073
+ });
17074
+ }
17075
+ async assertNoDuplicateAIEmployeeAction(actionName, parentUid, values, transaction) {
17076
+ var _a;
17077
+ const identity = this.buildAIEmployeeActionDedupIdentity(values);
17078
+ if (!identity) {
17079
+ return;
17080
+ }
17081
+ const parentNode = await this.repository.findModelById(parentUid, {
17082
+ transaction,
17083
+ includeAsyncNode: true
17084
+ });
17085
+ const duplicate = import_lodash.default.castArray(((_a = parentNode == null ? void 0 : parentNode.subModels) == null ? void 0 : _a.actions) || []).find((action) => {
17086
+ if (!this.isAIEmployeeActionUse(action == null ? void 0 : action.use)) {
17087
+ return false;
17088
+ }
17089
+ return this.buildAIEmployeeActionDedupIdentity({
17090
+ props: action.props,
17091
+ stepParams: action.stepParams
17092
+ }) === identity;
17093
+ });
17094
+ if (!(duplicate == null ? void 0 : duplicate.uid)) {
17095
+ return;
17096
+ }
17097
+ (0, import_errors.throwBadRequest)(
17098
+ `flowSurfaces ${actionName} duplicates an existing AI employee action in the same action container; reuse or update the existing button instead of appending another identical AI employee action`,
17099
+ {
17100
+ ruleId: "duplicate-ai-employee-action",
17101
+ details: {
17102
+ existingUid: duplicate.uid,
17103
+ repairHint: "Remove the duplicate aiEmployee action, or change username, task title/key, or workContext if this is a genuinely different AI employee action."
17104
+ }
17105
+ }
17106
+ );
17107
+ }
16489
17108
  hasAIEmployeePublicSettings(settings) {
16490
17109
  return import_lodash.default.isPlainObject(settings) && AI_EMPLOYEE_PUBLIC_SETTING_KEYS.some((key) => Object.prototype.hasOwnProperty.call(settings, key));
16491
17110
  }
@@ -16493,16 +17112,31 @@ class FlowSurfacesService {
16493
17112
  const unsupportedKeys = Object.keys(settings || {}).filter((key) => !AI_EMPLOYEE_PUBLIC_SETTING_KEYS.includes(key));
16494
17113
  if (unsupportedKeys.length) {
16495
17114
  (0, import_errors.throwBadRequest)(
16496
- `flowSurfaces ${actionName} AI employee settings do not support keys: ${unsupportedKeys.join(", ")}`
17115
+ `flowSurfaces ${actionName} AI employee settings do not support keys: ${unsupportedKeys.join(
17116
+ ", "
17117
+ )}; allowed keys: ${AI_EMPLOYEE_PUBLIC_SETTING_KEYS.join(
17118
+ ", "
17119
+ )}; repairHint: use top-level username, auto, workContext, tasks, or style instead of raw props/stepParams`
16497
17120
  );
16498
17121
  }
16499
17122
  }
17123
+ aiEmployeeNestedRepairHint(path) {
17124
+ if (path.includes("workContext")) {
17125
+ return AI_EMPLOYEE_WORK_CONTEXT_REPAIR_HINT;
17126
+ }
17127
+ if (path.includes("tasks")) {
17128
+ return AI_EMPLOYEE_TASK_REPAIR_HINT;
17129
+ }
17130
+ return "Use only the documented public AI employee settings keys for this nested object.";
17131
+ }
16500
17132
  assertOnlyAIEmployeeNestedPublicSettings(actionName, path, value, allowedKeys) {
16501
17133
  const unsupportedKeys = Object.keys(value || {}).filter((key) => !allowedKeys.includes(key));
16502
17134
  if (!unsupportedKeys.length) {
16503
17135
  return;
16504
17136
  }
16505
- (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path} does not support key '${unsupportedKeys[0]}'`);
17137
+ (0, import_errors.throwBadRequest)(
17138
+ `flowSurfaces ${actionName} ${path} does not support key '${unsupportedKeys[0]}'; allowed keys: ${allowedKeys.join(", ")}; repairHint: ${this.aiEmployeeNestedRepairHint(path)}`
17139
+ );
16506
17140
  }
16507
17141
  assertNoAIEmployeeInternalPropSettings(actionName, values) {
16508
17142
  const props = values == null ? void 0 : values.props;
@@ -16665,8 +17299,13 @@ class FlowSurfacesService {
16665
17299
  return username;
16666
17300
  }
16667
17301
  assertAIEmployeeWorkContextType(actionName, itemPath, item) {
17302
+ if (!Object.prototype.hasOwnProperty.call(item, "type") || import_lodash.default.isUndefined(item.type) || item.type === null) {
17303
+ return;
17304
+ }
16668
17305
  if (String(item.type || "").trim() !== "flow-model") {
16669
- (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${itemPath}.type must be 'flow-model'`);
17306
+ (0, import_errors.throwBadRequest)(
17307
+ `flowSurfaces ${actionName} ${itemPath}.type only supports 'flow-model'; omit type to use the default flow-model context. repairHint: ${AI_EMPLOYEE_WORK_CONTEXT_REPAIR_HINT}`
17308
+ );
16670
17309
  }
16671
17310
  }
16672
17311
  normalizeAIEmployeeWorkContext(actionName, value, options) {
@@ -17099,9 +17738,18 @@ ${AI_EMPLOYEE_CURRENT_RECORD_PROMPT_VARIABLE}` : AI_EMPLOYEE_CURRENT_RECORD_PROM
17099
17738
  (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${options.path} must be an object`);
17100
17739
  }
17101
17740
  const next = import_lodash.default.pick(import_lodash.default.isPlainObject(existing) ? import_lodash.default.cloneDeep(existing) : {}, AI_EMPLOYEE_TASK_PUBLIC_SETTING_KEYS);
17741
+ if (Object.prototype.hasOwnProperty.call(patch, "prompt") && import_lodash.default.isPlainObject(patch.message) && Object.prototype.hasOwnProperty.call(patch.message, "user")) {
17742
+ (0, import_errors.throwBadRequest)(
17743
+ `flowSurfaces ${actionName} ${options.path} cannot set both prompt and message.user; prompt is an alias for message.user. repairHint: keep only one of ${options.path}.prompt or ${options.path}.message.user`
17744
+ );
17745
+ }
17102
17746
  Object.entries(patch).forEach(([key, value]) => {
17103
- if (!AI_EMPLOYEE_TASK_PUBLIC_SETTING_KEYS.includes(key)) {
17104
- (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${options.path} does not support key '${key}'`);
17747
+ if (!AI_EMPLOYEE_TASK_PATCH_PUBLIC_SETTING_KEYS.includes(key)) {
17748
+ (0, import_errors.throwBadRequest)(
17749
+ `flowSurfaces ${actionName} ${options.path} does not support key '${key}'; allowed keys: ${AI_EMPLOYEE_TASK_PATCH_PUBLIC_SETTING_KEYS.join(
17750
+ ", "
17751
+ )}; repairHint: ${AI_EMPLOYEE_TASK_REPAIR_HINT}`
17752
+ );
17105
17753
  }
17106
17754
  if (key === "message") {
17107
17755
  next.message = this.normalizeAIEmployeeTaskMessage(actionName, `${options.path}.message`, value, next.message, {
@@ -17110,6 +17758,18 @@ ${AI_EMPLOYEE_CURRENT_RECORD_PROMPT_VARIABLE}` : AI_EMPLOYEE_CURRENT_RECORD_PROM
17110
17758
  });
17111
17759
  return;
17112
17760
  }
17761
+ if (key === "prompt") {
17762
+ if (typeof value !== "string") {
17763
+ (0, import_errors.throwBadRequest)(
17764
+ `flowSurfaces ${actionName} ${options.path}.prompt must be a string; repairHint: ${AI_EMPLOYEE_TASK_REPAIR_HINT}`
17765
+ );
17766
+ }
17767
+ next.message = {
17768
+ ...import_lodash.default.isPlainObject(next.message) ? import_lodash.default.cloneDeep(next.message) : {},
17769
+ user: value
17770
+ };
17771
+ return;
17772
+ }
17113
17773
  if (key === "title") {
17114
17774
  if (typeof value !== "string") {
17115
17775
  (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${options.path}.title must be a string`);
@@ -17324,6 +17984,9 @@ ${AI_EMPLOYEE_CURRENT_RECORD_PROMPT_VARIABLE}` : AI_EMPLOYEE_CURRENT_RECORD_PROM
17324
17984
  }) : null);
17325
17985
  changes = await this.normalizeActionPanelActionChanges(changes, options);
17326
17986
  const allowedKeys = (0, import_configure_options.getConfigureOptionKeysForUse)(use);
17987
+ if ((0, import_service_utils.hasOwnDefined)(changes, "openView") && !allowedKeys.includes("openView") && JS_POPUP_GUIDANCE_USES.has(use)) {
17988
+ (0, import_errors.throwBadRequest)(withJsPopupGuidance(`flowSurfaces configure action '${use}' does not support openView`, use));
17989
+ }
17327
17990
  (0, import_service_utils.assertSupportedSimpleChanges)("action", changes, allowedKeys);
17328
17991
  if (this.isAIEmployeeActionUse(use)) {
17329
17992
  const aiEmployeeSettingsPayload = await this.normalizeAIEmployeeActionPublicSettings(
@@ -17417,7 +18080,7 @@ ${AI_EMPLOYEE_CURRENT_RECORD_PROMPT_VARIABLE}` : AI_EMPLOYEE_CURRENT_RECORD_PROM
17417
18080
  openView: import_lodash.default.cloneDeep(changes.openView)
17418
18081
  };
17419
18082
  } else if (!POPUP_ACTION_USES.has(use)) {
17420
- (0, import_errors.throwBadRequest)(`flowSurfaces configure action '${use}' does not support openView`);
18083
+ (0, import_errors.throwBadRequest)(withJsPopupGuidance(`flowSurfaces configure action '${use}' does not support openView`, use));
17421
18084
  } else {
17422
18085
  let openView = import_lodash.default.cloneDeep(changes.openView);
17423
18086
  if (use === "AddChildActionModel") {