@qingflow-tech/qingflow-cli 1.1.47-beta.1 → 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 +10 -0
- package/skills/qingflow-common/SKILL.md +7 -3
- package/src/qingflow_mcp/builder_facade/service.py +119 -1
- package/src/qingflow_mcp/cli/commands/auth.py +1 -1
- package/src/qingflow_mcp/cli/context.py +1 -1
- package/src/qingflow_mcp/cli/formatters.py +62 -3
- package/src/qingflow_mcp/public_surface.py +1 -0
- package/src/qingflow_mcp/resource_uri.py +184 -0
- package/src/qingflow_mcp/response_trim.py +3 -3
- package/src/qingflow_mcp/server_app_builder.py +7 -0
- package/src/qingflow_mcp/server_app_user.py +3 -2
- package/src/qingflow_mcp/tools/ai_builder_tools.py +208 -39
- package/src/qingflow_mcp/tools/app_tools.py +52 -13
- package/src/qingflow_mcp/tools/login_link_tools.py +21 -1
- package/src/qingflow_mcp/tools/resource_read_tools.py +13 -0
|
@@ -9,6 +9,7 @@ from .config import DEFAULT_PROFILE
|
|
|
9
9
|
from .response_trim import BUILDER_SERVER_METHOD_MAP, trim_error_response, trim_public_response, wrap_trimmed_methods
|
|
10
10
|
from .session_store import SessionStore
|
|
11
11
|
from .tools.ai_builder_tools import AiBuilderTools
|
|
12
|
+
from .tools.login_link_tools import LOGIN_LINK_DESCRIPTION, LoginLinkTools
|
|
12
13
|
from .tools.auth_tools import AuthTools
|
|
13
14
|
from .tools.feedback_tools import FeedbackTools
|
|
14
15
|
from .tools.file_tools import FileTools
|
|
@@ -41,6 +42,7 @@ def build_builder_server() -> FastMCP:
|
|
|
41
42
|
"member_search/role_search/role_create when workflow assignees must come from the directory or role catalog, preferring roles over explicit members unless the user explicitly names members, "
|
|
42
43
|
"then app_form_apply for complete app form targets, explicit app_delete for application deletion, and app_flow_apply/app_views_apply/app_associated_resources_apply/app_charts_apply/portal_apply/portal_delete for non-form patches. app_custom_buttons_apply is maintenance-only for explicit standalone button work outside normal view design. These apply/delete tools perform validation, normalization, writes, and readback verification where applicable. AppForm publishes only after a draft change or when the published form lags behind; views noop requests skip publish, app_custom_buttons_apply and app_associated_resources_apply publish after at least one write succeeds and expose no draft-only parameter, charts are immediate-live without publish and resolve targets by chart_id first then exact unique chart name, portal updates use replace semantics only when sections are supplied and edit-mode base-info-only updates may omit sections, portal delete separates DELETE execution from readback verification, portal pc layout is a 24-column grid and mobile is a 6-column grid so omit position or use layout_preset when unsure, publish=false only guarantees draft/base-info updates for tools that still expose that parameter, and flow should use publish=false whenever you only want draft/precheck behavior. "
|
|
43
44
|
"Builder apply/write outputs include schema_version, operation, summary, and resources[]; use resources[].id/key/name/ids/parent as the stable UI and agent display entry, and keep legacy fields such as field_diff/views_diff/chart_results only for compatibility or troubleshooting. "
|
|
45
|
+
"For page handoff, prefer stable resource uri values in read/apply outputs. Do not issue tickets for intermediate reads. If the target browser state is unknown, issue one fresh get_login_link ticket per delivered resource uri; only deliver the stable uri directly after the user confirms the same browser, web origin, account, and workspace are already logged in. A prior ticket, an automated browser session, or another resource ticket is not that evidence, and consumed tickets cannot be reused. Do not open a returned URL unless the user asks; if ticket issuance fails, state that login is required and provide the stable uri as the fallback. "
|
|
44
46
|
"For existing object parameter replacement, prefer patch_views, patch_buttons, patch_resources, and patch_charts with set/unset; the tool reads current config and full-saves internally, while upsert_* is for creation or full target configuration and should not be used as an incomplete partial update. "
|
|
45
47
|
"For builder delete/remove apply results, separate delete execution from readback verification: after DELETE is sent, resources expose delete_executed, readback_status, and safe_to_retry_delete=false. If readback_status is unavailable or still_exists, do not blindly repeat the delete; confirm later with app_get/view_get/chart_get or the relevant apply readback. Views/buttons use single-item readback; associated resources use one app-level resource-pool readback because there is no confirmed single-item GET. "
|
|
46
48
|
"For builder write/apply results, separate write execution from final readback verification: status=partial_success with write_executed=true and safe_to_retry=false means the write was sent or succeeded, while final readback, publish verification, or metadata confirmation is pending or permission-restricted. Report it as written but unverified; do not repeat the same write blindly. "
|
|
@@ -64,6 +66,7 @@ def build_builder_server() -> FastMCP:
|
|
|
64
66
|
workspace = wrap_trimmed_methods(WorkspaceTools(sessions, backend), BUILDER_SERVER_METHOD_MAP)
|
|
65
67
|
files = wrap_trimmed_methods(FileTools(sessions, backend), BUILDER_SERVER_METHOD_MAP)
|
|
66
68
|
ai_builder = wrap_trimmed_methods(AiBuilderTools(sessions, backend), BUILDER_SERVER_METHOD_MAP)
|
|
69
|
+
login_link = LoginLinkTools(sessions, backend)
|
|
67
70
|
feedback = FeedbackTools(backend, mcp_side="App Builder MCP")
|
|
68
71
|
|
|
69
72
|
@server.tool()
|
|
@@ -90,6 +93,10 @@ def build_builder_server() -> FastMCP:
|
|
|
90
93
|
def auth_logout(profile: str = DEFAULT_PROFILE, forget_persisted: bool = False) -> dict:
|
|
91
94
|
return auth.auth_logout(profile=profile, forget_persisted=forget_persisted)
|
|
92
95
|
|
|
96
|
+
@server.tool(description=LOGIN_LINK_DESCRIPTION)
|
|
97
|
+
def get_login_link(profile: str = DEFAULT_PROFILE, redirect_path: str = "") -> dict:
|
|
98
|
+
return login_link.get_login_link(profile=profile, redirect_path=redirect_path)
|
|
99
|
+
|
|
93
100
|
@server.tool()
|
|
94
101
|
def workspace_list(
|
|
95
102
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -33,11 +33,12 @@ def build_user_server(*, stateless_http: bool = False) -> FastMCP:
|
|
|
33
33
|
|
|
34
34
|
## 页面交接
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
用户需要查看结果、继续编辑、处理业务或进入工作区页面时,优先使用业务输出中的稳定 `uri`。纯数据查询和中间读取不签发链接。
|
|
37
37
|
目标路径由业务工具或领域 Skill 根据真实前端路由确定,不由票据工具根据业务标识推导。
|
|
38
38
|
已知目标传可选 `redirect_path`;明确指定页面但路径未知时继续定位或说明限制,不猜测,也不静默改为首页。
|
|
39
39
|
搜索未命中时继续搜索 `login` 或查看服务工具列表,不擅自打开固定正式站点;鉴权或工具失败时如实说明。
|
|
40
|
-
|
|
40
|
+
目标浏览器登录态未知时,默认对每个要交付的资源 `uri` 分别调用一次 `get_login_link`;只有用户确认同一浏览器、网页域名、账号和工作区已经登录时才直接交付稳定 `uri`。调用过票据工具、自动化浏览器已登录或之前签发过票据均不能证明用户浏览器已登录,已消费链接也不能复用。
|
|
41
|
+
临近交付时再签发,使用返回的完整 URL。要求“给我链接”时仅交付,不提前消费;要求“打开”时才调用浏览器。签发失败时明确说明需要登录,并提供对应稳定 `uri` 作为备用入口。
|
|
41
42
|
浏览器不可用时提供可点击链接;用“查看记录”“继续编辑”等文案,不展示工作区数字 ID 或原始 JSON。
|
|
42
43
|
链接约 5 分钟内一次性有效,不能作为长期书签;安全验证及原有业务权限不变。
|
|
43
44
|
|
|
@@ -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,30 @@ 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
|
|
854
889
|
|
|
855
890
|
def app_form_evaluate_formula(
|
|
856
891
|
self,
|
|
@@ -917,12 +952,24 @@ class AiBuilderTools(ToolBase):
|
|
|
917
952
|
def package_list(self, *, profile: str, trial_status: str = "all", query: str = "") -> JSONObject:
|
|
918
953
|
"""执行分组与包相关逻辑。"""
|
|
919
954
|
normalized_args = {"trial_status": trial_status, "query": query}
|
|
920
|
-
|
|
955
|
+
result = _safe_tool_call(
|
|
921
956
|
lambda: self._facade.package_list(profile=profile, trial_status=trial_status, query=query),
|
|
922
957
|
error_code="PACKAGE_LIST_FAILED",
|
|
923
958
|
normalized_args=normalized_args,
|
|
924
959
|
suggested_next_call={"tool_name": "package_list", "arguments": {"profile": profile, "trial_status": trial_status, "query": query}},
|
|
925
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
|
|
926
973
|
|
|
927
974
|
def package_resolve(self, *, profile: str, package_name: str) -> JSONObject:
|
|
928
975
|
"""执行分组与包相关逻辑。"""
|
|
@@ -1062,12 +1109,23 @@ class AiBuilderTools(ToolBase):
|
|
|
1062
1109
|
def package_get(self, *, profile: str, package_id: int | None = None) -> JSONObject:
|
|
1063
1110
|
"""执行分组与包相关逻辑。"""
|
|
1064
1111
|
normalized_args = {"package_id": package_id}
|
|
1065
|
-
|
|
1112
|
+
result = _publicize_package_fields(_safe_tool_call(
|
|
1066
1113
|
lambda: self._facade.package_get(profile=profile, package_id=package_id),
|
|
1067
1114
|
error_code="PACKAGE_GET_FAILED",
|
|
1068
1115
|
normalized_args=normalized_args,
|
|
1069
1116
|
suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
|
|
1070
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
|
|
1071
1129
|
|
|
1072
1130
|
def package_apply(
|
|
1073
1131
|
self,
|
|
@@ -1097,7 +1155,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1097
1155
|
try:
|
|
1098
1156
|
visibility_patch = VisibilityPatch.model_validate(visibility)
|
|
1099
1157
|
except ValidationError as exc:
|
|
1100
|
-
return
|
|
1158
|
+
return self._attach_apply_envelope(profile,
|
|
1101
1159
|
"package_apply",
|
|
1102
1160
|
_visibility_validation_failure(str(exc), tool_name="package_apply", exc=exc),
|
|
1103
1161
|
)
|
|
@@ -1108,7 +1166,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1108
1166
|
creating=package_id is None and bool(str(package_name or "").strip()),
|
|
1109
1167
|
)
|
|
1110
1168
|
if icon_failure is not None:
|
|
1111
|
-
return
|
|
1169
|
+
return self._attach_apply_envelope(profile, "package_apply", icon_failure)
|
|
1112
1170
|
normalized_args = {
|
|
1113
1171
|
"package_id": package_id,
|
|
1114
1172
|
**({"package_name": package_name} if str(package_name or "").strip() else {}),
|
|
@@ -1133,7 +1191,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1133
1191
|
normalized_args=normalized_args,
|
|
1134
1192
|
suggested_next_call={"tool_name": "package_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1135
1193
|
))
|
|
1136
|
-
return
|
|
1194
|
+
return self._attach_apply_envelope(profile, "package_apply", result)
|
|
1137
1195
|
|
|
1138
1196
|
def package_update(
|
|
1139
1197
|
self,
|
|
@@ -1187,7 +1245,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1187
1245
|
"being_copy_data": being_copy_data,
|
|
1188
1246
|
"solution_source": solution_source,
|
|
1189
1247
|
}
|
|
1190
|
-
|
|
1248
|
+
result = _safe_tool_call(
|
|
1191
1249
|
lambda: self._facade.solution_install(
|
|
1192
1250
|
profile=profile,
|
|
1193
1251
|
solution_key=solution_key,
|
|
@@ -1198,6 +1256,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1198
1256
|
normalized_args=normalized_args,
|
|
1199
1257
|
suggested_next_call={"tool_name": "solution_install", "arguments": {"profile": profile, **normalized_args}},
|
|
1200
1258
|
)
|
|
1259
|
+
return self._attach_apply_envelope(profile, "solution_install", result)
|
|
1201
1260
|
|
|
1202
1261
|
def member_search(
|
|
1203
1262
|
self,
|
|
@@ -1394,7 +1453,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1394
1453
|
try:
|
|
1395
1454
|
request = CustomButtonsApplyRequest.model_validate(raw_request)
|
|
1396
1455
|
except ValidationError as exc:
|
|
1397
|
-
return
|
|
1456
|
+
return self._attach_apply_envelope(profile, "app_custom_buttons_apply", _validation_failure(
|
|
1398
1457
|
str(exc),
|
|
1399
1458
|
tool_name="app_custom_buttons_apply",
|
|
1400
1459
|
exc=exc,
|
|
@@ -1418,7 +1477,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1418
1477
|
},
|
|
1419
1478
|
))
|
|
1420
1479
|
normalized_args = request.model_dump(mode="json")
|
|
1421
|
-
return
|
|
1480
|
+
return self._attach_apply_envelope(profile, "app_custom_buttons_apply", _safe_tool_call(
|
|
1422
1481
|
lambda: self._facade.app_custom_buttons_apply(profile=profile, request=request),
|
|
1423
1482
|
error_code="CUSTOM_BUTTONS_APPLY_FAILED",
|
|
1424
1483
|
normalized_args=normalized_args,
|
|
@@ -1475,7 +1534,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1475
1534
|
try:
|
|
1476
1535
|
request = AssociatedResourcesApplyRequest.model_validate(raw_request)
|
|
1477
1536
|
except ValidationError as exc:
|
|
1478
|
-
return
|
|
1537
|
+
return self._attach_apply_envelope(profile, "app_associated_resources_apply", _validation_failure(
|
|
1479
1538
|
str(exc),
|
|
1480
1539
|
tool_name="app_associated_resources_apply",
|
|
1481
1540
|
exc=exc,
|
|
@@ -1504,7 +1563,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1504
1563
|
},
|
|
1505
1564
|
))
|
|
1506
1565
|
normalized_args = request.model_dump(mode="json", exclude_none=True)
|
|
1507
|
-
return
|
|
1566
|
+
return self._attach_apply_envelope(profile, "app_associated_resources_apply", _safe_tool_call(
|
|
1508
1567
|
lambda: self._facade.app_associated_resources_apply(profile=profile, request=request),
|
|
1509
1568
|
error_code="ASSOCIATED_RESOURCES_APPLY_FAILED",
|
|
1510
1569
|
normalized_args=normalized_args,
|
|
@@ -1629,12 +1688,27 @@ class AiBuilderTools(ToolBase):
|
|
|
1629
1688
|
read_one=lambda item_app_key: self.app_get(profile=profile, app_key=item_app_key, app_keys=None),
|
|
1630
1689
|
)
|
|
1631
1690
|
normalized_args = {"app_key": app_key}
|
|
1632
|
-
|
|
1691
|
+
result = _publicize_package_fields(_safe_tool_call(
|
|
1633
1692
|
lambda: self._facade.app_get(profile=profile, app_key=app_key),
|
|
1634
1693
|
error_code="APP_GET_FAILED",
|
|
1635
1694
|
normalized_args=normalized_args,
|
|
1636
1695
|
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1637
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
|
|
1638
1712
|
|
|
1639
1713
|
def app_read_fields(self, *, profile: str, app_key: str) -> JSONObject:
|
|
1640
1714
|
"""执行应用相关逻辑。"""
|
|
@@ -1727,12 +1801,27 @@ class AiBuilderTools(ToolBase):
|
|
|
1727
1801
|
read_one=lambda item_app_key: self.app_get_views(profile=profile, app_key=item_app_key, app_keys=None),
|
|
1728
1802
|
)
|
|
1729
1803
|
normalized_args = {"app_key": app_key}
|
|
1730
|
-
|
|
1804
|
+
result = _safe_tool_call(
|
|
1731
1805
|
lambda: self._facade.app_get_views(profile=profile, app_key=app_key),
|
|
1732
1806
|
error_code="APP_GET_VIEWS_FAILED",
|
|
1733
1807
|
normalized_args=normalized_args,
|
|
1734
1808
|
suggested_next_call={"tool_name": "app_get_views", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1735
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
|
|
1736
1825
|
|
|
1737
1826
|
def app_read_flow_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
1738
1827
|
"""执行应用相关逻辑。"""
|
|
@@ -1871,12 +1960,27 @@ class AiBuilderTools(ToolBase):
|
|
|
1871
1960
|
"""执行视图相关逻辑。"""
|
|
1872
1961
|
resolved_view_key = _normalize_builder_view_key(str(view_key or viewgraph_key or "").strip())
|
|
1873
1962
|
normalized_args = {"view_key": resolved_view_key}
|
|
1874
|
-
|
|
1963
|
+
result = _safe_tool_call(
|
|
1875
1964
|
lambda: self._facade.view_get(profile=profile, view_key=resolved_view_key),
|
|
1876
1965
|
error_code="VIEW_GET_FAILED",
|
|
1877
1966
|
normalized_args=normalized_args,
|
|
1878
1967
|
suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, **normalized_args}},
|
|
1879
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
|
|
1880
1984
|
|
|
1881
1985
|
def chart_get(
|
|
1882
1986
|
self,
|
|
@@ -2165,7 +2269,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2165
2269
|
)
|
|
2166
2270
|
failure = normalized_apps_payload.get("failure")
|
|
2167
2271
|
if isinstance(failure, dict):
|
|
2168
|
-
return
|
|
2272
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", failure)
|
|
2169
2273
|
package_id = normalized_apps_payload.get("package_id") # type: ignore[assignment]
|
|
2170
2274
|
apps = normalized_apps_payload.get("apps") # type: ignore[assignment]
|
|
2171
2275
|
input_warnings = list(normalized_apps_payload.get("warnings") or [])
|
|
@@ -2181,7 +2285,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2181
2285
|
)
|
|
2182
2286
|
failure = form_result.get("failure")
|
|
2183
2287
|
if isinstance(failure, dict):
|
|
2184
|
-
return
|
|
2288
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", failure)
|
|
2185
2289
|
normalized_apps.append(form_result.get("payload") or app_item)
|
|
2186
2290
|
apps = normalized_apps
|
|
2187
2291
|
result = self._app_schema_apply_multi(
|
|
@@ -2195,7 +2299,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2195
2299
|
result_warnings = list(result.get("warnings") or [])
|
|
2196
2300
|
result_warnings.extend(deepcopy(input_warnings))
|
|
2197
2301
|
result["warnings"] = result_warnings
|
|
2198
|
-
return
|
|
2302
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", result)
|
|
2199
2303
|
inline_form_layout_sections: list[JSONObject] = []
|
|
2200
2304
|
if form:
|
|
2201
2305
|
form_payload: JSONObject = {"form": form}
|
|
@@ -2208,7 +2312,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2208
2312
|
)
|
|
2209
2313
|
failure = form_result.get("failure")
|
|
2210
2314
|
if isinstance(failure, dict):
|
|
2211
|
-
return
|
|
2315
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", failure)
|
|
2212
2316
|
normalized_payload = form_result.get("payload") if isinstance(form_result.get("payload"), dict) else {}
|
|
2213
2317
|
add_fields = list(normalized_payload.get("add_fields") or [])
|
|
2214
2318
|
inline_form_layout_sections = list(form_result.get("layout_sections") or [])
|
|
@@ -2246,7 +2350,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2246
2350
|
inline_form_layout_sections=inline_form_layout_sections,
|
|
2247
2351
|
),
|
|
2248
2352
|
)
|
|
2249
|
-
return
|
|
2353
|
+
return self._attach_apply_envelope(profile, "app_schema_apply", result)
|
|
2250
2354
|
|
|
2251
2355
|
def _app_schema_apply_multi(
|
|
2252
2356
|
self,
|
|
@@ -3525,7 +3629,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3525
3629
|
sections=sections,
|
|
3526
3630
|
),
|
|
3527
3631
|
)
|
|
3528
|
-
return
|
|
3632
|
+
return self._attach_apply_envelope(profile, "app_layout_apply", result)
|
|
3529
3633
|
|
|
3530
3634
|
def _app_layout_apply_once(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
3531
3635
|
"""执行内部辅助逻辑。"""
|
|
@@ -3590,7 +3694,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3590
3694
|
"idempotency_key": idempotency_key,
|
|
3591
3695
|
"schema_version": schema_version,
|
|
3592
3696
|
}
|
|
3593
|
-
return
|
|
3697
|
+
return self._attach_apply_envelope(profile, "app_flow_apply", _safe_tool_call(
|
|
3594
3698
|
lambda: self._facade.flow_patch_nodes(
|
|
3595
3699
|
profile=profile,
|
|
3596
3700
|
app_key=app_key,
|
|
@@ -3611,7 +3715,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3611
3715
|
"idempotency_key": idempotency_key,
|
|
3612
3716
|
"schema_version": schema_version,
|
|
3613
3717
|
}
|
|
3614
|
-
return
|
|
3718
|
+
return self._attach_apply_envelope(profile, "app_flow_apply", _safe_tool_call(
|
|
3615
3719
|
lambda: self._facade.flow_apply(
|
|
3616
3720
|
profile=profile,
|
|
3617
3721
|
app_key=app_key,
|
|
@@ -3647,9 +3751,9 @@ class AiBuilderTools(ToolBase):
|
|
|
3647
3751
|
transitions=effective_transitions,
|
|
3648
3752
|
),
|
|
3649
3753
|
)
|
|
3650
|
-
return
|
|
3754
|
+
return self._attach_apply_envelope(profile, "app_flow_apply", result)
|
|
3651
3755
|
normalized_args = {"app_key": app_key, "publish": publish}
|
|
3652
|
-
return
|
|
3756
|
+
return self._attach_apply_envelope(profile,
|
|
3653
3757
|
"app_flow_apply",
|
|
3654
3758
|
{
|
|
3655
3759
|
"status": "failed",
|
|
@@ -3784,7 +3888,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3784
3888
|
patch_views=patch_views or [],
|
|
3785
3889
|
),
|
|
3786
3890
|
)
|
|
3787
|
-
return
|
|
3891
|
+
return self._attach_apply_envelope(profile, "app_views_apply", result)
|
|
3788
3892
|
|
|
3789
3893
|
def _app_views_apply_view_batch(
|
|
3790
3894
|
self,
|
|
@@ -3795,7 +3899,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3795
3899
|
) -> JSONObject:
|
|
3796
3900
|
started_at = time.perf_counter()
|
|
3797
3901
|
if not isinstance(views, list) or not views:
|
|
3798
|
-
return
|
|
3902
|
+
return self._attach_apply_envelope(profile,
|
|
3799
3903
|
"app_views_apply",
|
|
3800
3904
|
_config_failure(
|
|
3801
3905
|
tool_name="app_views_apply",
|
|
@@ -4026,7 +4130,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4026
4130
|
view_write_parallel_wall_ms=view_write_parallel_wall_ms,
|
|
4027
4131
|
total_ms=_elapsed_ms(started_at),
|
|
4028
4132
|
)
|
|
4029
|
-
return
|
|
4133
|
+
return self._attach_apply_envelope(profile, "app_views_apply", payload)
|
|
4030
4134
|
|
|
4031
4135
|
def _app_views_apply_once(
|
|
4032
4136
|
self,
|
|
@@ -4149,7 +4253,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4149
4253
|
}
|
|
4150
4254
|
)
|
|
4151
4255
|
except ValidationError as exc:
|
|
4152
|
-
return
|
|
4256
|
+
return self._attach_apply_envelope(profile, "app_charts_apply", _visibility_validation_failure(
|
|
4153
4257
|
str(exc),
|
|
4154
4258
|
tool_name="app_charts_apply",
|
|
4155
4259
|
exc=exc,
|
|
@@ -4165,7 +4269,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4165
4269
|
},
|
|
4166
4270
|
))
|
|
4167
4271
|
normalized_args = request.model_dump(mode="json")
|
|
4168
|
-
return
|
|
4272
|
+
return self._attach_apply_envelope(profile, "app_charts_apply", _safe_tool_call(
|
|
4169
4273
|
lambda: self._facade.chart_apply(profile=profile, request=request),
|
|
4170
4274
|
error_code="CHART_APPLY_FAILED",
|
|
4171
4275
|
normalized_args=normalized_args,
|
|
@@ -4201,7 +4305,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4201
4305
|
"publish": publish,
|
|
4202
4306
|
"patch_sections": patch_sections,
|
|
4203
4307
|
}
|
|
4204
|
-
return
|
|
4308
|
+
return self._attach_apply_envelope(profile, "portal_apply", _publicize_package_fields(_safe_tool_call(
|
|
4205
4309
|
lambda: self._facade.portal_patch_sections(
|
|
4206
4310
|
profile=profile,
|
|
4207
4311
|
dash_key=dash_key,
|
|
@@ -4248,7 +4352,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4248
4352
|
try:
|
|
4249
4353
|
request = PortalApplyRequest.model_validate(request_payload)
|
|
4250
4354
|
except ValidationError as exc:
|
|
4251
|
-
return
|
|
4355
|
+
return self._attach_apply_envelope(profile, "portal_apply", _visibility_validation_failure(
|
|
4252
4356
|
str(exc),
|
|
4253
4357
|
tool_name="portal_apply",
|
|
4254
4358
|
exc=exc,
|
|
@@ -4279,14 +4383,14 @@ class AiBuilderTools(ToolBase):
|
|
|
4279
4383
|
creating=not bool(str(request.dash_key or "").strip()),
|
|
4280
4384
|
)
|
|
4281
4385
|
if icon_failure is not None:
|
|
4282
|
-
return
|
|
4386
|
+
return self._attach_apply_envelope(profile, "portal_apply", icon_failure)
|
|
4283
4387
|
result = _publicize_package_fields(_safe_tool_call(
|
|
4284
4388
|
lambda: self._facade.portal_apply(profile=profile, request=request),
|
|
4285
4389
|
error_code="PORTAL_APPLY_FAILED",
|
|
4286
4390
|
normalized_args=normalized_args,
|
|
4287
4391
|
suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
4288
4392
|
))
|
|
4289
|
-
return
|
|
4393
|
+
return self._attach_apply_envelope(profile, "portal_apply", result)
|
|
4290
4394
|
|
|
4291
4395
|
def portal_delete(self, *, profile: str, dash_key: str) -> JSONObject:
|
|
4292
4396
|
"""执行门户删除逻辑。"""
|
|
@@ -4297,7 +4401,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4297
4401
|
normalized_args=normalized_args,
|
|
4298
4402
|
suggested_next_call={"tool_name": "portal_delete", "arguments": {"profile": profile, **normalized_args}},
|
|
4299
4403
|
))
|
|
4300
|
-
return
|
|
4404
|
+
return self._attach_apply_envelope(profile, "portal_delete", result)
|
|
4301
4405
|
|
|
4302
4406
|
def app_publish_verify(
|
|
4303
4407
|
self,
|
|
@@ -4336,7 +4440,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4336
4440
|
expected_package_tag_id=expected_package_id,
|
|
4337
4441
|
),
|
|
4338
4442
|
))
|
|
4339
|
-
return
|
|
4443
|
+
return self._attach_apply_envelope(profile, "app_publish_verify", result)
|
|
4340
4444
|
|
|
4341
4445
|
def _retry_after_self_lock_release(self, *, profile: str, result: JSONObject, retry_call) -> JSONObject:
|
|
4342
4446
|
"""执行内部辅助逻辑。"""
|
|
@@ -5702,6 +5806,68 @@ def _publicize_package_fields(value):
|
|
|
5702
5806
|
return public
|
|
5703
5807
|
|
|
5704
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
|
+
|
|
5705
5871
|
def _builder_contract_with_apply_output(tool_name: str, contract: JSONObject) -> JSONObject:
|
|
5706
5872
|
public = deepcopy(contract)
|
|
5707
5873
|
if tool_name not in BUILDER_APPLY_TOOL_NAMES:
|
|
@@ -5714,7 +5880,7 @@ def _builder_contract_with_apply_output(tool_name: str, contract: JSONObject) ->
|
|
|
5714
5880
|
public["output_contract"] = {
|
|
5715
5881
|
"schema_version": BUILDER_APPLY_SCHEMA_VERSION,
|
|
5716
5882
|
"preferred_ui_fields": ["operation", "summary", "resources"],
|
|
5717
|
-
"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"],
|
|
5718
5884
|
"json_paths": _builder_apply_json_paths(),
|
|
5719
5885
|
"legacy_fields_preserved": True,
|
|
5720
5886
|
}
|
|
@@ -5766,6 +5932,7 @@ def _builder_apply_json_paths() -> JSONObject:
|
|
|
5766
5932
|
"resource_keys": "$.resources[].key",
|
|
5767
5933
|
"resource_ids": "$.resources[].id",
|
|
5768
5934
|
"resource_statuses": "$.resources[].status",
|
|
5935
|
+
"resource_uris": "$.resources[].uri",
|
|
5769
5936
|
}
|
|
5770
5937
|
|
|
5771
5938
|
|
|
@@ -5855,7 +6022,9 @@ def _builder_verification_truthy(value: object) -> bool:
|
|
|
5855
6022
|
|
|
5856
6023
|
def _builder_apply_resources(tool_name: str, payload: JSONObject) -> list[JSONObject]:
|
|
5857
6024
|
resources: list[JSONObject]
|
|
5858
|
-
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":
|
|
5859
6028
|
resources = _builder_package_resources(payload)
|
|
5860
6029
|
elif tool_name == "app_schema_apply":
|
|
5861
6030
|
resources = _builder_schema_resources(payload)
|