@qingflow-tech/qingflow-app-user-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.
- package/README.md +2 -4
- package/docs/local-agent-install.md +4 -4
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-user/SKILL.md +5 -3
- package/skills/qingflow-mcp-setup/SKILL.md +2 -0
- package/skills/qingflow-record-analysis/SKILL.md +3 -1
- package/skills/qingflow-record-delete/SKILL.md +2 -0
- package/skills/qingflow-record-import/SKILL.md +29 -0
- package/skills/qingflow-record-insert/SKILL.md +24 -1
- package/skills/qingflow-record-update/SKILL.md +3 -0
- package/skills/qingflow-task-ops/SKILL.md +2 -0
- package/src/qingflow_mcp/builder_facade/models.py +183 -0
- package/src/qingflow_mcp/builder_facade/service.py +823 -75
- package/src/qingflow_mcp/cli/commands/builder.py +80 -6
- package/src/qingflow_mcp/cli/formatters.py +1 -0
- package/src/qingflow_mcp/cli/main.py +2 -0
- package/src/qingflow_mcp/response_trim.py +6 -4
- package/src/qingflow_mcp/tools/ai_builder_tools.py +388 -17
- package/src/qingflow_mcp/tools/record_tools.py +28 -2
- package/skills/qingflow-app-builder/SKILL.md +0 -280
- package/skills/qingflow-app-builder/agents/openai.yaml +0 -4
- package/skills/qingflow-app-builder/references/create-app.md +0 -160
- package/skills/qingflow-app-builder/references/environments.md +0 -63
- package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +0 -123
- package/skills/qingflow-app-builder/references/gotchas.md +0 -107
- package/skills/qingflow-app-builder/references/match-rules.md +0 -129
- package/skills/qingflow-app-builder/references/public-surface-sync.md +0 -75
- package/skills/qingflow-app-builder/references/solution-playbooks.md +0 -52
- package/skills/qingflow-app-builder/references/tool-selection.md +0 -106
- package/skills/qingflow-app-builder/references/update-flow.md +0 -158
- package/skills/qingflow-app-builder/references/update-layout.md +0 -68
- package/skills/qingflow-app-builder/references/update-schema.md +0 -75
- package/skills/qingflow-app-builder/references/update-views.md +0 -286
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +0 -137
- package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +0 -4
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +0 -66
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +0 -77
|
@@ -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=
|
|
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 =
|
|
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=
|
|
509
|
-
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
|
-
|
|
1074
|
-
|
|
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:
|