@josephyan/qingflow-cli 1.0.10 → 1.1.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.
Files changed (65) hide show
  1. package/README.md +3 -3
  2. package/npm/bin/qingflow.mjs +32 -1
  3. package/npm/lib/runtime.mjs +43 -2
  4. package/package.json +1 -1
  5. package/pyproject.toml +2 -1
  6. package/skills/qingflow-cli/SKILL.md +440 -0
  7. package/skills/qingflow-cli/manifest.yaml +10 -0
  8. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
  9. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +485 -0
  10. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md +237 -0
  11. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_MATCH_RULES.md +137 -0
  12. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md +263 -0
  13. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +304 -0
  14. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md +41 -0
  15. package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
  16. package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
  17. package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
  18. package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
  19. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
  20. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +20 -0
  21. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
  22. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +163 -0
  23. package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +107 -0
  24. package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +151 -0
  25. package/skills/qingflow-cli/reference/_batch_schema_complex.json +18 -0
  26. package/skills/qingflow-cli/reference/_batch_schema_scalar.json +17 -0
  27. package/skills/qingflow-cli/reference/charts_remove.example.json +1 -0
  28. package/skills/qingflow-cli/reference/charts_reorder.example.json +1 -0
  29. package/skills/qingflow-cli/reference/charts_upsert_bar.example.json +8 -0
  30. package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +37 -0
  31. package/skills/qingflow-cli/reference/charts_upsert_minimal.example.json +13 -0
  32. package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +131 -0
  33. package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +126 -0
  34. package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +128 -0
  35. package/skills/qingflow-cli/reference/schema_add_fields_minimal.example.json +7 -0
  36. package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +78 -0
  37. package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +7 -0
  38. package/skills/qingflow-cli/scripts/builder-package-from-app-list.py +140 -0
  39. package/skills/qingflow-cli/scripts/find-app-by-keyword.py +132 -0
  40. package/skills/qingflow-cli/scripts/validate_qingflow_output_files.py +87 -0
  41. package/src/qingflow_mcp/__init__.py +1 -1
  42. package/src/qingflow_mcp/builder_facade/models.py +532 -48
  43. package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
  44. package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
  45. package/src/qingflow_mcp/cli/commands/app.py +3 -16
  46. package/src/qingflow_mcp/cli/commands/builder.py +354 -56
  47. package/src/qingflow_mcp/cli/commands/record.py +89 -4
  48. package/src/qingflow_mcp/cli/formatters.py +53 -15
  49. package/src/qingflow_mcp/cli/main.py +204 -3
  50. package/src/qingflow_mcp/public_surface.py +11 -8
  51. package/src/qingflow_mcp/response_trim.py +185 -46
  52. package/src/qingflow_mcp/server.py +18 -15
  53. package/src/qingflow_mcp/server_app_builder.py +108 -30
  54. package/src/qingflow_mcp/server_app_user.py +20 -21
  55. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  56. package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
  57. package/src/qingflow_mcp/solution/executor.py +3 -133
  58. package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
  59. package/src/qingflow_mcp/tools/app_tools.py +53 -8
  60. package/src/qingflow_mcp/tools/package_tools.py +16 -2
  61. package/src/qingflow_mcp/tools/record_tools.py +3408 -599
  62. package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
  63. package/src/qingflow_mcp/tools/solution_tools.py +30 -2
  64. package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
  65. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
@@ -5,6 +5,215 @@ import re
5
5
 
6
6
 
7
7
  DEFAULT_ICON_COLOR = "qing-orange"
8
+ WORKSPACE_ICON_COLORS: tuple[str, ...] = (
9
+ "qing-orange",
10
+ "yellow",
11
+ "green",
12
+ "emerald",
13
+ "blue",
14
+ "azure",
15
+ "indigo",
16
+ "qing-purple",
17
+ "purple",
18
+ "pink",
19
+ "red",
20
+ "orange",
21
+ )
22
+ WORKSPACE_ICON_NAMES: tuple[str, ...] = (
23
+ "user",
24
+ "user-group",
25
+ "user-remove",
26
+ "user-add",
27
+ "user-circle",
28
+ "base-camera",
29
+ "view-grid",
30
+ "inbox",
31
+ "inbox-in",
32
+ "share",
33
+ "sitemap",
34
+ "airplane",
35
+ "template",
36
+ "music-note",
37
+ "movie-play",
38
+ "clock",
39
+ "document",
40
+ "document-search",
41
+ "clipboard-check",
42
+ "document-download",
43
+ "document-text",
44
+ "clipboard-copy",
45
+ "presentation-chart-bar",
46
+ "chart-square-bar",
47
+ "database",
48
+ "server",
49
+ "calendar",
50
+ "mail",
51
+ "annotation",
52
+ "chat",
53
+ "bell",
54
+ "key",
55
+ "shopping-bag",
56
+ "download",
57
+ "eye",
58
+ "eye-off",
59
+ "emoji-happy",
60
+ "emoji-sad",
61
+ "sun",
62
+ "moon",
63
+ "cloud",
64
+ "lightning-bolt",
65
+ "fire",
66
+ "star",
67
+ "sparkles",
68
+ "heart",
69
+ "cake",
70
+ "gift",
71
+ "light-bulb",
72
+ "exclamation",
73
+ "cog",
74
+ "thumb-up",
75
+ "thumb-down",
76
+ "cloud-download",
77
+ "cloud-upload",
78
+ "printer",
79
+ "phone-incoming",
80
+ "phone-missed-call",
81
+ "terminal",
82
+ "search-circle",
83
+ "x-circle",
84
+ "check-circle",
85
+ "exclamation-circle",
86
+ "question-mark-circle",
87
+ "information-circle",
88
+ "academic-cap",
89
+ "briefcase",
90
+ "home",
91
+ "phone",
92
+ "photograph",
93
+ "puzzle",
94
+ "color-swatch",
95
+ "lock-open",
96
+ "lock-closed",
97
+ "shield-check",
98
+ "shield-exclamation",
99
+ "currency-dollar",
100
+ "currency-yen",
101
+ "globe",
102
+ "at-symbol",
103
+ "slack",
104
+ "microphone",
105
+ "speakerphone",
106
+ "trash",
107
+ "book-open",
108
+ "truck",
109
+ "filter",
110
+ "essetional-filter-search",
111
+ "essetional-filter-tick",
112
+ "table",
113
+ "calculator",
114
+ "location-radar",
115
+ "essetional-weight",
116
+ "school-award",
117
+ "comp-cloud-connection",
118
+ "comp-cloud-remove",
119
+ "comp-cpu-charge",
120
+ "comp-cpu-setting",
121
+ "comp-cpu",
122
+ "comp-devices",
123
+ "comp-driver-2",
124
+ "comp-driver-refresh",
125
+ "location-global",
126
+ "location-location",
127
+ "location-map",
128
+ "location-gps",
129
+ "essetional-ranking",
130
+ "chart-bar",
131
+ "business-graph",
132
+ "business-status-up",
133
+ "business-trend-down",
134
+ "business-trend-up",
135
+ "business-presention-chart",
136
+ "business-favorite-chart",
137
+ "business-health",
138
+ "receipt-refund",
139
+ "receipt-tax",
140
+ "money-receipt-2-1",
141
+ "money-transaction-minus",
142
+ "action-hourglass-full",
143
+ "action-work",
144
+ "bug-f",
145
+ "essetional-pet",
146
+ "files-folder",
147
+ "badge-check",
148
+ "money-wallet-1",
149
+ "money-ticket",
150
+ "money-money",
151
+ "money-tag",
152
+ "money-wallet-2",
153
+ "business-personalcard",
154
+ "car-airplane",
155
+ "car-bus",
156
+ "car-car",
157
+ "car-driving",
158
+ "car-gas-station",
159
+ "car-smart-car",
160
+ "car-ship",
161
+ "location-map-1",
162
+ "location-route-square",
163
+ "cone",
164
+ "design-brush-4",
165
+ "paint-roll",
166
+ "wrench-f",
167
+ "essetional-reserve",
168
+ "essetional-broom",
169
+ "design-brush-2",
170
+ "essetional-judge",
171
+ "design-bucket",
172
+ "palette",
173
+ "comp-electricity",
174
+ "vial",
175
+ "beaker",
176
+ "leaf-f",
177
+ "cursor-click",
178
+ "solid-search-alt-2",
179
+ "md-library",
180
+ "building-3",
181
+ "office-building",
182
+ "building-hospital",
183
+ "school",
184
+ "store",
185
+ "video-camera-vintage-f",
186
+ "comp-monitor",
187
+ "delivery-truck",
188
+ "delivery-box-1",
189
+ "delivery-box-add",
190
+ "delivery-box-remove",
191
+ "settings-setting-3",
192
+ "document-duplicate",
193
+ "essetional-flag-2",
194
+ "flag",
195
+ "icon-currency-dollar",
196
+ "clipboard-list",
197
+ "save-as",
198
+ "wifi",
199
+ "status-online",
200
+ "scissors",
201
+ "globe-alt",
202
+ "ban",
203
+ "finger-print",
204
+ "qrcode",
205
+ "paper-clip",
206
+ "translate",
207
+ "cube-transparent",
208
+ "variable",
209
+ "switch-vertical",
210
+ "sports-baseball",
211
+ "sports-basketball",
212
+ "sports-soccer",
213
+ "sports-football",
214
+ "sports-volleyball",
215
+ )
216
+ GENERIC_WORKSPACE_ICON_NAMES: tuple[str, ...] = ("template",)
8
217
  DEFAULT_ICON_STYLE_POOL: tuple[tuple[str, str], ...] = (
9
218
  ("briefcase", "qing-orange"),
10
219
  ("calendar", "emerald"),
@@ -115,6 +324,91 @@ def parse_workspace_icon(value: str | None) -> tuple[str | None, str | None, str
115
324
  return normalize_workspace_icon_name(stripped), None, None
116
325
 
117
326
 
327
+ def workspace_icon_config(value: str | None) -> dict[str, str | None]:
328
+ icon_name, icon_color, icon_text = parse_workspace_icon(value)
329
+ raw = str(value).strip() if value not in (None, "") else None
330
+ return {
331
+ "icon_name": icon_name,
332
+ "icon_color": icon_color,
333
+ "icon_text": icon_text,
334
+ "raw": raw,
335
+ }
336
+
337
+
338
+ def workspace_icon_catalog_payload() -> dict[str, object]:
339
+ return {
340
+ "icon_names": list(WORKSPACE_ICON_NAMES),
341
+ "icon_colors": list(WORKSPACE_ICON_COLORS),
342
+ "generic_icon_names": list(GENERIC_WORKSPACE_ICON_NAMES),
343
+ "notes": [
344
+ "Use explicit icon + color for app/package/portal creation.",
345
+ "Do not use template for newly created workspace resources.",
346
+ "The CLI validates candidates only; it does not infer an icon from business names.",
347
+ ],
348
+ "common_examples": {
349
+ "employee": ["business-personalcard", "user-group", "user"],
350
+ "task": ["clipboard-check", "action-work"],
351
+ "worklog": ["clock", "action-hourglass-full"],
352
+ "order": ["delivery-box-1", "shopping-bag"],
353
+ "payment": ["money-receipt-2-1", "money-wallet-1"],
354
+ "opportunity": ["business-graph", "business-trend-up"],
355
+ "dashboard": ["view-grid", "chart-square-bar", "presentation-chart-bar"],
356
+ },
357
+ }
358
+
359
+
360
+ def validate_workspace_icon_choice(
361
+ *,
362
+ icon: str | None,
363
+ color: str | None,
364
+ require_explicit: bool,
365
+ disallow_generic: bool,
366
+ ) -> tuple[bool, str | None, str | None, dict[str, object]]:
367
+ normalized_icon = _normalize_workspace_icon_candidate(icon)
368
+ normalized_color = str(color or "").strip() or None
369
+ details: dict[str, object] = {
370
+ "icon": icon,
371
+ "normalized_icon": normalized_icon,
372
+ "color": color,
373
+ "icon_catalog_command": "qingflow --json builder icon catalog",
374
+ }
375
+ if require_explicit and not normalized_icon:
376
+ return False, "WORKSPACE_ICON_REQUIRED", "icon is required when creating a workspace resource", details
377
+ if require_explicit and not normalized_color:
378
+ return False, "WORKSPACE_ICON_COLOR_REQUIRED", "color is required when creating a workspace resource", details
379
+ if normalized_icon and normalized_icon not in WORKSPACE_ICON_NAMES:
380
+ details["allowed_icon_names"] = list(WORKSPACE_ICON_NAMES)
381
+ return False, "WORKSPACE_ICON_NOT_FOUND", "icon is not in the workspace icon catalog", details
382
+ if normalized_color and normalized_color not in WORKSPACE_ICON_COLORS:
383
+ details["allowed_icon_colors"] = list(WORKSPACE_ICON_COLORS)
384
+ return False, "WORKSPACE_ICON_COLOR_NOT_FOUND", "color is not in the workspace icon color catalog", details
385
+ if disallow_generic and normalized_icon in GENERIC_WORKSPACE_ICON_NAMES:
386
+ details["generic_icon_names"] = list(GENERIC_WORKSPACE_ICON_NAMES)
387
+ return False, "GENERIC_WORKSPACE_ICON_NOT_ALLOWED", "template is a generic icon and is not allowed for new workspace resources", details
388
+ return True, None, None, details
389
+
390
+
391
+ def _normalize_workspace_icon_candidate(icon: str | None) -> str | None:
392
+ if not icon:
393
+ return None
394
+ raw = str(icon).strip()
395
+ if not raw:
396
+ return None
397
+ if _looks_like_icon_json(raw):
398
+ try:
399
+ payload = json.loads(raw)
400
+ except Exception:
401
+ return None
402
+ return _normalize_workspace_icon_candidate(payload.get("iconName"))
403
+ normalized = raw.lower()
404
+ if normalized in WORKSPACE_ICON_NAMES:
405
+ return normalized
406
+ legacy = LEGACY_EX_ICON_MAP.get(normalized)
407
+ if legacy in WORKSPACE_ICON_NAMES:
408
+ return legacy
409
+ return normalized
410
+
411
+
118
412
  def encode_workspace_icon_with_defaults(
119
413
  *,
120
414
  icon: str | None,
@@ -14,7 +14,6 @@ from ..tools.qingbi_report_tools import QingbiReportTools
14
14
  from ..tools.record_tools import RecordTools
15
15
  from ..tools.role_tools import RoleTools
16
16
  from ..tools.view_tools import ViewTools
17
- from ..tools.workflow_tools import WorkflowTools
18
17
  from ..tools.workspace_tools import WorkspaceTools
19
18
  from .compiler import CompiledEntity, CompiledRole, CompiledSolution
20
19
  from .compiler.form_compiler import QUESTION_TYPE_MAP
@@ -36,7 +35,6 @@ class SolutionExecutor:
36
35
  role_tools: RoleTools,
37
36
  app_tools: AppTools,
38
37
  record_tools: RecordTools,
39
- workflow_tools: WorkflowTools,
40
38
  view_tools: ViewTools,
41
39
  chart_tools: QingbiReportTools,
42
40
  portal_tools: PortalTools,
@@ -47,7 +45,6 @@ class SolutionExecutor:
47
45
  self.role_tools = role_tools
48
46
  self.app_tools = app_tools
49
47
  self.record_tools = record_tools
50
- self.workflow_tools = workflow_tools
51
48
  self.view_tools = view_tools
52
49
  self.chart_tools = chart_tools
53
50
  self.portal_tools = portal_tools
@@ -419,123 +416,10 @@ class SolutionExecutor:
419
416
  def _build_workflow(self, profile: str, entity: CompiledEntity, store: RunArtifactStore) -> None:
420
417
  if entity.workflow_plan is None:
421
418
  return
422
- app_key = self._get_app_key(store, entity.entity_id)
423
- workflow_edit_version_no = self._ensure_edit_version(
424
- profile,
425
- entity.entity_id,
426
- store,
427
- app_key=app_key,
428
- force_new=True,
429
- )
430
- node_artifacts = store.get_artifact("apps", entity.entity_id, {}).get("workflow_nodes", {})
431
- existing_nodes = self.workflow_tools.workflow_list_nodes(profile=profile, app_key=app_key).get("result") or {}
432
- current_nodes = _coerce_workflow_nodes(existing_nodes)
433
- existing_nodes_by_name = {
434
- node.get("auditNodeName"): int(node_id)
435
- for node_id, node in current_nodes.items()
436
- if isinstance(node, dict) and node.get("auditNodeName")
437
- }
438
- applicant_node_id = next(
439
- (
440
- int(node_id)
441
- for node_id, node in current_nodes.items()
442
- if isinstance(node, dict) and node.get("type") == 0 and node.get("dealType") == 3
443
- ),
444
- None,
419
+ raise RuntimeError(
420
+ "Legacy auditNode workflow execution was removed. "
421
+ "Pass {app_key, spec} to solution_build_flow or use qingflow builder flow apply."
445
422
  )
446
- if applicant_node_id is not None:
447
- node_artifacts.setdefault("__applicant__", applicant_node_id)
448
-
449
- desired_global_settings = deepcopy(entity.workflow_plan["global_settings"])
450
- explicit_global_settings = _has_explicit_workflow_global_settings(desired_global_settings)
451
- current_global_settings: dict[str, Any] = {}
452
- if explicit_global_settings:
453
- current_global_settings = self.workflow_tools.workflow_get_global_settings(profile=profile, app_key=app_key).get("result") or {}
454
- else:
455
- try:
456
- current_global_settings = self.workflow_tools.workflow_get_global_settings(profile=profile, app_key=app_key).get("result") or {}
457
- except (QingflowApiError, RuntimeError) as error:
458
- api_error = QingflowApiError(**_coerce_nested_error_payload(error))
459
- if api_error.http_status != 404:
460
- raise
461
- current_global_settings = {}
462
- if explicit_global_settings:
463
- global_settings = deepcopy(current_global_settings if isinstance(current_global_settings, dict) else {})
464
- global_settings.update(desired_global_settings)
465
- global_settings["editVersionNo"] = workflow_edit_version_no or global_settings.get("editVersionNo") or 1
466
- self.workflow_tools.workflow_update_global_settings(profile=profile, app_key=app_key, payload=global_settings)
467
- for action in entity.workflow_plan["actions"]:
468
- if action["action"] == "create_sub_branch" and node_artifacts.get(action["node_id"]) is not None:
469
- continue
470
- if action["action"] == "add_node":
471
- if action.get("node_type") == "branch":
472
- existing_branch_id = node_artifacts.get(action["node_id"])
473
- if existing_branch_id is not None and not _workflow_node_is_branch(current_nodes, existing_branch_id):
474
- existing_branch_id = None
475
- if existing_branch_id is not None:
476
- for branch_index, lane_id in enumerate(_find_branch_lane_ids(current_nodes, existing_branch_id), start=1):
477
- node_artifacts[_branch_lane_ref(action["node_id"], branch_index)] = lane_id
478
- apps_artifact = store.get_artifact("apps", entity.entity_id, {})
479
- apps_artifact["workflow_nodes"] = node_artifacts
480
- store.set_artifact("apps", entity.entity_id, apps_artifact)
481
- continue
482
- existing_node_id = node_artifacts.get(action["node_id"]) or existing_nodes_by_name.get(action.get("node_name"))
483
- if existing_node_id is not None:
484
- node_artifacts[action["node_id"]] = existing_node_id
485
- apps_artifact = store.get_artifact("apps", entity.entity_id, {})
486
- apps_artifact["workflow_nodes"] = node_artifacts
487
- store.set_artifact("apps", entity.entity_id, apps_artifact)
488
- continue
489
- before_node_ids = set(current_nodes)
490
- payload = self._resolve_workflow_payload(action["payload"], node_artifacts)
491
- if workflow_edit_version_no is not None:
492
- payload["editVersionNo"] = int(workflow_edit_version_no)
493
- if action["action"] == "create_sub_branch":
494
- result = self.workflow_tools.workflow_create_sub_branch(profile=profile, app_key=app_key, payload=payload)
495
- elif action["action"] == "update_node":
496
- target_node_id = node_artifacts.get(action["node_id"])
497
- if target_node_id is None:
498
- raise RuntimeError(f"workflow lane '{action['node_id']}' could not be resolved before update")
499
- result = self.workflow_tools.workflow_update_node(
500
- profile=profile,
501
- app_key=app_key,
502
- audit_node_id=target_node_id,
503
- payload=payload,
504
- )
505
- else:
506
- result = self.workflow_tools.workflow_add_node(profile=profile, app_key=app_key, payload=payload)
507
- expected_type = 1 if action.get("node_type") == "branch" else None
508
- audit_node_id = _extract_workflow_node_id(result.get("result"), expected_type=expected_type)
509
- if action.get("node_type") == "branch" or action["action"] == "create_sub_branch":
510
- current_nodes = _coerce_workflow_nodes(
511
- self.workflow_tools.workflow_list_nodes(profile=profile, app_key=app_key).get("result") or {}
512
- )
513
- if audit_node_id is not None:
514
- node_artifacts[action["node_id"]] = audit_node_id
515
- if action.get("node_type") == "branch":
516
- branch_node_id = node_artifacts.get(action["node_id"]) or _find_created_branch_node_id(
517
- current_nodes,
518
- before_node_ids=before_node_ids,
519
- prev_id=payload.get("prevId"),
520
- )
521
- if branch_node_id is not None:
522
- node_artifacts[action["node_id"]] = branch_node_id
523
- for branch_index, lane_id in enumerate(_find_branch_lane_ids(current_nodes, branch_node_id), start=1):
524
- node_artifacts[_branch_lane_ref(action["node_id"], branch_index)] = lane_id
525
- if action["action"] == "create_sub_branch" and node_artifacts.get(action["node_id"]) is None:
526
- created_lane_id = audit_node_id or _find_created_sub_branch_lane_id(
527
- current_nodes,
528
- before_node_ids=before_node_ids,
529
- branch_node_id=payload.get("auditNodeId"),
530
- )
531
- if created_lane_id is not None:
532
- node_artifacts[action["node_id"]] = created_lane_id
533
- apps_artifact = store.get_artifact("apps", entity.entity_id, {})
534
- apps_artifact["workflow_nodes"] = node_artifacts
535
- store.set_artifact("apps", entity.entity_id, apps_artifact)
536
- apps_artifact = store.get_artifact("apps", entity.entity_id, {})
537
- apps_artifact["workflow_nodes"] = node_artifacts
538
- store.set_artifact("apps", entity.entity_id, apps_artifact)
539
423
 
540
424
  def _build_views(self, profile: str, entity: CompiledEntity, store: RunArtifactStore) -> None:
541
425
  app_key = self._get_app_key(store, entity.entity_id)
@@ -2097,20 +1981,6 @@ def _find_created_sub_branch_lane_id(
2097
1981
  return candidates[0] if candidates else None
2098
1982
 
2099
1983
 
2100
- def _has_explicit_workflow_global_settings(global_settings: dict[str, Any] | None) -> bool:
2101
- if not isinstance(global_settings, dict):
2102
- return False
2103
- for key, value in global_settings.items():
2104
- if key == "editVersionNo":
2105
- continue
2106
- if value is None:
2107
- continue
2108
- if isinstance(value, (list, dict)) and not value:
2109
- continue
2110
- return True
2111
- return False
2112
-
2113
-
2114
1984
  def _is_navigation_plugin_unavailable(error: QingflowApiError) -> bool:
2115
1985
  try:
2116
1986
  backend_code = int(error.backend_code)