@josephyan/qingflow-app-user-mcp 0.2.0-beta.88 → 0.2.0-beta.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +190 -16
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.89
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.89 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
|
@@ -4789,12 +4789,22 @@ class AiBuilderFacade:
|
|
|
4789
4789
|
response = _apply_permission_outcomes(response, relation_permission_outcome)
|
|
4790
4790
|
return finalize(self._append_publish_result(profile=profile, app_key=target.app_key, publish=publish, response=response))
|
|
4791
4791
|
|
|
4792
|
-
payload =
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4792
|
+
payload = (
|
|
4793
|
+
_build_form_payload_from_fields(
|
|
4794
|
+
title=effective_app_name,
|
|
4795
|
+
current_schema=schema_result,
|
|
4796
|
+
fields=current_fields,
|
|
4797
|
+
layout=layout,
|
|
4798
|
+
question_relations=compiled_question_relations,
|
|
4799
|
+
)
|
|
4800
|
+
if bool(resolved.get("created"))
|
|
4801
|
+
else _build_form_payload_for_edit_fields(
|
|
4802
|
+
title=effective_app_name,
|
|
4803
|
+
current_schema=schema_result,
|
|
4804
|
+
fields=current_fields,
|
|
4805
|
+
layout=layout,
|
|
4806
|
+
question_relations=compiled_question_relations,
|
|
4807
|
+
)
|
|
4798
4808
|
)
|
|
4799
4809
|
payload["editVersionNo"] = self._resolve_form_edit_version(
|
|
4800
4810
|
profile=profile,
|
|
@@ -4897,12 +4907,22 @@ class AiBuilderFacade:
|
|
|
4897
4907
|
},
|
|
4898
4908
|
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
|
|
4899
4909
|
)
|
|
4900
|
-
rebound_payload =
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4910
|
+
rebound_payload = (
|
|
4911
|
+
_build_form_payload_from_fields(
|
|
4912
|
+
title=effective_app_name,
|
|
4913
|
+
current_schema=rebound_schema,
|
|
4914
|
+
fields=rebound_fields,
|
|
4915
|
+
layout=rebound_layout,
|
|
4916
|
+
question_relations=compiled_question_relations,
|
|
4917
|
+
)
|
|
4918
|
+
if bool(resolved.get("created"))
|
|
4919
|
+
else _build_form_payload_for_edit_fields(
|
|
4920
|
+
title=effective_app_name,
|
|
4921
|
+
current_schema=rebound_schema,
|
|
4922
|
+
fields=rebound_fields,
|
|
4923
|
+
layout=rebound_layout,
|
|
4924
|
+
question_relations=compiled_question_relations,
|
|
4925
|
+
)
|
|
4906
4926
|
)
|
|
4907
4927
|
rebound_payload["editVersionNo"] = self._resolve_form_edit_version(
|
|
4908
4928
|
profile=profile,
|
|
@@ -9844,8 +9864,8 @@ def _parse_field(question: dict[str, Any], *, field_id_hint: str | None = None)
|
|
|
9844
9864
|
"subfields": [],
|
|
9845
9865
|
"que_id": que_id,
|
|
9846
9866
|
"que_type": que_type,
|
|
9847
|
-
"default_type": _coerce_positive_int(question.get("queDefaultType"))
|
|
9848
|
-
"default_value": question.get("queDefaultValue"),
|
|
9867
|
+
"default_type": _coerce_positive_int(question.get("queDefaultType")) if "queDefaultType" in question else None,
|
|
9868
|
+
"default_value": question.get("queDefaultValue") if "queDefaultValue" in question else None,
|
|
9849
9869
|
}
|
|
9850
9870
|
if field_type in {FieldType.single_select.value, FieldType.multi_select.value, FieldType.boolean.value}:
|
|
9851
9871
|
options = question.get("options")
|
|
@@ -9893,7 +9913,6 @@ def _parse_field(question: dict[str, Any], *, field_id_hint: str | None = None)
|
|
|
9893
9913
|
field["visible_fields"] = visible_fields
|
|
9894
9914
|
field["field_name_show"] = bool(reference.get("fieldNameShow", True))
|
|
9895
9915
|
field["_reference_config_template"] = deepcopy(reference)
|
|
9896
|
-
field["_question_template"] = deepcopy(question)
|
|
9897
9916
|
field["_relation_config_explicit"] = False
|
|
9898
9917
|
if field_type == FieldType.department:
|
|
9899
9918
|
department_scope = _normalize_department_scope_from_question(question)
|
|
@@ -9961,6 +9980,7 @@ def _parse_field(question: dict[str, Any], *, field_id_hint: str | None = None)
|
|
|
9961
9980
|
continue
|
|
9962
9981
|
subfields.append(_parse_field(sub_question))
|
|
9963
9982
|
field["subfields"] = subfields
|
|
9983
|
+
field["_question_template"] = deepcopy(question)
|
|
9964
9984
|
return field
|
|
9965
9985
|
|
|
9966
9986
|
|
|
@@ -10847,53 +10867,82 @@ def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
|
|
|
10847
10867
|
payload.get("type") == FieldType.relation.value
|
|
10848
10868
|
or any(key in payload for key in ("target_app_key", "display_field", "visible_fields", "relation_mode"))
|
|
10849
10869
|
)
|
|
10870
|
+
question_overlay_keys = set(cast(list[str], field.get("_question_overlay_keys") or []))
|
|
10871
|
+
question_rebuild_required = bool(field.get("_question_rebuild_required"))
|
|
10850
10872
|
if "name" in payload:
|
|
10851
10873
|
field["name"] = payload["name"]
|
|
10874
|
+
question_overlay_keys.add("name")
|
|
10852
10875
|
if "type" in payload:
|
|
10853
10876
|
field["type"] = payload["type"]
|
|
10877
|
+
question_rebuild_required = True
|
|
10854
10878
|
if "required" in payload:
|
|
10855
10879
|
field["required"] = payload["required"]
|
|
10880
|
+
question_overlay_keys.add("required")
|
|
10856
10881
|
if "description" in payload:
|
|
10857
10882
|
field["description"] = payload["description"]
|
|
10883
|
+
question_overlay_keys.add("description")
|
|
10858
10884
|
if "options" in payload:
|
|
10859
10885
|
field["options"] = list(payload["options"])
|
|
10886
|
+
question_rebuild_required = True
|
|
10860
10887
|
if "target_app_key" in payload:
|
|
10861
10888
|
field["target_app_key"] = payload["target_app_key"]
|
|
10889
|
+
question_rebuild_required = True
|
|
10862
10890
|
if "display_field" in payload:
|
|
10863
10891
|
field["display_field"] = payload["display_field"]
|
|
10892
|
+
question_rebuild_required = True
|
|
10864
10893
|
if "visible_fields" in payload:
|
|
10865
10894
|
field["visible_fields"] = list(payload["visible_fields"])
|
|
10895
|
+
question_rebuild_required = True
|
|
10866
10896
|
if "relation_mode" in payload:
|
|
10867
10897
|
field["relation_mode"] = payload["relation_mode"]
|
|
10898
|
+
question_rebuild_required = True
|
|
10868
10899
|
if "department_scope" in payload:
|
|
10869
10900
|
field["department_scope"] = payload["department_scope"]
|
|
10901
|
+
question_rebuild_required = True
|
|
10870
10902
|
if "remote_lookup_config" in payload:
|
|
10871
10903
|
field["remote_lookup_config"] = payload["remote_lookup_config"]
|
|
10872
10904
|
field["config"] = deepcopy(payload["remote_lookup_config"])
|
|
10873
10905
|
field["_explicit_remote_lookup_config"] = True
|
|
10906
|
+
question_rebuild_required = True
|
|
10874
10907
|
if "q_linker_binding" in payload:
|
|
10875
10908
|
field["q_linker_binding"] = payload["q_linker_binding"]
|
|
10876
10909
|
if "remote_lookup_config" not in payload:
|
|
10877
10910
|
field["_explicit_remote_lookup_config"] = False
|
|
10911
|
+
question_rebuild_required = True
|
|
10878
10912
|
if "code_block_config" in payload:
|
|
10879
10913
|
field["code_block_config"] = payload["code_block_config"]
|
|
10880
10914
|
field["config"] = deepcopy(payload["code_block_config"])
|
|
10915
|
+
question_rebuild_required = True
|
|
10881
10916
|
if "code_block_binding" in payload:
|
|
10882
10917
|
field["code_block_binding"] = payload["code_block_binding"]
|
|
10883
10918
|
field["_explicit_code_block_binding"] = True
|
|
10919
|
+
question_rebuild_required = True
|
|
10884
10920
|
if "auto_trigger" in payload:
|
|
10885
10921
|
field["auto_trigger"] = payload["auto_trigger"]
|
|
10922
|
+
question_rebuild_required = True
|
|
10886
10923
|
if "custom_button_text_enabled" in payload:
|
|
10887
10924
|
field["custom_button_text_enabled"] = payload["custom_button_text_enabled"]
|
|
10925
|
+
question_rebuild_required = True
|
|
10888
10926
|
if "custom_button_text" in payload:
|
|
10889
10927
|
field["custom_button_text"] = payload["custom_button_text"]
|
|
10928
|
+
question_rebuild_required = True
|
|
10890
10929
|
if "subfields" in payload:
|
|
10891
10930
|
field["subfields"] = [_field_patch_to_internal(item) for item in payload["subfields"]]
|
|
10931
|
+
question_rebuild_required = True
|
|
10892
10932
|
if relation_config_explicit:
|
|
10893
10933
|
field["_relation_config_explicit"] = True
|
|
10934
|
+
question_rebuild_required = True
|
|
10894
10935
|
elif payload.get("type") and payload.get("type") != FieldType.relation.value:
|
|
10895
10936
|
field.pop("_relation_config_explicit", None)
|
|
10896
10937
|
field.pop("_reference_config_template", None)
|
|
10938
|
+
if question_overlay_keys:
|
|
10939
|
+
field["_question_overlay_keys"] = sorted(question_overlay_keys)
|
|
10940
|
+
else:
|
|
10941
|
+
field.pop("_question_overlay_keys", None)
|
|
10942
|
+
if question_rebuild_required:
|
|
10943
|
+
field["_question_rebuild_required"] = True
|
|
10944
|
+
else:
|
|
10945
|
+
field.pop("_question_rebuild_required", None)
|
|
10897
10946
|
|
|
10898
10947
|
|
|
10899
10948
|
def _resolve_field_selector_with_uniqueness(
|
|
@@ -12691,6 +12740,42 @@ def _verify_package_attachment(packages: PackageTools, *, profile: str, tag_id:
|
|
|
12691
12740
|
return last_result
|
|
12692
12741
|
|
|
12693
12742
|
|
|
12743
|
+
def _field_question_overlay_keys(field: dict[str, Any]) -> set[str]:
|
|
12744
|
+
raw_value = field.get("_question_overlay_keys")
|
|
12745
|
+
if isinstance(raw_value, set):
|
|
12746
|
+
return {str(item) for item in raw_value if isinstance(item, str) and item}
|
|
12747
|
+
if isinstance(raw_value, list):
|
|
12748
|
+
return {str(item) for item in raw_value if isinstance(item, str) and item}
|
|
12749
|
+
return set()
|
|
12750
|
+
|
|
12751
|
+
|
|
12752
|
+
def _field_needs_question_rebuild(field: dict[str, Any]) -> bool:
|
|
12753
|
+
return not isinstance(field.get("_question_template"), dict) or bool(field.get("_question_rebuild_required"))
|
|
12754
|
+
|
|
12755
|
+
|
|
12756
|
+
def _materialize_preserved_question(field: dict[str, Any]) -> dict[str, Any] | None:
|
|
12757
|
+
template = deepcopy(field.get("_question_template"))
|
|
12758
|
+
if not isinstance(template, dict):
|
|
12759
|
+
return None
|
|
12760
|
+
overlay_keys = _field_question_overlay_keys(field)
|
|
12761
|
+
if "name" in overlay_keys:
|
|
12762
|
+
template["queTitle"] = str(field.get("name") or "")
|
|
12763
|
+
if "required" in overlay_keys:
|
|
12764
|
+
template["required"] = bool(field.get("required", False))
|
|
12765
|
+
if "description" in overlay_keys:
|
|
12766
|
+
description = field.get("description")
|
|
12767
|
+
template["queHint"] = "" if description is None else str(description)
|
|
12768
|
+
return template
|
|
12769
|
+
|
|
12770
|
+
|
|
12771
|
+
def _materialize_edit_question(field: dict[str, Any], *, temp_id: int) -> tuple[dict[str, Any], bool]:
|
|
12772
|
+
if not _field_needs_question_rebuild(field):
|
|
12773
|
+
preserved = _materialize_preserved_question(field)
|
|
12774
|
+
if preserved is not None:
|
|
12775
|
+
return preserved, True
|
|
12776
|
+
return _field_to_question(field, temp_id=temp_id), False
|
|
12777
|
+
|
|
12778
|
+
|
|
12694
12779
|
def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]:
|
|
12695
12780
|
built_question, _next_temp_id = build_question(
|
|
12696
12781
|
{
|
|
@@ -12746,7 +12831,6 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
|
|
|
12746
12831
|
)
|
|
12747
12832
|
if preserved_reference is not None:
|
|
12748
12833
|
preserved_reference["referAppKey"] = field.get("target_app_key")
|
|
12749
|
-
preserved_reference["_targetEntityId"] = field.get("target_app_key")
|
|
12750
12834
|
question["referenceConfig"] = preserved_reference
|
|
12751
12835
|
else:
|
|
12752
12836
|
reference = deepcopy(question.get("referenceConfig")) if isinstance(question.get("referenceConfig"), dict) else {}
|
|
@@ -12914,6 +12998,96 @@ def _build_form_payload_from_fields(
|
|
|
12914
12998
|
return payload
|
|
12915
12999
|
|
|
12916
13000
|
|
|
13001
|
+
def _build_form_payload_for_edit_fields(
|
|
13002
|
+
*,
|
|
13003
|
+
title: str,
|
|
13004
|
+
current_schema: dict[str, Any],
|
|
13005
|
+
fields: list[dict[str, Any]],
|
|
13006
|
+
layout: dict[str, Any],
|
|
13007
|
+
question_relations: list[dict[str, Any]] | None = None,
|
|
13008
|
+
) -> dict[str, Any]:
|
|
13009
|
+
_, section_templates = _extract_question_templates(current_schema)
|
|
13010
|
+
fields_by_name = {
|
|
13011
|
+
str(field.get("name") or ""): field
|
|
13012
|
+
for field in fields
|
|
13013
|
+
if isinstance(field, dict) and str(field.get("name") or "").strip()
|
|
13014
|
+
}
|
|
13015
|
+
form_rows: list[list[dict[str, Any]]] = []
|
|
13016
|
+
temp_id = -10000
|
|
13017
|
+
|
|
13018
|
+
for row in layout.get("root_rows", []) or []:
|
|
13019
|
+
questions: list[dict[str, Any]] = []
|
|
13020
|
+
row_preserved = True
|
|
13021
|
+
for name in row:
|
|
13022
|
+
field = fields_by_name.get(str(name))
|
|
13023
|
+
if field is None:
|
|
13024
|
+
continue
|
|
13025
|
+
question, preserved = _materialize_edit_question(field, temp_id=temp_id)
|
|
13026
|
+
questions.append(question)
|
|
13027
|
+
row_preserved = row_preserved and preserved
|
|
13028
|
+
temp_id -= 100
|
|
13029
|
+
if not questions:
|
|
13030
|
+
continue
|
|
13031
|
+
if not row_preserved:
|
|
13032
|
+
_apply_row_widths(questions)
|
|
13033
|
+
form_rows.append(questions)
|
|
13034
|
+
|
|
13035
|
+
for section in layout.get("sections", []) or []:
|
|
13036
|
+
inner_rows: list[list[dict[str, Any]]] = []
|
|
13037
|
+
for row in section.get("rows", []) or []:
|
|
13038
|
+
questions: list[dict[str, Any]] = []
|
|
13039
|
+
row_preserved = True
|
|
13040
|
+
for name in row:
|
|
13041
|
+
field = fields_by_name.get(str(name))
|
|
13042
|
+
if field is None:
|
|
13043
|
+
continue
|
|
13044
|
+
question, preserved = _materialize_edit_question(field, temp_id=temp_id)
|
|
13045
|
+
questions.append(question)
|
|
13046
|
+
row_preserved = row_preserved and preserved
|
|
13047
|
+
temp_id -= 100
|
|
13048
|
+
if not questions:
|
|
13049
|
+
continue
|
|
13050
|
+
if not row_preserved:
|
|
13051
|
+
_apply_row_widths(questions)
|
|
13052
|
+
inner_rows.append(questions)
|
|
13053
|
+
if not inner_rows:
|
|
13054
|
+
continue
|
|
13055
|
+
template = _select_section_template(section_templates, section)
|
|
13056
|
+
wrapper = deepcopy(template) if isinstance(template, dict) else {
|
|
13057
|
+
"queId": 0,
|
|
13058
|
+
"queTempId": -(20000 + sum(ord(ch) for ch in str(section.get("section_id") or section.get("title") or "section"))),
|
|
13059
|
+
"queType": 24,
|
|
13060
|
+
"queWidth": 100,
|
|
13061
|
+
"scanType": 1,
|
|
13062
|
+
"status": 1,
|
|
13063
|
+
"required": False,
|
|
13064
|
+
"queHint": "",
|
|
13065
|
+
"linkedQuestions": {},
|
|
13066
|
+
"logicalShow": True,
|
|
13067
|
+
"queDefaultValue": None,
|
|
13068
|
+
"queDefaultType": 1,
|
|
13069
|
+
"subQueWidth": 2,
|
|
13070
|
+
"beingHide": False,
|
|
13071
|
+
"beingDesensitized": False,
|
|
13072
|
+
}
|
|
13073
|
+
if section.get("title") is not None:
|
|
13074
|
+
wrapper["queTitle"] = section.get("title") or wrapper.get("queTitle") or "未命名分组"
|
|
13075
|
+
parsed_section_id = _coerce_positive_int(section.get("section_id"))
|
|
13076
|
+
if parsed_section_id is not None:
|
|
13077
|
+
wrapper["sectionId"] = parsed_section_id
|
|
13078
|
+
elif template is None and section.get("section_id") is not None:
|
|
13079
|
+
wrapper["sectionId"] = section.get("section_id")
|
|
13080
|
+
wrapper["innerQuestions"] = inner_rows
|
|
13081
|
+
form_rows.append([wrapper])
|
|
13082
|
+
|
|
13083
|
+
payload = deepcopy(current_schema)
|
|
13084
|
+
payload["formTitle"] = title
|
|
13085
|
+
payload["formQues"] = form_rows
|
|
13086
|
+
payload["questionRelations"] = deepcopy(question_relations if question_relations is not None else (current_schema.get("questionRelations") or []))
|
|
13087
|
+
payload["editVersionNo"] = int(current_schema.get("editVersionNo") or 1)
|
|
13088
|
+
return payload
|
|
13089
|
+
|
|
13090
|
+
|
|
12917
13091
|
def _apply_row_widths(row: list[dict[str, Any]]) -> None:
|
|
12918
13092
|
if not row:
|
|
12919
13093
|
return
|