@josephyan/qingflow-app-user-mcp 0.2.0-beta.87 → 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 +219 -18
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")
|
|
@@ -9960,6 +9980,7 @@ def _parse_field(question: dict[str, Any], *, field_id_hint: str | None = None)
|
|
|
9960
9980
|
continue
|
|
9961
9981
|
subfields.append(_parse_field(sub_question))
|
|
9962
9982
|
field["subfields"] = subfields
|
|
9983
|
+
field["_question_template"] = deepcopy(question)
|
|
9963
9984
|
return field
|
|
9964
9985
|
|
|
9965
9986
|
|
|
@@ -10846,53 +10867,82 @@ def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
|
|
|
10846
10867
|
payload.get("type") == FieldType.relation.value
|
|
10847
10868
|
or any(key in payload for key in ("target_app_key", "display_field", "visible_fields", "relation_mode"))
|
|
10848
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"))
|
|
10849
10872
|
if "name" in payload:
|
|
10850
10873
|
field["name"] = payload["name"]
|
|
10874
|
+
question_overlay_keys.add("name")
|
|
10851
10875
|
if "type" in payload:
|
|
10852
10876
|
field["type"] = payload["type"]
|
|
10877
|
+
question_rebuild_required = True
|
|
10853
10878
|
if "required" in payload:
|
|
10854
10879
|
field["required"] = payload["required"]
|
|
10880
|
+
question_overlay_keys.add("required")
|
|
10855
10881
|
if "description" in payload:
|
|
10856
10882
|
field["description"] = payload["description"]
|
|
10883
|
+
question_overlay_keys.add("description")
|
|
10857
10884
|
if "options" in payload:
|
|
10858
10885
|
field["options"] = list(payload["options"])
|
|
10886
|
+
question_rebuild_required = True
|
|
10859
10887
|
if "target_app_key" in payload:
|
|
10860
10888
|
field["target_app_key"] = payload["target_app_key"]
|
|
10889
|
+
question_rebuild_required = True
|
|
10861
10890
|
if "display_field" in payload:
|
|
10862
10891
|
field["display_field"] = payload["display_field"]
|
|
10892
|
+
question_rebuild_required = True
|
|
10863
10893
|
if "visible_fields" in payload:
|
|
10864
10894
|
field["visible_fields"] = list(payload["visible_fields"])
|
|
10895
|
+
question_rebuild_required = True
|
|
10865
10896
|
if "relation_mode" in payload:
|
|
10866
10897
|
field["relation_mode"] = payload["relation_mode"]
|
|
10898
|
+
question_rebuild_required = True
|
|
10867
10899
|
if "department_scope" in payload:
|
|
10868
10900
|
field["department_scope"] = payload["department_scope"]
|
|
10901
|
+
question_rebuild_required = True
|
|
10869
10902
|
if "remote_lookup_config" in payload:
|
|
10870
10903
|
field["remote_lookup_config"] = payload["remote_lookup_config"]
|
|
10871
10904
|
field["config"] = deepcopy(payload["remote_lookup_config"])
|
|
10872
10905
|
field["_explicit_remote_lookup_config"] = True
|
|
10906
|
+
question_rebuild_required = True
|
|
10873
10907
|
if "q_linker_binding" in payload:
|
|
10874
10908
|
field["q_linker_binding"] = payload["q_linker_binding"]
|
|
10875
10909
|
if "remote_lookup_config" not in payload:
|
|
10876
10910
|
field["_explicit_remote_lookup_config"] = False
|
|
10911
|
+
question_rebuild_required = True
|
|
10877
10912
|
if "code_block_config" in payload:
|
|
10878
10913
|
field["code_block_config"] = payload["code_block_config"]
|
|
10879
10914
|
field["config"] = deepcopy(payload["code_block_config"])
|
|
10915
|
+
question_rebuild_required = True
|
|
10880
10916
|
if "code_block_binding" in payload:
|
|
10881
10917
|
field["code_block_binding"] = payload["code_block_binding"]
|
|
10882
10918
|
field["_explicit_code_block_binding"] = True
|
|
10919
|
+
question_rebuild_required = True
|
|
10883
10920
|
if "auto_trigger" in payload:
|
|
10884
10921
|
field["auto_trigger"] = payload["auto_trigger"]
|
|
10922
|
+
question_rebuild_required = True
|
|
10885
10923
|
if "custom_button_text_enabled" in payload:
|
|
10886
10924
|
field["custom_button_text_enabled"] = payload["custom_button_text_enabled"]
|
|
10925
|
+
question_rebuild_required = True
|
|
10887
10926
|
if "custom_button_text" in payload:
|
|
10888
10927
|
field["custom_button_text"] = payload["custom_button_text"]
|
|
10928
|
+
question_rebuild_required = True
|
|
10889
10929
|
if "subfields" in payload:
|
|
10890
10930
|
field["subfields"] = [_field_patch_to_internal(item) for item in payload["subfields"]]
|
|
10931
|
+
question_rebuild_required = True
|
|
10891
10932
|
if relation_config_explicit:
|
|
10892
10933
|
field["_relation_config_explicit"] = True
|
|
10934
|
+
question_rebuild_required = True
|
|
10893
10935
|
elif payload.get("type") and payload.get("type") != FieldType.relation.value:
|
|
10894
10936
|
field.pop("_relation_config_explicit", None)
|
|
10895
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)
|
|
10896
10946
|
|
|
10897
10947
|
|
|
10898
10948
|
def _resolve_field_selector_with_uniqueness(
|
|
@@ -12690,8 +12740,44 @@ def _verify_package_attachment(packages: PackageTools, *, profile: str, tag_id:
|
|
|
12690
12740
|
return last_result
|
|
12691
12741
|
|
|
12692
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
|
+
|
|
12693
12779
|
def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]:
|
|
12694
|
-
|
|
12780
|
+
built_question, _next_temp_id = build_question(
|
|
12695
12781
|
{
|
|
12696
12782
|
"label": field["name"],
|
|
12697
12783
|
"type": field["type"],
|
|
@@ -12716,11 +12802,23 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
|
|
|
12716
12802
|
},
|
|
12717
12803
|
temp_id,
|
|
12718
12804
|
)
|
|
12805
|
+
relation_question_template = (
|
|
12806
|
+
deepcopy(field.get("_question_template"))
|
|
12807
|
+
if field.get("type") == FieldType.relation.value and isinstance(field.get("_question_template"), dict)
|
|
12808
|
+
else None
|
|
12809
|
+
)
|
|
12810
|
+
question = relation_question_template if relation_question_template is not None else built_question
|
|
12719
12811
|
if _coerce_nonnegative_int(field.get("que_id")) is not None:
|
|
12720
12812
|
question["queId"] = field["que_id"]
|
|
12813
|
+
question.pop("queTempId", None)
|
|
12721
12814
|
else:
|
|
12815
|
+
question["queId"] = 0
|
|
12722
12816
|
question["queTempId"] = temp_id
|
|
12723
12817
|
field["que_temp_id"] = temp_id
|
|
12818
|
+
question["queType"] = built_question.get("queType", question.get("queType"))
|
|
12819
|
+
question["queTitle"] = built_question.get("queTitle", field["name"])
|
|
12820
|
+
question["required"] = built_question.get("required", bool(field.get("required", False)))
|
|
12821
|
+
question["queHint"] = built_question.get("queHint", field.get("description") or "")
|
|
12724
12822
|
if field.get("default_type") is not None:
|
|
12725
12823
|
question["queDefaultType"] = _coerce_positive_int(field.get("default_type")) or 1
|
|
12726
12824
|
if "default_value" in field:
|
|
@@ -12733,15 +12831,28 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
|
|
|
12733
12831
|
)
|
|
12734
12832
|
if preserved_reference is not None:
|
|
12735
12833
|
preserved_reference["referAppKey"] = field.get("target_app_key")
|
|
12736
|
-
preserved_reference["_targetEntityId"] = field.get("target_app_key")
|
|
12737
12834
|
question["referenceConfig"] = preserved_reference
|
|
12738
12835
|
else:
|
|
12739
|
-
reference = question.get("referenceConfig") if isinstance(question.get("referenceConfig"), dict) else {}
|
|
12836
|
+
reference = deepcopy(question.get("referenceConfig")) if isinstance(question.get("referenceConfig"), dict) else {}
|
|
12837
|
+
built_reference = (
|
|
12838
|
+
deepcopy(built_question.get("referenceConfig"))
|
|
12839
|
+
if isinstance(built_question.get("referenceConfig"), dict)
|
|
12840
|
+
else {}
|
|
12841
|
+
)
|
|
12842
|
+
for key in (
|
|
12843
|
+
"referQueId",
|
|
12844
|
+
"referQuestions",
|
|
12845
|
+
"referAuthQues",
|
|
12846
|
+
"optionalDataNum",
|
|
12847
|
+
"fieldNameShow",
|
|
12848
|
+
"_targetFieldId",
|
|
12849
|
+
):
|
|
12850
|
+
if key in built_reference:
|
|
12851
|
+
reference[key] = deepcopy(built_reference[key])
|
|
12740
12852
|
reference["referAppKey"] = field.get("target_app_key")
|
|
12741
12853
|
reference["_targetEntityId"] = field.get("target_app_key")
|
|
12742
12854
|
if field.get("target_field_que_id") is not None:
|
|
12743
12855
|
reference["referQueId"] = field.get("target_field_que_id")
|
|
12744
|
-
reference["optionalDataNum"] = _relation_mode_to_optional_data_num(field.get("relation_mode"))
|
|
12745
12856
|
question["referenceConfig"] = reference
|
|
12746
12857
|
if field.get("type") == FieldType.department.value:
|
|
12747
12858
|
scope_type, scope_payload = _serialize_department_scope_for_question(field.get("department_scope"))
|
|
@@ -12887,6 +12998,96 @@ def _build_form_payload_from_fields(
|
|
|
12887
12998
|
return payload
|
|
12888
12999
|
|
|
12889
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
|
+
|
|
12890
13091
|
def _apply_row_widths(row: list[dict[str, Any]]) -> None:
|
|
12891
13092
|
if not row:
|
|
12892
13093
|
return
|