@qingflow-tech/qingflow-app-builder-mcp 1.0.40 → 1.0.42

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.
@@ -5,7 +5,7 @@ from copy import deepcopy
5
5
 
6
6
  from ..context import CliContext
7
7
  from ..json_io import load_json_value
8
- from .common import load_list_arg, load_object_arg, raise_config_error, require_list_arg
8
+ from .common import load_list_arg, load_object_arg, parse_bool_text, raise_config_error, require_list_arg
9
9
 
10
10
 
11
11
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
@@ -196,7 +196,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
196
196
  schema_apply_apply.add_argument("--color")
197
197
  schema_apply_apply.add_argument("--visibility-file")
198
198
  schema_apply_apply.add_argument("--create-if-missing", action="store_true")
199
- schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
199
+ schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=None)
200
200
  schema_apply_apply.add_argument("--apps-file", help="多应用 schema JSON 数组;每项可带 client_key/app_name/add_fields,支持 relation target_app_ref")
201
201
  schema_apply_apply.add_argument("--add-fields-file", help="字段 JSON 数组;字段可用 as_data_title/as_data_cover 标记数据标题/封面")
202
202
  schema_apply_apply.add_argument("--update-fields-file", help="字段更新 JSON 数组;set 内可用 as_data_title/as_data_cover 标记数据标题/封面")
@@ -483,8 +483,20 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
483
483
  apps_warnings = apps_payload.get("warnings") or []
484
484
  package_id = args.package_id
485
485
  if package_id is None and apps_payload.get("package_id") is not None:
486
- package_id = int(apps_payload["package_id"])
486
+ package_id = _coerce_apps_file_package_id(apps_payload["package_id"])
487
487
  if args.apps_file:
488
+ file_create_if_missing = _coerce_apps_file_bool(
489
+ apps_payload.get("create_if_missing"),
490
+ field_name="create_if_missing",
491
+ default=False,
492
+ )
493
+ file_publish = _coerce_apps_file_bool(
494
+ apps_payload.get("publish"),
495
+ field_name="publish",
496
+ default=True,
497
+ )
498
+ effective_create_if_missing = bool(args.create_if_missing or file_create_if_missing)
499
+ effective_publish = bool(args.publish if args.publish is not None else file_publish)
488
500
  if not apps:
489
501
  raise_config_error(
490
502
  "schema apply multi-app mode requires a non-empty --apps-file.",
@@ -505,8 +517,8 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
505
517
  profile=args.profile,
506
518
  package_id=package_id,
507
519
  visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
508
- create_if_missing=bool(args.create_if_missing),
509
- publish=bool(args.publish),
520
+ create_if_missing=effective_create_if_missing,
521
+ publish=effective_publish,
510
522
  apps=apps,
511
523
  add_fields=[],
512
524
  update_fields=[],
@@ -543,7 +555,7 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
543
555
  color=args.color,
544
556
  visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
545
557
  create_if_missing=bool(args.create_if_missing),
546
- publish=bool(args.publish),
558
+ publish=True if args.publish is None else bool(args.publish),
547
559
  add_fields=load_list_arg(args.add_fields_file, option_name="--add-fields-file"),
548
560
  update_fields=load_list_arg(args.update_fields_file, option_name="--update-fields-file"),
549
561
  remove_fields=load_list_arg(args.remove_fields_file, option_name="--remove-fields-file"),
@@ -595,6 +607,10 @@ def _load_apps_file_arg(path: str | None) -> dict[str, object]:
595
607
  }
596
608
  if wrapper.get("package_id") is not None:
597
609
  result["package_id"] = wrapper.get("package_id")
610
+ if wrapper.get("create_if_missing") is not None:
611
+ result["create_if_missing"] = wrapper.get("create_if_missing")
612
+ if wrapper.get("publish") is not None:
613
+ result["publish"] = wrapper.get("publish")
598
614
  return result
599
615
  if any(isinstance(item, dict) and "apps" in item for item in payload):
600
616
  raise_config_error(
@@ -616,6 +632,10 @@ def _load_apps_file_arg(path: str | None) -> dict[str, object]:
616
632
  result: dict[str, object] = {"apps": apps}
617
633
  if payload.get("package_id") is not None:
618
634
  result["package_id"] = payload.get("package_id")
635
+ if payload.get("create_if_missing") is not None:
636
+ result["create_if_missing"] = payload.get("create_if_missing")
637
+ if payload.get("publish") is not None:
638
+ result["publish"] = payload.get("publish")
619
639
  return result
620
640
  raise_config_error(
621
641
  "--apps-file must be a JSON array or an object containing apps.",
@@ -625,6 +645,60 @@ def _load_apps_file_arg(path: str | None) -> dict[str, object]:
625
645
  )
626
646
 
627
647
 
648
+ def _coerce_apps_file_package_id(value: object) -> int:
649
+ package_id: int
650
+ if isinstance(value, bool):
651
+ _raise_apps_file_package_id_invalid(value)
652
+ if isinstance(value, int):
653
+ package_id = value
654
+ elif isinstance(value, str):
655
+ stripped = value.strip()
656
+ try:
657
+ package_id = int(stripped)
658
+ except ValueError:
659
+ _raise_apps_file_package_id_invalid(value)
660
+ else:
661
+ _raise_apps_file_package_id_invalid(value)
662
+ if package_id <= 0:
663
+ _raise_apps_file_package_id_invalid(value)
664
+ return package_id
665
+
666
+
667
+ def _raise_apps_file_package_id_invalid(value: object) -> None:
668
+ raise_config_error(
669
+ "--apps-file package_id must be a positive integer.",
670
+ fix_hint='Use a numeric package_id, for example {"package_id":1001,"apps":[...]}.',
671
+ error_code="APPS_FILE_PACKAGE_ID_INVALID",
672
+ details={
673
+ "field": "package_id",
674
+ "value": value,
675
+ "expected": "positive integer or numeric string",
676
+ },
677
+ )
678
+
679
+
680
+ def _coerce_apps_file_bool(value: object, *, field_name: str, default: bool) -> bool:
681
+ if value is None:
682
+ return default
683
+ if isinstance(value, bool):
684
+ return value
685
+ if isinstance(value, str):
686
+ try:
687
+ return parse_bool_text(value)
688
+ except argparse.ArgumentTypeError:
689
+ pass
690
+ raise_config_error(
691
+ f"--apps-file {field_name} must be a boolean.",
692
+ fix_hint=f'Use "{field_name}": true or "{field_name}": false in --apps-file.',
693
+ error_code="APPS_FILE_BOOLEAN_INVALID",
694
+ details={
695
+ "field": field_name,
696
+ "value": value,
697
+ "expected": "boolean true/false or string true/false/1/0/yes/no",
698
+ },
699
+ )
700
+
701
+
628
702
  def _handle_layout_apply(args: argparse.Namespace, context: CliContext) -> dict:
629
703
  return context.builder.app_layout_apply(
630
704
  profile=args.profile,
@@ -36,6 +36,7 @@ def _is_executed_nonfatal_result(result: dict[str, Any]) -> bool:
36
36
  status = str(result.get("status") or "").lower()
37
37
  executed = bool(
38
38
  result.get("write_executed")
39
+ or result.get("write_may_have_succeeded")
39
40
  or result.get("delete_executed")
40
41
  or result.get("action_executed")
41
42
  or result.get("export_executed")
@@ -423,6 +423,7 @@ def _is_executed_nonfatal_result(result: dict[str, Any]) -> bool:
423
423
  status = str(result.get("status") or "").lower()
424
424
  executed = bool(
425
425
  result.get("write_executed")
426
+ or result.get("write_may_have_succeeded")
426
427
  or result.get("delete_executed")
427
428
  or result.get("action_executed")
428
429
  or result.get("export_executed")
@@ -441,6 +442,7 @@ def _has_readback_unavailable_verification(result: dict[str, Any]) -> bool:
441
442
  "readback_pending",
442
443
  "metadata_unverified",
443
444
  "views_read_unavailable",
445
+ "readback_before_retry",
444
446
  )
445
447
  )
446
448
 
@@ -100,7 +100,7 @@ def trim_error_response(payload: dict[str, Any]) -> dict[str, Any]:
100
100
  details = trimmed.get("details")
101
101
  if isinstance(details, dict):
102
102
  preserved = {}
103
- for key in ("blocking_issues", "compiled_match_rules"):
103
+ for key in ("blocking_issues", "compiled_match_rules", "issues", "expected_shape"):
104
104
  if key in details:
105
105
  preserved[key] = details.get(key)
106
106
  compact_details = _compact_scalar_dict(details)
@@ -169,6 +169,7 @@ def _is_executed_nonfatal_payload(payload: dict[str, Any]) -> bool:
169
169
  status = str(payload.get("status") or "").lower()
170
170
  executed = bool(
171
171
  payload.get("write_executed")
172
+ or payload.get("write_may_have_succeeded")
172
173
  or payload.get("delete_executed")
173
174
  or payload.get("action_executed")
174
175
  or payload.get("export_executed")
@@ -183,7 +184,7 @@ def _trim_returned_failure(payload: dict[str, Any]) -> dict[str, Any]:
183
184
  details = trimmed.get("details")
184
185
  if isinstance(details, dict):
185
186
  preserved = {}
186
- for key in ("blocking_issues", "compiled_match_rules"):
187
+ for key in ("blocking_issues", "compiled_match_rules", "issues", "expected_shape"):
187
188
  if key in details:
188
189
  preserved[key] = details.get(key)
189
190
  compact_details = _compact_scalar_dict(details)
@@ -1070,8 +1071,9 @@ def _trim_builder_envelope(payload: JSONObject) -> None:
1070
1071
  if isinstance(details, dict):
1071
1072
  _drop_deep_keys(details, {"request_route", "base_url", "normalized_args", "suggested_next_call", "transport", "response", "body", "raw"})
1072
1073
  preserved = {}
1073
- if isinstance(details.get("compiled_match_rules"), dict):
1074
- preserved["compiled_match_rules"] = details.get("compiled_match_rules")
1074
+ for key in ("compiled_match_rules", "issues", "expected_shape"):
1075
+ if key in details:
1076
+ preserved[key] = details.get(key)
1075
1077
  compact = _compact_scalar_dict(details)
1076
1078
  compact.update(preserved)
1077
1079
  if compact: