@josephyan/qingflow-cli 0.2.0-beta.1000

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.
Files changed (92) hide show
  1. package/README.md +31 -0
  2. package/docs/local-agent-install.md +309 -0
  3. package/entry_point.py +13 -0
  4. package/npm/bin/qingflow.mjs +5 -0
  5. package/npm/lib/runtime.mjs +346 -0
  6. package/npm/scripts/postinstall.mjs +16 -0
  7. package/package.json +34 -0
  8. package/pyproject.toml +67 -0
  9. package/qingflow +15 -0
  10. package/src/qingflow_mcp/__init__.py +37 -0
  11. package/src/qingflow_mcp/__main__.py +5 -0
  12. package/src/qingflow_mcp/backend_client.py +649 -0
  13. package/src/qingflow_mcp/builder_facade/__init__.py +3 -0
  14. package/src/qingflow_mcp/builder_facade/models.py +1846 -0
  15. package/src/qingflow_mcp/builder_facade/service.py +16502 -0
  16. package/src/qingflow_mcp/cli/__init__.py +1 -0
  17. package/src/qingflow_mcp/cli/commands/__init__.py +18 -0
  18. package/src/qingflow_mcp/cli/commands/app.py +40 -0
  19. package/src/qingflow_mcp/cli/commands/auth.py +112 -0
  20. package/src/qingflow_mcp/cli/commands/builder.py +539 -0
  21. package/src/qingflow_mcp/cli/commands/chart.py +18 -0
  22. package/src/qingflow_mcp/cli/commands/common.py +62 -0
  23. package/src/qingflow_mcp/cli/commands/imports.py +96 -0
  24. package/src/qingflow_mcp/cli/commands/portal.py +25 -0
  25. package/src/qingflow_mcp/cli/commands/record.py +331 -0
  26. package/src/qingflow_mcp/cli/commands/repo.py +80 -0
  27. package/src/qingflow_mcp/cli/commands/task.py +141 -0
  28. package/src/qingflow_mcp/cli/commands/view.py +18 -0
  29. package/src/qingflow_mcp/cli/commands/workspace.py +110 -0
  30. package/src/qingflow_mcp/cli/context.py +60 -0
  31. package/src/qingflow_mcp/cli/formatters.py +573 -0
  32. package/src/qingflow_mcp/cli/json_io.py +50 -0
  33. package/src/qingflow_mcp/cli/main.py +186 -0
  34. package/src/qingflow_mcp/cli/qingflow_login.py +116 -0
  35. package/src/qingflow_mcp/cli/terminal_ui.py +173 -0
  36. package/src/qingflow_mcp/config.py +407 -0
  37. package/src/qingflow_mcp/errors.py +66 -0
  38. package/src/qingflow_mcp/id_utils.py +49 -0
  39. package/src/qingflow_mcp/import_store.py +121 -0
  40. package/src/qingflow_mcp/json_types.py +18 -0
  41. package/src/qingflow_mcp/list_type_labels.py +76 -0
  42. package/src/qingflow_mcp/public_surface.py +243 -0
  43. package/src/qingflow_mcp/repository_store.py +71 -0
  44. package/src/qingflow_mcp/response_trim.py +841 -0
  45. package/src/qingflow_mcp/server.py +216 -0
  46. package/src/qingflow_mcp/server_app_builder.py +543 -0
  47. package/src/qingflow_mcp/server_app_user.py +386 -0
  48. package/src/qingflow_mcp/session_store.py +369 -0
  49. package/src/qingflow_mcp/solution/__init__.py +6 -0
  50. package/src/qingflow_mcp/solution/build_assembly_store.py +181 -0
  51. package/src/qingflow_mcp/solution/compiler/__init__.py +282 -0
  52. package/src/qingflow_mcp/solution/compiler/chart_compiler.py +96 -0
  53. package/src/qingflow_mcp/solution/compiler/form_compiler.py +495 -0
  54. package/src/qingflow_mcp/solution/compiler/icon_utils.py +187 -0
  55. package/src/qingflow_mcp/solution/compiler/navigation_compiler.py +57 -0
  56. package/src/qingflow_mcp/solution/compiler/package_compiler.py +19 -0
  57. package/src/qingflow_mcp/solution/compiler/portal_compiler.py +60 -0
  58. package/src/qingflow_mcp/solution/compiler/view_compiler.py +51 -0
  59. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
  60. package/src/qingflow_mcp/solution/design_session.py +222 -0
  61. package/src/qingflow_mcp/solution/design_store.py +100 -0
  62. package/src/qingflow_mcp/solution/executor.py +2398 -0
  63. package/src/qingflow_mcp/solution/normalizer.py +23 -0
  64. package/src/qingflow_mcp/solution/requirements_builder.py +536 -0
  65. package/src/qingflow_mcp/solution/run_store.py +244 -0
  66. package/src/qingflow_mcp/solution/spec_models.py +855 -0
  67. package/src/qingflow_mcp/tools/__init__.py +1 -0
  68. package/src/qingflow_mcp/tools/ai_builder_tools.py +3449 -0
  69. package/src/qingflow_mcp/tools/app_tools.py +926 -0
  70. package/src/qingflow_mcp/tools/approval_tools.py +1062 -0
  71. package/src/qingflow_mcp/tools/auth_tools.py +1133 -0
  72. package/src/qingflow_mcp/tools/base.py +281 -0
  73. package/src/qingflow_mcp/tools/code_block_tools.py +777 -0
  74. package/src/qingflow_mcp/tools/custom_button_tools.py +202 -0
  75. package/src/qingflow_mcp/tools/directory_tools.py +675 -0
  76. package/src/qingflow_mcp/tools/feedback_tools.py +238 -0
  77. package/src/qingflow_mcp/tools/file_tools.py +409 -0
  78. package/src/qingflow_mcp/tools/import_tools.py +2223 -0
  79. package/src/qingflow_mcp/tools/navigation_tools.py +210 -0
  80. package/src/qingflow_mcp/tools/package_tools.py +326 -0
  81. package/src/qingflow_mcp/tools/portal_tools.py +158 -0
  82. package/src/qingflow_mcp/tools/qingbi_report_tools.py +374 -0
  83. package/src/qingflow_mcp/tools/record_tools.py +14291 -0
  84. package/src/qingflow_mcp/tools/repository_dev_tools.py +552 -0
  85. package/src/qingflow_mcp/tools/resource_read_tools.py +503 -0
  86. package/src/qingflow_mcp/tools/role_tools.py +112 -0
  87. package/src/qingflow_mcp/tools/solution_tools.py +4054 -0
  88. package/src/qingflow_mcp/tools/task_context_tools.py +2986 -0
  89. package/src/qingflow_mcp/tools/task_tools.py +889 -0
  90. package/src/qingflow_mcp/tools/view_tools.py +335 -0
  91. package/src/qingflow_mcp/tools/workflow_tools.py +376 -0
  92. package/src/qingflow_mcp/tools/workspace_tools.py +266 -0
@@ -0,0 +1,3449 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ import json
5
+ import time
6
+
7
+ from pydantic import ValidationError
8
+
9
+ from ..public_surface import public_builder_contract_tool_names
10
+ from ..config import DEFAULT_PROFILE
11
+ from ..errors import QingflowApiError
12
+ from ..json_types import JSONObject
13
+ from ..builder_facade.models import (
14
+ ChartApplyRequest,
15
+ CustomButtonPatch,
16
+ FIELD_TYPE_ID_ALIASES,
17
+ FieldPatch,
18
+ FieldRemovePatch,
19
+ FieldUpdatePatch,
20
+ FlowPreset,
21
+ FlowNodePatch,
22
+ FlowPlanRequest,
23
+ FlowTransitionPatch,
24
+ LayoutApplyMode,
25
+ LayoutPlanRequest,
26
+ LayoutPreset,
27
+ LayoutSectionPatch,
28
+ PortalApplyRequest,
29
+ PublicButtonTriggerAction,
30
+ PublicFieldType,
31
+ PublicRelationMode,
32
+ PublicChartType,
33
+ PublicViewType,
34
+ SchemaPlanRequest,
35
+ VisibilityPatch,
36
+ ViewFilterOperator,
37
+ ViewUpsertPatch,
38
+ ViewsPreset,
39
+ ViewsPlanRequest,
40
+ )
41
+ from ..builder_facade.service import AiBuilderFacade, INTEGRATION_OUTPUT_TARGET_FIELD_TYPES
42
+ from .app_tools import AppTools
43
+ from .base import ToolBase, tool_cn_name
44
+ from .custom_button_tools import CustomButtonTools
45
+ from .directory_tools import DirectoryTools
46
+ from .package_tools import PackageTools
47
+ from .portal_tools import PortalTools
48
+ from .qingbi_report_tools import QingbiReportTools
49
+ from .role_tools import RoleTools
50
+ from .solution_tools import SolutionTools
51
+ from .view_tools import ViewTools
52
+ from .workflow_tools import WorkflowTools
53
+
54
+ PUBLIC_STABLE_FLOW_NODE_TYPES = ["start", "approve", "fill", "copy", "webhook", "end"]
55
+
56
+
57
+ class AiBuilderTools(ToolBase):
58
+ """AI Builder 工具(中文名:AI 搭建编排)。
59
+
60
+ 类型:应用搭建编排工具。
61
+ 主要职责:
62
+ 1. 编排 schema/layout/views/flow/charts 的计划与应用;
63
+ 2. 聚合 builder 侧读写与校验能力;
64
+ 3. 将复杂搭建流程收敛为可执行步骤。
65
+ """
66
+
67
+ def __init__(self, sessions, backend) -> None:
68
+ """执行内部辅助逻辑。"""
69
+ super().__init__(sessions, backend)
70
+ self._facade = AiBuilderFacade(
71
+ apps=AppTools(sessions, backend),
72
+ buttons=CustomButtonTools(sessions, backend),
73
+ packages=PackageTools(sessions, backend),
74
+ views=ViewTools(sessions, backend),
75
+ workflows=WorkflowTools(sessions, backend),
76
+ portals=PortalTools(sessions, backend),
77
+ charts=QingbiReportTools(sessions, backend),
78
+ roles=RoleTools(sessions, backend),
79
+ directory=DirectoryTools(sessions, backend),
80
+ solutions=SolutionTools(sessions, backend),
81
+ )
82
+
83
+ def register(self, mcp) -> None:
84
+ """注册当前工具到 MCP 服务。"""
85
+ @mcp.tool()
86
+ def builder_tool_contract(tool_name: str = "") -> JSONObject:
87
+ return self.builder_tool_contract(tool_name=tool_name)
88
+
89
+ @mcp.tool()
90
+ def package_get(profile: str = DEFAULT_PROFILE, package_id: int = 0) -> JSONObject:
91
+ return self.package_get(profile=profile, package_id=package_id)
92
+
93
+ @mcp.tool()
94
+ def package_apply(
95
+ profile: str = DEFAULT_PROFILE,
96
+ package_id: int | None = None,
97
+ package_name: str | None = None,
98
+ create_if_missing: bool = False,
99
+ icon: str | None = None,
100
+ color: str | None = None,
101
+ visibility: JSONObject | None = None,
102
+ items: list[dict] | None = None,
103
+ allow_detach: bool = False,
104
+ ) -> JSONObject:
105
+ return self.package_apply(
106
+ profile=profile,
107
+ package_id=package_id,
108
+ package_name=package_name,
109
+ create_if_missing=create_if_missing,
110
+ icon=icon,
111
+ color=color,
112
+ visibility=visibility,
113
+ items=items or None,
114
+ allow_detach=allow_detach,
115
+ )
116
+
117
+ @mcp.tool()
118
+ def solution_install(
119
+ profile: str = DEFAULT_PROFILE,
120
+ solution_key: str = "",
121
+ being_copy_data: bool = True,
122
+ solution_source: str = "solutionDetail",
123
+ ) -> JSONObject:
124
+ return self.solution_install(
125
+ profile=profile,
126
+ solution_key=solution_key,
127
+ being_copy_data=being_copy_data,
128
+ solution_source=solution_source,
129
+ )
130
+
131
+ @mcp.tool()
132
+ def member_search(
133
+ profile: str = DEFAULT_PROFILE,
134
+ query: str = "",
135
+ page_num: int = 1,
136
+ page_size: int = 20,
137
+ contain_disable: bool = False,
138
+ ) -> JSONObject:
139
+ return self.member_search(
140
+ profile=profile,
141
+ query=query,
142
+ page_num=page_num,
143
+ page_size=page_size,
144
+ contain_disable=contain_disable,
145
+ )
146
+
147
+ @mcp.tool()
148
+ def role_search(
149
+ profile: str = DEFAULT_PROFILE,
150
+ keyword: str = "",
151
+ page_num: int = 1,
152
+ page_size: int = 20,
153
+ ) -> JSONObject:
154
+ return self.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size)
155
+
156
+ @mcp.tool()
157
+ def role_create(
158
+ profile: str = DEFAULT_PROFILE,
159
+ role_name: str = "",
160
+ member_uids: list[int] | None = None,
161
+ member_emails: list[str] | None = None,
162
+ member_names: list[str] | None = None,
163
+ role_icon: str = "ex-user-outlined",
164
+ ) -> JSONObject:
165
+ return self.role_create(
166
+ profile=profile,
167
+ role_name=role_name,
168
+ member_uids=member_uids or [],
169
+ member_emails=member_emails or [],
170
+ member_names=member_names or [],
171
+ role_icon=role_icon,
172
+ )
173
+
174
+ @mcp.tool()
175
+ def app_release_edit_lock_if_mine(
176
+ profile: str = DEFAULT_PROFILE,
177
+ app_key: str = "",
178
+ lock_owner_email: str = "",
179
+ lock_owner_name: str = "",
180
+ ) -> JSONObject:
181
+ return self.app_release_edit_lock_if_mine(
182
+ profile=profile,
183
+ app_key=app_key,
184
+ lock_owner_email=lock_owner_email,
185
+ lock_owner_name=lock_owner_name,
186
+ )
187
+
188
+ @mcp.tool()
189
+ def app_resolve(
190
+ profile: str = DEFAULT_PROFILE,
191
+ app_key: str = "",
192
+ app_name: str = "",
193
+ package_id: int | None = None,
194
+ ) -> JSONObject:
195
+ has_app_key = bool((app_key or "").strip())
196
+ has_app_name = bool((app_name or "").strip())
197
+ has_package_id = package_id is not None
198
+ if has_app_key and (has_app_name or has_package_id):
199
+ return _config_failure(
200
+ tool_name="app_resolve",
201
+ message="app_resolve accepts exactly one selector mode.",
202
+ fix_hint="Use only `app_key`, or use `app_name` together with `package_id`.",
203
+ )
204
+ if not has_app_key and not (has_app_name and has_package_id):
205
+ return _config_failure(
206
+ tool_name="app_resolve",
207
+ message="app_resolve requires either app_key, or app_name together with package_id.",
208
+ fix_hint="For an existing known app, pass `app_key`. For package-scoped lookup, pass both `app_name` and `package_id`.",
209
+ )
210
+ return self.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_id=package_id)
211
+
212
+ @mcp.tool()
213
+ def app_custom_button_list(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
214
+ return self.app_custom_button_list(profile=profile, app_key=app_key)
215
+
216
+ @mcp.tool()
217
+ def app_custom_button_get(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> JSONObject:
218
+ return self.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id)
219
+
220
+ @mcp.tool()
221
+ def app_custom_button_create(
222
+ profile: str = DEFAULT_PROFILE,
223
+ app_key: str = "",
224
+ payload: JSONObject | None = None,
225
+ ) -> JSONObject:
226
+ return self.app_custom_button_create(profile=profile, app_key=app_key, payload=payload or {})
227
+
228
+ @mcp.tool()
229
+ def app_custom_button_update(
230
+ profile: str = DEFAULT_PROFILE,
231
+ app_key: str = "",
232
+ button_id: int = 0,
233
+ payload: JSONObject | None = None,
234
+ ) -> JSONObject:
235
+ return self.app_custom_button_update(profile=profile, app_key=app_key, button_id=button_id, payload=payload or {})
236
+
237
+ @mcp.tool()
238
+ def app_custom_button_delete(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> JSONObject:
239
+ return self.app_custom_button_delete(profile=profile, app_key=app_key, button_id=button_id)
240
+
241
+ @mcp.tool()
242
+ def app_get(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
243
+ return self.app_get(profile=profile, app_key=app_key)
244
+
245
+ @mcp.tool()
246
+ def app_get_fields(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
247
+ return self.app_get_fields(profile=profile, app_key=app_key)
248
+
249
+ @mcp.tool()
250
+ def app_repair_code_blocks(
251
+ profile: str = DEFAULT_PROFILE,
252
+ app_key: str = "",
253
+ field: str | None = None,
254
+ apply: bool = False,
255
+ ) -> JSONObject:
256
+ return self.app_repair_code_blocks(profile=profile, app_key=app_key, field=field, apply=apply)
257
+
258
+ @mcp.tool()
259
+ def app_get_layout(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
260
+ return self.app_get_layout(profile=profile, app_key=app_key)
261
+
262
+ @mcp.tool()
263
+ def app_get_views(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
264
+ return self.app_get_views(profile=profile, app_key=app_key)
265
+
266
+ @mcp.tool()
267
+ def app_get_flow(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
268
+ return self.app_get_flow(profile=profile, app_key=app_key)
269
+
270
+ @mcp.tool()
271
+ def app_get_charts(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
272
+ return self.app_get_charts(profile=profile, app_key=app_key)
273
+
274
+ @mcp.tool()
275
+ def portal_list(profile: str = DEFAULT_PROFILE) -> JSONObject:
276
+ return self.portal_list(profile=profile)
277
+
278
+ @mcp.tool()
279
+ def portal_get(
280
+ profile: str = DEFAULT_PROFILE,
281
+ dash_key: str = "",
282
+ being_draft: bool = True,
283
+ ) -> JSONObject:
284
+ return self.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft)
285
+
286
+ @mcp.tool()
287
+ def view_get(profile: str = DEFAULT_PROFILE, view_key: str = "") -> JSONObject:
288
+ return self.view_get(profile=profile, view_key=view_key)
289
+
290
+ @mcp.tool()
291
+ def chart_get(
292
+ profile: str = DEFAULT_PROFILE,
293
+ chart_id: str = "",
294
+ ) -> JSONObject:
295
+ return self.chart_get(profile=profile, chart_id=chart_id)
296
+
297
+ @mcp.tool()
298
+ def app_schema_apply(
299
+ profile: str = DEFAULT_PROFILE,
300
+ app_key: str = "",
301
+ package_id: int | None = None,
302
+ app_name: str = "",
303
+ app_title: str = "",
304
+ icon: str = "",
305
+ color: str = "",
306
+ visibility: JSONObject | None = None,
307
+ create_if_missing: bool = False,
308
+ publish: bool = True,
309
+ add_fields: list[JSONObject] | None = None,
310
+ update_fields: list[JSONObject] | None = None,
311
+ remove_fields: list[JSONObject] | None = None,
312
+ ) -> JSONObject:
313
+ has_app_key = bool((app_key or "").strip())
314
+ has_app_name = bool((app_name or "").strip())
315
+ has_app_title = bool((app_title or "").strip())
316
+ has_package_id = package_id is not None
317
+ if has_app_key:
318
+ if create_if_missing or has_package_id:
319
+ return _config_failure(
320
+ tool_name="app_schema_apply",
321
+ message="app_schema_apply edit mode accepts app_key and optional app_name rename only.",
322
+ fix_hint="For existing apps, use `app_key` and optionally `app_name`. For create mode, use `package_id + app_name + create_if_missing=true`.",
323
+ )
324
+ elif not (create_if_missing and has_package_id and (has_app_name or has_app_title)):
325
+ return _config_failure(
326
+ tool_name="app_schema_apply",
327
+ message="app_schema_apply create mode requires package_id, app_name, and create_if_missing=true.",
328
+ fix_hint="Use `app_key` for existing apps, or pass `package_id + app_name + create_if_missing=true` to create a new app.",
329
+ )
330
+ return self.app_schema_apply(
331
+ profile=profile,
332
+ app_key=app_key,
333
+ package_id=package_id,
334
+ app_name=app_name,
335
+ app_title=app_title,
336
+ icon=icon,
337
+ color=color,
338
+ visibility=visibility,
339
+ create_if_missing=create_if_missing,
340
+ publish=publish,
341
+ add_fields=add_fields or [],
342
+ update_fields=update_fields or [],
343
+ remove_fields=remove_fields or [],
344
+ )
345
+
346
+ @mcp.tool()
347
+ def app_layout_apply(
348
+ profile: str = DEFAULT_PROFILE,
349
+ app_key: str = "",
350
+ mode: str = "merge",
351
+ publish: bool = True,
352
+ sections: list[JSONObject] | None = None,
353
+ ) -> JSONObject:
354
+ return self.app_layout_apply(profile=profile, app_key=app_key, mode=mode, publish=publish, sections=sections or [])
355
+
356
+ @mcp.tool()
357
+ def app_flow_apply(
358
+ profile: str = DEFAULT_PROFILE,
359
+ app_key: str = "",
360
+ mode: str = "replace",
361
+ publish: bool = True,
362
+ nodes: list[JSONObject] | None = None,
363
+ transitions: list[JSONObject] | None = None,
364
+ ) -> JSONObject:
365
+ return self.app_flow_apply(
366
+ profile=profile,
367
+ app_key=app_key,
368
+ mode=mode,
369
+ publish=publish,
370
+ nodes=nodes or [],
371
+ transitions=transitions or [],
372
+ )
373
+
374
+ @mcp.tool()
375
+ def app_views_apply(
376
+ profile: str = DEFAULT_PROFILE,
377
+ app_key: str = "",
378
+ publish: bool = True,
379
+ upsert_views: list[JSONObject] | None = None,
380
+ remove_views: list[str] | None = None,
381
+ ) -> JSONObject:
382
+ return self.app_views_apply(
383
+ profile=profile,
384
+ app_key=app_key,
385
+ publish=publish,
386
+ upsert_views=upsert_views or [],
387
+ remove_views=remove_views or [],
388
+ )
389
+
390
+ @mcp.tool()
391
+ def app_charts_apply(
392
+ profile: str = DEFAULT_PROFILE,
393
+ app_key: str = "",
394
+ upsert_charts: list[JSONObject] | None = None,
395
+ remove_chart_ids: list[str] | None = None,
396
+ reorder_chart_ids: list[str] | None = None,
397
+ ) -> JSONObject:
398
+ return self.app_charts_apply(
399
+ profile=profile,
400
+ app_key=app_key,
401
+ upsert_charts=upsert_charts or [],
402
+ remove_chart_ids=remove_chart_ids or [],
403
+ reorder_chart_ids=reorder_chart_ids or [],
404
+ )
405
+
406
+ @mcp.tool()
407
+ def portal_apply(
408
+ profile: str = DEFAULT_PROFILE,
409
+ dash_key: str = "",
410
+ dash_name: str = "",
411
+ package_id: int | None = None,
412
+ publish: bool = True,
413
+ sections: list[JSONObject] | None = None,
414
+ visibility: JSONObject | None = None,
415
+ auth: JSONObject | None = None,
416
+ icon: str | None = None,
417
+ color: str | None = None,
418
+ hide_copyright: bool | None = None,
419
+ dash_global_config: JSONObject | None = None,
420
+ config: JSONObject | None = None,
421
+ ) -> JSONObject:
422
+ has_dash_key = bool((dash_key or "").strip())
423
+ has_dash_name = bool((dash_name or "").strip())
424
+ has_package_id = package_id is not None
425
+ if has_dash_key and has_package_id:
426
+ return _config_failure(
427
+ tool_name="portal_apply",
428
+ message="portal_apply accepts exactly one selector mode.",
429
+ fix_hint="Use `dash_key` to update an existing portal, or use `package_id + dash_name` to create a new portal.",
430
+ )
431
+ if not has_dash_key and not (has_package_id and has_dash_name):
432
+ return _config_failure(
433
+ tool_name="portal_apply",
434
+ message="portal_apply requires either dash_key, or package_id together with dash_name.",
435
+ fix_hint="Use `dash_key` for an existing portal. For create mode, pass `package_id + dash_name`.",
436
+ )
437
+ return self.portal_apply(
438
+ profile=profile,
439
+ dash_key=dash_key,
440
+ dash_name=dash_name,
441
+ package_id=package_id,
442
+ publish=publish,
443
+ sections=sections or [],
444
+ visibility=visibility,
445
+ auth=auth,
446
+ icon=icon,
447
+ color=color,
448
+ hide_copyright=hide_copyright,
449
+ dash_global_config=dash_global_config,
450
+ config=config or {},
451
+ )
452
+
453
+ @mcp.tool()
454
+ def app_publish_verify(
455
+ profile: str = DEFAULT_PROFILE,
456
+ app_key: str = "",
457
+ expected_package_id: int | None = None,
458
+ ) -> JSONObject:
459
+ return self.app_publish_verify(
460
+ profile=profile,
461
+ app_key=app_key,
462
+ expected_package_id=expected_package_id,
463
+ )
464
+
465
+ @tool_cn_name("分组列表查询")
466
+ def package_list(self, *, profile: str, trial_status: str = "all") -> JSONObject:
467
+ """执行分组与包相关逻辑。"""
468
+ normalized_args = {"trial_status": trial_status}
469
+ return _safe_tool_call(
470
+ lambda: self._facade.package_list(profile=profile, trial_status=trial_status),
471
+ error_code="PACKAGE_LIST_FAILED",
472
+ normalized_args=normalized_args,
473
+ suggested_next_call={"tool_name": "package_list", "arguments": {"profile": profile, "trial_status": trial_status}},
474
+ )
475
+
476
+ @tool_cn_name("分组解析")
477
+ def package_resolve(self, *, profile: str, package_name: str) -> JSONObject:
478
+ """执行分组与包相关逻辑。"""
479
+ normalized_args = {"package_name": package_name}
480
+ return _safe_tool_call(
481
+ lambda: self._facade.package_resolve(profile=profile, package_name=package_name),
482
+ error_code="PACKAGE_RESOLVE_FAILED",
483
+ normalized_args=normalized_args,
484
+ suggested_next_call={"tool_name": "package_resolve", "arguments": {"profile": profile, "package_name": package_name}},
485
+ )
486
+
487
+ @tool_cn_name("搭建工具契约查询")
488
+ def builder_tool_contract(self, *, tool_name: str) -> JSONObject:
489
+ """执行工具方法逻辑。"""
490
+ requested = str(tool_name or "").strip()
491
+ public_tool_names = public_builder_contract_tool_names()
492
+ if requested in _PRIVATE_BUILDER_TOOL_CONTRACTS:
493
+ lookup_name = ""
494
+ else:
495
+ lookup_name = _BUILDER_TOOL_CONTRACT_ALIASES.get(requested, requested)
496
+ if lookup_name not in public_tool_names:
497
+ lookup_name = ""
498
+ contract = _BUILDER_TOOL_CONTRACTS.get(lookup_name)
499
+ if contract is None:
500
+ return {
501
+ "status": "failed",
502
+ "error_code": "TOOL_CONTRACT_NOT_FOUND",
503
+ "recoverable": True,
504
+ "message": "tool contract is not defined for the requested public builder tool",
505
+ "normalized_args": {"tool_name": requested},
506
+ "missing_fields": [],
507
+ "allowed_values": {"tool_name": public_tool_names},
508
+ "details": {"reason_path": "tool_name"},
509
+ "suggested_next_call": None,
510
+ "request_id": None,
511
+ "backend_code": None,
512
+ "http_status": None,
513
+ "noop": False,
514
+ "warnings": [],
515
+ "verification": {},
516
+ "verified": False,
517
+ }
518
+ return {
519
+ "status": "success",
520
+ "error_code": None,
521
+ "recoverable": False,
522
+ "message": "loaded builder tool contract",
523
+ "normalized_args": {"tool_name": requested},
524
+ "missing_fields": [],
525
+ "allowed_values": {},
526
+ "details": {},
527
+ "suggested_next_call": None,
528
+ "request_id": None,
529
+ "backend_code": None,
530
+ "http_status": None,
531
+ "noop": False,
532
+ "warnings": [],
533
+ "verification": {},
534
+ "verified": True,
535
+ "tool_name": requested,
536
+ "contract": contract,
537
+ }
538
+
539
+ @tool_cn_name("分组创建")
540
+ def package_create(
541
+ self,
542
+ *,
543
+ profile: str,
544
+ package_name: str,
545
+ icon: str | None = None,
546
+ color: str | None = None,
547
+ visibility: JSONObject | None = None,
548
+ ) -> JSONObject:
549
+ """执行分组与包相关逻辑。"""
550
+ visibility_patch = None
551
+ if visibility is not None:
552
+ try:
553
+ visibility_patch = VisibilityPatch.model_validate(visibility)
554
+ except ValidationError as exc:
555
+ return _visibility_validation_failure(str(exc), tool_name="package_create", exc=exc)
556
+ normalized_args = {
557
+ "package_name": package_name,
558
+ **({"icon": icon} if icon else {}),
559
+ **({"color": color} if color else {}),
560
+ **({"visibility": visibility_patch.model_dump(mode="json")} if visibility_patch is not None else {}),
561
+ }
562
+ return _safe_tool_call(
563
+ lambda: self._facade.package_create(
564
+ profile=profile,
565
+ package_name=package_name,
566
+ icon=icon,
567
+ color=color,
568
+ visibility=visibility_patch,
569
+ ),
570
+ error_code="PACKAGE_CREATE_FAILED",
571
+ normalized_args=normalized_args,
572
+ suggested_next_call={
573
+ "tool_name": "package_create",
574
+ "arguments": {
575
+ "profile": profile,
576
+ "package_name": package_name,
577
+ **({"icon": icon} if icon else {}),
578
+ **({"color": color} if color else {}),
579
+ **({"visibility": visibility_patch.model_dump(mode="json")} if visibility_patch is not None else {}),
580
+ },
581
+ },
582
+ )
583
+
584
+ @tool_cn_name("分组详情查询")
585
+ def package_get(self, *, profile: str, package_id: int | None = None) -> JSONObject:
586
+ """执行分组与包相关逻辑。"""
587
+ normalized_args = {"package_id": package_id}
588
+ return _publicize_package_fields(_safe_tool_call(
589
+ lambda: self._facade.package_get(profile=profile, package_id=package_id),
590
+ error_code="PACKAGE_GET_FAILED",
591
+ normalized_args=normalized_args,
592
+ suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
593
+ ))
594
+
595
+ @tool_cn_name("分组配置应用")
596
+ def package_apply(
597
+ self,
598
+ *,
599
+ profile: str,
600
+ package_id: int | None = None,
601
+ package_name: str | None = None,
602
+ create_if_missing: bool = False,
603
+ icon: str | None = None,
604
+ color: str | None = None,
605
+ visibility: JSONObject | None = None,
606
+ items: list[dict] | None = None,
607
+ allow_detach: bool = False,
608
+ ) -> JSONObject:
609
+ """执行分组与包相关逻辑。"""
610
+ visibility_patch = None
611
+ if visibility is not None:
612
+ try:
613
+ visibility_patch = VisibilityPatch.model_validate(visibility)
614
+ except ValidationError as exc:
615
+ return _visibility_validation_failure(str(exc), tool_name="package_apply", exc=exc)
616
+ normalized_args = {
617
+ "package_id": package_id,
618
+ **({"package_name": package_name} if str(package_name or "").strip() else {}),
619
+ "create_if_missing": bool(create_if_missing),
620
+ **({"icon": icon} if icon else {}),
621
+ **({"color": color} if color else {}),
622
+ **({"visibility": visibility_patch.model_dump(mode="json")} if visibility_patch is not None else {}),
623
+ **({"items": deepcopy(items)} if items is not None else {}),
624
+ "allow_detach": bool(allow_detach),
625
+ }
626
+ return _publicize_package_fields(_safe_tool_call(
627
+ lambda: self._facade.package_apply(
628
+ profile=profile,
629
+ package_id=package_id,
630
+ package_name=package_name,
631
+ create_if_missing=create_if_missing,
632
+ icon=icon,
633
+ color=color,
634
+ visibility=visibility_patch,
635
+ items=items,
636
+ allow_detach=allow_detach,
637
+ ),
638
+ error_code="PACKAGE_APPLY_FAILED",
639
+ normalized_args=normalized_args,
640
+ suggested_next_call={"tool_name": "package_apply", "arguments": {"profile": profile, **normalized_args}},
641
+ ))
642
+
643
+ @tool_cn_name("分组更新")
644
+ def package_update(
645
+ self,
646
+ *,
647
+ profile: str,
648
+ tag_id: int,
649
+ package_name: str | None = None,
650
+ icon: str | None = None,
651
+ color: str | None = None,
652
+ visibility: JSONObject | None = None,
653
+ ) -> JSONObject:
654
+ """执行分组与包相关逻辑。"""
655
+ visibility_patch = None
656
+ if visibility is not None:
657
+ try:
658
+ visibility_patch = VisibilityPatch.model_validate(visibility)
659
+ except ValidationError as exc:
660
+ return _visibility_validation_failure(str(exc), tool_name="package_update", exc=exc)
661
+ normalized_args = {
662
+ "tag_id": tag_id,
663
+ **({"package_name": package_name} if str(package_name or "").strip() else {}),
664
+ **({"icon": icon} if icon else {}),
665
+ **({"color": color} if color else {}),
666
+ **({"visibility": visibility_patch.model_dump(mode="json")} if visibility_patch is not None else {}),
667
+ }
668
+ return _safe_tool_call(
669
+ lambda: self._facade.package_update(
670
+ profile=profile,
671
+ tag_id=tag_id,
672
+ package_name=package_name,
673
+ icon=icon,
674
+ color=color,
675
+ visibility=visibility_patch,
676
+ ),
677
+ error_code="PACKAGE_UPDATE_FAILED",
678
+ normalized_args=normalized_args,
679
+ suggested_next_call={"tool_name": "package_update", "arguments": {"profile": profile, **normalized_args}},
680
+ )
681
+
682
+ @tool_cn_name("方案安装")
683
+ def solution_install(
684
+ self,
685
+ *,
686
+ profile: str,
687
+ solution_key: str,
688
+ being_copy_data: bool = True,
689
+ solution_source: str = "solutionDetail",
690
+ ) -> JSONObject:
691
+ """执行方案相关逻辑。"""
692
+ normalized_args = {
693
+ "solution_key": solution_key,
694
+ "being_copy_data": being_copy_data,
695
+ "solution_source": solution_source,
696
+ }
697
+ return _safe_tool_call(
698
+ lambda: self._facade.solution_install(
699
+ profile=profile,
700
+ solution_key=solution_key,
701
+ being_copy_data=being_copy_data,
702
+ solution_source=solution_source,
703
+ ),
704
+ error_code="SOLUTION_INSTALL_FAILED",
705
+ normalized_args=normalized_args,
706
+ suggested_next_call={"tool_name": "solution_install", "arguments": {"profile": profile, **normalized_args}},
707
+ )
708
+
709
+ @tool_cn_name("成员检索")
710
+ def member_search(
711
+ self,
712
+ *,
713
+ profile: str,
714
+ query: str,
715
+ page_num: int = 1,
716
+ page_size: int = 20,
717
+ contain_disable: bool = False,
718
+ ) -> JSONObject:
719
+ """执行工具方法逻辑。"""
720
+ normalized_args = {
721
+ "query": query,
722
+ "page_num": page_num,
723
+ "page_size": page_size,
724
+ "contain_disable": contain_disable,
725
+ }
726
+ return _safe_tool_call(
727
+ lambda: self._facade.member_search(
728
+ profile=profile,
729
+ query=query,
730
+ page_num=page_num,
731
+ page_size=page_size,
732
+ contain_disable=contain_disable,
733
+ ),
734
+ error_code="MEMBER_SEARCH_FAILED",
735
+ normalized_args=normalized_args,
736
+ suggested_next_call={"tool_name": "member_search", "arguments": {"profile": profile, **normalized_args}},
737
+ )
738
+
739
+ @tool_cn_name("角色检索")
740
+ def role_search(self, *, profile: str, keyword: str, page_num: int = 1, page_size: int = 20) -> JSONObject:
741
+ """执行角色相关逻辑。"""
742
+ normalized_args = {"keyword": keyword, "page_num": page_num, "page_size": page_size}
743
+ return _safe_tool_call(
744
+ lambda: self._facade.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size),
745
+ error_code="ROLE_SEARCH_FAILED",
746
+ normalized_args=normalized_args,
747
+ suggested_next_call={"tool_name": "role_search", "arguments": {"profile": profile, **normalized_args}},
748
+ )
749
+
750
+ @tool_cn_name("角色创建")
751
+ def role_create(
752
+ self,
753
+ *,
754
+ profile: str,
755
+ role_name: str,
756
+ member_uids: list[int],
757
+ member_emails: list[str],
758
+ member_names: list[str],
759
+ role_icon: str = "ex-user-outlined",
760
+ ) -> JSONObject:
761
+ """执行角色相关逻辑。"""
762
+ normalized_args = {
763
+ "role_name": role_name,
764
+ "member_uids": member_uids,
765
+ "member_emails": member_emails,
766
+ "member_names": member_names,
767
+ "role_icon": role_icon,
768
+ }
769
+ return _safe_tool_call(
770
+ lambda: self._facade.role_create(
771
+ profile=profile,
772
+ role_name=role_name,
773
+ member_uids=member_uids,
774
+ member_emails=member_emails,
775
+ member_names=member_names,
776
+ role_icon=role_icon,
777
+ ),
778
+ error_code="ROLE_CREATE_FAILED",
779
+ normalized_args=normalized_args,
780
+ suggested_next_call={"tool_name": "role_create", "arguments": {"profile": profile, **normalized_args}},
781
+ )
782
+
783
+ @tool_cn_name("分组挂载应用")
784
+ def package_attach_app(self, *, profile: str, tag_id: int, app_key: str, app_title: str = "") -> JSONObject:
785
+ """执行分组与包相关逻辑。"""
786
+ normalized_args = {"tag_id": tag_id, "app_key": app_key, "app_title": app_title}
787
+ result = _safe_tool_call(
788
+ lambda: self._facade.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key, app_title=app_title),
789
+ error_code="PACKAGE_ATTACH_FAILED",
790
+ normalized_args=normalized_args,
791
+ suggested_next_call={"tool_name": "package_attach_app", "arguments": {"profile": profile, **normalized_args}},
792
+ )
793
+ return self._retry_after_self_lock_release(
794
+ profile=profile,
795
+ result=result,
796
+ retry_call=lambda: self._facade.package_attach_app(
797
+ profile=profile,
798
+ tag_id=tag_id,
799
+ app_key=app_key,
800
+ app_title=app_title,
801
+ ),
802
+ )
803
+
804
+ @tool_cn_name("释放应用编辑锁")
805
+ def app_release_edit_lock_if_mine(
806
+ self,
807
+ *,
808
+ profile: str,
809
+ app_key: str,
810
+ lock_owner_email: str = "",
811
+ lock_owner_name: str = "",
812
+ ) -> JSONObject:
813
+ """执行应用相关逻辑。"""
814
+ normalized_args = {
815
+ "app_key": app_key,
816
+ "lock_owner_email": lock_owner_email,
817
+ "lock_owner_name": lock_owner_name,
818
+ }
819
+ return _safe_tool_call(
820
+ lambda: self._facade.app_release_edit_lock_if_mine(
821
+ profile=profile,
822
+ app_key=app_key,
823
+ lock_owner_email=lock_owner_email,
824
+ lock_owner_name=lock_owner_name,
825
+ ),
826
+ error_code="EDIT_LOCK_RELEASE_FAILED",
827
+ normalized_args=normalized_args,
828
+ suggested_next_call={"tool_name": "app_release_edit_lock_if_mine", "arguments": {"profile": profile, **normalized_args}},
829
+ )
830
+
831
+ @tool_cn_name("应用解析")
832
+ def app_resolve(
833
+ self,
834
+ *,
835
+ profile: str,
836
+ app_key: str = "",
837
+ app_name: str = "",
838
+ package_id: int | None = None,
839
+ ) -> JSONObject:
840
+ """执行应用相关逻辑。"""
841
+ normalized_args = {"app_key": app_key, "app_name": app_name, "package_id": package_id}
842
+ return _publicize_package_fields(_safe_tool_call(
843
+ lambda: self._facade.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_tag_id=package_id),
844
+ error_code="APP_RESOLVE_FAILED",
845
+ normalized_args=normalized_args,
846
+ suggested_next_call={"tool_name": "app_resolve", "arguments": {"profile": profile, **normalized_args}},
847
+ ))
848
+
849
+ @tool_cn_name("应用按钮列表")
850
+ def app_custom_button_list(self, *, profile: str, app_key: str) -> JSONObject:
851
+ """执行应用相关逻辑。"""
852
+ normalized_args = {"app_key": app_key}
853
+ return _safe_tool_call(
854
+ lambda: self._facade.app_custom_button_list(profile=profile, app_key=app_key),
855
+ error_code="CUSTOM_BUTTON_LIST_FAILED",
856
+ normalized_args=normalized_args,
857
+ suggested_next_call={"tool_name": "app_custom_button_list", "arguments": {"profile": profile, **normalized_args}},
858
+ )
859
+
860
+ @tool_cn_name("应用按钮详情")
861
+ def app_custom_button_get(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
862
+ """执行应用相关逻辑。"""
863
+ normalized_args = {"app_key": app_key, "button_id": button_id}
864
+ return _safe_tool_call(
865
+ lambda: self._facade.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id),
866
+ error_code="CUSTOM_BUTTON_GET_FAILED",
867
+ normalized_args=normalized_args,
868
+ suggested_next_call={"tool_name": "app_custom_button_get", "arguments": {"profile": profile, **normalized_args}},
869
+ )
870
+
871
+ @tool_cn_name("应用按钮创建")
872
+ def app_custom_button_create(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
873
+ """执行应用相关逻辑。"""
874
+ try:
875
+ request = CustomButtonPatch.model_validate(payload)
876
+ except ValidationError as exc:
877
+ return _validation_failure(
878
+ str(exc),
879
+ tool_name="app_custom_button_create",
880
+ exc=exc,
881
+ suggested_next_call={
882
+ "tool_name": "app_custom_button_create",
883
+ "arguments": {
884
+ "profile": profile,
885
+ "app_key": app_key,
886
+ "payload": {
887
+ "button_text": "新增记录",
888
+ "background_color": "#FFFFFF",
889
+ "text_color": "#494F57",
890
+ "button_icon": "ex-add-outlined",
891
+ "trigger_action": "addData",
892
+ "trigger_add_data_config": {"related_app_key": "TARGET_APP_KEY", "que_relation": []},
893
+ },
894
+ },
895
+ },
896
+ )
897
+ normalized_args = {"app_key": app_key, "payload": request.model_dump(mode="json")}
898
+ return _safe_tool_call(
899
+ lambda: self._facade.app_custom_button_create(profile=profile, app_key=app_key, payload=request),
900
+ error_code="CUSTOM_BUTTON_CREATE_FAILED",
901
+ normalized_args=normalized_args,
902
+ suggested_next_call={"tool_name": "app_custom_button_create", "arguments": {"profile": profile, **normalized_args}},
903
+ )
904
+
905
+ @tool_cn_name("应用按钮更新")
906
+ def app_custom_button_update(self, *, profile: str, app_key: str, button_id: int, payload: JSONObject) -> JSONObject:
907
+ """执行应用相关逻辑。"""
908
+ try:
909
+ request = CustomButtonPatch.model_validate(payload)
910
+ except ValidationError as exc:
911
+ return _validation_failure(
912
+ str(exc),
913
+ tool_name="app_custom_button_update",
914
+ exc=exc,
915
+ suggested_next_call={
916
+ "tool_name": "app_custom_button_update",
917
+ "arguments": {
918
+ "profile": profile,
919
+ "app_key": app_key,
920
+ "button_id": button_id,
921
+ "payload": {
922
+ "button_text": "新增记录",
923
+ "background_color": "#FFFFFF",
924
+ "text_color": "#494F57",
925
+ "button_icon": "ex-add-outlined",
926
+ "trigger_action": "link",
927
+ "trigger_link_url": "https://example.com",
928
+ },
929
+ },
930
+ },
931
+ )
932
+ normalized_args = {"app_key": app_key, "button_id": button_id, "payload": request.model_dump(mode="json")}
933
+ return _safe_tool_call(
934
+ lambda: self._facade.app_custom_button_update(profile=profile, app_key=app_key, button_id=button_id, payload=request),
935
+ error_code="CUSTOM_BUTTON_UPDATE_FAILED",
936
+ normalized_args=normalized_args,
937
+ suggested_next_call={"tool_name": "app_custom_button_update", "arguments": {"profile": profile, **normalized_args}},
938
+ )
939
+
940
+ @tool_cn_name("应用按钮删除")
941
+ def app_custom_button_delete(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
942
+ """执行应用相关逻辑。"""
943
+ normalized_args = {"app_key": app_key, "button_id": button_id}
944
+ return _safe_tool_call(
945
+ lambda: self._facade.app_custom_button_delete(profile=profile, app_key=app_key, button_id=button_id),
946
+ error_code="CUSTOM_BUTTON_DELETE_FAILED",
947
+ normalized_args=normalized_args,
948
+ suggested_next_call={"tool_name": "app_custom_button_delete", "arguments": {"profile": profile, **normalized_args}},
949
+ )
950
+
951
+ @tool_cn_name("应用摘要读取")
952
+ def app_read_summary(self, *, profile: str, app_key: str) -> JSONObject:
953
+ """执行应用相关逻辑。"""
954
+ normalized_args = {"app_key": app_key}
955
+ return _publicize_package_fields(_safe_tool_call(
956
+ lambda: self._facade.app_read_summary(profile=profile, app_key=app_key),
957
+ error_code="APP_READ_FAILED",
958
+ normalized_args=normalized_args,
959
+ suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
960
+ ))
961
+
962
+ @tool_cn_name("应用详情查询")
963
+ def app_get(self, *, profile: str, app_key: str) -> JSONObject:
964
+ """执行应用相关逻辑。"""
965
+ normalized_args = {"app_key": app_key}
966
+ return _publicize_package_fields(_safe_tool_call(
967
+ lambda: self._facade.app_get(profile=profile, app_key=app_key),
968
+ error_code="APP_GET_FAILED",
969
+ normalized_args=normalized_args,
970
+ suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
971
+ ))
972
+
973
+ @tool_cn_name("应用字段摘要读取")
974
+ def app_read_fields(self, *, profile: str, app_key: str) -> JSONObject:
975
+ """执行应用相关逻辑。"""
976
+ normalized_args = {"app_key": app_key}
977
+ return _safe_tool_call(
978
+ lambda: self._facade.app_read_fields(profile=profile, app_key=app_key),
979
+ error_code="FIELDS_READ_FAILED",
980
+ normalized_args=normalized_args,
981
+ suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
982
+ )
983
+
984
+ @tool_cn_name("应用字段详情查询")
985
+ def app_get_fields(self, *, profile: str, app_key: str) -> JSONObject:
986
+ """执行应用相关逻辑。"""
987
+ normalized_args = {"app_key": app_key}
988
+ return _safe_tool_call(
989
+ lambda: self._facade.app_get_fields(profile=profile, app_key=app_key),
990
+ error_code="APP_GET_FIELDS_FAILED",
991
+ normalized_args=normalized_args,
992
+ suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
993
+ )
994
+
995
+ @tool_cn_name("修复代码块字段")
996
+ def app_repair_code_blocks(
997
+ self,
998
+ *,
999
+ profile: str,
1000
+ app_key: str,
1001
+ field: str | None = None,
1002
+ apply: bool = False,
1003
+ ) -> JSONObject:
1004
+ """执行应用相关逻辑。"""
1005
+ normalized_args = {"app_key": app_key, "field": field, "apply": apply}
1006
+ return _safe_tool_call(
1007
+ lambda: self._facade.app_repair_code_blocks(profile=profile, app_key=app_key, field=field, apply=apply),
1008
+ error_code="APP_REPAIR_CODE_BLOCKS_FAILED",
1009
+ normalized_args=normalized_args,
1010
+ suggested_next_call={"tool_name": "app_repair_code_blocks", "arguments": {"profile": profile, **normalized_args}},
1011
+ )
1012
+
1013
+ @tool_cn_name("应用布局摘要读取")
1014
+ def app_read_layout_summary(self, *, profile: str, app_key: str) -> JSONObject:
1015
+ """执行应用相关逻辑。"""
1016
+ normalized_args = {"app_key": app_key}
1017
+ return _safe_tool_call(
1018
+ lambda: self._facade.app_read_layout_summary(profile=profile, app_key=app_key),
1019
+ error_code="LAYOUT_READ_FAILED",
1020
+ normalized_args=normalized_args,
1021
+ suggested_next_call={"tool_name": "app_get_layout", "arguments": {"profile": profile, "app_key": app_key}},
1022
+ )
1023
+
1024
+ @tool_cn_name("应用布局详情查询")
1025
+ def app_get_layout(self, *, profile: str, app_key: str) -> JSONObject:
1026
+ """执行应用相关逻辑。"""
1027
+ normalized_args = {"app_key": app_key}
1028
+ return _safe_tool_call(
1029
+ lambda: self._facade.app_get_layout(profile=profile, app_key=app_key),
1030
+ error_code="APP_GET_LAYOUT_FAILED",
1031
+ normalized_args=normalized_args,
1032
+ suggested_next_call={"tool_name": "app_get_layout", "arguments": {"profile": profile, "app_key": app_key}},
1033
+ )
1034
+
1035
+ @tool_cn_name("应用视图摘要读取")
1036
+ def app_read_views_summary(self, *, profile: str, app_key: str) -> JSONObject:
1037
+ """执行应用相关逻辑。"""
1038
+ normalized_args = {"app_key": app_key}
1039
+ return _safe_tool_call(
1040
+ lambda: self._facade.app_read_views_summary(profile=profile, app_key=app_key),
1041
+ error_code="VIEWS_READ_FAILED",
1042
+ normalized_args=normalized_args,
1043
+ suggested_next_call={"tool_name": "app_get_views", "arguments": {"profile": profile, "app_key": app_key}},
1044
+ )
1045
+
1046
+ @tool_cn_name("应用视图详情查询")
1047
+ def app_get_views(self, *, profile: str, app_key: str) -> JSONObject:
1048
+ """执行应用相关逻辑。"""
1049
+ normalized_args = {"app_key": app_key}
1050
+ return _safe_tool_call(
1051
+ lambda: self._facade.app_get_views(profile=profile, app_key=app_key),
1052
+ error_code="APP_GET_VIEWS_FAILED",
1053
+ normalized_args=normalized_args,
1054
+ suggested_next_call={"tool_name": "app_get_views", "arguments": {"profile": profile, "app_key": app_key}},
1055
+ )
1056
+
1057
+ @tool_cn_name("应用流程摘要读取")
1058
+ def app_read_flow_summary(self, *, profile: str, app_key: str) -> JSONObject:
1059
+ """执行应用相关逻辑。"""
1060
+ normalized_args = {"app_key": app_key}
1061
+ return _safe_tool_call(
1062
+ lambda: self._facade.app_read_flow_summary(profile=profile, app_key=app_key),
1063
+ error_code="FLOW_READ_FAILED",
1064
+ normalized_args=normalized_args,
1065
+ suggested_next_call={"tool_name": "app_get_flow", "arguments": {"profile": profile, "app_key": app_key}},
1066
+ )
1067
+
1068
+ @tool_cn_name("应用流程详情查询")
1069
+ def app_get_flow(self, *, profile: str, app_key: str) -> JSONObject:
1070
+ """执行应用相关逻辑。"""
1071
+ normalized_args = {"app_key": app_key}
1072
+ return _safe_tool_call(
1073
+ lambda: self._facade.app_get_flow(profile=profile, app_key=app_key),
1074
+ error_code="APP_GET_FLOW_FAILED",
1075
+ normalized_args=normalized_args,
1076
+ suggested_next_call={"tool_name": "app_get_flow", "arguments": {"profile": profile, "app_key": app_key}},
1077
+ )
1078
+
1079
+ @tool_cn_name("应用图表摘要读取")
1080
+ def app_read_charts_summary(self, *, profile: str, app_key: str) -> JSONObject:
1081
+ """执行应用相关逻辑。"""
1082
+ normalized_args = {"app_key": app_key}
1083
+ return _safe_tool_call(
1084
+ lambda: self._facade.app_read_charts_summary(profile=profile, app_key=app_key),
1085
+ error_code="CHARTS_READ_FAILED",
1086
+ normalized_args=normalized_args,
1087
+ suggested_next_call={"tool_name": "app_get_charts", "arguments": {"profile": profile, "app_key": app_key}},
1088
+ )
1089
+
1090
+ @tool_cn_name("应用图表详情查询")
1091
+ def app_get_charts(self, *, profile: str, app_key: str) -> JSONObject:
1092
+ """执行应用相关逻辑。"""
1093
+ normalized_args = {"app_key": app_key}
1094
+ return _safe_tool_call(
1095
+ lambda: self._facade.app_get_charts(profile=profile, app_key=app_key),
1096
+ error_code="APP_GET_CHARTS_FAILED",
1097
+ normalized_args=normalized_args,
1098
+ suggested_next_call={"tool_name": "app_get_charts", "arguments": {"profile": profile, "app_key": app_key}},
1099
+ )
1100
+
1101
+ @tool_cn_name("门户列表查询")
1102
+ def portal_list(self, *, profile: str) -> JSONObject:
1103
+ """执行门户相关逻辑。"""
1104
+ return _publicize_package_fields(_safe_tool_call(
1105
+ lambda: self._facade.portal_list(profile=profile),
1106
+ error_code="PORTAL_LIST_FAILED",
1107
+ normalized_args={},
1108
+ suggested_next_call={"tool_name": "portal_list", "arguments": {"profile": profile}},
1109
+ ))
1110
+
1111
+ @tool_cn_name("门户详情查询")
1112
+ def portal_get(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
1113
+ """执行门户相关逻辑。"""
1114
+ normalized_args = {"dash_key": dash_key, "being_draft": being_draft}
1115
+ return _publicize_package_fields(_safe_tool_call(
1116
+ lambda: self._facade.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft),
1117
+ error_code="PORTAL_GET_FAILED",
1118
+ normalized_args=normalized_args,
1119
+ suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, **normalized_args}},
1120
+ ))
1121
+
1122
+ @tool_cn_name("门户摘要读取")
1123
+ def portal_read_summary(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
1124
+ """执行门户相关逻辑。"""
1125
+ normalized_args = {"dash_key": dash_key, "being_draft": being_draft}
1126
+ return _publicize_package_fields(_safe_tool_call(
1127
+ lambda: self._facade.portal_read_summary(profile=profile, dash_key=dash_key, being_draft=being_draft),
1128
+ error_code="PORTAL_READ_FAILED",
1129
+ normalized_args=normalized_args,
1130
+ suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, **normalized_args}},
1131
+ ))
1132
+
1133
+ @tool_cn_name("视图详情查询")
1134
+ def view_get(self, *, profile: str, view_key: str = "", viewgraph_key: str = "") -> JSONObject:
1135
+ """执行视图相关逻辑。"""
1136
+ resolved_view_key = str(view_key or viewgraph_key or "").strip()
1137
+ normalized_args = {"view_key": resolved_view_key}
1138
+ return _safe_tool_call(
1139
+ lambda: self._facade.view_get(profile=profile, view_key=resolved_view_key),
1140
+ error_code="VIEW_GET_FAILED",
1141
+ normalized_args=normalized_args,
1142
+ suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, **normalized_args}},
1143
+ )
1144
+
1145
+ @tool_cn_name("图表详情查询")
1146
+ def chart_get(
1147
+ self,
1148
+ *,
1149
+ profile: str,
1150
+ chart_id: str,
1151
+ ) -> JSONObject:
1152
+ """执行图表相关逻辑。"""
1153
+ normalized_args = {"chart_id": chart_id}
1154
+ return _safe_tool_call(
1155
+ lambda: self._facade.chart_get(profile=profile, chart_id=chart_id),
1156
+ error_code="CHART_GET_FAILED",
1157
+ normalized_args=normalized_args,
1158
+ suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": chart_id}},
1159
+ )
1160
+
1161
+ @tool_cn_name("应用结构规划")
1162
+ def app_schema_plan(
1163
+ self,
1164
+ *,
1165
+ profile: str,
1166
+ app_key: str = "",
1167
+ package_id: int | None = None,
1168
+ app_name: str = "",
1169
+ icon: str = "",
1170
+ color: str = "",
1171
+ visibility: JSONObject | None = None,
1172
+ create_if_missing: bool = False,
1173
+ add_fields: list[JSONObject],
1174
+ update_fields: list[JSONObject],
1175
+ remove_fields: list[JSONObject],
1176
+ ) -> JSONObject:
1177
+ """执行应用相关逻辑。"""
1178
+ try:
1179
+ request = SchemaPlanRequest.model_validate(
1180
+ {
1181
+ "app_key": app_key,
1182
+ "package_tag_id": package_id,
1183
+ "app_name": app_name,
1184
+ "icon": icon,
1185
+ "color": color,
1186
+ "visibility": visibility,
1187
+ "create_if_missing": create_if_missing,
1188
+ "add_fields": add_fields,
1189
+ "update_fields": update_fields,
1190
+ "remove_fields": remove_fields,
1191
+ }
1192
+ )
1193
+ except ValidationError as exc:
1194
+ return _visibility_validation_failure(
1195
+ str(exc),
1196
+ tool_name="app_schema_plan",
1197
+ exc=exc,
1198
+ suggested_next_call={
1199
+ "tool_name": "app_schema_plan",
1200
+ "arguments": {
1201
+ "profile": profile,
1202
+ "app_key": app_key,
1203
+ "package_id": package_id,
1204
+ "app_name": app_name,
1205
+ "icon": icon,
1206
+ "color": color,
1207
+ "visibility": visibility,
1208
+ "create_if_missing": create_if_missing,
1209
+ "add_fields": [{"name": "字段名称", "type": "text"}],
1210
+ "update_fields": [],
1211
+ "remove_fields": [],
1212
+ },
1213
+ },
1214
+ )
1215
+ normalized_args = _publicize_package_fields(request.model_dump(mode="json"))
1216
+ return _publicize_package_fields(_safe_tool_call(
1217
+ lambda: self._facade.app_schema_plan(profile=profile, request=request),
1218
+ error_code="SCHEMA_PLAN_FAILED",
1219
+ normalized_args=normalized_args,
1220
+ suggested_next_call={"tool_name": "app_schema_plan", "arguments": {"profile": profile, **normalized_args}},
1221
+ ))
1222
+
1223
+ @tool_cn_name("应用布局规划")
1224
+ def app_layout_plan(
1225
+ self,
1226
+ *,
1227
+ profile: str,
1228
+ app_key: str,
1229
+ mode: str = "merge",
1230
+ sections: list[JSONObject] | None = None,
1231
+ preset: str | None = None,
1232
+ ) -> JSONObject:
1233
+ """执行应用相关逻辑。"""
1234
+ try:
1235
+ request = LayoutPlanRequest.model_validate(
1236
+ {
1237
+ "app_key": app_key,
1238
+ "mode": mode,
1239
+ "sections": sections or [],
1240
+ "preset": preset,
1241
+ }
1242
+ )
1243
+ except ValidationError as exc:
1244
+ return _validation_failure(
1245
+ str(exc),
1246
+ tool_name="app_layout_plan",
1247
+ exc=exc,
1248
+ suggested_next_call={
1249
+ "tool_name": "app_layout_plan",
1250
+ "arguments": {
1251
+ "profile": profile,
1252
+ "app_key": app_key,
1253
+ "mode": "merge",
1254
+ "sections": [{
1255
+ "type": "paragraph",
1256
+ "paragraph_id": "basic",
1257
+ "title": "基础信息",
1258
+ "rows": [["字段A", "字段B", "字段C", "字段D"]],
1259
+ }],
1260
+ },
1261
+ },
1262
+ )
1263
+ normalized_request = request.model_dump(mode="json", exclude_none=True)
1264
+ return _safe_tool_call(
1265
+ lambda: self._facade.app_layout_plan(profile=profile, request=request),
1266
+ error_code="LAYOUT_PLAN_FAILED",
1267
+ normalized_args=normalized_request,
1268
+ suggested_next_call={"tool_name": "app_layout_plan", "arguments": {"profile": profile, **normalized_request}},
1269
+ )
1270
+
1271
+ @tool_cn_name("应用流程规划")
1272
+ def app_flow_plan(
1273
+ self,
1274
+ *,
1275
+ profile: str,
1276
+ app_key: str,
1277
+ mode: str = "replace",
1278
+ nodes: list[JSONObject] | None = None,
1279
+ transitions: list[JSONObject] | None = None,
1280
+ preset: str | None = None,
1281
+ ) -> JSONObject:
1282
+ """执行应用相关逻辑。"""
1283
+ try:
1284
+ request = FlowPlanRequest.model_validate(
1285
+ {
1286
+ "app_key": app_key,
1287
+ "mode": mode,
1288
+ "nodes": nodes or [],
1289
+ "transitions": transitions or [],
1290
+ "preset": preset,
1291
+ }
1292
+ )
1293
+ except ValidationError as exc:
1294
+ return _validation_failure(
1295
+ str(exc),
1296
+ tool_name="app_flow_plan",
1297
+ exc=exc,
1298
+ suggested_next_call={
1299
+ "tool_name": "app_flow_plan",
1300
+ "arguments": {
1301
+ "profile": profile,
1302
+ "app_key": app_key,
1303
+ "mode": "replace",
1304
+ "preset": "basic_approval",
1305
+ "nodes": [],
1306
+ "transitions": [],
1307
+ },
1308
+ },
1309
+ )
1310
+ return _safe_tool_call(
1311
+ lambda: self._facade.app_flow_plan(profile=profile, request=request),
1312
+ error_code="FLOW_PLAN_FAILED",
1313
+ normalized_args=request.model_dump(mode="json"),
1314
+ suggested_next_call={"tool_name": "app_flow_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
1315
+ )
1316
+
1317
+ @tool_cn_name("应用视图规划")
1318
+ def app_views_plan(
1319
+ self,
1320
+ *,
1321
+ profile: str,
1322
+ app_key: str,
1323
+ upsert_views: list[JSONObject] | None = None,
1324
+ remove_views: list[str] | None = None,
1325
+ preset: str | None = None,
1326
+ ) -> JSONObject:
1327
+ """执行应用相关逻辑。"""
1328
+ try:
1329
+ request = ViewsPlanRequest.model_validate(
1330
+ {
1331
+ "app_key": app_key,
1332
+ "upsert_views": upsert_views or [],
1333
+ "remove_views": remove_views or [],
1334
+ "preset": preset,
1335
+ }
1336
+ )
1337
+ except ValidationError as exc:
1338
+ return _visibility_validation_failure(
1339
+ str(exc),
1340
+ tool_name="app_views_plan",
1341
+ exc=exc,
1342
+ suggested_next_call={
1343
+ "tool_name": "app_views_plan",
1344
+ "arguments": {
1345
+ "profile": profile,
1346
+ "app_key": app_key,
1347
+ "preset": "default_table",
1348
+ "upsert_views": [],
1349
+ "remove_views": [],
1350
+ },
1351
+ },
1352
+ )
1353
+ return _safe_tool_call(
1354
+ lambda: self._facade.app_views_plan(profile=profile, request=request),
1355
+ error_code="VIEWS_PLAN_FAILED",
1356
+ normalized_args=request.model_dump(mode="json"),
1357
+ suggested_next_call={"tool_name": "app_views_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
1358
+ )
1359
+
1360
+ @tool_cn_name("应用结构应用")
1361
+ def app_schema_apply(
1362
+ self,
1363
+ *,
1364
+ profile: str,
1365
+ app_key: str = "",
1366
+ package_id: int | None = None,
1367
+ app_name: str = "",
1368
+ app_title: str = "",
1369
+ icon: str = "",
1370
+ color: str = "",
1371
+ visibility: JSONObject | None = None,
1372
+ create_if_missing: bool = False,
1373
+ publish: bool = True,
1374
+ add_fields: list[JSONObject],
1375
+ update_fields: list[JSONObject],
1376
+ remove_fields: list[JSONObject],
1377
+ ) -> JSONObject:
1378
+ """执行应用相关逻辑。"""
1379
+ result = self._app_schema_apply_once(
1380
+ profile=profile,
1381
+ app_key=app_key,
1382
+ package_id=package_id,
1383
+ app_name=app_name,
1384
+ app_title=app_title,
1385
+ icon=icon,
1386
+ color=color,
1387
+ visibility=visibility,
1388
+ create_if_missing=create_if_missing,
1389
+ publish=publish,
1390
+ add_fields=add_fields,
1391
+ update_fields=update_fields,
1392
+ remove_fields=remove_fields,
1393
+ )
1394
+ return self._retry_after_self_lock_release(
1395
+ profile=profile,
1396
+ result=result,
1397
+ retry_call=lambda: self._app_schema_apply_once(
1398
+ profile=profile,
1399
+ app_key=app_key,
1400
+ package_id=package_id,
1401
+ app_name=app_name,
1402
+ app_title=app_title,
1403
+ icon=icon,
1404
+ color=color,
1405
+ visibility=visibility,
1406
+ create_if_missing=create_if_missing,
1407
+ publish=publish,
1408
+ add_fields=add_fields,
1409
+ update_fields=update_fields,
1410
+ remove_fields=remove_fields,
1411
+ ),
1412
+ )
1413
+
1414
+ def _app_schema_apply_once(
1415
+ self,
1416
+ *,
1417
+ profile: str,
1418
+ app_key: str = "",
1419
+ package_id: int | None = None,
1420
+ app_name: str = "",
1421
+ app_title: str = "",
1422
+ icon: str = "",
1423
+ color: str = "",
1424
+ visibility: JSONObject | None = None,
1425
+ create_if_missing: bool = False,
1426
+ publish: bool = True,
1427
+ add_fields: list[JSONObject],
1428
+ update_fields: list[JSONObject],
1429
+ remove_fields: list[JSONObject],
1430
+ ) -> JSONObject:
1431
+ """执行内部辅助逻辑。"""
1432
+ effective_app_name = app_name or app_title
1433
+ plan_result = self._rewrite_plan_result_for_apply(
1434
+ result=self.app_schema_plan(
1435
+ profile=profile,
1436
+ app_key=app_key,
1437
+ package_id=package_id,
1438
+ app_name=effective_app_name,
1439
+ icon=icon,
1440
+ color=color,
1441
+ visibility=visibility,
1442
+ create_if_missing=create_if_missing,
1443
+ add_fields=add_fields,
1444
+ update_fields=update_fields,
1445
+ remove_fields=remove_fields,
1446
+ ),
1447
+ profile=profile,
1448
+ publish=publish,
1449
+ plan_tool_name="app_schema_plan",
1450
+ apply_tool_name="app_schema_apply",
1451
+ )
1452
+ if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
1453
+ return plan_result
1454
+ plan_args = plan_result.get("normalized_args")
1455
+ if not isinstance(plan_args, dict):
1456
+ plan_args = {}
1457
+ try:
1458
+ parsed_add = [FieldPatch.model_validate(item) for item in plan_args.get("add_fields") or []]
1459
+ parsed_update = [FieldUpdatePatch.model_validate(item) for item in plan_args.get("update_fields") or []]
1460
+ parsed_remove = [FieldRemovePatch.model_validate(item) for item in plan_args.get("remove_fields") or []]
1461
+ except ValidationError as exc:
1462
+ return _validation_failure(
1463
+ str(exc),
1464
+ tool_name="app_schema_apply",
1465
+ exc=exc,
1466
+ suggested_next_call={
1467
+ "tool_name": "app_schema_apply",
1468
+ "arguments": {
1469
+ "profile": profile,
1470
+ "app_key": str(plan_args.get("app_key") or app_key),
1471
+ "package_id": plan_args.get("package_id", package_id),
1472
+ "app_name": str(plan_args.get("app_name") or effective_app_name),
1473
+ "icon": str(plan_args.get("icon") or icon),
1474
+ "color": str(plan_args.get("color") or color),
1475
+ "visibility": plan_args.get("visibility") or visibility,
1476
+ "create_if_missing": bool(plan_args.get("create_if_missing", create_if_missing)),
1477
+ "publish": publish,
1478
+ "add_fields": plan_args.get("add_fields") or [{"name": "字段名称", "type": "text", "required": False}],
1479
+ "update_fields": plan_args.get("update_fields") or [],
1480
+ "remove_fields": plan_args.get("remove_fields") or [],
1481
+ },
1482
+ },
1483
+ )
1484
+ normalized_args = {
1485
+ "app_key": str(plan_args.get("app_key") or app_key),
1486
+ "package_id": plan_args.get("package_id", package_id),
1487
+ "app_name": str(plan_args.get("app_name") or effective_app_name),
1488
+ "icon": str(plan_args.get("icon") or icon or ""),
1489
+ "color": str(plan_args.get("color") or color or ""),
1490
+ "visibility": plan_args.get("visibility") or visibility,
1491
+ "create_if_missing": bool(plan_args.get("create_if_missing", create_if_missing)),
1492
+ "publish": publish,
1493
+ "add_fields": [patch.model_dump(mode="json") for patch in parsed_add],
1494
+ "update_fields": [patch.model_dump(mode="json") for patch in parsed_update],
1495
+ "remove_fields": [patch.model_dump(mode="json") for patch in parsed_remove],
1496
+ }
1497
+ result = _safe_tool_call(
1498
+ lambda: self._facade.app_schema_apply(
1499
+ profile=profile,
1500
+ app_key=str(plan_args.get("app_key") or app_key),
1501
+ package_tag_id=plan_args.get("package_id", package_id),
1502
+ app_name=str(plan_args.get("app_name") or effective_app_name),
1503
+ icon=str(plan_args.get("icon") or icon or ""),
1504
+ color=str(plan_args.get("color") or color or ""),
1505
+ visibility=VisibilityPatch.model_validate(plan_args.get("visibility")) if plan_args.get("visibility") is not None else None,
1506
+ create_if_missing=bool(plan_args.get("create_if_missing", create_if_missing)),
1507
+ publish=publish,
1508
+ add_fields=parsed_add,
1509
+ update_fields=parsed_update,
1510
+ remove_fields=parsed_remove,
1511
+ ),
1512
+ error_code="SCHEMA_APPLY_FAILED",
1513
+ normalized_args=normalized_args,
1514
+ suggested_next_call={"tool_name": "app_schema_apply", "arguments": {"profile": profile, **normalized_args}},
1515
+ )
1516
+ return _publicize_package_fields(result)
1517
+
1518
+ @tool_cn_name("应用布局应用")
1519
+ def app_layout_apply(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
1520
+ """执行应用相关逻辑。"""
1521
+ result = self._app_layout_apply_once(
1522
+ profile=profile,
1523
+ app_key=app_key,
1524
+ mode=mode,
1525
+ publish=publish,
1526
+ sections=sections,
1527
+ )
1528
+ return self._retry_after_self_lock_release(
1529
+ profile=profile,
1530
+ result=result,
1531
+ retry_call=lambda: self._app_layout_apply_once(
1532
+ profile=profile,
1533
+ app_key=app_key,
1534
+ mode=mode,
1535
+ publish=publish,
1536
+ sections=sections,
1537
+ ),
1538
+ )
1539
+
1540
+ def _app_layout_apply_once(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
1541
+ """执行内部辅助逻辑。"""
1542
+ plan_result = self._rewrite_plan_result_for_apply(
1543
+ result=self.app_layout_plan(
1544
+ profile=profile,
1545
+ app_key=app_key,
1546
+ mode=mode,
1547
+ sections=sections,
1548
+ preset=None,
1549
+ ),
1550
+ profile=profile,
1551
+ publish=publish,
1552
+ plan_tool_name="app_layout_plan",
1553
+ apply_tool_name="app_layout_apply",
1554
+ )
1555
+ if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
1556
+ return plan_result
1557
+ plan_args = plan_result.get("normalized_args")
1558
+ if not isinstance(plan_args, dict):
1559
+ plan_args = {}
1560
+ try:
1561
+ parsed_mode = LayoutApplyMode(str(plan_args.get("mode") or mode))
1562
+ parsed_sections = [LayoutSectionPatch.model_validate(item) for item in plan_args.get("sections") or []]
1563
+ except (ValueError, ValidationError) as exc:
1564
+ return _validation_failure(
1565
+ str(exc),
1566
+ tool_name="app_layout_apply",
1567
+ exc=exc if isinstance(exc, ValidationError) else None,
1568
+ suggested_next_call={
1569
+ "tool_name": "app_layout_apply",
1570
+ "arguments": {
1571
+ "profile": profile,
1572
+ "app_key": str(plan_args.get("app_key") or app_key),
1573
+ "mode": str(plan_args.get("mode") or "merge"),
1574
+ "publish": publish,
1575
+ "sections": plan_args.get("sections") or [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
1576
+ },
1577
+ },
1578
+ )
1579
+ normalized_args = {
1580
+ "app_key": str(plan_args.get("app_key") or app_key),
1581
+ "mode": parsed_mode.value,
1582
+ "publish": publish,
1583
+ "sections": [section.model_dump(mode="json", exclude_none=True) for section in parsed_sections],
1584
+ }
1585
+ return _safe_tool_call(
1586
+ lambda: self._facade.app_layout_apply(
1587
+ profile=profile,
1588
+ app_key=str(plan_args.get("app_key") or app_key),
1589
+ mode=parsed_mode,
1590
+ publish=publish,
1591
+ sections=parsed_sections,
1592
+ ),
1593
+ error_code="LAYOUT_APPLY_FAILED",
1594
+ normalized_args=normalized_args,
1595
+ suggested_next_call={"tool_name": "app_layout_apply", "arguments": {"profile": profile, **normalized_args}},
1596
+ )
1597
+
1598
+ @tool_cn_name("应用流程应用")
1599
+ def app_flow_apply(
1600
+ self,
1601
+ *,
1602
+ profile: str,
1603
+ app_key: str,
1604
+ mode: str = "replace",
1605
+ publish: bool = True,
1606
+ nodes: list[JSONObject],
1607
+ transitions: list[JSONObject],
1608
+ ) -> JSONObject:
1609
+ """执行应用相关逻辑。"""
1610
+ result = self._app_flow_apply_once(
1611
+ profile=profile,
1612
+ app_key=app_key,
1613
+ mode=mode,
1614
+ publish=publish,
1615
+ nodes=nodes,
1616
+ transitions=transitions,
1617
+ )
1618
+ return self._retry_after_self_lock_release(
1619
+ profile=profile,
1620
+ result=result,
1621
+ retry_call=lambda: self._app_flow_apply_once(
1622
+ profile=profile,
1623
+ app_key=app_key,
1624
+ mode=mode,
1625
+ publish=publish,
1626
+ nodes=nodes,
1627
+ transitions=transitions,
1628
+ ),
1629
+ )
1630
+
1631
+ def _app_flow_apply_once(
1632
+ self,
1633
+ *,
1634
+ profile: str,
1635
+ app_key: str,
1636
+ mode: str = "replace",
1637
+ publish: bool = True,
1638
+ nodes: list[JSONObject],
1639
+ transitions: list[JSONObject],
1640
+ ) -> JSONObject:
1641
+ """执行内部辅助逻辑。"""
1642
+ plan_result = self._rewrite_plan_result_for_apply(
1643
+ result=self.app_flow_plan(
1644
+ profile=profile,
1645
+ app_key=app_key,
1646
+ mode=mode,
1647
+ nodes=nodes,
1648
+ transitions=transitions,
1649
+ preset=None,
1650
+ ),
1651
+ profile=profile,
1652
+ publish=publish,
1653
+ plan_tool_name="app_flow_plan",
1654
+ apply_tool_name="app_flow_apply",
1655
+ )
1656
+ if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
1657
+ return plan_result
1658
+ plan_args = plan_result.get("normalized_args")
1659
+ if not isinstance(plan_args, dict):
1660
+ plan_args = {}
1661
+ try:
1662
+ request = FlowPlanRequest.model_validate(
1663
+ {
1664
+ "app_key": plan_args.get("app_key") or app_key,
1665
+ "mode": plan_args.get("mode") or mode,
1666
+ "nodes": plan_args.get("nodes") or [],
1667
+ "transitions": plan_args.get("transitions") or [],
1668
+ "preset": None,
1669
+ }
1670
+ )
1671
+ except ValidationError as exc:
1672
+ return _validation_failure(
1673
+ str(exc),
1674
+ tool_name="app_flow_apply",
1675
+ exc=exc,
1676
+ suggested_next_call={
1677
+ "tool_name": "app_flow_apply",
1678
+ "arguments": {
1679
+ "profile": profile,
1680
+ "app_key": str(plan_args.get("app_key") or app_key),
1681
+ "mode": str(plan_args.get("mode") or "replace"),
1682
+ "publish": publish,
1683
+ "nodes": plan_args.get("nodes") or [{"id": "start", "type": "start", "name": "发起"}],
1684
+ "transitions": plan_args.get("transitions") or [],
1685
+ },
1686
+ },
1687
+ )
1688
+ normalized_args = {
1689
+ "app_key": request.app_key,
1690
+ "mode": request.mode,
1691
+ "publish": publish,
1692
+ "nodes": [node.model_dump(mode="json") for node in request.nodes],
1693
+ "transitions": [transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
1694
+ }
1695
+ return _safe_tool_call(
1696
+ lambda: self._facade.app_flow_apply(
1697
+ profile=profile,
1698
+ app_key=request.app_key,
1699
+ mode=request.mode,
1700
+ publish=publish,
1701
+ nodes=[node.model_dump(mode="json") for node in request.nodes],
1702
+ transitions=[transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
1703
+ ),
1704
+ error_code="FLOW_APPLY_FAILED",
1705
+ normalized_args=normalized_args,
1706
+ suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}},
1707
+ )
1708
+
1709
+ @tool_cn_name("应用视图应用")
1710
+ def app_views_apply(
1711
+ self,
1712
+ *,
1713
+ profile: str,
1714
+ app_key: str,
1715
+ publish: bool = True,
1716
+ upsert_views: list[JSONObject],
1717
+ remove_views: list[str],
1718
+ ) -> JSONObject:
1719
+ """执行应用相关逻辑。"""
1720
+ result = self._app_views_apply_once(
1721
+ profile=profile,
1722
+ app_key=app_key,
1723
+ publish=publish,
1724
+ upsert_views=upsert_views,
1725
+ remove_views=remove_views,
1726
+ )
1727
+ return self._retry_after_self_lock_release(
1728
+ profile=profile,
1729
+ result=result,
1730
+ retry_call=lambda: self._app_views_apply_once(
1731
+ profile=profile,
1732
+ app_key=app_key,
1733
+ publish=publish,
1734
+ remove_views=remove_views,
1735
+ upsert_views=upsert_views,
1736
+ ),
1737
+ )
1738
+
1739
+ def _app_views_apply_once(
1740
+ self,
1741
+ *,
1742
+ profile: str,
1743
+ app_key: str,
1744
+ publish: bool = True,
1745
+ upsert_views: list[JSONObject],
1746
+ remove_views: list[str],
1747
+ ) -> JSONObject:
1748
+ """执行内部辅助逻辑。"""
1749
+ plan_result = self._rewrite_plan_result_for_apply(
1750
+ result=self.app_views_plan(
1751
+ profile=profile,
1752
+ app_key=app_key,
1753
+ upsert_views=upsert_views,
1754
+ remove_views=remove_views,
1755
+ preset=None,
1756
+ ),
1757
+ profile=profile,
1758
+ publish=publish,
1759
+ plan_tool_name="app_views_plan",
1760
+ apply_tool_name="app_views_apply",
1761
+ )
1762
+ if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
1763
+ return plan_result
1764
+ plan_args = plan_result.get("normalized_args")
1765
+ if not isinstance(plan_args, dict):
1766
+ plan_args = {}
1767
+ try:
1768
+ parsed_views = [ViewUpsertPatch.model_validate(item) for item in plan_args.get("upsert_views") or []]
1769
+ except ValidationError as exc:
1770
+ return _visibility_validation_failure(
1771
+ str(exc),
1772
+ tool_name="app_views_apply",
1773
+ exc=exc,
1774
+ suggested_next_call={
1775
+ "tool_name": "app_views_apply",
1776
+ "arguments": {
1777
+ "profile": profile,
1778
+ "app_key": str(plan_args.get("app_key") or app_key),
1779
+ "publish": publish,
1780
+ "upsert_views": plan_args.get("upsert_views") or [{"name": "全部数据", "type": "table", "columns": ["字段A"]}],
1781
+ "remove_views": plan_args.get("remove_views") or [],
1782
+ },
1783
+ },
1784
+ )
1785
+ normalized_args = {
1786
+ "app_key": str(plan_args.get("app_key") or app_key),
1787
+ "publish": publish,
1788
+ "upsert_views": [view.model_dump(mode="json") for view in parsed_views],
1789
+ "remove_views": list(plan_args.get("remove_views") or remove_views),
1790
+ }
1791
+ return _safe_tool_call(
1792
+ lambda: self._facade.app_views_apply(
1793
+ profile=profile,
1794
+ app_key=str(plan_args.get("app_key") or app_key),
1795
+ publish=publish,
1796
+ upsert_views=parsed_views,
1797
+ remove_views=list(plan_args.get("remove_views") or remove_views),
1798
+ ),
1799
+ error_code="VIEWS_APPLY_FAILED",
1800
+ normalized_args=normalized_args,
1801
+ suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
1802
+ )
1803
+
1804
+ @tool_cn_name("图表配置应用")
1805
+ def chart_apply(
1806
+ self,
1807
+ *,
1808
+ profile: str,
1809
+ app_key: str,
1810
+ upsert_charts: list[JSONObject],
1811
+ remove_chart_ids: list[str],
1812
+ reorder_chart_ids: list[str],
1813
+ ) -> JSONObject:
1814
+ """执行图表相关逻辑。"""
1815
+ return self.app_charts_apply(
1816
+ profile=profile,
1817
+ app_key=app_key,
1818
+ upsert_charts=upsert_charts,
1819
+ remove_chart_ids=remove_chart_ids,
1820
+ reorder_chart_ids=reorder_chart_ids,
1821
+ )
1822
+
1823
+ @tool_cn_name("应用图表应用")
1824
+ def app_charts_apply(
1825
+ self,
1826
+ *,
1827
+ profile: str,
1828
+ app_key: str,
1829
+ upsert_charts: list[JSONObject],
1830
+ remove_chart_ids: list[str],
1831
+ reorder_chart_ids: list[str],
1832
+ ) -> JSONObject:
1833
+ """执行应用相关逻辑。"""
1834
+ try:
1835
+ request = ChartApplyRequest.model_validate(
1836
+ {
1837
+ "app_key": app_key,
1838
+ "upsert_charts": upsert_charts or [],
1839
+ "remove_chart_ids": remove_chart_ids or [],
1840
+ "reorder_chart_ids": reorder_chart_ids or [],
1841
+ }
1842
+ )
1843
+ except ValidationError as exc:
1844
+ return _visibility_validation_failure(
1845
+ str(exc),
1846
+ tool_name="app_charts_apply",
1847
+ exc=exc,
1848
+ suggested_next_call={
1849
+ "tool_name": "app_charts_apply",
1850
+ "arguments": {
1851
+ "profile": profile,
1852
+ "app_key": app_key,
1853
+ "upsert_charts": [{"name": "销售总量", "chart_type": "target", "indicator_field_ids": []}],
1854
+ "remove_chart_ids": [],
1855
+ "reorder_chart_ids": [],
1856
+ },
1857
+ },
1858
+ )
1859
+ normalized_args = request.model_dump(mode="json")
1860
+ return _safe_tool_call(
1861
+ lambda: self._facade.chart_apply(profile=profile, request=request),
1862
+ error_code="CHART_APPLY_FAILED",
1863
+ normalized_args=normalized_args,
1864
+ suggested_next_call={"tool_name": "app_charts_apply", "arguments": {"profile": profile, **normalized_args}},
1865
+ )
1866
+
1867
+ @tool_cn_name("门户配置应用")
1868
+ def portal_apply(
1869
+ self,
1870
+ *,
1871
+ profile: str,
1872
+ dash_key: str = "",
1873
+ dash_name: str = "",
1874
+ package_id: int | None = None,
1875
+ publish: bool = True,
1876
+ sections: list[JSONObject] | None = None,
1877
+ visibility: JSONObject | None = None,
1878
+ auth: JSONObject | None = None,
1879
+ icon: str | None = None,
1880
+ color: str | None = None,
1881
+ hide_copyright: bool | None = None,
1882
+ dash_global_config: JSONObject | None = None,
1883
+ config: JSONObject | None = None,
1884
+ ) -> JSONObject:
1885
+ """执行门户相关逻辑。"""
1886
+ try:
1887
+ request = PortalApplyRequest.model_validate(
1888
+ {
1889
+ "dash_key": dash_key or None,
1890
+ "dash_name": dash_name or None,
1891
+ "package_tag_id": package_id,
1892
+ "publish": publish,
1893
+ "sections": sections or [],
1894
+ "visibility": visibility,
1895
+ "auth": auth,
1896
+ "icon": icon,
1897
+ "color": color,
1898
+ "hide_copyright": hide_copyright,
1899
+ "dash_global_config": dash_global_config,
1900
+ "config": config or {},
1901
+ }
1902
+ )
1903
+ except ValidationError as exc:
1904
+ return _visibility_validation_failure(
1905
+ str(exc),
1906
+ tool_name="portal_apply",
1907
+ exc=exc,
1908
+ suggested_next_call={
1909
+ "tool_name": "portal_apply",
1910
+ "arguments": {
1911
+ "profile": profile,
1912
+ "dash_name": dash_name or "业务门户",
1913
+ "package_id": package_id or 1001,
1914
+ "publish": True,
1915
+ "sections": [
1916
+ {
1917
+ "title": "经营概览",
1918
+ "source_type": "text",
1919
+ "text": "欢迎使用业务门户",
1920
+ }
1921
+ ],
1922
+ },
1923
+ },
1924
+ )
1925
+ normalized_args = request.model_dump(mode="json")
1926
+ normalized_args["package_id"] = normalized_args.pop("package_tag_id", package_id)
1927
+ return _publicize_package_fields(_safe_tool_call(
1928
+ lambda: self._facade.portal_apply(profile=profile, request=request),
1929
+ error_code="PORTAL_APPLY_FAILED",
1930
+ normalized_args=normalized_args,
1931
+ suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
1932
+ ))
1933
+
1934
+ @tool_cn_name("应用发布校验")
1935
+ def app_publish_verify(
1936
+ self,
1937
+ *,
1938
+ profile: str,
1939
+ app_key: str,
1940
+ expected_package_id: int | None = None,
1941
+ ) -> JSONObject:
1942
+ """执行应用相关逻辑。"""
1943
+ normalized_args = {"app_key": app_key, "expected_package_id": expected_package_id}
1944
+ result = _publicize_package_fields(_safe_tool_call(
1945
+ lambda: self._facade.app_publish_verify(profile=profile, app_key=app_key, expected_package_tag_id=expected_package_id),
1946
+ error_code="PUBLISH_VERIFY_FAILED",
1947
+ normalized_args=normalized_args,
1948
+ suggested_next_call={"tool_name": "app_publish_verify", "arguments": {"profile": profile, **normalized_args}},
1949
+ ))
1950
+ return _publicize_package_fields(self._retry_after_self_lock_release(
1951
+ profile=profile,
1952
+ result=result,
1953
+ retry_call=lambda: self._facade.app_publish_verify(
1954
+ profile=profile,
1955
+ app_key=app_key,
1956
+ expected_package_tag_id=expected_package_id,
1957
+ ),
1958
+ ))
1959
+
1960
+ def _retry_after_self_lock_release(self, *, profile: str, result: JSONObject, retry_call) -> JSONObject:
1961
+ """执行内部辅助逻辑。"""
1962
+ if not isinstance(result, dict) or result.get("status") != "failed" or result.get("error_code") != "APP_EDIT_LOCKED":
1963
+ return result
1964
+ suggested = result.get("suggested_next_call")
1965
+ if not isinstance(suggested, dict) or suggested.get("tool_name") != "app_release_edit_lock_if_mine":
1966
+ return result
1967
+ arguments = suggested.get("arguments")
1968
+ if not isinstance(arguments, dict):
1969
+ return result
1970
+ app_key = str(arguments.get("app_key") or "")
1971
+ lock_owner_email = str(arguments.get("lock_owner_email") or "")
1972
+ lock_owner_name = str(arguments.get("lock_owner_name") or "")
1973
+ release_attempts: list[JSONObject] = []
1974
+ retried: JSONObject = result
1975
+ for _ in range(3):
1976
+ release_result = self.app_release_edit_lock_if_mine(
1977
+ profile=profile,
1978
+ app_key=app_key,
1979
+ lock_owner_email=lock_owner_email,
1980
+ lock_owner_name=lock_owner_name,
1981
+ )
1982
+ release_attempts.append(release_result)
1983
+ if not isinstance(release_result, dict) or release_result.get("status") != "success":
1984
+ result.setdefault("details", {})
1985
+ if isinstance(result["details"], dict):
1986
+ result["details"]["edit_lock_release_result"] = release_result
1987
+ result["details"]["edit_lock_release_attempts"] = release_attempts
1988
+ return result
1989
+ retried = retry_call()
1990
+ if not (
1991
+ isinstance(retried, dict)
1992
+ and retried.get("status") == "failed"
1993
+ and retried.get("error_code") == "APP_EDIT_LOCKED"
1994
+ ):
1995
+ break
1996
+ time.sleep(0.2)
1997
+ if (
1998
+ isinstance(retried, dict)
1999
+ and retried.get("status") == "failed"
2000
+ and retried.get("error_code") == "APP_EDIT_LOCKED"
2001
+ ):
2002
+ retried = {
2003
+ **retried,
2004
+ "error_code": "PERSISTENT_SELF_LOCK",
2005
+ "message": "app remains locked by the current user's active editor session after repeated forced release attempts",
2006
+ "recoverable": True,
2007
+ "suggested_next_call": None,
2008
+ }
2009
+ if isinstance(retried, dict):
2010
+ retried.setdefault("details", {})
2011
+ if isinstance(retried["details"], dict):
2012
+ retried["details"]["edit_lock_release_result"] = release_attempts[-1] if release_attempts else None
2013
+ retried["details"]["edit_lock_release_attempts"] = release_attempts
2014
+ retried["edit_lock_released"] = bool(release_attempts)
2015
+ retried["retried_after_edit_lock_release"] = True
2016
+ return retried
2017
+
2018
+ def _rewrite_plan_result_for_apply(
2019
+ self,
2020
+ *,
2021
+ result: JSONObject,
2022
+ profile: str,
2023
+ publish: bool,
2024
+ plan_tool_name: str,
2025
+ apply_tool_name: str,
2026
+ ) -> JSONObject:
2027
+ """执行内部辅助逻辑。"""
2028
+ if not isinstance(result, dict):
2029
+ return result
2030
+ rewritten = dict(result)
2031
+ if rewritten.get("error_code") == "VALIDATION_ERROR":
2032
+ contract = _BUILDER_TOOL_CONTRACTS.get(apply_tool_name)
2033
+ details = rewritten.get("details")
2034
+ if not isinstance(details, dict):
2035
+ details = {}
2036
+ rewritten["details"] = details
2037
+ if isinstance(contract, dict):
2038
+ rewritten["allowed_values"] = deepcopy(contract.get("allowed_values", {}))
2039
+ details["allowed_keys"] = deepcopy(contract.get("allowed_keys", []))
2040
+ details["section_allowed_keys"] = deepcopy(contract.get("section_allowed_keys", []))
2041
+ details["section_aliases"] = deepcopy(contract.get("section_aliases", {}))
2042
+ details["minimal_section_example"] = deepcopy(contract.get("minimal_section_example"))
2043
+ suggested_next_call = rewritten.get("suggested_next_call")
2044
+ if isinstance(suggested_next_call, dict):
2045
+ if suggested_next_call.get("tool_name") == plan_tool_name:
2046
+ arguments = suggested_next_call.get("arguments")
2047
+ normalized_arguments = dict(arguments) if isinstance(arguments, dict) else {}
2048
+ normalized_arguments.setdefault("profile", profile)
2049
+ normalized_arguments["publish"] = publish
2050
+ rewritten["suggested_next_call"] = {
2051
+ **suggested_next_call,
2052
+ "tool_name": apply_tool_name,
2053
+ "arguments": normalized_arguments,
2054
+ }
2055
+ return rewritten
2056
+ if rewritten.get("error_code") == "VALIDATION_ERROR" and suggested_next_call.get("tool_name") == apply_tool_name:
2057
+ arguments = suggested_next_call.get("arguments")
2058
+ normalized_arguments = dict(arguments) if isinstance(arguments, dict) else {}
2059
+ normalized_arguments.setdefault("profile", profile)
2060
+ normalized_arguments["publish"] = publish
2061
+ if isinstance(details, dict):
2062
+ details["canonical_arguments"] = normalized_arguments
2063
+ rewritten["suggested_next_call"] = {
2064
+ **suggested_next_call,
2065
+ "arguments": normalized_arguments,
2066
+ }
2067
+ if rewritten.get("status") == "success":
2068
+ normalized_args = rewritten.get("normalized_args")
2069
+ if isinstance(normalized_args, dict):
2070
+ rewritten["suggested_next_call"] = {
2071
+ "tool_name": apply_tool_name,
2072
+ "arguments": {"profile": profile, **normalized_args, "publish": publish},
2073
+ }
2074
+ return rewritten
2075
+
2076
+
2077
+ def _validation_failure(
2078
+ detail: str,
2079
+ *,
2080
+ tool_name: str | None = None,
2081
+ exc: ValidationError | None = None,
2082
+ suggested_next_call: JSONObject | None = None,
2083
+ ) -> JSONObject:
2084
+ contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
2085
+ reason_path = None
2086
+ if exc is not None:
2087
+ errors = exc.errors()
2088
+ if errors:
2089
+ loc = errors[0].get("loc")
2090
+ if isinstance(loc, (tuple, list)):
2091
+ reason_path = ".".join(str(part) for part in loc)
2092
+ canonical_arguments = None
2093
+ if isinstance(suggested_next_call, dict):
2094
+ arguments = suggested_next_call.get("arguments")
2095
+ if isinstance(arguments, dict):
2096
+ canonical_arguments = arguments
2097
+ return {
2098
+ "status": "failed",
2099
+ "error_code": "VALIDATION_ERROR",
2100
+ "recoverable": True,
2101
+ "message": detail,
2102
+ "normalized_args": {},
2103
+ "missing_fields": [],
2104
+ "allowed_values": deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {},
2105
+ "details": {
2106
+ "validation_detail": detail,
2107
+ "reason_path": reason_path,
2108
+ "allowed_keys": deepcopy(contract.get("allowed_keys", [])) if isinstance(contract, dict) else [],
2109
+ "canonical_arguments": canonical_arguments,
2110
+ "section_allowed_keys": deepcopy(contract.get("section_allowed_keys", [])) if isinstance(contract, dict) else [],
2111
+ "section_aliases": deepcopy(contract.get("section_aliases", {})) if isinstance(contract, dict) else {},
2112
+ "minimal_section_example": deepcopy(contract.get("minimal_section_example")) if isinstance(contract, dict) else None,
2113
+ },
2114
+ "suggested_next_call": suggested_next_call,
2115
+ "request_id": None,
2116
+ "backend_code": None,
2117
+ "http_status": None,
2118
+ "noop": False,
2119
+ "verification": {},
2120
+ }
2121
+
2122
+
2123
+ def _visibility_validation_failure(
2124
+ detail: str,
2125
+ *,
2126
+ tool_name: str,
2127
+ exc: ValidationError | None,
2128
+ suggested_next_call: JSONObject | None = None,
2129
+ ) -> JSONObject:
2130
+ result = _validation_failure(
2131
+ detail,
2132
+ tool_name=tool_name,
2133
+ exc=exc,
2134
+ suggested_next_call=suggested_next_call,
2135
+ )
2136
+ lowered = detail.lower()
2137
+ code_by_fragment = [
2138
+ ("visibility and auth cannot be provided together", "VISIBILITY_CONFLICT"),
2139
+ ("specific visibility requires selectors", "VISIBILITY_SELECTOR_REQUIRED"),
2140
+ ("selectors are only allowed when mode=specific", "VISIBILITY_SELECTOR_UNEXPECTED"),
2141
+ ("external_mode=specific requires external_selectors", "EXTERNAL_VISIBILITY_SELECTOR_REQUIRED"),
2142
+ ("external_selectors are only allowed when external_mode=specific", "EXTERNAL_VISIBILITY_SELECTOR_UNEXPECTED"),
2143
+ ("mode=everyone requires external_mode=workspace", "VISIBILITY_EXTERNAL_MODE_CONFLICT"),
2144
+ ("external_selectors are not allowed when mode=everyone", "VISIBILITY_SELECTOR_UNEXPECTED"),
2145
+ ]
2146
+ for fragment, error_code in code_by_fragment:
2147
+ if fragment in lowered:
2148
+ result["error_code"] = error_code
2149
+ result["message"] = fragment
2150
+ details = result.get("details")
2151
+ if isinstance(details, dict):
2152
+ details["visibility_error"] = True
2153
+ break
2154
+ return result
2155
+
2156
+
2157
+ def _config_failure(*, tool_name: str, message: str, fix_hint: str) -> JSONObject:
2158
+ contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
2159
+ return {
2160
+ "status": "failed",
2161
+ "error_code": "CONFIG_ERROR",
2162
+ "recoverable": True,
2163
+ "message": message,
2164
+ "normalized_args": {},
2165
+ "missing_fields": [],
2166
+ "allowed_values": deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {},
2167
+ "details": {
2168
+ "fix_hint": fix_hint,
2169
+ "allowed_keys": deepcopy(contract.get("allowed_keys", [])) if isinstance(contract, dict) else [],
2170
+ },
2171
+ "suggested_next_call": None,
2172
+ "request_id": None,
2173
+ "backend_code": None,
2174
+ "http_status": None,
2175
+ "noop": False,
2176
+ "verification": {},
2177
+ }
2178
+
2179
+
2180
+ def _safe_tool_call(
2181
+ call,
2182
+ *,
2183
+ error_code: str,
2184
+ normalized_args: JSONObject,
2185
+ suggested_next_call: JSONObject | None,
2186
+ ) -> JSONObject:
2187
+ try:
2188
+ return call()
2189
+ except (QingflowApiError, RuntimeError) as error:
2190
+ api_error = _coerce_api_error(error)
2191
+ public_http_status = None if api_error.http_status == 404 else api_error.http_status
2192
+ return {
2193
+ "status": "failed",
2194
+ "error_code": error_code,
2195
+ "recoverable": True,
2196
+ "message": _public_error_message(error_code, api_error),
2197
+ "normalized_args": normalized_args,
2198
+ "missing_fields": [],
2199
+ "allowed_values": {},
2200
+ "details": {
2201
+ "transport_error": {
2202
+ "http_status": api_error.http_status,
2203
+ "backend_code": api_error.backend_code,
2204
+ "category": api_error.category,
2205
+ }
2206
+ },
2207
+ "suggested_next_call": suggested_next_call,
2208
+ "request_id": api_error.request_id,
2209
+ "backend_code": api_error.backend_code,
2210
+ "http_status": public_http_status,
2211
+ "noop": False,
2212
+ "verification": {},
2213
+ }
2214
+
2215
+
2216
+ def _publicize_package_fields(value):
2217
+ if isinstance(value, list):
2218
+ return [_publicize_package_fields(item) for item in value]
2219
+ if not isinstance(value, dict):
2220
+ return value
2221
+ key_map = {
2222
+ "tag_id": "package_id",
2223
+ "tag_ids": "package_ids",
2224
+ "tag_ids_before": "package_ids_before",
2225
+ "tag_ids_after": "package_ids_after",
2226
+ "tag_name": "package_name",
2227
+ "tag_icon": "icon",
2228
+ "package_tag_id": "package_id",
2229
+ "package_tag_ids": "package_ids",
2230
+ "expected_package_tag_id": "expected_package_id",
2231
+ }
2232
+ public: JSONObject = {}
2233
+ for key, item in value.items():
2234
+ public_key = key_map.get(key, key)
2235
+ public[public_key] = _publicize_package_fields(item)
2236
+ return public
2237
+
2238
+
2239
+ def _coerce_api_error(error: Exception) -> QingflowApiError:
2240
+ if isinstance(error, QingflowApiError):
2241
+ return error
2242
+ if isinstance(error, RuntimeError):
2243
+ try:
2244
+ payload = json.loads(str(error))
2245
+ except json.JSONDecodeError:
2246
+ payload = None
2247
+ if isinstance(payload, dict) and payload.get("category") and payload.get("message"):
2248
+ details = payload.get("details")
2249
+ return QingflowApiError(
2250
+ category=str(payload.get("category")),
2251
+ message=str(payload.get("message")),
2252
+ backend_code=payload.get("backend_code"),
2253
+ request_id=payload.get("request_id"),
2254
+ http_status=payload.get("http_status"),
2255
+ details=details if isinstance(details, dict) else None,
2256
+ )
2257
+ return QingflowApiError(category="runtime", message=str(error))
2258
+
2259
+
2260
+ def _public_error_message(error_code: str, error: QingflowApiError) -> str:
2261
+ if error.backend_code == 40074 or error_code == "APP_EDIT_LOCKED":
2262
+ return "app is currently locked by another active editor session"
2263
+ if error.http_status != 404:
2264
+ return error.message
2265
+ mapping = {
2266
+ "PACKAGE_LIST_FAILED": "package list is unavailable in the current route",
2267
+ "PACKAGE_RESOLVE_FAILED": "package resolution is unavailable in the current route",
2268
+ "SOLUTION_INSTALL_FAILED": "solution install could not complete because the app creation route or solution source is unavailable",
2269
+ "PACKAGE_ATTACH_FAILED": "package attachment could not be verified in the current route",
2270
+ "APP_RESOLVE_FAILED": "app resolution is unavailable in the current route",
2271
+ "CUSTOM_BUTTON_LIST_FAILED": "custom button list is unavailable in the current route",
2272
+ "CUSTOM_BUTTON_GET_FAILED": "custom button detail is unavailable in the current route",
2273
+ "CUSTOM_BUTTON_CREATE_FAILED": "custom button create could not complete because the route or readback is unavailable",
2274
+ "CUSTOM_BUTTON_UPDATE_FAILED": "custom button update could not complete because the route or readback is unavailable",
2275
+ "CUSTOM_BUTTON_DELETE_FAILED": "custom button delete could not complete because the route is unavailable",
2276
+ "APP_READ_FAILED": "app base or schema is unavailable in the current route",
2277
+ "FIELDS_READ_FAILED": "app fields are unavailable in the current route",
2278
+ "LAYOUT_READ_FAILED": "layout resource is unavailable for this app in the current route",
2279
+ "VIEWS_READ_FAILED": "views resource is unavailable for this app in the current route",
2280
+ "FLOW_READ_FAILED": "workflow resource is unavailable for this app in the current route",
2281
+ "SCHEMA_PLAN_FAILED": "schema planning could not load the required app state in the current route",
2282
+ "LAYOUT_PLAN_FAILED": "layout planning could not load the required app state in the current route",
2283
+ "FLOW_PLAN_FAILED": "flow planning could not load the required app state in the current route",
2284
+ "VIEWS_PLAN_FAILED": "views planning could not load the required app state in the current route",
2285
+ "SCHEMA_APPLY_FAILED": "schema apply could not complete because the app route or readback is unavailable",
2286
+ "LAYOUT_APPLY_FAILED": "layout apply could not complete because the layout route or readback is unavailable",
2287
+ "FLOW_APPLY_FAILED": "flow apply could not complete because the workflow route or readback is unavailable",
2288
+ "VIEWS_APPLY_FAILED": "views apply could not complete because the views route or readback is unavailable",
2289
+ "CHART_APPLY_FAILED": "chart apply could not complete because the QingBI route or readback is unavailable",
2290
+ "PORTAL_APPLY_FAILED": "portal apply could not complete because the portal route or readback is unavailable",
2291
+ "PUBLISH_VERIFY_FAILED": "publish verification is unavailable in the current route",
2292
+ }
2293
+ return mapping.get(error_code, "requested builder resource is unavailable in the current route")
2294
+
2295
+
2296
+ _VISIBILITY_ALLOWED_VALUES: JSONObject = {
2297
+ "visibility.mode": ["workspace", "everyone", "specific"],
2298
+ "visibility.external_mode": ["not", "workspace", "specific"],
2299
+ "visibility.selectors": [
2300
+ "member_uids",
2301
+ "member_emails",
2302
+ "member_names",
2303
+ "dept_ids",
2304
+ "dept_names",
2305
+ "role_ids",
2306
+ "role_names",
2307
+ "include_sub_departs",
2308
+ ],
2309
+ "visibility.external_selectors": ["member_ids", "member_emails", "dept_ids"],
2310
+ }
2311
+
2312
+
2313
+ _VISIBILITY_EXECUTION_NOTES = [
2314
+ "visibility is the canonical public auth shape for packages, apps, views, charts, and portals",
2315
+ "mode=workspace means internal workspace members; mode=everyone means all users; mode=specific requires selectors",
2316
+ "external_mode defaults to not and controls external-user visibility independently unless mode=everyone",
2317
+ "name selectors must resolve to exactly one member, department, or role; ambiguous names fail instead of guessing",
2318
+ "when updating an existing resource, omit visibility to preserve the current backend auth",
2319
+ ]
2320
+
2321
+
2322
+ _VISIBILITY_WORKSPACE_EXAMPLE: JSONObject = {
2323
+ "mode": "workspace",
2324
+ "selectors": {},
2325
+ "external_mode": "not",
2326
+ "external_selectors": {},
2327
+ }
2328
+
2329
+
2330
+ _VISIBILITY_SPECIFIC_EXAMPLE: JSONObject = {
2331
+ "mode": "specific",
2332
+ "selectors": {
2333
+ "member_emails": ["owner@example.com"],
2334
+ "role_names": ["项目经理"],
2335
+ "include_sub_departs": False,
2336
+ },
2337
+ "external_mode": "not",
2338
+ "external_selectors": {},
2339
+ }
2340
+
2341
+
2342
+ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2343
+ "file_upload_local": {
2344
+ "allowed_keys": ["upload_kind", "file_path", "upload_mark", "content_type", "bucket_type", "path_id", "file_related_url"],
2345
+ "aliases": {},
2346
+ "allowed_values": {"upload_kind": ["attachment", "login"]},
2347
+ "execution_notes": [
2348
+ "returns upload metadata plus attachment-ready values for later builder or user writes",
2349
+ "upload_kind=attachment may fall back to login upload info when the backend rejects attachment upload info",
2350
+ "upload_mark is recommended for attachment uploads because it affects the remote storage path",
2351
+ ],
2352
+ "minimal_example": {
2353
+ "profile": "default",
2354
+ "upload_kind": "attachment",
2355
+ "file_path": "/absolute/path/to/local-file.txt",
2356
+ "upload_mark": "APP_KEY",
2357
+ },
2358
+ },
2359
+ "feedback_submit": {
2360
+ "allowed_keys": [
2361
+ "category",
2362
+ "title",
2363
+ "description",
2364
+ "expected_behavior",
2365
+ "actual_behavior",
2366
+ "impact_scope",
2367
+ "tool_name",
2368
+ "app_key",
2369
+ "record_id",
2370
+ "workflow_node_id",
2371
+ "note",
2372
+ ],
2373
+ "aliases": {},
2374
+ "allowed_values": {"category": ["bug", "missing_capability", "awkward_workflow", "ux", "docs"]},
2375
+ "execution_notes": [
2376
+ "submit feedback only after explicit user confirmation",
2377
+ "use this when the current builder capability is unsupported or awkward after reasonable attempts",
2378
+ "requires feedback qsource configuration but does not require Qingflow login or workspace selection",
2379
+ ],
2380
+ "minimal_example": {
2381
+ "category": "missing_capability",
2382
+ "title": "builder chart data read gap",
2383
+ "description": "The current builder capability cannot satisfy the requested workflow.",
2384
+ "tool_name": "chart_get",
2385
+ },
2386
+ },
2387
+ "package_get": {
2388
+ "allowed_keys": ["package_id"],
2389
+ "aliases": {"packageId": "package_id"},
2390
+ "allowed_values": {},
2391
+ "execution_notes": [
2392
+ "returns complete public package schema with package_id, package_name, icon, visibility, and items",
2393
+ "package_id maps internally to backend tagId; do not use tag_id in public calls",
2394
+ "visibility is normalized from backend MemberAuthInfoVO auth",
2395
+ ],
2396
+ "minimal_example": {
2397
+ "profile": "default",
2398
+ "package_id": 1001,
2399
+ },
2400
+ },
2401
+ "package_apply": {
2402
+ "allowed_keys": ["package_id", "package_name", "create_if_missing", "icon", "color", "visibility", "items", "allow_detach"],
2403
+ "aliases": {
2404
+ "packageId": "package_id",
2405
+ "packageName": "package_name",
2406
+ "createIfMissing": "create_if_missing",
2407
+ "iconName": "icon",
2408
+ "iconColor": "color",
2409
+ "allowDetach": "allow_detach",
2410
+ },
2411
+ "allowed_values": deepcopy(_VISIBILITY_ALLOWED_VALUES),
2412
+ "execution_notes": [
2413
+ "create or update package metadata, visibility, grouping, and ordering in one call",
2414
+ "package_id maps internally to backend tagId; do not use tag_id in public calls",
2415
+ "items is a full package layout tree; omitting existing app/portal items is blocked unless allow_detach=true",
2416
+ "item shapes: {type:'app', app_key}, {type:'portal', dash_key}, or {type:'group', group_id?, name, items:[...]}",
2417
+ *_VISIBILITY_EXECUTION_NOTES,
2418
+ ],
2419
+ "minimal_example": {
2420
+ "profile": "default",
2421
+ "package_id": 1001,
2422
+ "package_name": "项目管理",
2423
+ "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
2424
+ "items": [{"type": "app", "app_key": "APP_KEY", "title": "项目台账"}],
2425
+ "allow_detach": False,
2426
+ },
2427
+ "specific_visibility_example": {
2428
+ "profile": "default",
2429
+ "package_id": 1001,
2430
+ "visibility": deepcopy(_VISIBILITY_SPECIFIC_EXAMPLE),
2431
+ },
2432
+ "create_example": {
2433
+ "profile": "default",
2434
+ "package_name": "项目管理",
2435
+ "create_if_missing": True,
2436
+ "icon": "files-folder",
2437
+ "color": "azure",
2438
+ "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
2439
+ },
2440
+ },
2441
+ "solution_install": {
2442
+ "allowed_keys": ["solution_key", "being_copy_data", "solution_source"],
2443
+ "aliases": {
2444
+ "solutionKey": "solution_key",
2445
+ "beingCopyData": "being_copy_data",
2446
+ "copy_data": "being_copy_data",
2447
+ "copyData": "being_copy_data",
2448
+ "solutionSource": "solution_source",
2449
+ "source": "solution_source",
2450
+ },
2451
+ "allowed_values": {
2452
+ "being_copy_data": [True, False],
2453
+ "solution_source": ["solutionDetail"],
2454
+ },
2455
+ "execution_notes": [
2456
+ "solution_install calls the backend app creation route with a solutionKey payload",
2457
+ "this is a write operation that may create multiple apps at once",
2458
+ "app_keys are verified from the backend response when available",
2459
+ ],
2460
+ "minimal_example": {
2461
+ "profile": "default",
2462
+ "solution_key": "cfqrhth99401",
2463
+ "being_copy_data": True,
2464
+ "solution_source": "solutionDetail",
2465
+ },
2466
+ },
2467
+ "member_search": {
2468
+ "allowed_keys": ["query", "page_num", "page_size", "contain_disable"],
2469
+ "aliases": {},
2470
+ "allowed_values": {},
2471
+ "minimal_example": {
2472
+ "profile": "default",
2473
+ "query": "严琪东",
2474
+ "page_num": 1,
2475
+ "page_size": 20,
2476
+ "contain_disable": False,
2477
+ },
2478
+ },
2479
+ "role_search": {
2480
+ "allowed_keys": ["keyword", "page_num", "page_size"],
2481
+ "aliases": {},
2482
+ "allowed_values": {},
2483
+ "minimal_example": {
2484
+ "profile": "default",
2485
+ "keyword": "项目经理",
2486
+ "page_num": 1,
2487
+ "page_size": 20,
2488
+ },
2489
+ },
2490
+ "role_create": {
2491
+ "allowed_keys": ["role_name", "member_uids", "member_emails", "member_names", "role_icon"],
2492
+ "aliases": {},
2493
+ "allowed_values": {},
2494
+ "minimal_example": {
2495
+ "profile": "default",
2496
+ "role_name": "研发负责人",
2497
+ "member_names": ["严琪东"],
2498
+ "member_uids": [],
2499
+ "member_emails": [],
2500
+ "role_icon": "ex-user-outlined",
2501
+ },
2502
+ },
2503
+ "app_resolve": {
2504
+ "allowed_keys": ["app_key", "app_name", "package_id"],
2505
+ "aliases": {"packageId": "package_id"},
2506
+ "allowed_values": {},
2507
+ "execution_notes": [
2508
+ "use exactly one selector mode",
2509
+ "mode 1: app_key",
2510
+ "mode 2: app_name + package_id",
2511
+ ],
2512
+ "minimal_example": {
2513
+ "profile": "default",
2514
+ "app_key": "APP_KEY",
2515
+ },
2516
+ "package_scoped_example": {
2517
+ "profile": "default",
2518
+ "app_name": "研发项目管理",
2519
+ "package_id": 1001,
2520
+ },
2521
+ },
2522
+ "app_release_edit_lock_if_mine": {
2523
+ "allowed_keys": ["app_key", "lock_owner_email", "lock_owner_name"],
2524
+ "aliases": {},
2525
+ "allowed_values": {},
2526
+ "execution_notes": [
2527
+ "use this only after a builder write fails with APP_EDIT_LOCKED and the lock belongs to the current user",
2528
+ "supply the lock owner details from the failed write result for a safe release attempt",
2529
+ "after a successful release, retry the blocked builder write or publish verification call",
2530
+ ],
2531
+ "minimal_example": {
2532
+ "profile": "default",
2533
+ "app_key": "APP_KEY",
2534
+ "lock_owner_email": "user@example.com",
2535
+ "lock_owner_name": "当前用户",
2536
+ },
2537
+ },
2538
+ "app_custom_button_list": {
2539
+ "allowed_keys": ["app_key"],
2540
+ "aliases": {},
2541
+ "allowed_values": {},
2542
+ "minimal_example": {
2543
+ "profile": "default",
2544
+ "app_key": "APP_KEY",
2545
+ },
2546
+ },
2547
+ "app_custom_button_get": {
2548
+ "allowed_keys": ["app_key", "button_id"],
2549
+ "aliases": {"buttonId": "button_id"},
2550
+ "allowed_values": {},
2551
+ "minimal_example": {
2552
+ "profile": "default",
2553
+ "app_key": "APP_KEY",
2554
+ "button_id": 1001,
2555
+ },
2556
+ },
2557
+ "app_custom_button_create": {
2558
+ "allowed_keys": ["app_key", "payload"],
2559
+ "aliases": {
2560
+ "payload.buttonText": "payload.button_text",
2561
+ "payload.backgroundColor": "payload.background_color",
2562
+ "payload.textColor": "payload.text_color",
2563
+ "payload.buttonIcon": "payload.button_icon",
2564
+ "payload.iconColor": "payload.icon_color",
2565
+ "payload.triggerAction": "payload.trigger_action",
2566
+ "payload.triggerLinkUrl": "payload.trigger_link_url",
2567
+ "payload.triggerAddDataConfig": "payload.trigger_add_data_config",
2568
+ "payload.externalQrobotConfig": "payload.external_qrobot_config",
2569
+ "payload.customButtonExternalQRobotRelationVO": "payload.external_qrobot_config",
2570
+ "payload.triggerWingsConfig": "payload.trigger_wings_config",
2571
+ },
2572
+ "allowed_values": {
2573
+ "payload.trigger_action": [member.value for member in PublicButtonTriggerAction],
2574
+ },
2575
+ "execution_notes": [
2576
+ "custom button writes now auto-publish the current app draft as a fixed closing step",
2577
+ "background_color and text_color cannot both be white",
2578
+ "for addData buttons, put field mappings in payload.trigger_add_data_config.que_relation",
2579
+ ],
2580
+ "minimal_example": {
2581
+ "profile": "default",
2582
+ "app_key": "APP_KEY",
2583
+ "payload": {
2584
+ "button_text": "新增记录",
2585
+ "background_color": "#FFFFFF",
2586
+ "text_color": "#494F57",
2587
+ "button_icon": "ex-add-outlined",
2588
+ "icon_color": "#494F57",
2589
+ "trigger_action": "link",
2590
+ "trigger_link_url": "https://example.com",
2591
+ },
2592
+ },
2593
+ },
2594
+ "app_custom_button_update": {
2595
+ "allowed_keys": ["app_key", "button_id", "payload"],
2596
+ "aliases": {
2597
+ "buttonId": "button_id",
2598
+ "payload.buttonText": "payload.button_text",
2599
+ "payload.backgroundColor": "payload.background_color",
2600
+ "payload.textColor": "payload.text_color",
2601
+ "payload.buttonIcon": "payload.button_icon",
2602
+ "payload.iconColor": "payload.icon_color",
2603
+ "payload.triggerAction": "payload.trigger_action",
2604
+ "payload.triggerLinkUrl": "payload.trigger_link_url",
2605
+ "payload.triggerAddDataConfig": "payload.trigger_add_data_config",
2606
+ "payload.externalQrobotConfig": "payload.external_qrobot_config",
2607
+ "payload.customButtonExternalQRobotRelationVO": "payload.external_qrobot_config",
2608
+ "payload.triggerWingsConfig": "payload.trigger_wings_config",
2609
+ },
2610
+ "allowed_values": {
2611
+ "payload.trigger_action": [member.value for member in PublicButtonTriggerAction],
2612
+ },
2613
+ "execution_notes": [
2614
+ "custom button writes now auto-publish the current app draft as a fixed closing step",
2615
+ "background_color and text_color cannot both be white",
2616
+ "for addData buttons, put field mappings in payload.trigger_add_data_config.que_relation",
2617
+ ],
2618
+ "minimal_example": {
2619
+ "profile": "default",
2620
+ "app_key": "APP_KEY",
2621
+ "button_id": 1001,
2622
+ "payload": {
2623
+ "button_text": "查看详情",
2624
+ "background_color": "#FFFFFF",
2625
+ "text_color": "#494F57",
2626
+ "button_icon": "ex-link-outlined",
2627
+ "icon_color": "#494F57",
2628
+ "trigger_action": "link",
2629
+ "trigger_link_url": "https://example.com/detail",
2630
+ },
2631
+ },
2632
+ },
2633
+ "app_custom_button_delete": {
2634
+ "allowed_keys": ["app_key", "button_id"],
2635
+ "aliases": {"buttonId": "button_id"},
2636
+ "allowed_values": {},
2637
+ "minimal_example": {
2638
+ "profile": "default",
2639
+ "app_key": "APP_KEY",
2640
+ "button_id": 1001,
2641
+ },
2642
+ },
2643
+ "app_schema_plan": {
2644
+ "allowed_keys": ["app_key", "package_id", "app_name", "icon", "color", "visibility", "create_if_missing", "add_fields", "update_fields", "remove_fields"],
2645
+ "aliases": {
2646
+ "app_title": "app_name",
2647
+ "title": "app_name",
2648
+ "packageId": "package_id",
2649
+ "field.title": "field.name",
2650
+ "field.label": "field.name",
2651
+ "field.fields": "field.subfields",
2652
+ "field.type_id": "field.type",
2653
+ "field.relationMode": "field.relation_mode",
2654
+ "field.selection_mode": "field.relation_mode",
2655
+ "field.selectionMode": "field.relation_mode",
2656
+ "field.multiple": "field.relation_mode",
2657
+ "field.allow_multiple": "field.relation_mode",
2658
+ "field.optional_data_num": "field.relation_mode",
2659
+ "field.optionalDataNum": "field.relation_mode",
2660
+ "field.departmentScope": "field.department_scope",
2661
+ "field.remoteLookupConfig": "field.remote_lookup_config",
2662
+ "field.qLinkerBinding": "field.q_linker_binding",
2663
+ "field.codeBlockConfig": "field.code_block_config",
2664
+ "field.codeBlockBinding": "field.code_block_binding",
2665
+ "field.autoTrigger": "field.auto_trigger",
2666
+ "field.customBtnTextStatus": "field.custom_button_text_enabled",
2667
+ "field.customBtnText": "field.custom_button_text",
2668
+ "field.subfieldUpdates": "field.subfield_updates",
2669
+ },
2670
+ "allowed_values": {
2671
+ "field.type": [member.value for member in PublicFieldType],
2672
+ "field.relation_mode": [member.value for member in PublicRelationMode],
2673
+ "field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
2674
+ **deepcopy(_VISIBILITY_ALLOWED_VALUES),
2675
+ },
2676
+ "execution_notes": [
2677
+ "create mode may set visibility for the new app; edit mode may update visibility on an existing app",
2678
+ *_VISIBILITY_EXECUTION_NOTES,
2679
+ ],
2680
+ "minimal_example": {
2681
+ "profile": "default",
2682
+ "app_name": "研发项目管理",
2683
+ "package_id": 1001,
2684
+ "icon": "template",
2685
+ "color": "emerald",
2686
+ "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
2687
+ "create_if_missing": True,
2688
+ "add_fields": [{"name": "项目名称", "type": "text"}],
2689
+ "update_fields": [],
2690
+ "remove_fields": [],
2691
+ },
2692
+ "relation_example": {
2693
+ "profile": "default",
2694
+ "app_key": "APP_ITERATION",
2695
+ "add_fields": [
2696
+ {
2697
+ "name": "需求反馈引用",
2698
+ "type": "relation",
2699
+ "target_app_key": "APP_FEEDBACK",
2700
+ "relation_mode": "multiple",
2701
+ "display_field": {"name": "反馈标题"},
2702
+ "visible_fields": [{"name": "反馈标题"}, {"name": "优先级判断"}],
2703
+ }
2704
+ ],
2705
+ "update_fields": [],
2706
+ "remove_fields": [],
2707
+ },
2708
+ },
2709
+ "app_schema_apply": {
2710
+ "allowed_keys": ["app_key", "package_id", "app_name", "icon", "color", "visibility", "create_if_missing", "publish", "add_fields", "update_fields", "remove_fields"],
2711
+ "aliases": {
2712
+ "app_title": "app_name",
2713
+ "title": "app_name",
2714
+ "packageId": "package_id",
2715
+ "field.title": "field.name",
2716
+ "field.label": "field.name",
2717
+ "field.fields": "field.subfields",
2718
+ "field.type_id": "field.type",
2719
+ "field.relationMode": "field.relation_mode",
2720
+ "field.selection_mode": "field.relation_mode",
2721
+ "field.selectionMode": "field.relation_mode",
2722
+ "field.multiple": "field.relation_mode",
2723
+ "field.allow_multiple": "field.relation_mode",
2724
+ "field.optional_data_num": "field.relation_mode",
2725
+ "field.optionalDataNum": "field.relation_mode",
2726
+ "field.departmentScope": "field.department_scope",
2727
+ "field.remoteLookupConfig": "field.remote_lookup_config",
2728
+ "field.qLinkerBinding": "field.q_linker_binding",
2729
+ "field.codeBlockConfig": "field.code_block_config",
2730
+ "field.codeBlockBinding": "field.code_block_binding",
2731
+ "field.autoTrigger": "field.auto_trigger",
2732
+ "field.customBtnTextStatus": "field.custom_button_text_enabled",
2733
+ "field.customBtnText": "field.custom_button_text",
2734
+ "field.subfieldUpdates": "field.subfield_updates",
2735
+ },
2736
+ "allowed_values": {
2737
+ "field.type": [member.value for member in PublicFieldType],
2738
+ "field.relation_mode": [member.value for member in PublicRelationMode],
2739
+ "field.department_scope.mode": ["all", "custom"],
2740
+ "field.code_block_binding.outputs.target_field.type": list(INTEGRATION_OUTPUT_TARGET_FIELD_TYPES),
2741
+ "field.q_linker_binding.outputs.target_field.type": list(INTEGRATION_OUTPUT_TARGET_FIELD_TYPES),
2742
+ "field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
2743
+ **deepcopy(_VISIBILITY_ALLOWED_VALUES),
2744
+ },
2745
+ "execution_notes": [
2746
+ "use exactly one resource mode",
2747
+ "edit mode: app_key, optional app_name to rename the existing app",
2748
+ "create mode: package_id + app_name + create_if_missing=true",
2749
+ "create mode defaults new app visibility to workspace/not when visibility is omitted; edit mode preserves current visibility when omitted",
2750
+ *_VISIBILITY_EXECUTION_NOTES,
2751
+ "multiple relation fields are backend-risky; read verification.relation_field_limit_verified and warnings before declaring the schema stable",
2752
+ "backend 49614 is normalized to MULTIPLE_RELATION_FIELDS_UNSUPPORTED with a workaround message",
2753
+ "relation_mode=multiple maps to referenceConfig.optionalDataNum=0",
2754
+ "relation fields now require both display_field and visible_fields in MCP/CLI payloads",
2755
+ "if relation target metadata lookup is blocked by 40161/40002/40027, explicit display_field.name and visible_fields[].name let builder degrade verification and still continue schema write",
2756
+ "update_fields[].set.subfield_updates is the safe patch path for editing existing subtable child fields without rebuilding the entire subtable",
2757
+ "subfield_updates only supports safe child overlays: name, required, description, and nested subfield_updates",
2758
+ "set.subfields remains the full replace/rebuild path for a subtable and is higher risk when hidden relation/reference children exist",
2759
+ "department fields accept department_scope with mode=all or mode=custom; custom scope requires explicit departments[].dept_id and optional include_sub_departs",
2760
+ "q_linker_binding lets you declare request config, dynamic inputs, alias parsing, and target-field bindings in one step; builder writes remoteLookupConfig plus the existing backend relation-default and questionRelations structures",
2761
+ "code_block_binding lets you declare inputs, code, alias parsing, and target-field bindings in one step; builder writes codeBlockConfig plus the existing backend relation-default and questionRelations structures",
2762
+ "builder configures code blocks only; it does not execute or trigger code blocks",
2763
+ "code block outputs must be emitted through qf_output assignment; use qf_output = {...} or assign qf_output after building the result object, never const/let qf_output =",
2764
+ "builder automatically normalizes const/let qf_output assignments on write and rejects output-bound code blocks that still do not contain a valid qf_output assignment",
2765
+ "code_block_binding and q_linker_binding target fields are limited to text, long_text, number, amount, date, datetime, single_select, multi_select, and boolean",
2766
+ ],
2767
+ "minimal_example": {
2768
+ "profile": "default",
2769
+ "app_name": "研发项目管理",
2770
+ "package_id": 1001,
2771
+ "icon": "template",
2772
+ "color": "emerald",
2773
+ "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
2774
+ "create_if_missing": True,
2775
+ "publish": True,
2776
+ "add_fields": [{"name": "项目名称", "type": "text"}],
2777
+ "update_fields": [],
2778
+ "remove_fields": [],
2779
+ },
2780
+ "rename_example": {
2781
+ "profile": "default",
2782
+ "app_key": "APP_PROJECT",
2783
+ "app_name": "项目主数据",
2784
+ "publish": True,
2785
+ "add_fields": [],
2786
+ "update_fields": [],
2787
+ "remove_fields": [],
2788
+ },
2789
+ "relation_example": {
2790
+ "profile": "default",
2791
+ "app_key": "APP_ITERATION",
2792
+ "publish": True,
2793
+ "add_fields": [
2794
+ {
2795
+ "name": "需求反馈引用",
2796
+ "type": "relation",
2797
+ "target_app_key": "APP_FEEDBACK",
2798
+ "relation_mode": "multiple",
2799
+ "display_field": {"name": "反馈标题"},
2800
+ "visible_fields": [{"name": "反馈标题"}, {"name": "优先级判断"}],
2801
+ }
2802
+ ],
2803
+ "update_fields": [],
2804
+ "remove_fields": [],
2805
+ },
2806
+ "subfield_update_example": {
2807
+ "profile": "default",
2808
+ "app_key": "APP_REWARD",
2809
+ "publish": True,
2810
+ "add_fields": [],
2811
+ "update_fields": [
2812
+ {
2813
+ "selector": {"que_id": 441305044},
2814
+ "set": {
2815
+ "subfield_updates": [
2816
+ {
2817
+ "selector": {"que_id": 441305045},
2818
+ "set": {"name": "景品名"},
2819
+ }
2820
+ ]
2821
+ },
2822
+ }
2823
+ ],
2824
+ "remove_fields": [],
2825
+ },
2826
+ "department_scope_example": {
2827
+ "profile": "default",
2828
+ "app_key": "APP_LEAD",
2829
+ "publish": True,
2830
+ "add_fields": [
2831
+ {
2832
+ "name": "归属部门",
2833
+ "type": "department",
2834
+ "department_scope": {
2835
+ "mode": "custom",
2836
+ "departments": [{"dept_id": 334952, "dept_name": "生产"}],
2837
+ "include_sub_departs": False,
2838
+ },
2839
+ }
2840
+ ],
2841
+ "update_fields": [],
2842
+ "remove_fields": [],
2843
+ },
2844
+ "code_block_example": {
2845
+ "profile": "default",
2846
+ "app_key": "APP_SCRIPT",
2847
+ "publish": True,
2848
+ "add_fields": [
2849
+ {"name": "客户等级", "type": "text"},
2850
+ {
2851
+ "name": "查询代码块",
2852
+ "type": "code_block",
2853
+ "code_block_binding": {
2854
+ "inputs": [
2855
+ {"field": {"name": "客户名称"}, "var": "customerName"},
2856
+ {"field": {"name": "预算金额"}, "var": "budget"},
2857
+ ],
2858
+ "code": "qf_output = {}; qf_output.customerLevel = budget > 100000 ? 'A' : 'B';",
2859
+ "auto_trigger": True,
2860
+ "custom_button_text_enabled": True,
2861
+ "custom_button_text": "评估客户",
2862
+ "outputs": [
2863
+ {"alias": "customerLevel", "path": "$.customerLevel", "target_field": {"name": "客户等级"}},
2864
+ ],
2865
+ },
2866
+ }
2867
+ ],
2868
+ "update_fields": [],
2869
+ "remove_fields": [],
2870
+ },
2871
+ "q_linker_example": {
2872
+ "profile": "default",
2873
+ "app_key": "APP_CUSTOMER",
2874
+ "publish": True,
2875
+ "add_fields": [
2876
+ {"name": "客户名称", "type": "text"},
2877
+ {"name": "企业名称", "type": "text"},
2878
+ {"name": "统一社会信用代码", "type": "text"},
2879
+ {
2880
+ "name": "企业信息查询",
2881
+ "type": "q_linker",
2882
+ "q_linker_binding": {
2883
+ "inputs": [
2884
+ {"field": {"name": "客户名称"}, "key": "keyword", "source": "query_param"},
2885
+ ],
2886
+ "request": {
2887
+ "url": "https://example.com/company/search",
2888
+ "method": "GET",
2889
+ "headers": [],
2890
+ "query_params": [],
2891
+ "body_type": 1,
2892
+ "url_encoded_value": [],
2893
+ "result_type": 1,
2894
+ "auto_trigger": True,
2895
+ "custom_button_text_enabled": True,
2896
+ "custom_button_text": "查询企业信息",
2897
+ },
2898
+ "outputs": [
2899
+ {"alias": "company_name", "path": "$.data.name", "target_field": {"name": "企业名称"}},
2900
+ {"alias": "credit_code", "path": "$.data.creditCode", "target_field": {"name": "统一社会信用代码"}},
2901
+ ],
2902
+ },
2903
+ },
2904
+ ],
2905
+ "update_fields": [],
2906
+ "remove_fields": [],
2907
+ },
2908
+ },
2909
+ "app_layout_plan": {
2910
+ "allowed_keys": ["app_key", "mode", "sections", "preset"],
2911
+ "aliases": {"overwrite": "replace", "sectionId": "section_id"},
2912
+ "section_allowed_keys": ["type", "paragraph_id", "section_id", "title", "rows"],
2913
+ "section_aliases": {
2914
+ "name": "title",
2915
+ "paragraphId": "paragraph_id",
2916
+ "sectionId": "section_id",
2917
+ "fields": "rows",
2918
+ "field_ids": "rows",
2919
+ "columns": "rows_chunk_size",
2920
+ },
2921
+ "allowed_values": {"mode": [member.value for member in LayoutApplyMode], "preset": [member.value for member in LayoutPreset]},
2922
+ "minimal_section_example": {"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]},
2923
+ "minimal_example": {
2924
+ "profile": "default",
2925
+ "app_key": "APP_KEY",
2926
+ "mode": "merge",
2927
+ "sections": [{"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]}],
2928
+ },
2929
+ "preset_example": {"profile": "default", "app_key": "APP_KEY", "mode": "merge", "preset": "balanced", "sections": []},
2930
+ },
2931
+ "app_layout_apply": {
2932
+ "allowed_keys": ["app_key", "mode", "publish", "sections"],
2933
+ "aliases": {"overwrite": "replace", "sectionId": "section_id"},
2934
+ "section_allowed_keys": ["type", "paragraph_id", "section_id", "title", "rows"],
2935
+ "section_aliases": {
2936
+ "name": "title",
2937
+ "paragraphId": "paragraph_id",
2938
+ "sectionId": "section_id",
2939
+ "fields": "rows",
2940
+ "field_ids": "rows",
2941
+ "columns": "rows_chunk_size",
2942
+ },
2943
+ "allowed_values": {"mode": [member.value for member in LayoutApplyMode]},
2944
+ "execution_notes": [
2945
+ "layout verification is split into layout_verified and layout_summary_verified",
2946
+ "LAYOUT_SUMMARY_UNVERIFIED means raw form readback is stronger than the compact summary",
2947
+ ],
2948
+ "minimal_section_example": {"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]},
2949
+ "minimal_example": {
2950
+ "profile": "default",
2951
+ "app_key": "APP_KEY",
2952
+ "mode": "merge",
2953
+ "publish": True,
2954
+ "sections": [{"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["项目名称", "项目负责人", "项目阶段", "优先级"]]}],
2955
+ },
2956
+ },
2957
+ "app_flow_plan": {
2958
+ "allowed_keys": ["app_key", "mode", "nodes", "transitions", "preset"],
2959
+ "aliases": {
2960
+ "overwrite": "replace",
2961
+ "base_preset": "preset",
2962
+ "default_approval": "basic_approval",
2963
+ "node.role_names": "node.assignees.role_names",
2964
+ "node.role_ids": "node.assignees.role_ids",
2965
+ "node.member_names": "node.assignees.member_names",
2966
+ "node.member_emails": "node.assignees.member_emails",
2967
+ "node.member_uids": "node.assignees.member_uids",
2968
+ "node.editable_fields": "node.permissions.editable_fields",
2969
+ "default_approval": "basic_approval",
2970
+ },
2971
+ "allowed_values": {
2972
+ "mode": ["replace"],
2973
+ "preset": [member.value for member in FlowPreset],
2974
+ "node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
2975
+ },
2976
+ "dependency_hints": [
2977
+ "approval-style workflows require an explicit status field",
2978
+ "approve/fill/copy nodes require at least one assignee",
2979
+ ],
2980
+ "execution_notes": [
2981
+ "public flow building is intentionally limited to linear workflows",
2982
+ "branch and condition nodes are disabled because the backend workflow route is not front-end stable for these node types",
2983
+ ],
2984
+ "minimal_example": {
2985
+ "profile": "default",
2986
+ "app_key": "APP_KEY",
2987
+ "mode": "replace",
2988
+ "preset": "basic_approval",
2989
+ "nodes": [
2990
+ {
2991
+ "id": "approve_1",
2992
+ "type": "approve",
2993
+ "name": "部门审批",
2994
+ "assignees": {"role_names": ["项目经理"]},
2995
+ "permissions": {"editable_fields": ["状态", "审批意见"]},
2996
+ }
2997
+ ],
2998
+ "transitions": [],
2999
+ },
3000
+ },
3001
+ "app_flow_apply": {
3002
+ "allowed_keys": ["app_key", "mode", "publish", "nodes", "transitions"],
3003
+ "aliases": {
3004
+ "overwrite": "replace",
3005
+ "node.role_names": "node.assignees.role_names",
3006
+ "node.role_ids": "node.assignees.role_ids",
3007
+ "node.member_names": "node.assignees.member_names",
3008
+ "node.member_emails": "node.assignees.member_emails",
3009
+ "node.member_uids": "node.assignees.member_uids",
3010
+ "node.editable_fields": "node.permissions.editable_fields",
3011
+ },
3012
+ "allowed_values": {
3013
+ "mode": ["replace"],
3014
+ "node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
3015
+ },
3016
+ "dependency_hints": [
3017
+ "approval-style workflows require an explicit status field",
3018
+ "approve/fill/copy nodes require at least one assignee",
3019
+ ],
3020
+ "execution_notes": [
3021
+ "public flow building is intentionally limited to linear workflows",
3022
+ "branch and condition nodes are disabled because the backend workflow route is not front-end stable for these node types",
3023
+ "workflow verification only covers linear node structure in the public tool surface",
3024
+ ],
3025
+ "minimal_example": {
3026
+ "profile": "default",
3027
+ "app_key": "APP_KEY",
3028
+ "mode": "replace",
3029
+ "publish": True,
3030
+ "nodes": [
3031
+ {"id": "start", "type": "start", "name": "发起"},
3032
+ {
3033
+ "id": "approve_1",
3034
+ "type": "approve",
3035
+ "name": "部门审批",
3036
+ "assignees": {"role_names": ["项目经理"]},
3037
+ "permissions": {"editable_fields": ["状态", "审批意见"]},
3038
+ },
3039
+ {"id": "end", "type": "end", "name": "结束"},
3040
+ ],
3041
+ "transitions": [{"from": "start", "to": "approve_1"}, {"from": "approve_1", "to": "end"}],
3042
+ },
3043
+ },
3044
+ "app_views_plan": {
3045
+ "allowed_keys": ["app_key", "upsert_views", "remove_views", "preset", "upsert_views[].view_key", "upsert_views[].buttons", "upsert_views[].visibility"],
3046
+ "aliases": {
3047
+ "fields": "columns",
3048
+ "column_names": "columns",
3049
+ "columnNames": "columns",
3050
+ "viewKey": "view_key",
3051
+ "tableView": "table",
3052
+ "cardView": "card",
3053
+ "kanban": "board",
3054
+ "filter_rules": "filters",
3055
+ "filterRules": "filters",
3056
+ "startField": "start_field",
3057
+ "endField": "end_field",
3058
+ "titleField": "title_field",
3059
+ "buttons[].buttonType": "buttons[].button_type",
3060
+ "buttons[].configType": "buttons[].config_type",
3061
+ "buttons[].buttonId": "buttons[].button_id",
3062
+ "buttons[].beingMain": "buttons[].being_main",
3063
+ "buttons[].buttonLimit": "buttons[].button_limit",
3064
+ "buttons[].buttonFormula": "buttons[].button_formula",
3065
+ "buttons[].buttonFormulaType": "buttons[].button_formula_type",
3066
+ "buttons[].printTpls": "buttons[].print_tpls",
3067
+ },
3068
+ "allowed_values": {
3069
+ "preset": [member.value for member in ViewsPreset],
3070
+ "view.type": [member.value for member in PublicViewType],
3071
+ "view.filter.operator": [member.value for member in ViewFilterOperator],
3072
+ "view.buttons.button_type": ["SYSTEM", "CUSTOM"],
3073
+ "view.buttons.config_type": ["TOP", "DETAIL"],
3074
+ **deepcopy(_VISIBILITY_ALLOWED_VALUES),
3075
+ },
3076
+ "execution_notes": [
3077
+ "upsert_views[].visibility may set per-view visibility; omit it to preserve an existing view's auth or default a new view to workspace/not",
3078
+ "for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
3079
+ *_VISIBILITY_EXECUTION_NOTES,
3080
+ ],
3081
+ "minimal_example": {
3082
+ "profile": "default",
3083
+ "app_key": "APP_KEY",
3084
+ "upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
3085
+ "remove_views": [],
3086
+ },
3087
+ "gantt_example": {
3088
+ "profile": "default",
3089
+ "app_key": "APP_KEY",
3090
+ "upsert_views": [
3091
+ {
3092
+ "name": "项目甘特图",
3093
+ "type": "gantt",
3094
+ "columns": ["项目名称", "开始日期", "结束日期"],
3095
+ "start_field": "开始日期",
3096
+ "end_field": "结束日期",
3097
+ "title_field": "项目名称",
3098
+ "filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
3099
+ }
3100
+ ],
3101
+ "remove_views": [],
3102
+ },
3103
+ },
3104
+ "app_views_apply": {
3105
+ "allowed_keys": ["app_key", "publish", "upsert_views", "remove_views", "upsert_views[].view_key", "upsert_views[].buttons", "upsert_views[].visibility"],
3106
+ "aliases": {
3107
+ "fields": "columns",
3108
+ "column_names": "columns",
3109
+ "columnNames": "columns",
3110
+ "viewKey": "view_key",
3111
+ "tableView": "table",
3112
+ "cardView": "card",
3113
+ "kanban": "board",
3114
+ "filter_rules": "filters",
3115
+ "filterRules": "filters",
3116
+ "startField": "start_field",
3117
+ "endField": "end_field",
3118
+ "titleField": "title_field",
3119
+ "buttons[].buttonType": "buttons[].button_type",
3120
+ "buttons[].configType": "buttons[].config_type",
3121
+ "buttons[].buttonId": "buttons[].button_id",
3122
+ "buttons[].beingMain": "buttons[].being_main",
3123
+ "buttons[].buttonLimit": "buttons[].button_limit",
3124
+ "buttons[].buttonFormula": "buttons[].button_formula",
3125
+ "buttons[].buttonFormulaType": "buttons[].button_formula_type",
3126
+ "buttons[].printTpls": "buttons[].print_tpls",
3127
+ },
3128
+ "allowed_values": {
3129
+ "view.type": [member.value for member in PublicViewType],
3130
+ "view.filter.operator": [member.value for member in ViewFilterOperator],
3131
+ "view.buttons.button_type": ["SYSTEM", "CUSTOM"],
3132
+ "view.buttons.config_type": ["TOP", "DETAIL"],
3133
+ **deepcopy(_VISIBILITY_ALLOWED_VALUES),
3134
+ },
3135
+ "execution_notes": [
3136
+ "apply may return partial_success when some views land and others fail",
3137
+ "when duplicate view names exist, supply view_key to target the exact view",
3138
+ "read back app_get_views after any failed or partial view apply",
3139
+ "view existence verification and saved-filter verification are separate; treat filters as unverified until verification.view_filters_verified is true",
3140
+ "buttons omitted preserves existing button config; buttons=[] clears all buttons; buttons=[...] replaces the full button config",
3141
+ "upsert_views[].visibility may set per-view visibility; omit it to preserve an existing view's auth or default a new view to workspace/not",
3142
+ "for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
3143
+ *_VISIBILITY_EXECUTION_NOTES,
3144
+ ],
3145
+ "minimal_example": {
3146
+ "profile": "default",
3147
+ "app_key": "APP_KEY",
3148
+ "publish": True,
3149
+ "upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
3150
+ "remove_views": [],
3151
+ },
3152
+ "gantt_example": {
3153
+ "profile": "default",
3154
+ "app_key": "APP_KEY",
3155
+ "publish": True,
3156
+ "upsert_views": [
3157
+ {
3158
+ "name": "项目甘特图",
3159
+ "type": "gantt",
3160
+ "columns": ["项目名称", "开始日期", "结束日期"],
3161
+ "start_field": "开始日期",
3162
+ "end_field": "结束日期",
3163
+ "title_field": "项目名称",
3164
+ "filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
3165
+ }
3166
+ ],
3167
+ "remove_views": [],
3168
+ },
3169
+ },
3170
+ "app_get": {
3171
+ "allowed_keys": ["app_key"],
3172
+ "aliases": {},
3173
+ "allowed_values": {},
3174
+ "execution_notes": [
3175
+ "returns builder-side app configuration summary and editability",
3176
+ "use this as the default builder discovery read before fields/layout/views/flow/charts detail reads",
3177
+ "editability is route-aware builder capability summary, not end-user data visibility",
3178
+ "can_edit_app_base covers app base-info writes such as app_name, icon, and visibility",
3179
+ "can_edit_form covers form/schema routes only and does not imply app base-info writes",
3180
+ "returns normalized app visibility when backend auth is readable",
3181
+ ],
3182
+ "minimal_example": {
3183
+ "profile": "default",
3184
+ "app_key": "APP_KEY",
3185
+ },
3186
+ },
3187
+ "app_get_fields": {
3188
+ "allowed_keys": ["app_key"],
3189
+ "aliases": {},
3190
+ "allowed_values": {},
3191
+ "execution_notes": [
3192
+ "returns compact current field configuration for one app",
3193
+ "use this before app_schema_apply when you need exact field definitions",
3194
+ "subtable fields include nested subfields using the same compact field shape",
3195
+ ],
3196
+ "minimal_example": {
3197
+ "profile": "default",
3198
+ "app_key": "APP_KEY",
3199
+ },
3200
+ },
3201
+ "app_get_layout": {
3202
+ "allowed_keys": ["app_key"],
3203
+ "aliases": {},
3204
+ "allowed_values": {},
3205
+ "execution_notes": [
3206
+ "returns compact current layout configuration for one app",
3207
+ "use this before app_layout_apply when you need paragraph and row structure",
3208
+ ],
3209
+ "minimal_example": {
3210
+ "profile": "default",
3211
+ "app_key": "APP_KEY",
3212
+ },
3213
+ },
3214
+ "app_get_views": {
3215
+ "allowed_keys": ["app_key"],
3216
+ "aliases": {},
3217
+ "allowed_values": {},
3218
+ "execution_notes": [
3219
+ "returns compact current view inventory for one app",
3220
+ "use this before app_views_apply when you need exact current view keys",
3221
+ "view items include visibility_summary when backend view auth is readable",
3222
+ ],
3223
+ "minimal_example": {
3224
+ "profile": "default",
3225
+ "app_key": "APP_KEY",
3226
+ },
3227
+ },
3228
+ "app_get_flow": {
3229
+ "allowed_keys": ["app_key"],
3230
+ "aliases": {},
3231
+ "allowed_values": {},
3232
+ "execution_notes": [
3233
+ "returns workflow configuration summary for one app",
3234
+ "use this before app_flow_apply when you need the current node structure",
3235
+ ],
3236
+ "minimal_example": {
3237
+ "profile": "default",
3238
+ "app_key": "APP_KEY",
3239
+ },
3240
+ },
3241
+ "app_get_charts": {
3242
+ "allowed_keys": ["app_key"],
3243
+ "aliases": {},
3244
+ "allowed_values": {},
3245
+ "execution_notes": [
3246
+ "returns a compact current chart inventory for one app",
3247
+ "use this before app_charts_apply when you need exact current chart_id values",
3248
+ "chart summaries do not include full qingbi config payloads",
3249
+ "chart items include visibility_summary when QingBI base info is readable",
3250
+ ],
3251
+ "minimal_example": {
3252
+ "profile": "default",
3253
+ "app_key": "APP_KEY",
3254
+ },
3255
+ },
3256
+ "portal_list": {
3257
+ "allowed_keys": [],
3258
+ "aliases": {},
3259
+ "allowed_values": {},
3260
+ "execution_notes": [
3261
+ "returns builder-configurable portal list items only",
3262
+ "use this as the builder portal discovery path before portal_get",
3263
+ "results are compact list items, not raw dash payloads",
3264
+ ],
3265
+ "minimal_example": {
3266
+ "profile": "default",
3267
+ },
3268
+ },
3269
+ "portal_get": {
3270
+ "allowed_keys": ["dash_key", "being_draft"],
3271
+ "aliases": {"beingDraft": "being_draft"},
3272
+ "allowed_values": {},
3273
+ "execution_notes": [
3274
+ "returns builder-side portal configuration detail plus a normalized component inventory",
3275
+ "chart and view components are returned as refs only; use builder chart_get or builder view_get for configuration detail",
3276
+ "being_draft=true reads the current draft view; being_draft=false reads live",
3277
+ "returns normalized portal visibility when backend auth is readable",
3278
+ ],
3279
+ "minimal_example": {
3280
+ "profile": "default",
3281
+ "dash_key": "DASH_KEY",
3282
+ "being_draft": True,
3283
+ },
3284
+ },
3285
+ "app_charts_apply": {
3286
+ "allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids", "upsert_charts[].visibility"],
3287
+ "aliases": {
3288
+ "chart.id": "chart.chart_id",
3289
+ "chart.type": "chart.chart_type",
3290
+ "chart.dimension_fields": "chart.dimension_field_ids",
3291
+ "chart.indicator_fields": "chart.indicator_field_ids",
3292
+ "chart.metric_field_ids": "chart.indicator_field_ids",
3293
+ "chart.filter.op": "chart.filter.operator",
3294
+ },
3295
+ "allowed_values": {
3296
+ "chart.chart_type": [member.value for member in PublicChartType],
3297
+ "chart.filter.operator": [member.value for member in ViewFilterOperator],
3298
+ **deepcopy(_VISIBILITY_ALLOWED_VALUES),
3299
+ },
3300
+ "execution_notes": [
3301
+ "app_charts_apply is immediate-live and does not publish",
3302
+ "chart matching precedence is chart_id first, then exact unique chart name",
3303
+ "when chart names are not unique, supply chart_id instead of guessing by name",
3304
+ "successful create results must return a real backend chart_id",
3305
+ "upsert_charts[].visibility compiles to QingBI base visibleAuth only",
3306
+ "visibility-only updates keep the existing chart config and do not rewrite rawDataConfigDTO.authInfo",
3307
+ *_VISIBILITY_EXECUTION_NOTES,
3308
+ ],
3309
+ "minimal_example": {
3310
+ "profile": "default",
3311
+ "app_key": "APP_KEY",
3312
+ "upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": [], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
3313
+ "remove_chart_ids": [],
3314
+ "reorder_chart_ids": [],
3315
+ },
3316
+ },
3317
+ "view_get": {
3318
+ "allowed_keys": ["view_key"],
3319
+ "aliases": {},
3320
+ "allowed_values": {},
3321
+ "execution_notes": [
3322
+ "returns one builder-side view definition detail",
3323
+ "does not return record data; use user-side view_get or record_list for runtime rows",
3324
+ "use this after builder portal_get when a component references a view_ref.view_key",
3325
+ "returns normalized view visibility when backend auth is readable",
3326
+ ],
3327
+ "minimal_example": {
3328
+ "profile": "default",
3329
+ "view_key": "VIEW_KEY",
3330
+ },
3331
+ },
3332
+ "chart_get": {
3333
+ "allowed_keys": ["chart_id"],
3334
+ "aliases": {},
3335
+ "allowed_values": {},
3336
+ "execution_notes": [
3337
+ "returns builder-side chart base info and chart config only",
3338
+ "chart_id is required; chart names are not accepted here",
3339
+ "does not return chart data; use user-side chart_get for runtime data access",
3340
+ "returns normalized chart visibility from QingBI visibleAuth when readable",
3341
+ ],
3342
+ "minimal_example": {
3343
+ "profile": "default",
3344
+ "chart_id": "CHART_ID",
3345
+ },
3346
+ },
3347
+ "portal_apply": {
3348
+ "allowed_keys": ["dash_key", "dash_name", "package_id", "publish", "sections", "visibility", "auth", "icon", "color", "hide_copyright", "dash_global_config", "config"],
3349
+ "aliases": {
3350
+ "packageId": "package_id",
3351
+ "sourceType": "source_type",
3352
+ "chartRef": "chart_ref",
3353
+ "viewRef": "view_ref",
3354
+ "dashStyleConfigBO": "dash_style_config",
3355
+ },
3356
+ "section_allowed_keys": ["title", "source_type", "position", "dash_style_config", "config", "chart_ref", "view_ref", "text", "url"],
3357
+ "section_aliases": {
3358
+ "sourceType": "source_type",
3359
+ "chartRef": "chart_ref",
3360
+ "viewRef": "view_ref",
3361
+ "dashStyleConfigBO": "dash_style_config",
3362
+ },
3363
+ "allowed_values": {"section.source_type": ["chart", "view", "grid", "filter", "text", "link"], **deepcopy(_VISIBILITY_ALLOWED_VALUES)},
3364
+ "execution_notes": [
3365
+ "use exactly one resource mode",
3366
+ "update mode: dash_key",
3367
+ "create mode: package_id + dash_name",
3368
+ "portal_apply uses replace semantics for sections",
3369
+ "when editing an existing portal, sections may be omitted to update only base info such as visibility, icon, or package",
3370
+ "remove a section by omitting it from the new sections list",
3371
+ "package_id is required when creating a new portal",
3372
+ "publish=false only guarantees draft and base-info updates; it does not claim live has changed",
3373
+ "chart_ref resolves by chart_id first, then exact unique chart_name",
3374
+ "view_ref resolves by view_key first, then exact unique view_name",
3375
+ "position.pc/mobile is the canonical portal layout shape",
3376
+ "visibility is the canonical public auth shape; auth is kept only as a deprecated compatibility alias",
3377
+ "passing visibility and auth together is rejected as VISIBILITY_CONFLICT",
3378
+ *_VISIBILITY_EXECUTION_NOTES,
3379
+ ],
3380
+ "minimal_example": {
3381
+ "profile": "default",
3382
+ "dash_name": "经营门户",
3383
+ "package_id": 1001,
3384
+ "publish": True,
3385
+ "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
3386
+ "sections": [
3387
+ {
3388
+ "title": "经营概览",
3389
+ "source_type": "chart",
3390
+ "chart_ref": {"app_key": "APP_KEY", "chart_name": "数据总量"},
3391
+ "position": {
3392
+ "pc": {"x": 0, "y": 0, "cols": 12, "rows": 6},
3393
+ "mobile": {"x": 0, "y": 0, "cols": 6, "rows": 6},
3394
+ },
3395
+ }
3396
+ ],
3397
+ },
3398
+ "minimal_section_example": {
3399
+ "title": "订单概览",
3400
+ "source_type": "view",
3401
+ "view_ref": {"app_key": "APP_KEY", "view_key": "VIEW_KEY"},
3402
+ "position": {
3403
+ "pc": {"x": 0, "y": 0, "cols": 24, "rows": 8},
3404
+ "mobile": {"x": 0, "y": 0, "cols": 6, "rows": 8},
3405
+ },
3406
+ },
3407
+ },
3408
+ "app_publish_verify": {
3409
+ "allowed_keys": ["app_key", "expected_package_id"],
3410
+ "aliases": {},
3411
+ "allowed_values": {},
3412
+ "execution_notes": [
3413
+ "verifies that the current app draft has been published and is readable through the builder surface",
3414
+ "expected_package_id is optional and adds an extra package consistency check",
3415
+ "when verification fails because of an edit lock owned by the current user, retry after app_release_edit_lock_if_mine",
3416
+ ],
3417
+ "minimal_example": {
3418
+ "profile": "default",
3419
+ "app_key": "APP_KEY",
3420
+ "expected_package_id": 1001,
3421
+ },
3422
+ },
3423
+ "app_repair_code_blocks": {
3424
+ "allowed_keys": ["app_key", "field", "apply"],
3425
+ "aliases": {},
3426
+ "allowed_values": {},
3427
+ "execution_notes": [
3428
+ "scan existing code_block fields for qf_output shadowing, missing output assignment, and broken writeback defaults",
3429
+ "apply=false is a dry-run and returns a repair plan without writing builder config",
3430
+ "apply=true rewrites only safe cases: const/let qf_output shadowing and stale relation-default output bindings reconstructed from readable code_block_binding config",
3431
+ "this tool does not invent missing output aliases or guess missing target bindings when only low-level codeBlockConfig is present",
3432
+ ],
3433
+ "minimal_example": {
3434
+ "profile": "default",
3435
+ "app_key": "APP_KEY",
3436
+ "field": "线索价值评估",
3437
+ "apply": False,
3438
+ },
3439
+ },
3440
+ }
3441
+
3442
+ _PRIVATE_BUILDER_TOOL_CONTRACTS = {
3443
+ "app_schema_plan",
3444
+ "app_layout_plan",
3445
+ "app_flow_plan",
3446
+ "app_views_plan",
3447
+ }
3448
+
3449
+ _BUILDER_TOOL_CONTRACT_ALIASES = {}