@qingflow-tech/qingflow-cli 1.1.46 → 1.1.47-beta.2
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 +3 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +26 -2
- package/skills/qingflow-common/SKILL.md +21 -1
- package/src/qingflow_mcp/app_form_validator.py +304 -0
- package/src/qingflow_mcp/builder_facade/service.py +124 -1
- package/src/qingflow_mcp/cli/commands/auth.py +8 -0
- package/src/qingflow_mcp/cli/commands/builder.py +327 -1
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +66 -2
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/public_surface.py +4 -0
- package/src/qingflow_mcp/resource_uri.py +184 -0
- package/src/qingflow_mcp/response_trim.py +16 -2
- package/src/qingflow_mcp/server.py +2 -0
- package/src/qingflow_mcp/server_app_builder.py +7 -0
- package/src/qingflow_mcp/server_app_user.py +18 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +266 -39
- package/src/qingflow_mcp/tools/app_tools.py +52 -13
- package/src/qingflow_mcp/tools/login_link_tools.py +134 -0
- package/src/qingflow_mcp/tools/resource_read_tools.py +13 -0
|
@@ -20,6 +20,7 @@ from ..public_surface import public_builder_contract_tool_names
|
|
|
20
20
|
from ..config import DEFAULT_PROFILE
|
|
21
21
|
from ..errors import QingflowApiError, backend_code_int, backend_code_value_int
|
|
22
22
|
from ..json_types import JSONObject
|
|
23
|
+
from ..resource_uri import try_build_resource_uri
|
|
23
24
|
from ..builder_facade.models import (
|
|
24
25
|
AssociatedResourcesApplyRequest,
|
|
25
26
|
ChartApplyRequest,
|
|
@@ -187,6 +188,26 @@ class AiBuilderTools(ToolBase):
|
|
|
187
188
|
registry=app_form_schema_registry(),
|
|
188
189
|
)
|
|
189
190
|
|
|
191
|
+
def _selected_ws_id(self, profile: str) -> int:
|
|
192
|
+
session_profile, _backend_session, _context = self._require_context(profile, require_workspace=True)
|
|
193
|
+
assert session_profile.selected_ws_id is not None
|
|
194
|
+
return session_profile.selected_ws_id
|
|
195
|
+
|
|
196
|
+
def _attach_apply_envelope(self, profile: str, tool_name: str, payload: JSONObject) -> JSONObject:
|
|
197
|
+
result = _attach_builder_apply_envelope(tool_name, payload)
|
|
198
|
+
eligible = [
|
|
199
|
+
item
|
|
200
|
+
for item in result.get("resources") or []
|
|
201
|
+
if isinstance(item, dict)
|
|
202
|
+
and item.get("resource_type") in {"package", "app", "view"}
|
|
203
|
+
and str(item.get("status") or "").lower()
|
|
204
|
+
not in {"failed", "pending", "planned", "readback_pending", "pending_readback", "unknown", "removed"}
|
|
205
|
+
and str(item.get("operation") or "").lower() not in {"failed", "removed", "planned"}
|
|
206
|
+
]
|
|
207
|
+
if eligible:
|
|
208
|
+
_attach_builder_resource_uris(result, eligible, ws_id=self._selected_ws_id(profile))
|
|
209
|
+
return result
|
|
210
|
+
|
|
190
211
|
def _apply_app_batch(self, *, tool_name: str, profile: str, apps: list[JSONObject], apply_one) -> JSONObject:
|
|
191
212
|
started_at = time.perf_counter()
|
|
192
213
|
batch_item_apply_ms = 0
|
|
@@ -199,7 +220,7 @@ class AiBuilderTools(ToolBase):
|
|
|
199
220
|
child_total_ms=child_total_ms,
|
|
200
221
|
total_ms=_elapsed_ms(started_at),
|
|
201
222
|
)
|
|
202
|
-
return
|
|
223
|
+
return self._attach_apply_envelope(profile, tool_name, payload)
|
|
203
224
|
|
|
204
225
|
if not isinstance(apps, list) or not apps:
|
|
205
226
|
return finish(
|
|
@@ -841,16 +862,88 @@ class AiBuilderTools(ToolBase):
|
|
|
841
862
|
being_draft: bool = False,
|
|
842
863
|
) -> JSONObject:
|
|
843
864
|
"""Read a canonical draft or published AppForm resource."""
|
|
844
|
-
|
|
865
|
+
result = self._app_form.get(
|
|
845
866
|
profile=profile,
|
|
846
867
|
app_key=app_key,
|
|
847
868
|
schema_version=schema_version,
|
|
848
869
|
being_draft=being_draft,
|
|
849
870
|
)
|
|
871
|
+
if result.get("status") == "success":
|
|
872
|
+
_attach_uri(result, result, ws_id=self._selected_ws_id(profile), resource_type="app", app_key=app_key)
|
|
873
|
+
return result
|
|
850
874
|
|
|
851
875
|
def app_form_apply(self, *, profile: str, spec: JSONObject) -> JSONObject:
|
|
852
876
|
"""Reconcile one complete declarative AppForm target."""
|
|
853
|
-
|
|
877
|
+
result = self._app_form.apply(profile=profile, spec=spec)
|
|
878
|
+
if result.get("appKey") and (
|
|
879
|
+
str(result.get("status") or "") == "success" or result.get("createdAppPreserved") is True
|
|
880
|
+
):
|
|
881
|
+
_attach_uri(
|
|
882
|
+
result,
|
|
883
|
+
result,
|
|
884
|
+
ws_id=self._selected_ws_id(profile),
|
|
885
|
+
resource_type="app",
|
|
886
|
+
app_key=result.get("appKey"),
|
|
887
|
+
)
|
|
888
|
+
return result
|
|
889
|
+
|
|
890
|
+
def app_form_evaluate_formula(
|
|
891
|
+
self,
|
|
892
|
+
*,
|
|
893
|
+
profile: str,
|
|
894
|
+
app_key: str,
|
|
895
|
+
formula: str,
|
|
896
|
+
test_case: dict[int, Any],
|
|
897
|
+
) -> JSONObject:
|
|
898
|
+
"""调用后端 POST /ai/coding/formula/validate 对公式求值。
|
|
899
|
+
|
|
900
|
+
本地语法校验由 CLI handler 负责;本方法只做后端求值,捕获后端错误为 evaluationError。
|
|
901
|
+
鉴权/会话错误(未登录/未选工作区)按既有约定向上抛出 QingflowApiError。
|
|
902
|
+
"""
|
|
903
|
+
_session_profile, _backend_session, context = self._require_context(profile, require_workspace=True)
|
|
904
|
+
evaluation_result: Any = None
|
|
905
|
+
evaluation_error: str | None = None
|
|
906
|
+
try:
|
|
907
|
+
evaluation_result = self.backend.request(
|
|
908
|
+
"POST",
|
|
909
|
+
context,
|
|
910
|
+
"/ai/coding/formula/validate",
|
|
911
|
+
json_body={"appKey": app_key, "formula": formula, "testCase": test_case},
|
|
912
|
+
)
|
|
913
|
+
except QingflowApiError as exc:
|
|
914
|
+
evaluation_error = exc.message
|
|
915
|
+
return {"evaluation": evaluation_result, "evaluationError": evaluation_error}
|
|
916
|
+
|
|
917
|
+
def app_form_run_code_block(
|
|
918
|
+
self,
|
|
919
|
+
*,
|
|
920
|
+
profile: str,
|
|
921
|
+
app_key: str,
|
|
922
|
+
code: str,
|
|
923
|
+
key_que_values: list[JSONObject],
|
|
924
|
+
) -> JSONObject:
|
|
925
|
+
"""调用后端 POST /data/{appKey}/codeBlock/detection 真跑代码块。
|
|
926
|
+
|
|
927
|
+
本地静态校验由 CLI handler 负责;本方法只做后端真跑,捕获后端错误为 runError。
|
|
928
|
+
``key_que_values`` 需符合后端 OpenMatchQueValue 结构 ``{keyQueId, ordinal?, values}``。
|
|
929
|
+
|
|
930
|
+
传入的 ``code`` 是原始 JS(未经前端字段公式加密),必须声明 ``unEncrypted=True``;
|
|
931
|
+
否则后端 ``DataSvcImpl.codeBlockTest`` 会走 ``FormulaDecoder.extractFormula``
|
|
932
|
+
对原始 JS 解密并抛 ``QFRuntimeException``,导致后端真跑对所有真实代码必然失败。
|
|
933
|
+
"""
|
|
934
|
+
_session_profile, _backend_session, context = self._require_context(profile, require_workspace=True)
|
|
935
|
+
run_result: Any = None
|
|
936
|
+
run_error: str | None = None
|
|
937
|
+
try:
|
|
938
|
+
run_result = self.backend.request(
|
|
939
|
+
"POST",
|
|
940
|
+
context,
|
|
941
|
+
f"/data/{app_key}/codeBlock/detection",
|
|
942
|
+
json_body={"code": code, "keyQueValues": key_que_values, "unEncrypted": True},
|
|
943
|
+
)
|
|
944
|
+
except QingflowApiError as exc:
|
|
945
|
+
run_error = exc.message
|
|
946
|
+
return {"runResult": run_result, "runError": run_error}
|
|
854
947
|
|
|
855
948
|
def app_delete(self, *, profile: str, app_key: str) -> JSONObject:
|
|
856
949
|
"""Delete an application explicitly."""
|
|
@@ -859,12 +952,24 @@ class AiBuilderTools(ToolBase):
|
|
|
859
952
|
def package_list(self, *, profile: str, trial_status: str = "all", query: str = "") -> JSONObject:
|
|
860
953
|
"""执行分组与包相关逻辑。"""
|
|
861
954
|
normalized_args = {"trial_status": trial_status, "query": query}
|
|
862
|
-
|
|
955
|
+
result = _safe_tool_call(
|
|
863
956
|
lambda: self._facade.package_list(profile=profile, trial_status=trial_status, query=query),
|
|
864
957
|
error_code="PACKAGE_LIST_FAILED",
|
|
865
958
|
normalized_args=normalized_args,
|
|
866
959
|
suggested_next_call={"tool_name": "package_list", "arguments": {"profile": profile, "trial_status": trial_status, "query": query}},
|
|
867
960
|
)
|
|
961
|
+
if result.get("status") == "success":
|
|
962
|
+
ws_id = self._selected_ws_id(profile)
|
|
963
|
+
for item in result.get("items") or []:
|
|
964
|
+
if isinstance(item, dict):
|
|
965
|
+
_attach_uri(
|
|
966
|
+
item,
|
|
967
|
+
result,
|
|
968
|
+
ws_id=ws_id,
|
|
969
|
+
resource_type="package",
|
|
970
|
+
package_id=item.get("package_id"),
|
|
971
|
+
)
|
|
972
|
+
return result
|
|
868
973
|
|
|
869
974
|
def package_resolve(self, *, profile: str, package_name: str) -> JSONObject:
|
|
870
975
|
"""执行分组与包相关逻辑。"""
|
|
@@ -1004,12 +1109,23 @@ class AiBuilderTools(ToolBase):
|
|
|
1004
1109
|
def package_get(self, *, profile: str, package_id: int | None = None) -> JSONObject:
|
|
1005
1110
|
"""执行分组与包相关逻辑。"""
|
|
1006
1111
|
normalized_args = {"package_id": package_id}
|
|
1007
|
-
|
|
1112
|
+
result = _publicize_package_fields(_safe_tool_call(
|
|
1008
1113
|
lambda: self._facade.package_get(profile=profile, package_id=package_id),
|
|
1009
1114
|
error_code="PACKAGE_GET_FAILED",
|
|
1010
1115
|
normalized_args=normalized_args,
|
|
1011
1116
|
suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
|
|
1012
1117
|
))
|
|
1118
|
+
if result.get("status") == "success":
|
|
1119
|
+
ws_id = self._selected_ws_id(profile)
|
|
1120
|
+
_attach_uri(
|
|
1121
|
+
result,
|
|
1122
|
+
result,
|
|
1123
|
+
ws_id=ws_id,
|
|
1124
|
+
resource_type="package",
|
|
1125
|
+
package_id=result.get("package_id"),
|
|
1126
|
+
)
|
|
1127
|
+
_attach_package_item_uris(result.get("items"), result, ws_id=ws_id)
|
|
1128
|
+
return result
|
|
1013
1129
|
|
|
1014
1130
|
def package_apply(
|
|
1015
1131
|
self,
|
|
@@ -1039,7 +1155,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1039
1155
|
try:
|
|
1040
1156
|
visibility_patch = VisibilityPatch.model_validate(visibility)
|
|
1041
1157
|
except ValidationError as exc:
|
|
1042
|
-
return
|
|
1158
|
+
return self._attach_apply_envelope(profile,
|
|
1043
1159
|
"package_apply",
|
|
1044
1160
|
_visibility_validation_failure(str(exc), tool_name="package_apply", exc=exc),
|
|
1045
1161
|
)
|
|
@@ -1050,7 +1166,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1050
1166
|
creating=package_id is None and bool(str(package_name or "").strip()),
|
|
1051
1167
|
)
|
|
1052
1168
|
if icon_failure is not None:
|
|
1053
|
-
return
|
|
1169
|
+
return self._attach_apply_envelope(profile, "package_apply", icon_failure)
|
|
1054
1170
|
normalized_args = {
|
|
1055
1171
|
"package_id": package_id,
|
|
1056
1172
|
**({"package_name": package_name} if str(package_name or "").strip() else {}),
|
|
@@ -1075,7 +1191,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1075
1191
|
normalized_args=normalized_args,
|
|
1076
1192
|
suggested_next_call={"tool_name": "package_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1077
1193
|
))
|
|
1078
|
-
return
|
|
1194
|
+
return self._attach_apply_envelope(profile, "package_apply", result)
|
|
1079
1195
|
|
|
1080
1196
|
def package_update(
|
|
1081
1197
|
self,
|
|
@@ -1129,7 +1245,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1129
1245
|
"being_copy_data": being_copy_data,
|
|
1130
1246
|
"solution_source": solution_source,
|
|
1131
1247
|
}
|
|
1132
|
-
|
|
1248
|
+
result = _safe_tool_call(
|
|
1133
1249
|
lambda: self._facade.solution_install(
|
|
1134
1250
|
profile=profile,
|
|
1135
1251
|
solution_key=solution_key,
|
|
@@ -1140,6 +1256,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1140
1256
|
normalized_args=normalized_args,
|
|
1141
1257
|
suggested_next_call={"tool_name": "solution_install", "arguments": {"profile": profile, **normalized_args}},
|
|
1142
1258
|
)
|
|
1259
|
+
return self._attach_apply_envelope(profile, "solution_install", result)
|
|
1143
1260
|
|
|
1144
1261
|
def member_search(
|
|
1145
1262
|
self,
|
|
@@ -1336,7 +1453,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1336
1453
|
try:
|
|
1337
1454
|
request = CustomButtonsApplyRequest.model_validate(raw_request)
|
|
1338
1455
|
except ValidationError as exc:
|
|
1339
|
-
return
|
|
1456
|
+
return self._attach_apply_envelope(profile, "app_custom_buttons_apply", _validation_failure(
|
|
1340
1457
|
str(exc),
|
|
1341
1458
|
tool_name="app_custom_buttons_apply",
|
|
1342
1459
|
exc=exc,
|
|
@@ -1360,7 +1477,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1360
1477
|
},
|
|
1361
1478
|
))
|
|
1362
1479
|
normalized_args = request.model_dump(mode="json")
|
|
1363
|
-
return
|
|
1480
|
+
return self._attach_apply_envelope(profile, "app_custom_buttons_apply", _safe_tool_call(
|
|
1364
1481
|
lambda: self._facade.app_custom_buttons_apply(profile=profile, request=request),
|
|
1365
1482
|
error_code="CUSTOM_BUTTONS_APPLY_FAILED",
|
|
1366
1483
|
normalized_args=normalized_args,
|
|
@@ -1417,7 +1534,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1417
1534
|
try:
|
|
1418
1535
|
request = AssociatedResourcesApplyRequest.model_validate(raw_request)
|
|
1419
1536
|
except ValidationError as exc:
|
|
1420
|
-
return
|
|
1537
|
+
return self._attach_apply_envelope(profile, "app_associated_resources_apply", _validation_failure(
|
|
1421
1538
|
str(exc),
|
|
1422
1539
|
tool_name="app_associated_resources_apply",
|
|
1423
1540
|
exc=exc,
|
|
@@ -1446,7 +1563,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1446
1563
|
},
|
|
1447
1564
|
))
|
|
1448
1565
|
normalized_args = request.model_dump(mode="json", exclude_none=True)
|
|
1449
|
-
return
|
|
1566
|
+
return self._attach_apply_envelope(profile, "app_associated_resources_apply", _safe_tool_call(
|
|
1450
1567
|
lambda: self._facade.app_associated_resources_apply(profile=profile, request=request),
|
|
1451
1568
|
error_code="ASSOCIATED_RESOURCES_APPLY_FAILED",
|
|
1452
1569
|
normalized_args=normalized_args,
|
|
@@ -1571,12 +1688,27 @@ class AiBuilderTools(ToolBase):
|
|
|
1571
1688
|
read_one=lambda item_app_key: self.app_get(profile=profile, app_key=item_app_key, app_keys=None),
|
|
1572
1689
|
)
|
|
1573
1690
|
normalized_args = {"app_key": app_key}
|
|
1574
|
-
|
|
1691
|
+
result = _publicize_package_fields(_safe_tool_call(
|
|
1575
1692
|
lambda: self._facade.app_get(profile=profile, app_key=app_key),
|
|
1576
1693
|
error_code="APP_GET_FAILED",
|
|
1577
1694
|
normalized_args=normalized_args,
|
|
1578
1695
|
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1579
1696
|
))
|
|
1697
|
+
if result.get("status") == "success":
|
|
1698
|
+
ws_id = self._selected_ws_id(profile)
|
|
1699
|
+
_attach_uri(result, result, ws_id=ws_id, resource_type="app", app_key=app_key)
|
|
1700
|
+
for view in result.get("views") or []:
|
|
1701
|
+
if isinstance(view, dict) and (view.get("view_key") or view.get("viewKey")):
|
|
1702
|
+
_attach_uri(
|
|
1703
|
+
view,
|
|
1704
|
+
result,
|
|
1705
|
+
ws_id=ws_id,
|
|
1706
|
+
resource_type="view",
|
|
1707
|
+
app_key=app_key,
|
|
1708
|
+
view_kind="custom",
|
|
1709
|
+
view_key=view.get("view_key") or view.get("viewKey"),
|
|
1710
|
+
)
|
|
1711
|
+
return result
|
|
1580
1712
|
|
|
1581
1713
|
def app_read_fields(self, *, profile: str, app_key: str) -> JSONObject:
|
|
1582
1714
|
"""执行应用相关逻辑。"""
|
|
@@ -1669,12 +1801,27 @@ class AiBuilderTools(ToolBase):
|
|
|
1669
1801
|
read_one=lambda item_app_key: self.app_get_views(profile=profile, app_key=item_app_key, app_keys=None),
|
|
1670
1802
|
)
|
|
1671
1803
|
normalized_args = {"app_key": app_key}
|
|
1672
|
-
|
|
1804
|
+
result = _safe_tool_call(
|
|
1673
1805
|
lambda: self._facade.app_get_views(profile=profile, app_key=app_key),
|
|
1674
1806
|
error_code="APP_GET_VIEWS_FAILED",
|
|
1675
1807
|
normalized_args=normalized_args,
|
|
1676
1808
|
suggested_next_call={"tool_name": "app_get_views", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1677
1809
|
)
|
|
1810
|
+
if result.get("status") == "success":
|
|
1811
|
+
ws_id = self._selected_ws_id(profile)
|
|
1812
|
+
_attach_uri(result, result, ws_id=ws_id, resource_type="app", app_key=app_key)
|
|
1813
|
+
for view in result.get("views") or []:
|
|
1814
|
+
if isinstance(view, dict) and (view.get("view_key") or view.get("viewKey")):
|
|
1815
|
+
_attach_uri(
|
|
1816
|
+
view,
|
|
1817
|
+
result,
|
|
1818
|
+
ws_id=ws_id,
|
|
1819
|
+
resource_type="view",
|
|
1820
|
+
app_key=app_key,
|
|
1821
|
+
view_kind="custom",
|
|
1822
|
+
view_key=view.get("view_key") or view.get("viewKey"),
|
|
1823
|
+
)
|
|
1824
|
+
return result
|
|
1678
1825
|
|
|
1679
1826
|
def app_read_flow_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
1680
1827
|
"""执行应用相关逻辑。"""
|
|
@@ -1813,12 +1960,27 @@ class AiBuilderTools(ToolBase):
|
|
|
1813
1960
|
"""执行视图相关逻辑。"""
|
|
1814
1961
|
resolved_view_key = _normalize_builder_view_key(str(view_key or viewgraph_key or "").strip())
|
|
1815
1962
|
normalized_args = {"view_key": resolved_view_key}
|
|
1816
|
-
|
|
1963
|
+
result = _safe_tool_call(
|
|
1817
1964
|
lambda: self._facade.view_get(profile=profile, view_key=resolved_view_key),
|
|
1818
1965
|
error_code="VIEW_GET_FAILED",
|
|
1819
1966
|
normalized_args=normalized_args,
|
|
1820
1967
|
suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, **normalized_args}},
|
|
1821
1968
|
)
|
|
1969
|
+
if result.get("status") == "success":
|
|
1970
|
+
config = result.get("config") if isinstance(result.get("config"), dict) else {}
|
|
1971
|
+
base_info = result.get("base_info") if isinstance(result.get("base_info"), dict) else {}
|
|
1972
|
+
app_key = str(config.get("appKey") or config.get("formKey") or base_info.get("appKey") or base_info.get("formKey") or "").strip()
|
|
1973
|
+
if app_key:
|
|
1974
|
+
_attach_uri(
|
|
1975
|
+
result,
|
|
1976
|
+
result,
|
|
1977
|
+
ws_id=self._selected_ws_id(profile),
|
|
1978
|
+
resource_type="view",
|
|
1979
|
+
app_key=app_key,
|
|
1980
|
+
view_kind="custom",
|
|
1981
|
+
view_key=resolved_view_key,
|
|
1982
|
+
)
|
|
1983
|
+
return result
|
|
1822
1984
|
|
|
1823
1985
|
def chart_get(
|
|
1824
1986
|
self,
|
|
@@ -2107,7 +2269,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2107
2269
|
)
|
|
2108
2270
|
failure = normalized_apps_payload.get("failure")
|
|
2109
2271
|
if isinstance(failure, dict):
|
|
2110
|
-
return
|
|
2272
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", failure)
|
|
2111
2273
|
package_id = normalized_apps_payload.get("package_id") # type: ignore[assignment]
|
|
2112
2274
|
apps = normalized_apps_payload.get("apps") # type: ignore[assignment]
|
|
2113
2275
|
input_warnings = list(normalized_apps_payload.get("warnings") or [])
|
|
@@ -2123,7 +2285,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2123
2285
|
)
|
|
2124
2286
|
failure = form_result.get("failure")
|
|
2125
2287
|
if isinstance(failure, dict):
|
|
2126
|
-
return
|
|
2288
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", failure)
|
|
2127
2289
|
normalized_apps.append(form_result.get("payload") or app_item)
|
|
2128
2290
|
apps = normalized_apps
|
|
2129
2291
|
result = self._app_schema_apply_multi(
|
|
@@ -2137,7 +2299,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2137
2299
|
result_warnings = list(result.get("warnings") or [])
|
|
2138
2300
|
result_warnings.extend(deepcopy(input_warnings))
|
|
2139
2301
|
result["warnings"] = result_warnings
|
|
2140
|
-
return
|
|
2302
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", result)
|
|
2141
2303
|
inline_form_layout_sections: list[JSONObject] = []
|
|
2142
2304
|
if form:
|
|
2143
2305
|
form_payload: JSONObject = {"form": form}
|
|
@@ -2150,7 +2312,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2150
2312
|
)
|
|
2151
2313
|
failure = form_result.get("failure")
|
|
2152
2314
|
if isinstance(failure, dict):
|
|
2153
|
-
return
|
|
2315
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", failure)
|
|
2154
2316
|
normalized_payload = form_result.get("payload") if isinstance(form_result.get("payload"), dict) else {}
|
|
2155
2317
|
add_fields = list(normalized_payload.get("add_fields") or [])
|
|
2156
2318
|
inline_form_layout_sections = list(form_result.get("layout_sections") or [])
|
|
@@ -2188,7 +2350,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2188
2350
|
inline_form_layout_sections=inline_form_layout_sections,
|
|
2189
2351
|
),
|
|
2190
2352
|
)
|
|
2191
|
-
return
|
|
2353
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", result)
|
|
2192
2354
|
|
|
2193
2355
|
def _app_schema_apply_multi(
|
|
2194
2356
|
self,
|
|
@@ -3467,7 +3629,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3467
3629
|
sections=sections,
|
|
3468
3630
|
),
|
|
3469
3631
|
)
|
|
3470
|
-
return
|
|
3632
|
+
return self._attach_apply_envelope(profile, "app_layout_apply", result)
|
|
3471
3633
|
|
|
3472
3634
|
def _app_layout_apply_once(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
3473
3635
|
"""执行内部辅助逻辑。"""
|
|
@@ -3532,7 +3694,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3532
3694
|
"idempotency_key": idempotency_key,
|
|
3533
3695
|
"schema_version": schema_version,
|
|
3534
3696
|
}
|
|
3535
|
-
return
|
|
3697
|
+
return self._attach_apply_envelope(profile, "app_flow_apply", _safe_tool_call(
|
|
3536
3698
|
lambda: self._facade.flow_patch_nodes(
|
|
3537
3699
|
profile=profile,
|
|
3538
3700
|
app_key=app_key,
|
|
@@ -3553,7 +3715,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3553
3715
|
"idempotency_key": idempotency_key,
|
|
3554
3716
|
"schema_version": schema_version,
|
|
3555
3717
|
}
|
|
3556
|
-
return
|
|
3718
|
+
return self._attach_apply_envelope(profile, "app_flow_apply", _safe_tool_call(
|
|
3557
3719
|
lambda: self._facade.flow_apply(
|
|
3558
3720
|
profile=profile,
|
|
3559
3721
|
app_key=app_key,
|
|
@@ -3589,9 +3751,9 @@ class AiBuilderTools(ToolBase):
|
|
|
3589
3751
|
transitions=effective_transitions,
|
|
3590
3752
|
),
|
|
3591
3753
|
)
|
|
3592
|
-
return
|
|
3754
|
+
return self._attach_apply_envelope(profile, "app_flow_apply", result)
|
|
3593
3755
|
normalized_args = {"app_key": app_key, "publish": publish}
|
|
3594
|
-
return
|
|
3756
|
+
return self._attach_apply_envelope(profile,
|
|
3595
3757
|
"app_flow_apply",
|
|
3596
3758
|
{
|
|
3597
3759
|
"status": "failed",
|
|
@@ -3726,7 +3888,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3726
3888
|
patch_views=patch_views or [],
|
|
3727
3889
|
),
|
|
3728
3890
|
)
|
|
3729
|
-
return
|
|
3891
|
+
return self._attach_apply_envelope(profile, "app_views_apply", result)
|
|
3730
3892
|
|
|
3731
3893
|
def _app_views_apply_view_batch(
|
|
3732
3894
|
self,
|
|
@@ -3737,7 +3899,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3737
3899
|
) -> JSONObject:
|
|
3738
3900
|
started_at = time.perf_counter()
|
|
3739
3901
|
if not isinstance(views, list) or not views:
|
|
3740
|
-
return
|
|
3902
|
+
return self._attach_apply_envelope(profile,
|
|
3741
3903
|
"app_views_apply",
|
|
3742
3904
|
_config_failure(
|
|
3743
3905
|
tool_name="app_views_apply",
|
|
@@ -3968,7 +4130,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3968
4130
|
view_write_parallel_wall_ms=view_write_parallel_wall_ms,
|
|
3969
4131
|
total_ms=_elapsed_ms(started_at),
|
|
3970
4132
|
)
|
|
3971
|
-
return
|
|
4133
|
+
return self._attach_apply_envelope(profile, "app_views_apply", payload)
|
|
3972
4134
|
|
|
3973
4135
|
def _app_views_apply_once(
|
|
3974
4136
|
self,
|
|
@@ -4091,7 +4253,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4091
4253
|
}
|
|
4092
4254
|
)
|
|
4093
4255
|
except ValidationError as exc:
|
|
4094
|
-
return
|
|
4256
|
+
return self._attach_apply_envelope(profile, "app_charts_apply", _visibility_validation_failure(
|
|
4095
4257
|
str(exc),
|
|
4096
4258
|
tool_name="app_charts_apply",
|
|
4097
4259
|
exc=exc,
|
|
@@ -4107,7 +4269,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4107
4269
|
},
|
|
4108
4270
|
))
|
|
4109
4271
|
normalized_args = request.model_dump(mode="json")
|
|
4110
|
-
return
|
|
4272
|
+
return self._attach_apply_envelope(profile, "app_charts_apply", _safe_tool_call(
|
|
4111
4273
|
lambda: self._facade.chart_apply(profile=profile, request=request),
|
|
4112
4274
|
error_code="CHART_APPLY_FAILED",
|
|
4113
4275
|
normalized_args=normalized_args,
|
|
@@ -4143,7 +4305,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4143
4305
|
"publish": publish,
|
|
4144
4306
|
"patch_sections": patch_sections,
|
|
4145
4307
|
}
|
|
4146
|
-
return
|
|
4308
|
+
return self._attach_apply_envelope(profile, "portal_apply", _publicize_package_fields(_safe_tool_call(
|
|
4147
4309
|
lambda: self._facade.portal_patch_sections(
|
|
4148
4310
|
profile=profile,
|
|
4149
4311
|
dash_key=dash_key,
|
|
@@ -4190,7 +4352,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4190
4352
|
try:
|
|
4191
4353
|
request = PortalApplyRequest.model_validate(request_payload)
|
|
4192
4354
|
except ValidationError as exc:
|
|
4193
|
-
return
|
|
4355
|
+
return self._attach_apply_envelope(profile, "portal_apply", _visibility_validation_failure(
|
|
4194
4356
|
str(exc),
|
|
4195
4357
|
tool_name="portal_apply",
|
|
4196
4358
|
exc=exc,
|
|
@@ -4221,14 +4383,14 @@ class AiBuilderTools(ToolBase):
|
|
|
4221
4383
|
creating=not bool(str(request.dash_key or "").strip()),
|
|
4222
4384
|
)
|
|
4223
4385
|
if icon_failure is not None:
|
|
4224
|
-
return
|
|
4386
|
+
return self._attach_apply_envelope(profile, "portal_apply", icon_failure)
|
|
4225
4387
|
result = _publicize_package_fields(_safe_tool_call(
|
|
4226
4388
|
lambda: self._facade.portal_apply(profile=profile, request=request),
|
|
4227
4389
|
error_code="PORTAL_APPLY_FAILED",
|
|
4228
4390
|
normalized_args=normalized_args,
|
|
4229
4391
|
suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
4230
4392
|
))
|
|
4231
|
-
return
|
|
4393
|
+
return self._attach_apply_envelope(profile, "portal_apply", result)
|
|
4232
4394
|
|
|
4233
4395
|
def portal_delete(self, *, profile: str, dash_key: str) -> JSONObject:
|
|
4234
4396
|
"""执行门户删除逻辑。"""
|
|
@@ -4239,7 +4401,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4239
4401
|
normalized_args=normalized_args,
|
|
4240
4402
|
suggested_next_call={"tool_name": "portal_delete", "arguments": {"profile": profile, **normalized_args}},
|
|
4241
4403
|
))
|
|
4242
|
-
return
|
|
4404
|
+
return self._attach_apply_envelope(profile, "portal_delete", result)
|
|
4243
4405
|
|
|
4244
4406
|
def app_publish_verify(
|
|
4245
4407
|
self,
|
|
@@ -4278,7 +4440,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4278
4440
|
expected_package_tag_id=expected_package_id,
|
|
4279
4441
|
),
|
|
4280
4442
|
))
|
|
4281
|
-
return
|
|
4443
|
+
return self._attach_apply_envelope(profile, "app_publish_verify", result)
|
|
4282
4444
|
|
|
4283
4445
|
def _retry_after_self_lock_release(self, *, profile: str, result: JSONObject, retry_call) -> JSONObject:
|
|
4284
4446
|
"""执行内部辅助逻辑。"""
|
|
@@ -5644,6 +5806,68 @@ def _publicize_package_fields(value):
|
|
|
5644
5806
|
return public
|
|
5645
5807
|
|
|
5646
5808
|
|
|
5809
|
+
def _append_uri_warning(payload: JSONObject, warning: JSONObject | None) -> None:
|
|
5810
|
+
if not warning:
|
|
5811
|
+
return
|
|
5812
|
+
warnings = payload.setdefault("warnings", [])
|
|
5813
|
+
if isinstance(warnings, list) and not any(
|
|
5814
|
+
isinstance(item, dict) and item.get("code") == warning.get("code") for item in warnings
|
|
5815
|
+
):
|
|
5816
|
+
warnings.append(warning)
|
|
5817
|
+
|
|
5818
|
+
|
|
5819
|
+
def _attach_uri(target: JSONObject, payload: JSONObject, *, ws_id: int, **identity: Any) -> None:
|
|
5820
|
+
uri, warning = try_build_resource_uri(ws_id=ws_id, **identity)
|
|
5821
|
+
if uri:
|
|
5822
|
+
target["uri"] = uri
|
|
5823
|
+
_append_uri_warning(payload, warning)
|
|
5824
|
+
|
|
5825
|
+
|
|
5826
|
+
def _attach_builder_resource_uris(payload: JSONObject, resources: list[JSONObject], *, ws_id: int) -> None:
|
|
5827
|
+
for resource in resources:
|
|
5828
|
+
resource_type = str(resource.get("resource_type") or "")
|
|
5829
|
+
ids = resource.get("ids") if isinstance(resource.get("ids"), dict) else {}
|
|
5830
|
+
if resource_type == "package":
|
|
5831
|
+
_attach_uri(
|
|
5832
|
+
resource,
|
|
5833
|
+
payload,
|
|
5834
|
+
ws_id=ws_id,
|
|
5835
|
+
resource_type="package",
|
|
5836
|
+
package_id=resource.get("id") or ids.get("package_id"),
|
|
5837
|
+
)
|
|
5838
|
+
elif resource_type == "app":
|
|
5839
|
+
_attach_uri(
|
|
5840
|
+
resource,
|
|
5841
|
+
payload,
|
|
5842
|
+
ws_id=ws_id,
|
|
5843
|
+
resource_type="app",
|
|
5844
|
+
app_key=resource.get("key") or ids.get("app_key"),
|
|
5845
|
+
)
|
|
5846
|
+
elif resource_type == "view":
|
|
5847
|
+
parent = resource.get("parent") if isinstance(resource.get("parent"), dict) else {}
|
|
5848
|
+
_attach_uri(
|
|
5849
|
+
resource,
|
|
5850
|
+
payload,
|
|
5851
|
+
ws_id=ws_id,
|
|
5852
|
+
resource_type="view",
|
|
5853
|
+
app_key=ids.get("app_key") or (parent.get("key") if parent.get("resource_type") == "app" else None),
|
|
5854
|
+
view_kind="custom",
|
|
5855
|
+
view_key=resource.get("key") or ids.get("view_key"),
|
|
5856
|
+
)
|
|
5857
|
+
|
|
5858
|
+
|
|
5859
|
+
def _attach_package_item_uris(items: Any, payload: JSONObject, *, ws_id: int) -> None:
|
|
5860
|
+
if not isinstance(items, list):
|
|
5861
|
+
return
|
|
5862
|
+
for item in items:
|
|
5863
|
+
if not isinstance(item, dict):
|
|
5864
|
+
continue
|
|
5865
|
+
if item.get("type") == "app" and item.get("app_key"):
|
|
5866
|
+
_attach_uri(item, payload, ws_id=ws_id, resource_type="app", app_key=item.get("app_key"))
|
|
5867
|
+
if item.get("type") == "group":
|
|
5868
|
+
_attach_package_item_uris(item.get("items"), payload, ws_id=ws_id)
|
|
5869
|
+
|
|
5870
|
+
|
|
5647
5871
|
def _builder_contract_with_apply_output(tool_name: str, contract: JSONObject) -> JSONObject:
|
|
5648
5872
|
public = deepcopy(contract)
|
|
5649
5873
|
if tool_name not in BUILDER_APPLY_TOOL_NAMES:
|
|
@@ -5656,7 +5880,7 @@ def _builder_contract_with_apply_output(tool_name: str, contract: JSONObject) ->
|
|
|
5656
5880
|
public["output_contract"] = {
|
|
5657
5881
|
"schema_version": BUILDER_APPLY_SCHEMA_VERSION,
|
|
5658
5882
|
"preferred_ui_fields": ["operation", "summary", "resources"],
|
|
5659
|
-
"resource_fields": ["resource_type", "operation", "status", "id", "key", "name", "ids", "parent", "icon_config", "error_code", "message"],
|
|
5883
|
+
"resource_fields": ["resource_type", "operation", "status", "id", "key", "name", "ids", "parent", "uri", "icon_config", "error_code", "message"],
|
|
5660
5884
|
"json_paths": _builder_apply_json_paths(),
|
|
5661
5885
|
"legacy_fields_preserved": True,
|
|
5662
5886
|
}
|
|
@@ -5708,6 +5932,7 @@ def _builder_apply_json_paths() -> JSONObject:
|
|
|
5708
5932
|
"resource_keys": "$.resources[].key",
|
|
5709
5933
|
"resource_ids": "$.resources[].id",
|
|
5710
5934
|
"resource_statuses": "$.resources[].status",
|
|
5935
|
+
"resource_uris": "$.resources[].uri",
|
|
5711
5936
|
}
|
|
5712
5937
|
|
|
5713
5938
|
|
|
@@ -5797,7 +6022,9 @@ def _builder_verification_truthy(value: object) -> bool:
|
|
|
5797
6022
|
|
|
5798
6023
|
def _builder_apply_resources(tool_name: str, payload: JSONObject) -> list[JSONObject]:
|
|
5799
6024
|
resources: list[JSONObject]
|
|
5800
|
-
if tool_name == "
|
|
6025
|
+
if tool_name == "solution_install":
|
|
6026
|
+
resources = [deepcopy(item) for item in payload.get("resources") or [] if isinstance(item, dict)]
|
|
6027
|
+
elif tool_name == "package_apply":
|
|
5801
6028
|
resources = _builder_package_resources(payload)
|
|
5802
6029
|
elif tool_name == "app_schema_apply":
|
|
5803
6030
|
resources = _builder_schema_resources(payload)
|