@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.
@@ -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 _attach_builder_apply_envelope(tool_name, payload)
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
- return self._app_form.get(
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
- return self._app_form.apply(profile=profile, spec=spec)
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
- return _safe_tool_call(
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
- return _publicize_package_fields(_safe_tool_call(
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 _attach_builder_apply_envelope(
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 _attach_builder_apply_envelope("package_apply", icon_failure)
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 _attach_builder_apply_envelope("package_apply", result)
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
- return _safe_tool_call(
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 _attach_builder_apply_envelope("app_custom_buttons_apply", _validation_failure(
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 _attach_builder_apply_envelope("app_custom_buttons_apply", _safe_tool_call(
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 _attach_builder_apply_envelope("app_associated_resources_apply", _validation_failure(
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 _attach_builder_apply_envelope("app_associated_resources_apply", _safe_tool_call(
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
- return _publicize_package_fields(_safe_tool_call(
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
- return _safe_tool_call(
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
- return _safe_tool_call(
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 _attach_builder_apply_envelope("app_schema_apply", failure)
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 _attach_builder_apply_envelope("app_schema_apply", failure)
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 _attach_builder_apply_envelope("app_schema_apply", result)
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 _attach_builder_apply_envelope("app_schema_apply", failure)
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 _attach_builder_apply_envelope("app_schema_apply", result)
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 _attach_builder_apply_envelope("app_layout_apply", result)
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 _attach_builder_apply_envelope("app_flow_apply", _safe_tool_call(
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 _attach_builder_apply_envelope("app_flow_apply", _safe_tool_call(
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 _attach_builder_apply_envelope("app_flow_apply", result)
3754
+ return self._attach_apply_envelope(profile, "app_flow_apply", result)
3593
3755
  normalized_args = {"app_key": app_key, "publish": publish}
3594
- return _attach_builder_apply_envelope(
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 _attach_builder_apply_envelope("app_views_apply", result)
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 _attach_builder_apply_envelope(
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 _attach_builder_apply_envelope("app_views_apply", payload)
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 _attach_builder_apply_envelope("app_charts_apply", _visibility_validation_failure(
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 _attach_builder_apply_envelope("app_charts_apply", _safe_tool_call(
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 _attach_builder_apply_envelope("portal_apply", _publicize_package_fields(_safe_tool_call(
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 _attach_builder_apply_envelope("portal_apply", _visibility_validation_failure(
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 _attach_builder_apply_envelope("portal_apply", icon_failure)
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 _attach_builder_apply_envelope("portal_apply", result)
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 _attach_builder_apply_envelope("portal_delete", result)
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 _attach_builder_apply_envelope("app_publish_verify", result)
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 == "package_apply":
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)