@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 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.87
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.87 qingflow-app-user-mcp
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-user-mcp",
3
- "version": "0.2.0-beta.87",
3
+ "version": "0.2.0-beta.89",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 = _build_form_payload_from_fields(
4793
- title=effective_app_name,
4794
- current_schema=schema_result,
4795
- fields=current_fields,
4796
- layout=layout,
4797
- question_relations=compiled_question_relations,
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 = _build_form_payload_from_fields(
4901
- title=effective_app_name,
4902
- current_schema=rebound_schema,
4903
- fields=rebound_fields,
4904
- layout=rebound_layout,
4905
- question_relations=compiled_question_relations,
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")) or 1,
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
- question, _next_temp_id = build_question(
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