@qingflow-tech/qingflow-app-user-mcp 1.0.7 → 1.0.9

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.
@@ -43,6 +43,7 @@ from ..builder_facade.models import (
43
43
  SchemaPlanRequest,
44
44
  VisibilityPatch,
45
45
  ViewFilterOperator,
46
+ ViewPartialPatch,
46
47
  ViewUpsertPatch,
47
48
  ViewsPreset,
48
49
  ViewsPlanRequest,
@@ -60,6 +61,14 @@ from .solution_tools import SolutionTools
60
61
  from .view_tools import ViewTools
61
62
  from .workflow_tools import WorkflowTools
62
63
 
64
+
65
+ def _normalize_builder_view_key(value: str) -> str:
66
+ raw = str(value or "").strip()
67
+ if raw.startswith("custom:"):
68
+ return raw.split(":", 1)[1].strip()
69
+ return raw
70
+
71
+
63
72
  PUBLIC_STABLE_FLOW_NODE_TYPES = ["start", "approve", "fill", "copy", "webhook", "end"]
64
73
 
65
74
 
@@ -227,6 +236,7 @@ class AiBuilderTools(ToolBase):
227
236
  profile: str = DEFAULT_PROFILE,
228
237
  app_key: str = "",
229
238
  upsert_buttons: list[JSONObject] | None = None,
239
+ patch_buttons: list[JSONObject] | None = None,
230
240
  remove_buttons: list[JSONObject] | None = None,
231
241
  view_configs: list[JSONObject] | None = None,
232
242
  ) -> JSONObject:
@@ -234,6 +244,7 @@ class AiBuilderTools(ToolBase):
234
244
  profile=profile,
235
245
  app_key=app_key,
236
246
  upsert_buttons=upsert_buttons or [],
247
+ patch_buttons=patch_buttons or [],
237
248
  remove_buttons=remove_buttons or [],
238
249
  view_configs=view_configs or [],
239
250
  )
@@ -243,6 +254,7 @@ class AiBuilderTools(ToolBase):
243
254
  profile: str = DEFAULT_PROFILE,
244
255
  app_key: str = "",
245
256
  upsert_resources: list[JSONObject] | None = None,
257
+ patch_resources: list[JSONObject] | None = None,
246
258
  remove_associated_item_ids: list[int] | None = None,
247
259
  reorder_associated_item_ids: list[int] | None = None,
248
260
  view_configs: list[JSONObject] | None = None,
@@ -251,6 +263,7 @@ class AiBuilderTools(ToolBase):
251
263
  profile=profile,
252
264
  app_key=app_key,
253
265
  upsert_resources=upsert_resources or [],
266
+ patch_resources=patch_resources or [],
254
267
  remove_associated_item_ids=remove_associated_item_ids or [],
255
268
  reorder_associated_item_ids=reorder_associated_item_ids or [],
256
269
  view_configs=view_configs or [],
@@ -395,6 +408,7 @@ class AiBuilderTools(ToolBase):
395
408
  app_key: str = "",
396
409
  publish: bool = True,
397
410
  upsert_views: list[JSONObject] | None = None,
411
+ patch_views: list[JSONObject] | None = None,
398
412
  remove_views: list[str] | None = None,
399
413
  ) -> JSONObject:
400
414
  return self.app_views_apply(
@@ -402,6 +416,7 @@ class AiBuilderTools(ToolBase):
402
416
  app_key=app_key,
403
417
  publish=publish,
404
418
  upsert_views=upsert_views or [],
419
+ patch_views=patch_views or [],
405
420
  remove_views=remove_views or [],
406
421
  )
407
422
 
@@ -410,6 +425,7 @@ class AiBuilderTools(ToolBase):
410
425
  profile: str = DEFAULT_PROFILE,
411
426
  app_key: str = "",
412
427
  upsert_charts: list[JSONObject] | None = None,
428
+ patch_charts: list[JSONObject] | None = None,
413
429
  remove_chart_ids: list[str] | None = None,
414
430
  reorder_chart_ids: list[str] | None = None,
415
431
  ) -> JSONObject:
@@ -417,6 +433,7 @@ class AiBuilderTools(ToolBase):
417
433
  profile=profile,
418
434
  app_key=app_key,
419
435
  upsert_charts=upsert_charts or [],
436
+ patch_charts=patch_charts or [],
420
437
  remove_chart_ids=remove_chart_ids or [],
421
438
  reorder_chart_ids=reorder_chart_ids or [],
422
439
  )
@@ -882,6 +899,7 @@ class AiBuilderTools(ToolBase):
882
899
  profile: str,
883
900
  app_key: str,
884
901
  upsert_buttons: list[JSONObject],
902
+ patch_buttons: list[JSONObject] | None = None,
885
903
  remove_buttons: list[JSONObject],
886
904
  view_configs: list[JSONObject] | None = None,
887
905
  ) -> JSONObject:
@@ -889,6 +907,7 @@ class AiBuilderTools(ToolBase):
889
907
  raw_request = {
890
908
  "app_key": app_key,
891
909
  "upsert_buttons": upsert_buttons,
910
+ "patch_buttons": patch_buttons or [],
892
911
  "remove_buttons": remove_buttons,
893
912
  "view_configs": view_configs or [],
894
913
  }
@@ -936,11 +955,13 @@ class AiBuilderTools(ToolBase):
936
955
  remove_associated_item_ids: list[int],
937
956
  reorder_associated_item_ids: list[int],
938
957
  view_configs: list[JSONObject],
958
+ patch_resources: list[JSONObject] | None = None,
939
959
  ) -> JSONObject:
940
960
  """执行应用关联资源 apply 逻辑。"""
941
961
  raw_request = {
942
962
  "app_key": app_key,
943
963
  "upsert_resources": upsert_resources,
964
+ "patch_resources": patch_resources or [],
944
965
  "remove_associated_item_ids": remove_associated_item_ids,
945
966
  "reorder_associated_item_ids": reorder_associated_item_ids,
946
967
  "view_configs": view_configs,
@@ -1272,7 +1293,7 @@ class AiBuilderTools(ToolBase):
1272
1293
  @tool_cn_name("视图详情查询")
1273
1294
  def view_get(self, *, profile: str, view_key: str = "", viewgraph_key: str = "") -> JSONObject:
1274
1295
  """执行视图相关逻辑。"""
1275
- resolved_view_key = str(view_key or viewgraph_key or "").strip()
1296
+ resolved_view_key = _normalize_builder_view_key(str(view_key or viewgraph_key or "").strip())
1276
1297
  normalized_args = {"view_key": resolved_view_key}
1277
1298
  return _safe_tool_call(
1278
1299
  lambda: self._facade.view_get(profile=profile, view_key=resolved_view_key),
@@ -1853,6 +1874,7 @@ class AiBuilderTools(ToolBase):
1853
1874
  app_key: str,
1854
1875
  publish: bool = True,
1855
1876
  upsert_views: list[JSONObject],
1877
+ patch_views: list[JSONObject] | None = None,
1856
1878
  remove_views: list[str],
1857
1879
  ) -> JSONObject:
1858
1880
  """执行应用相关逻辑。"""
@@ -1861,6 +1883,7 @@ class AiBuilderTools(ToolBase):
1861
1883
  app_key=app_key,
1862
1884
  publish=publish,
1863
1885
  upsert_views=upsert_views,
1886
+ patch_views=patch_views or [],
1864
1887
  remove_views=remove_views,
1865
1888
  )
1866
1889
  return self._retry_after_self_lock_release(
@@ -1872,6 +1895,7 @@ class AiBuilderTools(ToolBase):
1872
1895
  publish=publish,
1873
1896
  remove_views=remove_views,
1874
1897
  upsert_views=upsert_views,
1898
+ patch_views=patch_views or [],
1875
1899
  ),
1876
1900
  )
1877
1901
 
@@ -1882,9 +1906,53 @@ class AiBuilderTools(ToolBase):
1882
1906
  app_key: str,
1883
1907
  publish: bool = True,
1884
1908
  upsert_views: list[JSONObject],
1909
+ patch_views: list[JSONObject],
1885
1910
  remove_views: list[str],
1886
1911
  ) -> JSONObject:
1887
1912
  """执行内部辅助逻辑。"""
1913
+ if patch_views:
1914
+ try:
1915
+ parsed_views = [ViewUpsertPatch.model_validate(item) for item in (upsert_views or [])]
1916
+ parsed_patch_views = [ViewPartialPatch.model_validate(item) for item in patch_views]
1917
+ except ValidationError as exc:
1918
+ return _visibility_validation_failure(
1919
+ str(exc),
1920
+ tool_name="app_views_apply",
1921
+ exc=exc,
1922
+ suggested_next_call={
1923
+ "tool_name": "app_views_apply",
1924
+ "arguments": {
1925
+ "profile": profile,
1926
+ "app_key": app_key,
1927
+ "publish": publish,
1928
+ "upsert_views": upsert_views or [],
1929
+ "patch_views": [
1930
+ {"view_key": "VIEW_KEY", "set": {"query_conditions": {"enabled": True, "rows": [["字段A"]]}}},
1931
+ ],
1932
+ "remove_views": remove_views or [],
1933
+ },
1934
+ },
1935
+ )
1936
+ normalized_args = {
1937
+ "app_key": app_key,
1938
+ "publish": publish,
1939
+ "upsert_views": [view.model_dump(mode="json") for view in parsed_views],
1940
+ "patch_views": [patch.model_dump(mode="json") for patch in parsed_patch_views],
1941
+ "remove_views": list(remove_views or []),
1942
+ }
1943
+ return _safe_tool_call(
1944
+ lambda: self._facade.app_views_apply(
1945
+ profile=profile,
1946
+ app_key=app_key,
1947
+ publish=publish,
1948
+ upsert_views=parsed_views,
1949
+ patch_views=parsed_patch_views,
1950
+ remove_views=list(remove_views or []),
1951
+ ),
1952
+ error_code="VIEWS_APPLY_FAILED",
1953
+ normalized_args=normalized_args,
1954
+ suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
1955
+ )
1888
1956
  plan_result = self._rewrite_plan_result_for_apply(
1889
1957
  result=self.app_views_plan(
1890
1958
  profile=profile,
@@ -1933,6 +2001,7 @@ class AiBuilderTools(ToolBase):
1933
2001
  app_key=str(plan_args.get("app_key") or app_key),
1934
2002
  publish=publish,
1935
2003
  upsert_views=parsed_views,
2004
+ patch_views=[],
1936
2005
  remove_views=list(plan_args.get("remove_views") or remove_views),
1937
2006
  ),
1938
2007
  error_code="VIEWS_APPLY_FAILED",
@@ -1947,6 +2016,7 @@ class AiBuilderTools(ToolBase):
1947
2016
  profile: str,
1948
2017
  app_key: str,
1949
2018
  upsert_charts: list[JSONObject],
2019
+ patch_charts: list[JSONObject] | None = None,
1950
2020
  remove_chart_ids: list[str],
1951
2021
  reorder_chart_ids: list[str],
1952
2022
  ) -> JSONObject:
@@ -1955,6 +2025,7 @@ class AiBuilderTools(ToolBase):
1955
2025
  profile=profile,
1956
2026
  app_key=app_key,
1957
2027
  upsert_charts=upsert_charts,
2028
+ patch_charts=patch_charts or [],
1958
2029
  remove_chart_ids=remove_chart_ids,
1959
2030
  reorder_chart_ids=reorder_chart_ids,
1960
2031
  )
@@ -1968,6 +2039,7 @@ class AiBuilderTools(ToolBase):
1968
2039
  upsert_charts: list[JSONObject],
1969
2040
  remove_chart_ids: list[str],
1970
2041
  reorder_chart_ids: list[str],
2042
+ patch_charts: list[JSONObject] | None = None,
1971
2043
  ) -> JSONObject:
1972
2044
  """执行应用相关逻辑。"""
1973
2045
  try:
@@ -1975,6 +2047,7 @@ class AiBuilderTools(ToolBase):
1975
2047
  {
1976
2048
  "app_key": app_key,
1977
2049
  "upsert_charts": upsert_charts or [],
2050
+ "patch_charts": patch_charts or [],
1978
2051
  "remove_chart_ids": remove_chart_ids or [],
1979
2052
  "reorder_chart_ids": reorder_chart_ids or [],
1980
2053
  }
@@ -2550,6 +2623,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2550
2623
  "allowed_values": deepcopy(_VISIBILITY_ALLOWED_VALUES),
2551
2624
  "execution_notes": [
2552
2625
  "create or update package metadata, visibility, grouping, and ordering in one call",
2626
+ "metadata keys omitted on update are preserved",
2553
2627
  "package_id maps internally to backend tagId; do not use tag_id in public calls",
2554
2628
  "items is a full package layout tree; omitting existing app/portal items is blocked unless allow_detach=true",
2555
2629
  "item shapes: {type:'app', app_key}, {type:'portal', dash_key}, or {type:'group', group_id?, name, items:[...]}",
@@ -2696,6 +2770,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2696
2770
  "allowed_keys": [
2697
2771
  "app_key",
2698
2772
  "upsert_buttons",
2773
+ "patch_buttons",
2699
2774
  "remove_buttons",
2700
2775
  "view_configs",
2701
2776
  "upsert_buttons[].client_key",
@@ -2713,6 +2788,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2713
2788
  "upsert_buttons[].trigger_add_data_config.field_mappings[].source_field",
2714
2789
  "upsert_buttons[].trigger_add_data_config.field_mappings[].target_field",
2715
2790
  "upsert_buttons[].trigger_add_data_config.default_values",
2791
+ "patch_buttons[].button_id",
2792
+ "patch_buttons[].button_text",
2793
+ "patch_buttons[].set",
2794
+ "patch_buttons[].unset",
2716
2795
  "view_configs[].view_key",
2717
2796
  "view_configs[].mode",
2718
2797
  "view_configs[].buttons",
@@ -2728,6 +2807,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2728
2807
  ],
2729
2808
  "aliases": {
2730
2809
  "upsertButtons": "upsert_buttons",
2810
+ "patchButtons": "patch_buttons",
2731
2811
  "removeButtons": "remove_buttons",
2732
2812
  "viewConfigs": "view_configs",
2733
2813
  "clientKey": "client_key",
@@ -2767,15 +2847,19 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2767
2847
  },
2768
2848
  "execution_notes": [
2769
2849
  "this is the default custom-button write path; old list/get/create/update/delete tools are hidden from the public agent surface",
2850
+ "use patch_buttons for partial parameter replacement on existing buttons; the tool reads current button detail, merges patch_buttons[].set/unset, then submits the backend full-save payload internally",
2770
2851
  "button_id targets an existing button; without button_id, button_text is used as an exact unique upsert key, otherwise a new button is created",
2771
2852
  "for addData buttons, use trigger_add_data_config.target_app_key plus field_mappings/default_values; field_mappings compiles to backend queRelation",
2853
+ "field_mappings.source_field accepts source schema fields and supported system fields: 数据ID/row_record_id/apply_id/_id maps to current record id (-17), 编号/record_number maps to visible record number (0)",
2854
+ "to fill a target relation field with the current source record, map source_field='数据ID' to the target relation field; default_values is for static constants, not dynamic current-record values",
2772
2855
  "do not write raw que_relation unless maintaining a legacy config; field_mappings/default_values and que_relation are mutually exclusive",
2773
2856
  "view_configs binds custom buttons into views in the same apply call; button_ref may be a same-call client_key, a button_id, or an exact unique existing button_text",
2857
+ "view_configs[].view_key is the raw builder view key from app_get.views[].view_key; do not pass record-data view_id values like custom:VIEW_KEY",
2774
2858
  "view_configs[].buttons is required in merge mode; omitting buttons is blocked to avoid no-op writes and accidental publish",
2775
2859
  "view_configs[].mode defaults to merge; use mode=replace or an explicit empty buttons list to replace/clear a view's custom button bindings",
2776
2860
  "advanced view button binding supports button_limit, button_formula, button_formula_type, and print_tpls when visibility or print-template behavior is required",
2777
2861
  "default placements are header and detail; header maps to frontend top buttons",
2778
- "placement=list is accepted as an experimental row/list placement, but some backend deployments reject it; if that happens, the tool returns LIST_BUTTON_BACKEND_UNSUPPORTED and still applies header/detail placements when they were part of the same view config",
2862
+ "placement=list configures backend INSIDE row/list buttons; header maps to TOP and detail maps to DETAIL",
2779
2863
  "remove_buttons supports button_id or exact unique button_text",
2780
2864
  "all operations share one edit context and publish after at least one write succeeds; there is no draft-only mode for this tool",
2781
2865
  "background_color and text_color cannot both be white",
@@ -2792,6 +2876,12 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2792
2876
  "trigger_link_url": "https://example.com",
2793
2877
  }
2794
2878
  ],
2879
+ "patch_buttons": [
2880
+ {
2881
+ "button_text": "同步客户",
2882
+ "set": {"trigger_link_url": "https://example.com/new"},
2883
+ }
2884
+ ],
2795
2885
  "remove_buttons": [{"button_text": "旧按钮"}],
2796
2886
  "view_configs": [],
2797
2887
  },
@@ -2820,11 +2910,36 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2820
2910
  }
2821
2911
  ],
2822
2912
  },
2913
+ "relation_current_record_example": {
2914
+ "profile": "default",
2915
+ "app_key": "EMPLOYEE_APP",
2916
+ "upsert_buttons": [
2917
+ {
2918
+ "client_key": "add_worklog",
2919
+ "button_text": "快捷添加工时",
2920
+ "style_preset": "neutral_outline",
2921
+ "button_icon": "ex-plus-circle",
2922
+ "trigger_action": "addData",
2923
+ "trigger_add_data_config": {
2924
+ "target_app_key": "WORKLOG_APP",
2925
+ "field_mappings": [{"source_field": "数据ID", "target_field": "关联员工"}],
2926
+ "default_values": {"状态": "待提交"},
2927
+ },
2928
+ }
2929
+ ],
2930
+ "view_configs": [
2931
+ {
2932
+ "view_key": "RAW_VIEW_KEY",
2933
+ "buttons": [{"button_ref": "add_worklog", "placement": "detail", "primary": True}],
2934
+ }
2935
+ ],
2936
+ },
2823
2937
  },
2824
2938
  "app_associated_resources_apply": {
2825
2939
  "allowed_keys": [
2826
2940
  "app_key",
2827
2941
  "upsert_resources",
2942
+ "patch_resources",
2828
2943
  "remove_associated_item_ids",
2829
2944
  "reorder_associated_item_ids",
2830
2945
  "view_configs",
@@ -2835,7 +2950,15 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2835
2950
  "upsert_resources[].view_key",
2836
2951
  "upsert_resources[].chart_key",
2837
2952
  "upsert_resources[].report_source",
2953
+ "upsert_resources[].match_mappings",
2954
+ "upsert_resources[].match_mappings[].target_field",
2955
+ "upsert_resources[].match_mappings[].source_field",
2956
+ "upsert_resources[].match_mappings[].value",
2957
+ "upsert_resources[].match_mappings[].operator",
2838
2958
  "upsert_resources[].match_rules",
2959
+ "patch_resources[].associated_item_id",
2960
+ "patch_resources[].set",
2961
+ "patch_resources[].unset",
2839
2962
  "view_configs[].view_key",
2840
2963
  "view_configs[].visible",
2841
2964
  "view_configs[].limit_type",
@@ -2844,6 +2967,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2844
2967
  ],
2845
2968
  "aliases": {
2846
2969
  "upsertResources": "upsert_resources",
2970
+ "patchResources": "patch_resources",
2847
2971
  "resources": "upsert_resources",
2848
2972
  "removeAssociatedItemIds": "remove_associated_item_ids",
2849
2973
  "reorderAssociatedItemIds": "reorder_associated_item_ids",
@@ -2852,9 +2976,11 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2852
2976
  "graphType": "graph_type",
2853
2977
  "targetAppKey": "target_app_key",
2854
2978
  "chartKey": "chart_key",
2979
+ "chartId": "chart_key",
2855
2980
  "viewKey": "view_key",
2856
2981
  "viewgraphKey": "view_key",
2857
2982
  "reportSource": "report_source",
2983
+ "matchMappings": "match_mappings",
2858
2984
  "matchRules": "match_rules",
2859
2985
  "associatedItemRefs": "associated_item_refs",
2860
2986
  "associatedItemIds": "associated_item_ids",
@@ -2865,11 +2991,18 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2865
2991
  "view_configs[].limit_type": ["all", "select"],
2866
2992
  },
2867
2993
  "execution_notes": [
2994
+ "this tool manages Qingflow in-app associated report/view display; it does not create or edit QingBI report bodies/configs",
2995
+ "create or edit app-source BI report bodies first with app_charts_apply, then attach the resulting chart_id here with graph_type=chart; dataset BI reports can only be attached when they already exist",
2868
2996
  "this is the default associated report/view path; it manages both the app-level associated resource pool and per-view display config",
2869
- "associated_item_id is form_asos_chart.id from app_get.associated_resources[].associated_item_id; it is not chart_id, chart_key, or view_key",
2997
+ "use patch_resources for partial parameter replacement on existing associated resources; the tool reads the current resource including backend-required raw fields, merges patch_resources[].set/unset, then submits the backend full-save payload internally",
2998
+ "associated_item_id is form_asos_chart.id from app_get.associated_resources[].associated_item_id; view_configs/remove/reorder may also pass an existing associated resource's chart_id/chart_key/view_key and the tool resolves it to the internal id",
2999
+ "before creating an associated resource, read app_get.associated_resources and reuse an existing item with patch_resources when target_app_key + view_key/chart_key already matches; repeated upsert_resources without associated_item_id can create duplicate associated items",
2870
3000
  "graph_type=view uses view_key and internally compiles to the Qingflow view source; graph_type=chart uses chart_key and defaults to report_source=app",
2871
- "report_source=app maps to BI_QINGFLOW; report_source=dataset maps to BI_DATASET; do not pass raw backend sourceType",
2872
- "client_key lets a view_config reference a resource created earlier in the same apply call through associated_item_refs",
3001
+ "report_source=app maps to BI_QINGFLOW; report_source=dataset maps to BI_DATASET for associating an existing dataset report; do not pass raw backend sourceType",
3002
+ "use match_mappings for associated view/report filtering; dynamic conditions use source_field and static conditions use value",
3003
+ "match_mappings.source_field accepts source schema fields plus system fields 数据ID(-17) and 编号(0); match_mappings compiles to backend matchRules",
3004
+ "do not write raw match_rules unless preserving a legacy backend config; match_mappings and match_rules are mutually exclusive",
3005
+ "client_key only lets a view_config reference a resource created earlier in the same apply call through associated_item_refs; it is not persisted and cannot deduplicate later apply calls",
2873
3006
  "this tool publishes after at least one write succeeds; there is no draft-only mode",
2874
3007
  "visible=false hides the associated-resource area without clearing previous selected ids; visible=true with limit_type=all shows the whole app-level pool",
2875
3008
  ],
@@ -2882,7 +3015,13 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2882
3015
  "graph_type": "view",
2883
3016
  "target_app_key": "TARGET_APP",
2884
3017
  "view_key": "VIEW_KEY",
2885
- "match_rules": [],
3018
+ "match_mappings": [{"target_field": "关联员工", "source_field": "数据ID"}],
3019
+ }
3020
+ ],
3021
+ "patch_resources": [
3022
+ {
3023
+ "associated_item_id": 123,
3024
+ "set": {"match_mappings": [{"target_field": "状态", "value": "待提交"}]},
2886
3025
  }
2887
3026
  ],
2888
3027
  "remove_associated_item_ids": [],
@@ -3009,6 +3148,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3009
3148
  "create mode: package_id + app_name + create_if_missing=true",
3010
3149
  "create mode defaults new app visibility to workspace/not when visibility is omitted; edit mode preserves current visibility when omitted",
3011
3150
  *_VISIBILITY_EXECUTION_NOTES,
3151
+ "update_fields is the field-level partial update path; it reads current form schema and preserves untouched field config",
3012
3152
  "multiple relation fields are backend-risky; read verification.relation_field_limit_verified and warnings before declaring the schema stable",
3013
3153
  "backend 49614 is normalized to MULTIPLE_RELATION_FIELDS_UNSUPPORTED with a workaround message",
3014
3154
  "relation_mode=multiple maps to referenceConfig.optionalDataNum=0",
@@ -3208,6 +3348,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3208
3348
  },
3209
3349
  "allowed_values": {"mode": [member.value for member in LayoutApplyMode]},
3210
3350
  "execution_notes": [
3351
+ "mode=merge is the layout partial update path: mentioned sections are merged and unmentioned fields are preserved",
3352
+ "mode=replace is full layout replacement and should be used only when intentionally rewriting all sections",
3211
3353
  "layout verification is split into layout_verified and layout_summary_verified",
3212
3354
  "LAYOUT_SUMMARY_UNVERIFIED means raw form readback is stronger than the compact summary",
3213
3355
  ],
@@ -3285,6 +3427,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3285
3427
  ],
3286
3428
  "execution_notes": [
3287
3429
  "public flow building is intentionally limited to linear workflows",
3430
+ "app_flow_apply is replace-only; do not treat node snippets as partial patches",
3288
3431
  "branch and condition nodes are disabled because the backend workflow route is not front-end stable for these node types",
3289
3432
  "workflow verification only covers linear node structure in the public tool surface",
3290
3433
  ],
@@ -3308,7 +3451,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3308
3451
  },
3309
3452
  },
3310
3453
  "app_views_plan": {
3311
- "allowed_keys": ["app_key", "upsert_views", "remove_views", "preset", "upsert_views[].view_key", "upsert_views[].buttons", "upsert_views[].visibility", "upsert_views[].query_conditions"],
3454
+ "allowed_keys": ["app_key", "upsert_views", "patch_views", "remove_views", "preset", "upsert_views[].view_key", "upsert_views[].buttons", "upsert_views[].visibility", "upsert_views[].query_conditions", "patch_views[].view_key", "patch_views[].name", "patch_views[].set", "patch_views[].unset"],
3312
3455
  "aliases": {
3313
3456
  "fields": "columns",
3314
3457
  "column_names": "columns",
@@ -3322,6 +3465,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3322
3465
  "queryConditions": "query_conditions",
3323
3466
  "query_condition": "query_conditions",
3324
3467
  "queryCondition": "query_conditions",
3468
+ "patchViews": "patch_views",
3325
3469
  "startField": "start_field",
3326
3470
  "endField": "end_field",
3327
3471
  "titleField": "title_field",
@@ -3346,6 +3490,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3346
3490
  "upsert_views[].visibility may set per-view visibility; omit it to preserve an existing view's auth or default a new view to workspace/not",
3347
3491
  "filters are saved fixed filters that apply when the view opens; query_conditions configure the frontend query panel and only apply after a user enters query values",
3348
3492
  "upsert_views[].query_conditions.rows is a layout matrix of field names; it is compiled to backend queryCondition queIds",
3493
+ "use patch_views for partial parameter replacement on existing views; the tool reads current config, merges patch_views[].set/unset, then submits the backend full-save payload internally",
3349
3494
  "view associated report/view display is now configured through app_associated_resources_apply, not app_views_apply",
3350
3495
  "for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
3351
3496
  *_VISIBILITY_EXECUTION_NOTES,
@@ -3357,6 +3502,22 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3357
3502
  "remove_views": [],
3358
3503
  },
3359
3504
  "query_conditions_example": {
3505
+ "profile": "default",
3506
+ "app_key": "APP_KEY",
3507
+ "patch_views": [
3508
+ {
3509
+ "view_key": "VIEW_KEY",
3510
+ "set": {
3511
+ "query_conditions": {
3512
+ "enabled": True,
3513
+ "rows": [["客户名称", "负责人"], ["创建时间"]],
3514
+ }
3515
+ },
3516
+ }
3517
+ ],
3518
+ "remove_views": [],
3519
+ },
3520
+ "full_upsert_query_conditions_example": {
3360
3521
  "profile": "default",
3361
3522
  "app_key": "APP_KEY",
3362
3523
  "upsert_views": [
@@ -3393,7 +3554,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3393
3554
  },
3394
3555
  },
3395
3556
  "app_views_apply": {
3396
- "allowed_keys": ["app_key", "publish", "upsert_views", "remove_views", "upsert_views[].view_key", "upsert_views[].buttons", "upsert_views[].visibility", "upsert_views[].query_conditions"],
3557
+ "allowed_keys": ["app_key", "publish", "upsert_views", "patch_views", "remove_views", "upsert_views[].view_key", "upsert_views[].buttons", "upsert_views[].visibility", "upsert_views[].query_conditions", "patch_views[].view_key", "patch_views[].name", "patch_views[].set", "patch_views[].unset"],
3397
3558
  "aliases": {
3398
3559
  "fields": "columns",
3399
3560
  "column_names": "columns",
@@ -3407,6 +3568,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3407
3568
  "queryConditions": "query_conditions",
3408
3569
  "query_condition": "query_conditions",
3409
3570
  "queryCondition": "query_conditions",
3571
+ "patchViews": "patch_views",
3410
3572
  "startField": "start_field",
3411
3573
  "endField": "end_field",
3412
3574
  "titleField": "title_field",
@@ -3435,6 +3597,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3435
3597
  "upsert_views[].visibility may set per-view visibility; omit it to preserve an existing view's auth or default a new view to workspace/not",
3436
3598
  "filters are saved fixed filters that apply when the view opens; query_conditions configure the frontend query panel and only apply after a user enters query values",
3437
3599
  "upsert_views[].query_conditions.rows is a layout matrix of field names; it is compiled to backend queryCondition queIds",
3600
+ "use patch_views for partial parameter replacement on existing views; the public update mode is patch even though the backend save is still a full view payload",
3438
3601
  "view associated report/view display is now configured through app_associated_resources_apply; app_views_apply keeps legacy associated_resources input compatible but it is no longer the recommended public contract",
3439
3602
  "for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
3440
3603
  *_VISIBILITY_EXECUTION_NOTES,
@@ -3447,6 +3610,23 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3447
3610
  "remove_views": [],
3448
3611
  },
3449
3612
  "query_conditions_example": {
3613
+ "profile": "default",
3614
+ "app_key": "APP_KEY",
3615
+ "publish": True,
3616
+ "patch_views": [
3617
+ {
3618
+ "view_key": "VIEW_KEY",
3619
+ "set": {
3620
+ "query_conditions": {
3621
+ "enabled": True,
3622
+ "rows": [["客户名称", "负责人"], ["创建时间"]],
3623
+ }
3624
+ },
3625
+ }
3626
+ ],
3627
+ "remove_views": [],
3628
+ },
3629
+ "full_upsert_query_conditions_example": {
3450
3630
  "profile": "default",
3451
3631
  "app_key": "APP_KEY",
3452
3632
  "publish": True,
@@ -3496,7 +3676,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3496
3676
  "can_edit_form covers form/schema routes only and does not imply app base-info writes",
3497
3677
  "returns normalized app visibility when backend auth is readable",
3498
3678
  "custom_buttons[].button_id is the id required by app_custom_buttons_apply view_configs[].buttons[].button_ref",
3499
- "associated_resources[].associated_item_id is the id required by app_associated_resources_apply.view_configs.associated_item_ids; do not pass chart_id there",
3679
+ "associated_resources[].associated_item_id is the internal id; app_associated_resources_apply.view_configs/remove/reorder may also pass an existing resource's chart_id/chart_key/view_key",
3500
3680
  ],
3501
3681
  "minimal_example": {
3502
3682
  "profile": "default",
@@ -3604,8 +3784,9 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3604
3784
  },
3605
3785
  },
3606
3786
  "app_charts_apply": {
3607
- "allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids", "upsert_charts[].visibility"],
3787
+ "allowed_keys": ["app_key", "upsert_charts", "patch_charts", "remove_chart_ids", "reorder_chart_ids", "upsert_charts[].visibility", "patch_charts[].chart_id", "patch_charts[].name", "patch_charts[].set", "patch_charts[].unset"],
3608
3788
  "aliases": {
3789
+ "patchCharts": "patch_charts",
3609
3790
  "chart.id": "chart.chart_id",
3610
3791
  "chart.type": "chart.chart_type",
3611
3792
  "chart.dimension_fields": "chart.dimension_field_ids",
@@ -3619,7 +3800,12 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3619
3800
  **deepcopy(_VISIBILITY_ALLOWED_VALUES),
3620
3801
  },
3621
3802
  "execution_notes": [
3803
+ "this tool manages QingBI report bodies/configs; it does not attach reports to Qingflow app associated-resource display",
3804
+ "app_charts_apply creates/updates app-source QingBI reports only; generated payloads use dataSourceType=qingflow",
3805
+ "dataset BI reports are not created or edited by this tool yet; create them in QingBI first, then attach the existing report with app_associated_resources_apply report_source=dataset",
3806
+ "after creating or updating an app-source report body, use app_associated_resources_apply when the report should appear inside a Qingflow app/view",
3622
3807
  "app_charts_apply is immediate-live and does not publish",
3808
+ "use patch_charts for partial parameter replacement on existing charts; the tool reads current chart base/config, merges patch_charts[].set/unset, then submits full QingBI base/config payloads internally",
3623
3809
  "chart matching precedence is chart_id first, then exact unique chart name",
3624
3810
  "when chart names are not unique, supply chart_id instead of guessing by name",
3625
3811
  "successful create results must return a real backend chart_id",
@@ -3631,6 +3817,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3631
3817
  "profile": "default",
3632
3818
  "app_key": "APP_KEY",
3633
3819
  "upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": [], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
3820
+ "patch_charts": [{"chart_id": "CHART_ID", "set": {"name": "数据总量-新版"}}],
3634
3821
  "remove_chart_ids": [],
3635
3822
  "reorder_chart_ids": [],
3636
3823
  },
@@ -3642,6 +3829,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3642
3829
  "execution_notes": [
3643
3830
  "returns one builder-side view definition detail",
3644
3831
  "does not return record data; use user-side view_get or record_list for runtime rows",
3832
+ "view_key is a raw builder view key; if a record-data custom:VIEW_KEY value is passed, the tool normalizes it to VIEW_KEY",
3645
3833
  "use this after builder portal_get when a component references a view_ref.view_key",
3646
3834
  "returns normalized view visibility when backend auth is readable",
3647
3835
  ],
@@ -3688,6 +3876,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
3688
3876
  "create mode: package_id + dash_name",
3689
3877
  "portal_apply uses replace semantics for sections",
3690
3878
  "when editing an existing portal, sections may be omitted to update only base info such as visibility, icon, or package",
3879
+ "portal section-level patch is not exposed; supplying sections means full sections replacement",
3691
3880
  "remove a section by omitting it from the new sections list",
3692
3881
  "package_id is required when creating a new portal",
3693
3882
  "publish=false only guarantees draft and base-info updates; it does not claim live has changed",