@josephyan/qingflow-cli 1.0.11 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/npm/bin/qingflow.mjs +40 -2
- package/npm/lib/runtime.mjs +386 -15
- package/npm/scripts/postinstall.mjs +7 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +440 -0
- package/skills/qingflow-cli/manifest.yaml +10 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +485 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md +237 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_MATCH_RULES.md +137 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md +263 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +304 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md +41 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +20 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +163 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +107 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +151 -0
- package/skills/qingflow-cli/reference/_batch_schema_complex.json +18 -0
- package/skills/qingflow-cli/reference/_batch_schema_scalar.json +17 -0
- package/skills/qingflow-cli/reference/charts_remove.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_reorder.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_upsert_bar.example.json +8 -0
- package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +37 -0
- package/skills/qingflow-cli/reference/charts_upsert_minimal.example.json +13 -0
- package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +131 -0
- package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +126 -0
- package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +128 -0
- package/skills/qingflow-cli/reference/schema_add_fields_minimal.example.json +7 -0
- package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +78 -0
- package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +7 -0
- package/skills/qingflow-cli/scripts/builder-package-from-app-list.py +140 -0
- package/skills/qingflow-cli/scripts/find-app-by-keyword.py +132 -0
- package/skills/qingflow-cli/scripts/validate_qingflow_output_files.py +87 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +532 -48
- package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
- package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
- package/src/qingflow_mcp/cli/commands/app.py +3 -16
- package/src/qingflow_mcp/cli/commands/builder.py +354 -56
- package/src/qingflow_mcp/cli/commands/record.py +89 -2
- package/src/qingflow_mcp/cli/formatters.py +32 -1
- package/src/qingflow_mcp/cli/main.py +245 -3
- package/src/qingflow_mcp/public_surface.py +11 -8
- package/src/qingflow_mcp/response_trim.py +143 -14
- package/src/qingflow_mcp/server.py +15 -12
- package/src/qingflow_mcp/server_app_builder.py +108 -30
- package/src/qingflow_mcp/server_app_user.py +17 -18
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
- package/src/qingflow_mcp/solution/executor.py +3 -133
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
- package/src/qingflow_mcp/tools/app_tools.py +53 -8
- package/src/qingflow_mcp/tools/package_tools.py +16 -2
- package/src/qingflow_mcp/tools/record_tools.py +2095 -176
- package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
- package/src/qingflow_mcp/tools/solution_tools.py +30 -2
- package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
- package/src/qingflow_mcp/version.py +110 -0
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from copy import deepcopy
|
|
4
4
|
import json
|
|
5
5
|
import time
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
8
|
from pydantic import ValidationError
|
|
8
9
|
|
|
@@ -17,21 +18,20 @@ from ..config import DEFAULT_PROFILE
|
|
|
17
18
|
from ..errors import QingflowApiError
|
|
18
19
|
from ..json_types import JSONObject
|
|
19
20
|
from ..builder_facade.models import (
|
|
21
|
+
AssociatedResourcesApplyRequest,
|
|
20
22
|
ChartApplyRequest,
|
|
23
|
+
CustomButtonsApplyRequest,
|
|
21
24
|
CustomButtonPatch,
|
|
22
25
|
FIELD_TYPE_ID_ALIASES,
|
|
23
26
|
FieldPatch,
|
|
24
27
|
FieldRemovePatch,
|
|
25
28
|
FieldUpdatePatch,
|
|
26
|
-
FlowPreset,
|
|
27
|
-
FlowNodePatch,
|
|
28
|
-
FlowPlanRequest,
|
|
29
|
-
FlowTransitionPatch,
|
|
30
29
|
LayoutApplyMode,
|
|
31
30
|
LayoutPlanRequest,
|
|
32
31
|
LayoutPreset,
|
|
33
32
|
LayoutSectionPatch,
|
|
34
33
|
PortalApplyRequest,
|
|
34
|
+
PublicButtonPlacement,
|
|
35
35
|
PublicButtonTriggerAction,
|
|
36
36
|
PublicFieldType,
|
|
37
37
|
PublicRelationMode,
|
|
@@ -40,11 +40,21 @@ from ..builder_facade.models import (
|
|
|
40
40
|
SchemaPlanRequest,
|
|
41
41
|
VisibilityPatch,
|
|
42
42
|
ViewFilterOperator,
|
|
43
|
+
ViewPartialPatch,
|
|
43
44
|
ViewUpsertPatch,
|
|
44
45
|
ViewsPreset,
|
|
45
46
|
ViewsPlanRequest,
|
|
46
47
|
)
|
|
47
48
|
from ..builder_facade.service import AiBuilderFacade, INTEGRATION_OUTPUT_TARGET_FIELD_TYPES
|
|
49
|
+
from ..solution.compiler.icon_utils import (
|
|
50
|
+
GENERIC_WORKSPACE_ICON_NAMES,
|
|
51
|
+
WORKSPACE_ICON_COLORS,
|
|
52
|
+
WORKSPACE_ICON_NAMES,
|
|
53
|
+
normalize_workspace_icon_name,
|
|
54
|
+
validate_workspace_icon_choice,
|
|
55
|
+
workspace_icon_catalog_payload,
|
|
56
|
+
workspace_icon_config,
|
|
57
|
+
)
|
|
48
58
|
from .app_tools import AppTools
|
|
49
59
|
from .base import ToolBase, tool_cn_name
|
|
50
60
|
from .custom_button_tools import CustomButtonTools
|
|
@@ -55,9 +65,28 @@ from .qingbi_report_tools import QingbiReportTools
|
|
|
55
65
|
from .role_tools import RoleTools
|
|
56
66
|
from .solution_tools import SolutionTools
|
|
57
67
|
from .view_tools import ViewTools
|
|
58
|
-
from .workflow_tools import WorkflowTools
|
|
59
68
|
|
|
60
|
-
|
|
69
|
+
|
|
70
|
+
def _normalize_builder_view_key(value: str) -> str:
|
|
71
|
+
raw = str(value or "").strip()
|
|
72
|
+
if raw.startswith("custom:"):
|
|
73
|
+
return raw.split(":", 1)[1].strip()
|
|
74
|
+
return raw
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
BUILDER_APPLY_SCHEMA_VERSION = "builder.apply.v1"
|
|
78
|
+
BUILDER_APPLY_TOOL_NAMES = {
|
|
79
|
+
"package_apply",
|
|
80
|
+
"app_schema_apply",
|
|
81
|
+
"app_layout_apply",
|
|
82
|
+
"app_flow_apply",
|
|
83
|
+
"app_views_apply",
|
|
84
|
+
"app_custom_buttons_apply",
|
|
85
|
+
"app_associated_resources_apply",
|
|
86
|
+
"app_charts_apply",
|
|
87
|
+
"portal_apply",
|
|
88
|
+
"app_publish_verify",
|
|
89
|
+
}
|
|
61
90
|
|
|
62
91
|
|
|
63
92
|
class AiBuilderTools(ToolBase):
|
|
@@ -78,7 +107,6 @@ class AiBuilderTools(ToolBase):
|
|
|
78
107
|
buttons=CustomButtonTools(sessions, backend),
|
|
79
108
|
packages=PackageTools(sessions, backend),
|
|
80
109
|
views=ViewTools(sessions, backend),
|
|
81
|
-
workflows=WorkflowTools(sessions, backend),
|
|
82
110
|
portals=PortalTools(sessions, backend),
|
|
83
111
|
charts=QingbiReportTools(sessions, backend),
|
|
84
112
|
roles=RoleTools(sessions, backend),
|
|
@@ -92,6 +120,14 @@ class AiBuilderTools(ToolBase):
|
|
|
92
120
|
def builder_tool_contract(tool_name: str = "") -> JSONObject:
|
|
93
121
|
return self.builder_tool_contract(tool_name=tool_name)
|
|
94
122
|
|
|
123
|
+
@mcp.tool()
|
|
124
|
+
def workspace_icon_catalog_get(profile: str = DEFAULT_PROFILE) -> JSONObject:
|
|
125
|
+
return self.workspace_icon_catalog_get(profile=profile)
|
|
126
|
+
|
|
127
|
+
@mcp.tool()
|
|
128
|
+
def package_list(profile: str = DEFAULT_PROFILE, trial_status: str = "all", query: str = "") -> JSONObject:
|
|
129
|
+
return self.package_list(profile=profile, trial_status=trial_status, query=query)
|
|
130
|
+
|
|
95
131
|
@mcp.tool()
|
|
96
132
|
def package_get(profile: str = DEFAULT_PROFILE, package_id: int = 0) -> JSONObject:
|
|
97
133
|
return self.package_get(profile=profile, package_id=package_id)
|
|
@@ -220,40 +256,53 @@ class AiBuilderTools(ToolBase):
|
|
|
220
256
|
return self.button_style_catalog_get(profile=profile)
|
|
221
257
|
|
|
222
258
|
@mcp.tool()
|
|
223
|
-
def
|
|
224
|
-
return self.app_custom_button_list(profile=profile, app_key=app_key)
|
|
225
|
-
|
|
226
|
-
@mcp.tool()
|
|
227
|
-
def app_custom_button_get(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> JSONObject:
|
|
228
|
-
return self.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id)
|
|
229
|
-
|
|
230
|
-
@mcp.tool()
|
|
231
|
-
def app_custom_button_create(
|
|
259
|
+
def app_custom_buttons_apply(
|
|
232
260
|
profile: str = DEFAULT_PROFILE,
|
|
233
261
|
app_key: str = "",
|
|
234
|
-
|
|
262
|
+
upsert_buttons: list[JSONObject] | None = None,
|
|
263
|
+
patch_buttons: list[JSONObject] | None = None,
|
|
264
|
+
remove_buttons: list[JSONObject] | None = None,
|
|
265
|
+
view_configs: list[JSONObject] | None = None,
|
|
266
|
+
apps: list[JSONObject] | None = None,
|
|
235
267
|
) -> JSONObject:
|
|
236
|
-
return self.
|
|
268
|
+
return self.app_custom_buttons_apply(
|
|
269
|
+
profile=profile,
|
|
270
|
+
app_key=app_key,
|
|
271
|
+
upsert_buttons=upsert_buttons or [],
|
|
272
|
+
patch_buttons=patch_buttons or [],
|
|
273
|
+
remove_buttons=remove_buttons or [],
|
|
274
|
+
view_configs=view_configs or [],
|
|
275
|
+
apps=apps,
|
|
276
|
+
)
|
|
237
277
|
|
|
238
278
|
@mcp.tool()
|
|
239
|
-
def
|
|
279
|
+
def app_associated_resources_apply(
|
|
240
280
|
profile: str = DEFAULT_PROFILE,
|
|
241
281
|
app_key: str = "",
|
|
242
|
-
|
|
243
|
-
|
|
282
|
+
upsert_resources: list[JSONObject] | None = None,
|
|
283
|
+
patch_resources: list[JSONObject] | None = None,
|
|
284
|
+
remove_associated_item_ids: list[int] | None = None,
|
|
285
|
+
reorder_associated_item_ids: list[int] | None = None,
|
|
286
|
+
view_configs: list[JSONObject] | None = None,
|
|
244
287
|
) -> JSONObject:
|
|
245
|
-
return self.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
288
|
+
return self.app_associated_resources_apply(
|
|
289
|
+
profile=profile,
|
|
290
|
+
app_key=app_key,
|
|
291
|
+
upsert_resources=upsert_resources or [],
|
|
292
|
+
patch_resources=patch_resources or [],
|
|
293
|
+
remove_associated_item_ids=remove_associated_item_ids or [],
|
|
294
|
+
reorder_associated_item_ids=reorder_associated_item_ids or [],
|
|
295
|
+
view_configs=view_configs or [],
|
|
296
|
+
)
|
|
250
297
|
|
|
251
298
|
@mcp.tool()
|
|
252
299
|
def app_get(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
253
300
|
return self.app_get(profile=profile, app_key=app_key)
|
|
254
301
|
|
|
255
302
|
@mcp.tool()
|
|
256
|
-
def app_get_fields(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
303
|
+
def app_get_fields(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
304
|
+
if app_keys:
|
|
305
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_fields, data_key="fields", tool_name="app_get_fields")
|
|
257
306
|
return self.app_get_fields(profile=profile, app_key=app_key)
|
|
258
307
|
|
|
259
308
|
@mcp.tool()
|
|
@@ -266,21 +315,41 @@ class AiBuilderTools(ToolBase):
|
|
|
266
315
|
return self.app_repair_code_blocks(profile=profile, app_key=app_key, field=field, apply=apply)
|
|
267
316
|
|
|
268
317
|
@mcp.tool()
|
|
269
|
-
def app_get_layout(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
318
|
+
def app_get_layout(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
319
|
+
if app_keys:
|
|
320
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_layout, data_key="sections", tool_name="app_get_layout")
|
|
270
321
|
return self.app_get_layout(profile=profile, app_key=app_key)
|
|
271
322
|
|
|
272
323
|
@mcp.tool()
|
|
273
|
-
def app_get_views(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
324
|
+
def app_get_views(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
325
|
+
if app_keys:
|
|
326
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_views, data_key="views", tool_name="app_get_views")
|
|
274
327
|
return self.app_get_views(profile=profile, app_key=app_key)
|
|
275
328
|
|
|
276
329
|
@mcp.tool()
|
|
277
|
-
def app_get_flow(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
330
|
+
def app_get_flow(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
331
|
+
if app_keys:
|
|
332
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_flow, data_key="spec", tool_name="app_get_flow")
|
|
278
333
|
return self.app_get_flow(profile=profile, app_key=app_key)
|
|
279
334
|
|
|
280
335
|
@mcp.tool()
|
|
281
|
-
def app_get_charts(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
336
|
+
def app_get_charts(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
337
|
+
if app_keys:
|
|
338
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_charts, data_key="charts", tool_name="app_get_charts")
|
|
282
339
|
return self.app_get_charts(profile=profile, app_key=app_key)
|
|
283
340
|
|
|
341
|
+
@mcp.tool()
|
|
342
|
+
def app_get_buttons(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
343
|
+
if app_keys:
|
|
344
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_buttons, data_key="buttons", tool_name="app_get_buttons")
|
|
345
|
+
return self._facade.app_get_buttons(profile=profile, app_key=app_key)
|
|
346
|
+
|
|
347
|
+
@mcp.tool()
|
|
348
|
+
def app_get_associated_resources(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
349
|
+
if app_keys:
|
|
350
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_associated_resources, data_key="associated_resources", tool_name="app_get_associated_resources")
|
|
351
|
+
return self._facade.app_get_associated_resources(profile=profile, app_key=app_key)
|
|
352
|
+
|
|
284
353
|
@mcp.tool()
|
|
285
354
|
def portal_list(profile: str = DEFAULT_PROFILE) -> JSONObject:
|
|
286
355
|
return self.portal_list(profile=profile)
|
|
@@ -319,7 +388,32 @@ class AiBuilderTools(ToolBase):
|
|
|
319
388
|
add_fields: list[JSONObject] | None = None,
|
|
320
389
|
update_fields: list[JSONObject] | None = None,
|
|
321
390
|
remove_fields: list[JSONObject] | None = None,
|
|
391
|
+
apps: list[JSONObject] | None = None,
|
|
322
392
|
) -> JSONObject:
|
|
393
|
+
if apps:
|
|
394
|
+
if app_key or app_name or app_title or add_fields or update_fields or remove_fields:
|
|
395
|
+
return _config_failure(
|
|
396
|
+
tool_name="app_schema_apply",
|
|
397
|
+
message="app_schema_apply multi-app mode accepts package_id/create_if_missing plus apps only.",
|
|
398
|
+
fix_hint="Use `apps` for batch mode, or use the single-app arguments without `apps`.",
|
|
399
|
+
)
|
|
400
|
+
if package_id is None:
|
|
401
|
+
return _config_failure(
|
|
402
|
+
tool_name="app_schema_apply",
|
|
403
|
+
message="app_schema_apply multi-app mode requires package_id.",
|
|
404
|
+
fix_hint="Pass package_id and apps[].app_name for new apps, or apps[].app_key for existing apps.",
|
|
405
|
+
)
|
|
406
|
+
return self.app_schema_apply(
|
|
407
|
+
profile=profile,
|
|
408
|
+
package_id=package_id,
|
|
409
|
+
visibility=visibility,
|
|
410
|
+
create_if_missing=create_if_missing,
|
|
411
|
+
publish=publish,
|
|
412
|
+
apps=apps,
|
|
413
|
+
add_fields=[],
|
|
414
|
+
update_fields=[],
|
|
415
|
+
remove_fields=[],
|
|
416
|
+
)
|
|
323
417
|
has_app_key = bool((app_key or "").strip())
|
|
324
418
|
has_app_name = bool((app_name or "").strip())
|
|
325
419
|
has_app_title = bool((app_title or "").strip())
|
|
@@ -351,6 +445,7 @@ class AiBuilderTools(ToolBase):
|
|
|
351
445
|
add_fields=add_fields or [],
|
|
352
446
|
update_fields=update_fields or [],
|
|
353
447
|
remove_fields=remove_fields or [],
|
|
448
|
+
apps=[],
|
|
354
449
|
)
|
|
355
450
|
|
|
356
451
|
@mcp.tool()
|
|
@@ -360,25 +455,40 @@ class AiBuilderTools(ToolBase):
|
|
|
360
455
|
mode: str = "merge",
|
|
361
456
|
publish: bool = True,
|
|
362
457
|
sections: list[JSONObject] | None = None,
|
|
458
|
+
apps: list[JSONObject] | None = None,
|
|
459
|
+
) -> JSONObject:
|
|
460
|
+
return self.app_layout_apply(profile=profile, app_key=app_key, mode=mode, publish=publish, sections=sections or [], apps=apps)
|
|
461
|
+
|
|
462
|
+
@mcp.tool()
|
|
463
|
+
def app_flow_get(
|
|
464
|
+
profile: str = DEFAULT_PROFILE,
|
|
465
|
+
app_key: str = "",
|
|
466
|
+
version_id: str = "",
|
|
363
467
|
) -> JSONObject:
|
|
364
|
-
return self.
|
|
468
|
+
return self.app_get_flow(profile=profile, app_key=app_key, version_id=version_id or None)
|
|
469
|
+
|
|
470
|
+
@mcp.tool()
|
|
471
|
+
def app_flow_get_schema(profile: str = DEFAULT_PROFILE, schema_version: str = "") -> JSONObject:
|
|
472
|
+
return self.app_flow_get_schema(profile=profile, schema_version=schema_version or None)
|
|
365
473
|
|
|
366
474
|
@mcp.tool()
|
|
367
475
|
def app_flow_apply(
|
|
368
476
|
profile: str = DEFAULT_PROFILE,
|
|
369
477
|
app_key: str = "",
|
|
370
|
-
mode: str = "replace",
|
|
371
478
|
publish: bool = True,
|
|
372
|
-
|
|
373
|
-
|
|
479
|
+
spec: JSONObject | None = None,
|
|
480
|
+
idempotency_key: str = "",
|
|
481
|
+
schema_version: str = "",
|
|
482
|
+
patch_nodes: list[JSONObject] | None = None,
|
|
374
483
|
) -> JSONObject:
|
|
375
484
|
return self.app_flow_apply(
|
|
376
485
|
profile=profile,
|
|
377
486
|
app_key=app_key,
|
|
378
|
-
mode=mode,
|
|
379
487
|
publish=publish,
|
|
380
|
-
|
|
381
|
-
|
|
488
|
+
spec=spec or {},
|
|
489
|
+
idempotency_key=idempotency_key or None,
|
|
490
|
+
schema_version=schema_version or None,
|
|
491
|
+
patch_nodes=patch_nodes,
|
|
382
492
|
)
|
|
383
493
|
|
|
384
494
|
@mcp.tool()
|
|
@@ -387,14 +497,18 @@ class AiBuilderTools(ToolBase):
|
|
|
387
497
|
app_key: str = "",
|
|
388
498
|
publish: bool = True,
|
|
389
499
|
upsert_views: list[JSONObject] | None = None,
|
|
500
|
+
patch_views: list[JSONObject] | None = None,
|
|
390
501
|
remove_views: list[str] | None = None,
|
|
502
|
+
apps: list[JSONObject] | None = None,
|
|
391
503
|
) -> JSONObject:
|
|
392
504
|
return self.app_views_apply(
|
|
393
505
|
profile=profile,
|
|
394
506
|
app_key=app_key,
|
|
395
507
|
publish=publish,
|
|
396
508
|
upsert_views=upsert_views or [],
|
|
509
|
+
patch_views=patch_views or [],
|
|
397
510
|
remove_views=remove_views or [],
|
|
511
|
+
apps=apps,
|
|
398
512
|
)
|
|
399
513
|
|
|
400
514
|
@mcp.tool()
|
|
@@ -402,15 +516,19 @@ class AiBuilderTools(ToolBase):
|
|
|
402
516
|
profile: str = DEFAULT_PROFILE,
|
|
403
517
|
app_key: str = "",
|
|
404
518
|
upsert_charts: list[JSONObject] | None = None,
|
|
519
|
+
patch_charts: list[JSONObject] | None = None,
|
|
405
520
|
remove_chart_ids: list[str] | None = None,
|
|
406
521
|
reorder_chart_ids: list[str] | None = None,
|
|
522
|
+
apps: list[JSONObject] | None = None,
|
|
407
523
|
) -> JSONObject:
|
|
408
524
|
return self.app_charts_apply(
|
|
409
525
|
profile=profile,
|
|
410
526
|
app_key=app_key,
|
|
411
527
|
upsert_charts=upsert_charts or [],
|
|
528
|
+
patch_charts=patch_charts or [],
|
|
412
529
|
remove_chart_ids=remove_chart_ids or [],
|
|
413
530
|
reorder_chart_ids=reorder_chart_ids or [],
|
|
531
|
+
apps=apps,
|
|
414
532
|
)
|
|
415
533
|
|
|
416
534
|
@mcp.tool()
|
|
@@ -418,9 +536,12 @@ class AiBuilderTools(ToolBase):
|
|
|
418
536
|
profile: str = DEFAULT_PROFILE,
|
|
419
537
|
dash_key: str = "",
|
|
420
538
|
dash_name: str = "",
|
|
539
|
+
name: str = "",
|
|
421
540
|
package_id: int | None = None,
|
|
422
541
|
publish: bool = True,
|
|
423
542
|
sections: list[JSONObject] | None = None,
|
|
543
|
+
pages: list[JSONObject] | None = None,
|
|
544
|
+
layout_preset: str = "",
|
|
424
545
|
visibility: JSONObject | None = None,
|
|
425
546
|
auth: JSONObject | None = None,
|
|
426
547
|
icon: str | None = None,
|
|
@@ -428,10 +549,15 @@ class AiBuilderTools(ToolBase):
|
|
|
428
549
|
hide_copyright: bool | None = None,
|
|
429
550
|
dash_global_config: JSONObject | None = None,
|
|
430
551
|
config: JSONObject | None = None,
|
|
552
|
+
payload: JSONObject | None = None,
|
|
553
|
+
patch_sections: list[JSONObject] | None = None,
|
|
431
554
|
) -> JSONObject:
|
|
555
|
+
payload = payload if isinstance(payload, dict) else {}
|
|
432
556
|
has_dash_key = bool((dash_key or "").strip())
|
|
433
|
-
|
|
434
|
-
|
|
557
|
+
effective_dash_name = (dash_name or name or str(payload.get("dash_name") or payload.get("dashName") or payload.get("name") or "")).strip()
|
|
558
|
+
has_dash_name = bool(effective_dash_name)
|
|
559
|
+
effective_package_id = package_id if package_id is not None else payload.get("package_id") or payload.get("packageId") or payload.get("package_tag_id")
|
|
560
|
+
has_package_id = effective_package_id is not None
|
|
435
561
|
if has_dash_key and has_package_id:
|
|
436
562
|
return _config_failure(
|
|
437
563
|
tool_name="portal_apply",
|
|
@@ -448,9 +574,12 @@ class AiBuilderTools(ToolBase):
|
|
|
448
574
|
profile=profile,
|
|
449
575
|
dash_key=dash_key,
|
|
450
576
|
dash_name=dash_name,
|
|
577
|
+
name=name,
|
|
451
578
|
package_id=package_id,
|
|
452
579
|
publish=publish,
|
|
453
580
|
sections=sections or [],
|
|
581
|
+
pages=pages or [],
|
|
582
|
+
layout_preset=layout_preset,
|
|
454
583
|
visibility=visibility,
|
|
455
584
|
auth=auth,
|
|
456
585
|
icon=icon,
|
|
@@ -458,14 +587,25 @@ class AiBuilderTools(ToolBase):
|
|
|
458
587
|
hide_copyright=hide_copyright,
|
|
459
588
|
dash_global_config=dash_global_config,
|
|
460
589
|
config=config or {},
|
|
590
|
+
payload=payload,
|
|
591
|
+
patch_sections=patch_sections,
|
|
461
592
|
)
|
|
462
593
|
|
|
463
594
|
@mcp.tool()
|
|
464
595
|
def app_publish_verify(
|
|
465
596
|
profile: str = DEFAULT_PROFILE,
|
|
466
597
|
app_key: str = "",
|
|
598
|
+
app_keys: list[str] | None = None,
|
|
467
599
|
expected_package_id: int | None = None,
|
|
468
600
|
) -> JSONObject:
|
|
601
|
+
if app_keys:
|
|
602
|
+
return self._facade._batch_read_app_keys(
|
|
603
|
+
profile=profile,
|
|
604
|
+
app_keys=app_keys,
|
|
605
|
+
single_reader=lambda profile, app_key: self.app_publish_verify(profile=profile, app_key=app_key, expected_package_id=expected_package_id),
|
|
606
|
+
data_key="verification",
|
|
607
|
+
tool_name="app_publish_verify",
|
|
608
|
+
)
|
|
469
609
|
return self.app_publish_verify(
|
|
470
610
|
profile=profile,
|
|
471
611
|
app_key=app_key,
|
|
@@ -473,14 +613,14 @@ class AiBuilderTools(ToolBase):
|
|
|
473
613
|
)
|
|
474
614
|
|
|
475
615
|
@tool_cn_name("分组列表查询")
|
|
476
|
-
def package_list(self, *, profile: str, trial_status: str = "all") -> JSONObject:
|
|
616
|
+
def package_list(self, *, profile: str, trial_status: str = "all", query: str = "") -> JSONObject:
|
|
477
617
|
"""执行分组与包相关逻辑。"""
|
|
478
|
-
normalized_args = {"trial_status": trial_status}
|
|
618
|
+
normalized_args = {"trial_status": trial_status, "query": query}
|
|
479
619
|
return _safe_tool_call(
|
|
480
|
-
lambda: self._facade.package_list(profile=profile, trial_status=trial_status),
|
|
620
|
+
lambda: self._facade.package_list(profile=profile, trial_status=trial_status, query=query),
|
|
481
621
|
error_code="PACKAGE_LIST_FAILED",
|
|
482
622
|
normalized_args=normalized_args,
|
|
483
|
-
suggested_next_call={"tool_name": "package_list", "arguments": {"profile": profile, "trial_status": trial_status}},
|
|
623
|
+
suggested_next_call={"tool_name": "package_list", "arguments": {"profile": profile, "trial_status": trial_status, "query": query}},
|
|
484
624
|
)
|
|
485
625
|
|
|
486
626
|
@tool_cn_name("分组解析")
|
|
@@ -525,6 +665,7 @@ class AiBuilderTools(ToolBase):
|
|
|
525
665
|
"verification": {},
|
|
526
666
|
"verified": False,
|
|
527
667
|
}
|
|
668
|
+
contract = _builder_contract_with_apply_output(lookup_name, contract)
|
|
528
669
|
return {
|
|
529
670
|
"status": "success",
|
|
530
671
|
"error_code": None,
|
|
@@ -546,6 +687,27 @@ class AiBuilderTools(ToolBase):
|
|
|
546
687
|
"contract": contract,
|
|
547
688
|
}
|
|
548
689
|
|
|
690
|
+
@tool_cn_name("工作区图标目录")
|
|
691
|
+
def workspace_icon_catalog_get(self, *, profile: str = DEFAULT_PROFILE) -> JSONObject:
|
|
692
|
+
"""读取应用、应用包、门户可用的工作区图标候选。"""
|
|
693
|
+
catalog = workspace_icon_catalog_payload()
|
|
694
|
+
return {
|
|
695
|
+
"status": "success",
|
|
696
|
+
"error_code": None,
|
|
697
|
+
"recoverable": False,
|
|
698
|
+
"message": "loaded workspace icon catalog",
|
|
699
|
+
"profile": profile,
|
|
700
|
+
"icon_names": catalog["icon_names"],
|
|
701
|
+
"icon_colors": catalog["icon_colors"],
|
|
702
|
+
"generic_icon_names": catalog["generic_icon_names"],
|
|
703
|
+
"common_examples": catalog["common_examples"],
|
|
704
|
+
"notes": catalog["notes"],
|
|
705
|
+
"count": len(catalog["icon_names"]),
|
|
706
|
+
"color_count": len(catalog["icon_colors"]),
|
|
707
|
+
"warnings": [],
|
|
708
|
+
"verification": {"source": "backend AiBuildConstant ICON_NAMES/ICON_COLORS"},
|
|
709
|
+
}
|
|
710
|
+
|
|
549
711
|
@tool_cn_name("分组创建")
|
|
550
712
|
def package_create(
|
|
551
713
|
self,
|
|
@@ -622,7 +784,18 @@ class AiBuilderTools(ToolBase):
|
|
|
622
784
|
try:
|
|
623
785
|
visibility_patch = VisibilityPatch.model_validate(visibility)
|
|
624
786
|
except ValidationError as exc:
|
|
625
|
-
return
|
|
787
|
+
return _attach_builder_apply_envelope(
|
|
788
|
+
"package_apply",
|
|
789
|
+
_visibility_validation_failure(str(exc), tool_name="package_apply", exc=exc),
|
|
790
|
+
)
|
|
791
|
+
icon_failure = _validate_workspace_icon_for_builder(
|
|
792
|
+
tool_name="package_apply",
|
|
793
|
+
icon=icon,
|
|
794
|
+
color=color,
|
|
795
|
+
creating=package_id is None and bool(create_if_missing),
|
|
796
|
+
)
|
|
797
|
+
if icon_failure is not None:
|
|
798
|
+
return _attach_builder_apply_envelope("package_apply", icon_failure)
|
|
626
799
|
normalized_args = {
|
|
627
800
|
"package_id": package_id,
|
|
628
801
|
**({"package_name": package_name} if str(package_name or "").strip() else {}),
|
|
@@ -633,7 +806,7 @@ class AiBuilderTools(ToolBase):
|
|
|
633
806
|
**({"items": deepcopy(items)} if items is not None else {}),
|
|
634
807
|
"allow_detach": bool(allow_detach),
|
|
635
808
|
}
|
|
636
|
-
|
|
809
|
+
result = _publicize_package_fields(_safe_tool_call(
|
|
637
810
|
lambda: self._facade.package_apply(
|
|
638
811
|
profile=profile,
|
|
639
812
|
package_id=package_id,
|
|
@@ -649,6 +822,7 @@ class AiBuilderTools(ToolBase):
|
|
|
649
822
|
normalized_args=normalized_args,
|
|
650
823
|
suggested_next_call={"tool_name": "package_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
651
824
|
))
|
|
825
|
+
return _attach_builder_apply_envelope("package_apply", result)
|
|
652
826
|
|
|
653
827
|
@tool_cn_name("分组更新")
|
|
654
828
|
def package_update(
|
|
@@ -867,6 +1041,134 @@ class AiBuilderTools(ToolBase):
|
|
|
867
1041
|
suggested_next_call={"tool_name": "button_style_catalog_get", "arguments": {"profile": profile}},
|
|
868
1042
|
)
|
|
869
1043
|
|
|
1044
|
+
@tool_cn_name("应用按钮声明式应用")
|
|
1045
|
+
def app_custom_buttons_apply(
|
|
1046
|
+
self,
|
|
1047
|
+
*,
|
|
1048
|
+
profile: str,
|
|
1049
|
+
app_key: str,
|
|
1050
|
+
upsert_buttons: list[JSONObject],
|
|
1051
|
+
patch_buttons: list[JSONObject] | None = None,
|
|
1052
|
+
remove_buttons: list[JSONObject],
|
|
1053
|
+
view_configs: list[JSONObject] | None = None,
|
|
1054
|
+
apps: list[JSONObject] | None = None,
|
|
1055
|
+
) -> JSONObject:
|
|
1056
|
+
"""执行应用按钮 apply 逻辑。"""
|
|
1057
|
+
if apps:
|
|
1058
|
+
return self._facade._batch_write_apps(
|
|
1059
|
+
profile=profile,
|
|
1060
|
+
apps=apps,
|
|
1061
|
+
single_writer=lambda profile, app_key, **kw: self.app_custom_buttons_apply(
|
|
1062
|
+
profile=profile,
|
|
1063
|
+
app_key=app_key,
|
|
1064
|
+
upsert_buttons=kw.get("upsert_buttons", []),
|
|
1065
|
+
patch_buttons=kw.get("patch_buttons", []),
|
|
1066
|
+
remove_buttons=kw.get("remove_buttons", []),
|
|
1067
|
+
view_configs=kw.get("view_configs", []),
|
|
1068
|
+
),
|
|
1069
|
+
tool_name="app_custom_buttons_apply",
|
|
1070
|
+
)
|
|
1071
|
+
raw_request = {
|
|
1072
|
+
"app_key": app_key,
|
|
1073
|
+
"upsert_buttons": upsert_buttons,
|
|
1074
|
+
"patch_buttons": patch_buttons or [],
|
|
1075
|
+
"remove_buttons": remove_buttons,
|
|
1076
|
+
"view_configs": view_configs or [],
|
|
1077
|
+
}
|
|
1078
|
+
try:
|
|
1079
|
+
request = CustomButtonsApplyRequest.model_validate(raw_request)
|
|
1080
|
+
except ValidationError as exc:
|
|
1081
|
+
return _attach_builder_apply_envelope("app_custom_buttons_apply", _validation_failure(
|
|
1082
|
+
str(exc),
|
|
1083
|
+
tool_name="app_custom_buttons_apply",
|
|
1084
|
+
exc=exc,
|
|
1085
|
+
suggested_next_call={
|
|
1086
|
+
"tool_name": "app_custom_buttons_apply",
|
|
1087
|
+
"arguments": {
|
|
1088
|
+
"profile": profile,
|
|
1089
|
+
"app_key": app_key or "APP_KEY",
|
|
1090
|
+
"upsert_buttons": [
|
|
1091
|
+
{
|
|
1092
|
+
"button_text": "同步客户",
|
|
1093
|
+
"style_preset": "primary_blue",
|
|
1094
|
+
"button_icon": "ex-switch",
|
|
1095
|
+
"trigger_action": "link",
|
|
1096
|
+
"trigger_link_url": "https://example.com",
|
|
1097
|
+
}
|
|
1098
|
+
],
|
|
1099
|
+
"remove_buttons": [],
|
|
1100
|
+
"view_configs": [],
|
|
1101
|
+
},
|
|
1102
|
+
},
|
|
1103
|
+
))
|
|
1104
|
+
normalized_args = request.model_dump(mode="json")
|
|
1105
|
+
return _attach_builder_apply_envelope("app_custom_buttons_apply", _safe_tool_call(
|
|
1106
|
+
lambda: self._facade.app_custom_buttons_apply(profile=profile, request=request),
|
|
1107
|
+
error_code="CUSTOM_BUTTONS_APPLY_FAILED",
|
|
1108
|
+
normalized_args=normalized_args,
|
|
1109
|
+
suggested_next_call={"tool_name": "app_custom_buttons_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1110
|
+
))
|
|
1111
|
+
|
|
1112
|
+
@tool_cn_name("应用关联资源声明式应用")
|
|
1113
|
+
def app_associated_resources_apply(
|
|
1114
|
+
self,
|
|
1115
|
+
*,
|
|
1116
|
+
profile: str,
|
|
1117
|
+
app_key: str,
|
|
1118
|
+
upsert_resources: list[JSONObject],
|
|
1119
|
+
remove_associated_item_ids: list[int],
|
|
1120
|
+
reorder_associated_item_ids: list[int],
|
|
1121
|
+
view_configs: list[JSONObject],
|
|
1122
|
+
patch_resources: list[JSONObject] | None = None,
|
|
1123
|
+
) -> JSONObject:
|
|
1124
|
+
"""执行应用关联资源 apply 逻辑。"""
|
|
1125
|
+
raw_request = {
|
|
1126
|
+
"app_key": app_key,
|
|
1127
|
+
"upsert_resources": upsert_resources,
|
|
1128
|
+
"patch_resources": patch_resources or [],
|
|
1129
|
+
"remove_associated_item_ids": remove_associated_item_ids,
|
|
1130
|
+
"reorder_associated_item_ids": reorder_associated_item_ids,
|
|
1131
|
+
"view_configs": view_configs,
|
|
1132
|
+
}
|
|
1133
|
+
try:
|
|
1134
|
+
request = AssociatedResourcesApplyRequest.model_validate(raw_request)
|
|
1135
|
+
except ValidationError as exc:
|
|
1136
|
+
return _attach_builder_apply_envelope("app_associated_resources_apply", _validation_failure(
|
|
1137
|
+
str(exc),
|
|
1138
|
+
tool_name="app_associated_resources_apply",
|
|
1139
|
+
exc=exc,
|
|
1140
|
+
suggested_next_call={
|
|
1141
|
+
"tool_name": "app_associated_resources_apply",
|
|
1142
|
+
"arguments": {
|
|
1143
|
+
"profile": profile,
|
|
1144
|
+
"app_key": app_key or "APP_KEY",
|
|
1145
|
+
"upsert_resources": [
|
|
1146
|
+
{
|
|
1147
|
+
"client_key": "customer_view",
|
|
1148
|
+
"graph_type": "view",
|
|
1149
|
+
"target_app_key": "TARGET_APP",
|
|
1150
|
+
"view_key": "VIEW_KEY",
|
|
1151
|
+
}
|
|
1152
|
+
],
|
|
1153
|
+
"view_configs": [
|
|
1154
|
+
{
|
|
1155
|
+
"view_key": "MAIN_VIEW",
|
|
1156
|
+
"visible": True,
|
|
1157
|
+
"limit_type": "select",
|
|
1158
|
+
"associated_item_refs": ["customer_view"],
|
|
1159
|
+
}
|
|
1160
|
+
],
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
))
|
|
1164
|
+
normalized_args = request.model_dump(mode="json", exclude_none=True)
|
|
1165
|
+
return _attach_builder_apply_envelope("app_associated_resources_apply", _safe_tool_call(
|
|
1166
|
+
lambda: self._facade.app_associated_resources_apply(profile=profile, request=request),
|
|
1167
|
+
error_code="ASSOCIATED_RESOURCES_APPLY_FAILED",
|
|
1168
|
+
normalized_args=normalized_args,
|
|
1169
|
+
suggested_next_call={"tool_name": "app_associated_resources_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1170
|
+
))
|
|
1171
|
+
|
|
870
1172
|
@tool_cn_name("应用按钮列表")
|
|
871
1173
|
def app_custom_button_list(self, *, profile: str, app_key: str) -> JSONObject:
|
|
872
1174
|
"""执行应用相关逻辑。"""
|
|
@@ -909,7 +1211,10 @@ class AiBuilderTools(ToolBase):
|
|
|
909
1211
|
"style_preset": "primary_blue",
|
|
910
1212
|
"button_icon": "ex-plus-circle",
|
|
911
1213
|
"trigger_action": "addData",
|
|
912
|
-
"trigger_add_data_config": {
|
|
1214
|
+
"trigger_add_data_config": {
|
|
1215
|
+
"target_app_key": "TARGET_APP_KEY",
|
|
1216
|
+
"field_mappings": [{"source_field": "客户名称", "target_field": "客户"}],
|
|
1217
|
+
},
|
|
913
1218
|
},
|
|
914
1219
|
},
|
|
915
1220
|
},
|
|
@@ -1001,8 +1306,10 @@ class AiBuilderTools(ToolBase):
|
|
|
1001
1306
|
)
|
|
1002
1307
|
|
|
1003
1308
|
@tool_cn_name("应用字段详情查询")
|
|
1004
|
-
def app_get_fields(self, *, profile: str, app_key: str) -> JSONObject:
|
|
1309
|
+
def app_get_fields(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
1005
1310
|
"""执行应用相关逻辑。"""
|
|
1311
|
+
if app_keys:
|
|
1312
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_fields, data_key="fields", tool_name="app_get_fields")
|
|
1006
1313
|
normalized_args = {"app_key": app_key}
|
|
1007
1314
|
return _safe_tool_call(
|
|
1008
1315
|
lambda: self._facade.app_get_fields(profile=profile, app_key=app_key),
|
|
@@ -1041,8 +1348,10 @@ class AiBuilderTools(ToolBase):
|
|
|
1041
1348
|
)
|
|
1042
1349
|
|
|
1043
1350
|
@tool_cn_name("应用布局详情查询")
|
|
1044
|
-
def app_get_layout(self, *, profile: str, app_key: str) -> JSONObject:
|
|
1351
|
+
def app_get_layout(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
1045
1352
|
"""执行应用相关逻辑。"""
|
|
1353
|
+
if app_keys:
|
|
1354
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_layout, data_key="sections", tool_name="app_get_layout")
|
|
1046
1355
|
normalized_args = {"app_key": app_key}
|
|
1047
1356
|
return _safe_tool_call(
|
|
1048
1357
|
lambda: self._facade.app_get_layout(profile=profile, app_key=app_key),
|
|
@@ -1063,8 +1372,10 @@ class AiBuilderTools(ToolBase):
|
|
|
1063
1372
|
)
|
|
1064
1373
|
|
|
1065
1374
|
@tool_cn_name("应用视图详情查询")
|
|
1066
|
-
def app_get_views(self, *, profile: str, app_key: str) -> JSONObject:
|
|
1375
|
+
def app_get_views(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
1067
1376
|
"""执行应用相关逻辑。"""
|
|
1377
|
+
if app_keys:
|
|
1378
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_views, data_key="views", tool_name="app_get_views")
|
|
1068
1379
|
normalized_args = {"app_key": app_key}
|
|
1069
1380
|
return _safe_tool_call(
|
|
1070
1381
|
lambda: self._facade.app_get_views(profile=profile, app_key=app_key),
|
|
@@ -1073,26 +1384,27 @@ class AiBuilderTools(ToolBase):
|
|
|
1073
1384
|
suggested_next_call={"tool_name": "app_get_views", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1074
1385
|
)
|
|
1075
1386
|
|
|
1076
|
-
@tool_cn_name("
|
|
1077
|
-
def
|
|
1078
|
-
""
|
|
1079
|
-
normalized_args = {"app_key": app_key}
|
|
1387
|
+
@tool_cn_name("Workflow Spec Schema")
|
|
1388
|
+
def app_flow_get_schema(self, *, profile: str, schema_version: str | None = None) -> JSONObject:
|
|
1389
|
+
normalized_args = {"schema_version": schema_version}
|
|
1080
1390
|
return _safe_tool_call(
|
|
1081
|
-
lambda: self._facade.
|
|
1082
|
-
error_code="
|
|
1391
|
+
lambda: self._facade.flow_get_schema(profile=profile, schema_version=schema_version),
|
|
1392
|
+
error_code="FLOW_SPEC_SCHEMA_FAILED",
|
|
1083
1393
|
normalized_args=normalized_args,
|
|
1084
|
-
suggested_next_call={"tool_name": "
|
|
1394
|
+
suggested_next_call={"tool_name": "app_flow_get_schema", "arguments": {"profile": profile, **normalized_args}},
|
|
1085
1395
|
)
|
|
1086
1396
|
|
|
1087
|
-
@tool_cn_name("
|
|
1088
|
-
def app_get_flow(self, *, profile: str, app_key: str) -> JSONObject:
|
|
1397
|
+
@tool_cn_name("Workflow Spec 读取")
|
|
1398
|
+
def app_get_flow(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None, version_id: str | None = None) -> JSONObject:
|
|
1089
1399
|
"""执行应用相关逻辑。"""
|
|
1090
|
-
|
|
1400
|
+
if app_keys:
|
|
1401
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_flow, data_key="spec", tool_name="app_get_flow")
|
|
1402
|
+
normalized_args = {"app_key": app_key, "version_id": version_id}
|
|
1091
1403
|
return _safe_tool_call(
|
|
1092
|
-
lambda: self._facade.
|
|
1404
|
+
lambda: self._facade.flow_get(profile=profile, app_key=app_key, version_id=version_id),
|
|
1093
1405
|
error_code="APP_GET_FLOW_FAILED",
|
|
1094
1406
|
normalized_args=normalized_args,
|
|
1095
|
-
suggested_next_call={"tool_name": "
|
|
1407
|
+
suggested_next_call={"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1096
1408
|
)
|
|
1097
1409
|
|
|
1098
1410
|
@tool_cn_name("应用图表摘要读取")
|
|
@@ -1107,8 +1419,10 @@ class AiBuilderTools(ToolBase):
|
|
|
1107
1419
|
)
|
|
1108
1420
|
|
|
1109
1421
|
@tool_cn_name("应用图表详情查询")
|
|
1110
|
-
def app_get_charts(self, *, profile: str, app_key: str) -> JSONObject:
|
|
1422
|
+
def app_get_charts(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
1111
1423
|
"""执行应用相关逻辑。"""
|
|
1424
|
+
if app_keys:
|
|
1425
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_charts, data_key="charts", tool_name="app_get_charts")
|
|
1112
1426
|
normalized_args = {"app_key": app_key}
|
|
1113
1427
|
return _safe_tool_call(
|
|
1114
1428
|
lambda: self._facade.app_get_charts(profile=profile, app_key=app_key),
|
|
@@ -1117,6 +1431,32 @@ class AiBuilderTools(ToolBase):
|
|
|
1117
1431
|
suggested_next_call={"tool_name": "app_get_charts", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1118
1432
|
)
|
|
1119
1433
|
|
|
1434
|
+
@tool_cn_name("自定义按钮读取")
|
|
1435
|
+
def app_get_buttons(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
1436
|
+
"""执行按钮相关逻辑。"""
|
|
1437
|
+
if app_keys:
|
|
1438
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_buttons, data_key="buttons", tool_name="app_get_buttons")
|
|
1439
|
+
normalized_args = {"app_key": app_key}
|
|
1440
|
+
return _safe_tool_call(
|
|
1441
|
+
lambda: self._facade.app_get_buttons(profile=profile, app_key=app_key),
|
|
1442
|
+
error_code="APP_GET_BUTTONS_FAILED",
|
|
1443
|
+
normalized_args=normalized_args,
|
|
1444
|
+
suggested_next_call={"tool_name": "app_get_buttons", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
@tool_cn_name("关联资源读取")
|
|
1448
|
+
def app_get_associated_resources(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
1449
|
+
"""执行关联资源相关逻辑。"""
|
|
1450
|
+
if app_keys:
|
|
1451
|
+
return self._facade._batch_read_app_keys(profile=profile, app_keys=app_keys, single_reader=self._facade.app_get_associated_resources, data_key="associated_resources", tool_name="app_get_associated_resources")
|
|
1452
|
+
normalized_args = {"app_key": app_key}
|
|
1453
|
+
return _safe_tool_call(
|
|
1454
|
+
lambda: self._facade.app_get_associated_resources(profile=profile, app_key=app_key),
|
|
1455
|
+
error_code="APP_GET_ASSOCIATED_RESOURCES_FAILED",
|
|
1456
|
+
normalized_args=normalized_args,
|
|
1457
|
+
suggested_next_call={"tool_name": "app_get_associated_resources", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1120
1460
|
@tool_cn_name("门户列表查询")
|
|
1121
1461
|
def portal_list(self, *, profile: str) -> JSONObject:
|
|
1122
1462
|
"""执行门户相关逻辑。"""
|
|
@@ -1152,7 +1492,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1152
1492
|
@tool_cn_name("视图详情查询")
|
|
1153
1493
|
def view_get(self, *, profile: str, view_key: str = "", viewgraph_key: str = "") -> JSONObject:
|
|
1154
1494
|
"""执行视图相关逻辑。"""
|
|
1155
|
-
resolved_view_key = str(view_key or viewgraph_key or "").strip()
|
|
1495
|
+
resolved_view_key = _normalize_builder_view_key(str(view_key or viewgraph_key or "").strip())
|
|
1156
1496
|
normalized_args = {"view_key": resolved_view_key}
|
|
1157
1497
|
return _safe_tool_call(
|
|
1158
1498
|
lambda: self._facade.view_get(profile=profile, view_key=resolved_view_key),
|
|
@@ -1287,52 +1627,6 @@ class AiBuilderTools(ToolBase):
|
|
|
1287
1627
|
suggested_next_call={"tool_name": "app_layout_plan", "arguments": {"profile": profile, **normalized_request}},
|
|
1288
1628
|
)
|
|
1289
1629
|
|
|
1290
|
-
@tool_cn_name("应用流程规划")
|
|
1291
|
-
def app_flow_plan(
|
|
1292
|
-
self,
|
|
1293
|
-
*,
|
|
1294
|
-
profile: str,
|
|
1295
|
-
app_key: str,
|
|
1296
|
-
mode: str = "replace",
|
|
1297
|
-
nodes: list[JSONObject] | None = None,
|
|
1298
|
-
transitions: list[JSONObject] | None = None,
|
|
1299
|
-
preset: str | None = None,
|
|
1300
|
-
) -> JSONObject:
|
|
1301
|
-
"""执行应用相关逻辑。"""
|
|
1302
|
-
try:
|
|
1303
|
-
request = FlowPlanRequest.model_validate(
|
|
1304
|
-
{
|
|
1305
|
-
"app_key": app_key,
|
|
1306
|
-
"mode": mode,
|
|
1307
|
-
"nodes": nodes or [],
|
|
1308
|
-
"transitions": transitions or [],
|
|
1309
|
-
"preset": preset,
|
|
1310
|
-
}
|
|
1311
|
-
)
|
|
1312
|
-
except ValidationError as exc:
|
|
1313
|
-
return _validation_failure(
|
|
1314
|
-
str(exc),
|
|
1315
|
-
tool_name="app_flow_plan",
|
|
1316
|
-
exc=exc,
|
|
1317
|
-
suggested_next_call={
|
|
1318
|
-
"tool_name": "app_flow_plan",
|
|
1319
|
-
"arguments": {
|
|
1320
|
-
"profile": profile,
|
|
1321
|
-
"app_key": app_key,
|
|
1322
|
-
"mode": "replace",
|
|
1323
|
-
"preset": "basic_approval",
|
|
1324
|
-
"nodes": [],
|
|
1325
|
-
"transitions": [],
|
|
1326
|
-
},
|
|
1327
|
-
},
|
|
1328
|
-
)
|
|
1329
|
-
return _safe_tool_call(
|
|
1330
|
-
lambda: self._facade.app_flow_plan(profile=profile, request=request),
|
|
1331
|
-
error_code="FLOW_PLAN_FAILED",
|
|
1332
|
-
normalized_args=request.model_dump(mode="json"),
|
|
1333
|
-
suggested_next_call={"tool_name": "app_flow_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
1334
|
-
)
|
|
1335
|
-
|
|
1336
1630
|
@tool_cn_name("应用视图规划")
|
|
1337
1631
|
def app_views_plan(
|
|
1338
1632
|
self,
|
|
@@ -1393,8 +1687,19 @@ class AiBuilderTools(ToolBase):
|
|
|
1393
1687
|
add_fields: list[JSONObject],
|
|
1394
1688
|
update_fields: list[JSONObject],
|
|
1395
1689
|
remove_fields: list[JSONObject],
|
|
1690
|
+
apps: list[JSONObject] | None = None,
|
|
1396
1691
|
) -> JSONObject:
|
|
1397
1692
|
"""执行应用相关逻辑。"""
|
|
1693
|
+
if apps:
|
|
1694
|
+
result = self._app_schema_apply_multi(
|
|
1695
|
+
profile=profile,
|
|
1696
|
+
package_id=package_id,
|
|
1697
|
+
visibility=visibility,
|
|
1698
|
+
create_if_missing=create_if_missing,
|
|
1699
|
+
publish=publish,
|
|
1700
|
+
apps=apps,
|
|
1701
|
+
)
|
|
1702
|
+
return _attach_builder_apply_envelope("app_schema_apply", result)
|
|
1398
1703
|
result = self._app_schema_apply_once(
|
|
1399
1704
|
profile=profile,
|
|
1400
1705
|
app_key=app_key,
|
|
@@ -1410,7 +1715,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1410
1715
|
update_fields=update_fields,
|
|
1411
1716
|
remove_fields=remove_fields,
|
|
1412
1717
|
)
|
|
1413
|
-
|
|
1718
|
+
result = self._retry_after_self_lock_release(
|
|
1414
1719
|
profile=profile,
|
|
1415
1720
|
result=result,
|
|
1416
1721
|
retry_call=lambda: self._app_schema_apply_once(
|
|
@@ -1429,6 +1734,325 @@ class AiBuilderTools(ToolBase):
|
|
|
1429
1734
|
remove_fields=remove_fields,
|
|
1430
1735
|
),
|
|
1431
1736
|
)
|
|
1737
|
+
return _attach_builder_apply_envelope("app_schema_apply", result)
|
|
1738
|
+
|
|
1739
|
+
def _app_schema_apply_multi(
|
|
1740
|
+
self,
|
|
1741
|
+
*,
|
|
1742
|
+
profile: str,
|
|
1743
|
+
package_id: int | None,
|
|
1744
|
+
visibility: JSONObject | None,
|
|
1745
|
+
create_if_missing: bool,
|
|
1746
|
+
publish: bool,
|
|
1747
|
+
apps: list[JSONObject],
|
|
1748
|
+
) -> JSONObject:
|
|
1749
|
+
normalized_args: JSONObject = {
|
|
1750
|
+
"package_id": package_id,
|
|
1751
|
+
"create_if_missing": create_if_missing,
|
|
1752
|
+
"publish": publish,
|
|
1753
|
+
"apps": deepcopy(apps),
|
|
1754
|
+
}
|
|
1755
|
+
if visibility is not None:
|
|
1756
|
+
normalized_args["visibility"] = deepcopy(visibility)
|
|
1757
|
+
if package_id is None:
|
|
1758
|
+
return _config_failure(
|
|
1759
|
+
tool_name="app_schema_apply",
|
|
1760
|
+
message="app_schema_apply multi-app mode requires package_id.",
|
|
1761
|
+
fix_hint="Pass package_id and apps[].app_name for new apps, or apps[].app_key for existing apps.",
|
|
1762
|
+
)
|
|
1763
|
+
if not apps:
|
|
1764
|
+
return _config_failure(
|
|
1765
|
+
tool_name="app_schema_apply",
|
|
1766
|
+
message="app_schema_apply multi-app mode requires non-empty apps.",
|
|
1767
|
+
fix_hint="Pass apps as a non-empty list of app schema items.",
|
|
1768
|
+
)
|
|
1769
|
+
icon_errors: list[JSONObject] = []
|
|
1770
|
+
seen_new_app_icons: dict[str, int] = {}
|
|
1771
|
+
for index, raw_item in enumerate(apps):
|
|
1772
|
+
if not isinstance(raw_item, dict):
|
|
1773
|
+
continue
|
|
1774
|
+
app_key = str(raw_item.get("app_key") or raw_item.get("appKey") or "").strip()
|
|
1775
|
+
creating_item = not app_key
|
|
1776
|
+
icon_failure = _validate_workspace_icon_for_builder(
|
|
1777
|
+
tool_name="app_schema_apply",
|
|
1778
|
+
icon=str(raw_item.get("icon") or ""),
|
|
1779
|
+
color=str(raw_item.get("color") or ""),
|
|
1780
|
+
creating=creating_item,
|
|
1781
|
+
)
|
|
1782
|
+
if icon_failure is not None:
|
|
1783
|
+
icon_errors.append(
|
|
1784
|
+
{
|
|
1785
|
+
"index": index,
|
|
1786
|
+
"row_number": index + 1,
|
|
1787
|
+
"error_code": icon_failure.get("error_code"),
|
|
1788
|
+
"message": icon_failure.get("message"),
|
|
1789
|
+
"details": icon_failure.get("details"),
|
|
1790
|
+
}
|
|
1791
|
+
)
|
|
1792
|
+
continue
|
|
1793
|
+
_ok, _error_code, _message, icon_details = validate_workspace_icon_choice(
|
|
1794
|
+
icon=str(raw_item.get("icon") or ""),
|
|
1795
|
+
color=str(raw_item.get("color") or ""),
|
|
1796
|
+
require_explicit=creating_item,
|
|
1797
|
+
disallow_generic=creating_item,
|
|
1798
|
+
)
|
|
1799
|
+
normalized_icon = str(icon_details.get("normalized_icon") or "").strip()
|
|
1800
|
+
if creating_item and normalized_icon:
|
|
1801
|
+
if normalized_icon in seen_new_app_icons:
|
|
1802
|
+
icon_errors.append(
|
|
1803
|
+
{
|
|
1804
|
+
"index": index,
|
|
1805
|
+
"row_number": index + 1,
|
|
1806
|
+
"error_code": "DUPLICATE_WORKSPACE_ICON_IN_BATCH",
|
|
1807
|
+
"message": f"apps[{index}] reuses icon '{normalized_icon}' from apps[{seen_new_app_icons[normalized_icon]}]",
|
|
1808
|
+
"details": {
|
|
1809
|
+
"icon": normalized_icon,
|
|
1810
|
+
"first_index": seen_new_app_icons[normalized_icon],
|
|
1811
|
+
"duplicate_index": index,
|
|
1812
|
+
"icon_catalog_command": "qingflow --json builder icon catalog",
|
|
1813
|
+
},
|
|
1814
|
+
}
|
|
1815
|
+
)
|
|
1816
|
+
else:
|
|
1817
|
+
seen_new_app_icons[normalized_icon] = index
|
|
1818
|
+
if icon_errors:
|
|
1819
|
+
return _config_failure(
|
|
1820
|
+
tool_name="app_schema_apply",
|
|
1821
|
+
error_code="WORKSPACE_ICON_BATCH_INVALID",
|
|
1822
|
+
message="one or more apps have invalid workspace icon configuration",
|
|
1823
|
+
fix_hint="Call `qingflow --json builder icon catalog`, choose a distinct non-template icon and color for each new app, then retry.",
|
|
1824
|
+
details={"icon_errors": icon_errors},
|
|
1825
|
+
allowed_values={
|
|
1826
|
+
"workspace_icon.icon_names": list(WORKSPACE_ICON_NAMES),
|
|
1827
|
+
"workspace_icon.icon_colors": list(WORKSPACE_ICON_COLORS),
|
|
1828
|
+
"workspace_icon.generic_icon_names": list(GENERIC_WORKSPACE_ICON_NAMES),
|
|
1829
|
+
},
|
|
1830
|
+
)
|
|
1831
|
+
|
|
1832
|
+
client_key_to_app_key: dict[str, str] = {}
|
|
1833
|
+
created_app_keys: list[str] = []
|
|
1834
|
+
results: list[JSONObject] = []
|
|
1835
|
+
any_write_executed = False
|
|
1836
|
+
client_keys: set[str] = set()
|
|
1837
|
+
|
|
1838
|
+
for index, raw_item in enumerate(apps):
|
|
1839
|
+
if not isinstance(raw_item, dict):
|
|
1840
|
+
results.append(_multi_app_item_failure(index, raw_item, "INVALID_APP_ITEM", "apps[] items must be objects"))
|
|
1841
|
+
continue
|
|
1842
|
+
item = deepcopy(raw_item)
|
|
1843
|
+
client_key = str(item.get("client_key") or item.get("clientKey") or "").strip()
|
|
1844
|
+
app_name = str(item.get("app_name") or item.get("appTitle") or item.get("app_title") or item.get("title") or "").strip()
|
|
1845
|
+
app_key = str(item.get("app_key") or item.get("appKey") or "").strip()
|
|
1846
|
+
if client_key:
|
|
1847
|
+
if client_key in client_keys:
|
|
1848
|
+
results.append(_multi_app_item_failure(index, item, "DUPLICATE_CLIENT_KEY", f"duplicate client_key '{client_key}'"))
|
|
1849
|
+
continue
|
|
1850
|
+
client_keys.add(client_key)
|
|
1851
|
+
if not app_key and not app_name:
|
|
1852
|
+
results.append(_multi_app_item_failure(index, item, "APP_SELECTOR_REQUIRED", "apps[] requires app_key or app_name"))
|
|
1853
|
+
continue
|
|
1854
|
+
|
|
1855
|
+
initial_add_fields, deferred_add_fields = _split_multi_app_initial_add_fields(item, is_new_app=not bool(app_key))
|
|
1856
|
+
item["_deferred_add_fields"] = deferred_add_fields
|
|
1857
|
+
shell = self._app_schema_apply_once(
|
|
1858
|
+
profile=profile,
|
|
1859
|
+
app_key=app_key,
|
|
1860
|
+
package_id=package_id if not app_key else None,
|
|
1861
|
+
app_name=app_name,
|
|
1862
|
+
app_title="",
|
|
1863
|
+
icon=str(item.get("icon") or ""),
|
|
1864
|
+
color=str(item.get("color") or ""),
|
|
1865
|
+
visibility=item.get("visibility", visibility),
|
|
1866
|
+
create_if_missing=create_if_missing and not app_key,
|
|
1867
|
+
publish=publish and not deferred_add_fields,
|
|
1868
|
+
add_fields=initial_add_fields,
|
|
1869
|
+
update_fields=[],
|
|
1870
|
+
remove_fields=[],
|
|
1871
|
+
)
|
|
1872
|
+
public_shell = _publicize_package_fields(shell)
|
|
1873
|
+
resolved_key = str(public_shell.get("app_key") or "").strip()
|
|
1874
|
+
if public_shell.get("status") not in {"success", "partial_success"} or not resolved_key:
|
|
1875
|
+
results.append({
|
|
1876
|
+
"index": index,
|
|
1877
|
+
"row_number": index + 1,
|
|
1878
|
+
"client_key": client_key or None,
|
|
1879
|
+
"app_name": app_name or None,
|
|
1880
|
+
"app_key": resolved_key or app_key or None,
|
|
1881
|
+
"status": "failed",
|
|
1882
|
+
"stage": "resolve_or_create_shell",
|
|
1883
|
+
"error_code": public_shell.get("error_code") or "APP_SHELL_APPLY_FAILED",
|
|
1884
|
+
"message": public_shell.get("message") or "app shell resolve/create failed",
|
|
1885
|
+
"safe_to_retry": not any_write_executed,
|
|
1886
|
+
})
|
|
1887
|
+
continue
|
|
1888
|
+
if bool(public_shell.get("created")):
|
|
1889
|
+
created_app_keys.append(resolved_key)
|
|
1890
|
+
if _schema_apply_result_has_write(public_shell):
|
|
1891
|
+
any_write_executed = True
|
|
1892
|
+
if client_key:
|
|
1893
|
+
client_key_to_app_key[client_key] = resolved_key
|
|
1894
|
+
results.append({
|
|
1895
|
+
"index": index,
|
|
1896
|
+
"row_number": index + 1,
|
|
1897
|
+
"client_key": client_key or None,
|
|
1898
|
+
"app_name": str(public_shell.get("app_name_after") or public_shell.get("app_name") or app_name or "").strip() or None,
|
|
1899
|
+
"app_key": resolved_key,
|
|
1900
|
+
"status": "shell_ready",
|
|
1901
|
+
"created": bool(public_shell.get("created")),
|
|
1902
|
+
"shell_result": public_shell,
|
|
1903
|
+
"shell_field_diff": public_shell.get("field_diff") or {},
|
|
1904
|
+
"shell_field_diff_details": public_shell.get("field_diff_details") or {},
|
|
1905
|
+
"deferred_add_fields": deferred_add_fields,
|
|
1906
|
+
})
|
|
1907
|
+
|
|
1908
|
+
final_items: list[JSONObject] = []
|
|
1909
|
+
for index, raw_item in enumerate(apps):
|
|
1910
|
+
existing = next((item for item in results if item.get("index") == index), None)
|
|
1911
|
+
if not existing or existing.get("status") != "shell_ready":
|
|
1912
|
+
if existing:
|
|
1913
|
+
final_items.append(existing)
|
|
1914
|
+
continue
|
|
1915
|
+
item = deepcopy(raw_item)
|
|
1916
|
+
app_key = str(existing.get("app_key") or "").strip()
|
|
1917
|
+
try:
|
|
1918
|
+
compiled_item = _compile_multi_app_schema_item_refs(item, client_key_to_app_key)
|
|
1919
|
+
except ValueError as error:
|
|
1920
|
+
final_items.append({
|
|
1921
|
+
**{key: existing.get(key) for key in ("index", "row_number", "client_key", "app_name", "app_key", "created")},
|
|
1922
|
+
"status": "failed",
|
|
1923
|
+
"stage": "compile_relation_refs",
|
|
1924
|
+
"error_code": "TARGET_APP_REF_NOT_FOUND",
|
|
1925
|
+
"message": str(error),
|
|
1926
|
+
"safe_to_retry": False,
|
|
1927
|
+
})
|
|
1928
|
+
any_write_executed = True
|
|
1929
|
+
continue
|
|
1930
|
+
|
|
1931
|
+
deferred_add_fields = (
|
|
1932
|
+
_compiled_multi_app_deferred_add_fields(compiled_item, existing)
|
|
1933
|
+
if bool(existing.get("created"))
|
|
1934
|
+
else list(compiled_item.get("add_fields") or [])
|
|
1935
|
+
)
|
|
1936
|
+
update_fields = list(compiled_item.get("update_fields") or [])
|
|
1937
|
+
remove_fields = list(compiled_item.get("remove_fields") or [])
|
|
1938
|
+
if bool(existing.get("created")) and not deferred_add_fields and not update_fields and not remove_fields:
|
|
1939
|
+
shell_result = existing.get("shell_result") if isinstance(existing.get("shell_result"), dict) else {}
|
|
1940
|
+
item_status = shell_result.get("status") if shell_result.get("status") in {"success", "partial_success"} else "failed"
|
|
1941
|
+
shell_field_diff = existing.get("shell_field_diff") if isinstance(existing.get("shell_field_diff"), dict) else {}
|
|
1942
|
+
shell_field_diff_details = existing.get("shell_field_diff_details") if isinstance(existing.get("shell_field_diff_details"), dict) else {}
|
|
1943
|
+
final_items.append({
|
|
1944
|
+
**{key: existing.get(key) for key in ("index", "row_number", "client_key", "app_name", "app_key", "created")},
|
|
1945
|
+
"status": item_status,
|
|
1946
|
+
"stage": "schema_apply",
|
|
1947
|
+
"field_diff": shell_field_diff,
|
|
1948
|
+
"field_diff_details": shell_field_diff_details,
|
|
1949
|
+
"shell_field_diff": shell_field_diff,
|
|
1950
|
+
"shell_field_diff_details": shell_field_diff_details,
|
|
1951
|
+
"published": bool(shell_result.get("published")),
|
|
1952
|
+
"verified": bool(shell_result.get("verified")),
|
|
1953
|
+
"error_code": shell_result.get("error_code"),
|
|
1954
|
+
"message": shell_result.get("message"),
|
|
1955
|
+
"safe_to_retry": False,
|
|
1956
|
+
})
|
|
1957
|
+
continue
|
|
1958
|
+
|
|
1959
|
+
field_result = self._app_schema_apply_once(
|
|
1960
|
+
profile=profile,
|
|
1961
|
+
app_key=app_key,
|
|
1962
|
+
package_id=None,
|
|
1963
|
+
app_name=str(compiled_item.get("app_name") or compiled_item.get("appTitle") or compiled_item.get("app_title") or ""),
|
|
1964
|
+
app_title="",
|
|
1965
|
+
icon=str(compiled_item.get("icon") or ""),
|
|
1966
|
+
color=str(compiled_item.get("color") or ""),
|
|
1967
|
+
visibility=compiled_item.get("visibility"),
|
|
1968
|
+
create_if_missing=False,
|
|
1969
|
+
publish=publish,
|
|
1970
|
+
add_fields=deferred_add_fields,
|
|
1971
|
+
update_fields=update_fields,
|
|
1972
|
+
remove_fields=remove_fields,
|
|
1973
|
+
)
|
|
1974
|
+
public_result = _publicize_package_fields(field_result)
|
|
1975
|
+
if _schema_apply_result_has_write(public_result):
|
|
1976
|
+
any_write_executed = True
|
|
1977
|
+
item_status = public_result.get("status") if public_result.get("status") in {"success", "partial_success"} else "failed"
|
|
1978
|
+
shell_field_diff = existing.get("shell_field_diff") if isinstance(existing.get("shell_field_diff"), dict) else {}
|
|
1979
|
+
shell_field_diff_details = existing.get("shell_field_diff_details") if isinstance(existing.get("shell_field_diff_details"), dict) else {}
|
|
1980
|
+
field_diff = _merge_schema_field_diffs(shell_field_diff, public_result.get("field_diff") or {})
|
|
1981
|
+
field_diff_details = _merge_schema_field_diffs(shell_field_diff_details, public_result.get("field_diff_details") or {})
|
|
1982
|
+
final_items.append({
|
|
1983
|
+
**{key: existing.get(key) for key in ("index", "row_number", "client_key", "app_name", "app_key", "created")},
|
|
1984
|
+
"status": item_status,
|
|
1985
|
+
"stage": "schema_apply",
|
|
1986
|
+
"field_diff": field_diff,
|
|
1987
|
+
"field_diff_details": field_diff_details,
|
|
1988
|
+
"shell_field_diff": shell_field_diff,
|
|
1989
|
+
"shell_field_diff_details": shell_field_diff_details,
|
|
1990
|
+
"published": bool(public_result.get("published")),
|
|
1991
|
+
"verified": bool(public_result.get("verified")),
|
|
1992
|
+
"error_code": public_result.get("error_code"),
|
|
1993
|
+
"message": public_result.get("message"),
|
|
1994
|
+
"safe_to_retry": False,
|
|
1995
|
+
})
|
|
1996
|
+
|
|
1997
|
+
# Inline layout: apply layout for each successful app that included a layout field.
|
|
1998
|
+
for index, raw_item in enumerate(apps):
|
|
1999
|
+
if not isinstance(raw_item, dict):
|
|
2000
|
+
continue
|
|
2001
|
+
layout_spec = raw_item.get("layout")
|
|
2002
|
+
if not isinstance(layout_spec, dict) or not layout_spec:
|
|
2003
|
+
continue
|
|
2004
|
+
final_item = next((it for it in final_items if it.get("index") == index), None)
|
|
2005
|
+
if not final_item or final_item.get("status") not in {"success", "partial_success"}:
|
|
2006
|
+
continue
|
|
2007
|
+
resolved_app_key = str(final_item.get("app_key") or "").strip()
|
|
2008
|
+
if not resolved_app_key:
|
|
2009
|
+
continue
|
|
2010
|
+
layout_mode = str(layout_spec.get("mode") or "merge").strip() or "merge"
|
|
2011
|
+
layout_sections = layout_spec.get("sections") or []
|
|
2012
|
+
try:
|
|
2013
|
+
layout_result = self.app_layout_apply(
|
|
2014
|
+
profile=profile,
|
|
2015
|
+
app_key=resolved_app_key,
|
|
2016
|
+
mode=layout_mode,
|
|
2017
|
+
publish=publish,
|
|
2018
|
+
sections=list(layout_sections),
|
|
2019
|
+
)
|
|
2020
|
+
layout_ok = layout_result.get("status") in {"success", "partial_success"}
|
|
2021
|
+
except Exception as layout_error:
|
|
2022
|
+
layout_ok = False
|
|
2023
|
+
layout_result = {"status": "failed", "message": str(layout_error)}
|
|
2024
|
+
if layout_ok:
|
|
2025
|
+
final_item["layout_applied"] = True
|
|
2026
|
+
final_item["layout_status"] = layout_result.get("status")
|
|
2027
|
+
else:
|
|
2028
|
+
final_item["layout_warning"] = layout_result.get("error_code") or "LAYOUT_APPLY_FAILED"
|
|
2029
|
+
final_item["layout_message"] = layout_result.get("message")
|
|
2030
|
+
|
|
2031
|
+
succeeded = sum(1 for item in final_items if item.get("status") in {"success", "partial_success"})
|
|
2032
|
+
failed = len(final_items) - succeeded
|
|
2033
|
+
overall_status = "success" if failed == 0 else ("partial_success" if succeeded > 0 or any_write_executed else "failed")
|
|
2034
|
+
return {
|
|
2035
|
+
"status": overall_status,
|
|
2036
|
+
"mode": "multi_app",
|
|
2037
|
+
"total": len(apps),
|
|
2038
|
+
"succeeded": succeeded,
|
|
2039
|
+
"failed": failed,
|
|
2040
|
+
"created_app_keys": created_app_keys,
|
|
2041
|
+
"write_executed": any_write_executed,
|
|
2042
|
+
"safe_to_retry": not any_write_executed,
|
|
2043
|
+
"package_id": package_id,
|
|
2044
|
+
"publish_requested": publish,
|
|
2045
|
+
"apps": final_items,
|
|
2046
|
+
"normalized_args": normalized_args,
|
|
2047
|
+
"verification": {
|
|
2048
|
+
"all_apps_succeeded": failed == 0,
|
|
2049
|
+
"created_app_count": len(created_app_keys),
|
|
2050
|
+
},
|
|
2051
|
+
"request_id": None,
|
|
2052
|
+
"error_code": None if overall_status != "failed" else "MULTI_APP_SCHEMA_APPLY_FAILED",
|
|
2053
|
+
"recoverable": overall_status != "success",
|
|
2054
|
+
"message": "multi-app schema apply completed" if overall_status != "failed" else "multi-app schema apply failed",
|
|
2055
|
+
}
|
|
1432
2056
|
|
|
1433
2057
|
def _app_schema_apply_once(
|
|
1434
2058
|
self,
|
|
@@ -1449,6 +2073,14 @@ class AiBuilderTools(ToolBase):
|
|
|
1449
2073
|
) -> JSONObject:
|
|
1450
2074
|
"""执行内部辅助逻辑。"""
|
|
1451
2075
|
effective_app_name = app_name or app_title
|
|
2076
|
+
icon_failure = _validate_workspace_icon_for_builder(
|
|
2077
|
+
tool_name="app_schema_apply",
|
|
2078
|
+
icon=icon,
|
|
2079
|
+
color=color,
|
|
2080
|
+
creating=not bool(str(app_key or "").strip()) and bool(create_if_missing),
|
|
2081
|
+
)
|
|
2082
|
+
if icon_failure is not None:
|
|
2083
|
+
return icon_failure
|
|
1452
2084
|
plan_result = self._rewrite_plan_result_for_apply(
|
|
1453
2085
|
result=self.app_schema_plan(
|
|
1454
2086
|
profile=profile,
|
|
@@ -1535,8 +2167,21 @@ class AiBuilderTools(ToolBase):
|
|
|
1535
2167
|
return _publicize_package_fields(result)
|
|
1536
2168
|
|
|
1537
2169
|
@tool_cn_name("应用布局应用")
|
|
1538
|
-
def app_layout_apply(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
2170
|
+
def app_layout_apply(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject], apps: list[JSONObject] | None = None) -> JSONObject:
|
|
1539
2171
|
"""执行应用相关逻辑。"""
|
|
2172
|
+
if apps:
|
|
2173
|
+
return self._facade._batch_write_apps(
|
|
2174
|
+
profile=profile,
|
|
2175
|
+
apps=apps,
|
|
2176
|
+
single_writer=lambda profile, app_key, **kw: self.app_layout_apply(
|
|
2177
|
+
profile=profile,
|
|
2178
|
+
app_key=app_key,
|
|
2179
|
+
mode=kw.get("mode", mode),
|
|
2180
|
+
publish=kw.get("publish", publish),
|
|
2181
|
+
sections=kw.get("sections", []),
|
|
2182
|
+
),
|
|
2183
|
+
tool_name="app_layout_apply",
|
|
2184
|
+
)
|
|
1540
2185
|
result = self._app_layout_apply_once(
|
|
1541
2186
|
profile=profile,
|
|
1542
2187
|
app_key=app_key,
|
|
@@ -1544,7 +2189,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1544
2189
|
publish=publish,
|
|
1545
2190
|
sections=sections,
|
|
1546
2191
|
)
|
|
1547
|
-
|
|
2192
|
+
result = self._retry_after_self_lock_release(
|
|
1548
2193
|
profile=profile,
|
|
1549
2194
|
result=result,
|
|
1550
2195
|
retry_call=lambda: self._app_layout_apply_once(
|
|
@@ -1555,6 +2200,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1555
2200
|
sections=sections,
|
|
1556
2201
|
),
|
|
1557
2202
|
)
|
|
2203
|
+
return _attach_builder_apply_envelope("app_layout_apply", result)
|
|
1558
2204
|
|
|
1559
2205
|
def _app_layout_apply_once(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
1560
2206
|
"""执行内部辅助逻辑。"""
|
|
@@ -1620,130 +2266,104 @@ class AiBuilderTools(ToolBase):
|
|
|
1620
2266
|
*,
|
|
1621
2267
|
profile: str,
|
|
1622
2268
|
app_key: str,
|
|
1623
|
-
|
|
2269
|
+
spec: JSONObject,
|
|
1624
2270
|
publish: bool = True,
|
|
1625
|
-
|
|
1626
|
-
|
|
2271
|
+
idempotency_key: str | None = None,
|
|
2272
|
+
schema_version: str | None = None,
|
|
2273
|
+
patch_nodes: list[JSONObject] | None = None,
|
|
1627
2274
|
) -> JSONObject:
|
|
1628
2275
|
"""执行应用相关逻辑。"""
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
2276
|
+
if patch_nodes:
|
|
2277
|
+
result = _safe_tool_call(
|
|
2278
|
+
lambda: self._facade.flow_patch_nodes(
|
|
2279
|
+
profile=profile,
|
|
2280
|
+
app_key=app_key,
|
|
2281
|
+
patch_nodes=patch_nodes,
|
|
2282
|
+
publish=publish,
|
|
2283
|
+
idempotency_key=idempotency_key,
|
|
2284
|
+
schema_version=schema_version,
|
|
2285
|
+
),
|
|
2286
|
+
error_code="FLOW_APPLY_FAILED",
|
|
2287
|
+
normalized_args={"app_key": app_key, "patch_nodes": patch_nodes, "publish": publish},
|
|
2288
|
+
suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, "app_key": app_key, "patch_nodes": patch_nodes}},
|
|
2289
|
+
)
|
|
2290
|
+
return _attach_builder_apply_envelope("app_flow_apply", result)
|
|
2291
|
+
if not isinstance(spec, dict) or not spec:
|
|
2292
|
+
return _config_failure(
|
|
2293
|
+
tool_name="app_flow_apply",
|
|
2294
|
+
message="app_flow_apply requires a non-empty WorkflowSpecDTO `spec` object.",
|
|
2295
|
+
fix_hint="Call app_flow_get_schema, then app_flow_get for GET-first baseline, patch spec, and apply.",
|
|
2296
|
+
)
|
|
2297
|
+
normalized_args = {
|
|
2298
|
+
"app_key": app_key,
|
|
2299
|
+
"publish": publish,
|
|
2300
|
+
"spec": spec,
|
|
2301
|
+
"idempotency_key": idempotency_key,
|
|
2302
|
+
"schema_version": schema_version,
|
|
2303
|
+
}
|
|
2304
|
+
result = _safe_tool_call(
|
|
2305
|
+
lambda: self._facade.flow_apply(
|
|
2306
|
+
profile=profile,
|
|
2307
|
+
app_key=app_key,
|
|
2308
|
+
spec=spec,
|
|
2309
|
+
publish=publish,
|
|
2310
|
+
idempotency_key=idempotency_key,
|
|
2311
|
+
schema_version=schema_version,
|
|
2312
|
+
),
|
|
2313
|
+
error_code="FLOW_APPLY_FAILED",
|
|
2314
|
+
normalized_args=normalized_args,
|
|
2315
|
+
suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1636
2316
|
)
|
|
1637
|
-
|
|
2317
|
+
result = self._retry_after_self_lock_release(
|
|
1638
2318
|
profile=profile,
|
|
1639
2319
|
result=result,
|
|
1640
|
-
retry_call=lambda: self.
|
|
2320
|
+
retry_call=lambda: self._facade.flow_apply(
|
|
1641
2321
|
profile=profile,
|
|
1642
2322
|
app_key=app_key,
|
|
1643
|
-
|
|
2323
|
+
spec=spec,
|
|
1644
2324
|
publish=publish,
|
|
1645
|
-
|
|
1646
|
-
|
|
2325
|
+
idempotency_key=idempotency_key,
|
|
2326
|
+
schema_version=schema_version,
|
|
1647
2327
|
),
|
|
1648
2328
|
)
|
|
2329
|
+
return _attach_builder_apply_envelope("app_flow_apply", result)
|
|
1649
2330
|
|
|
1650
|
-
|
|
2331
|
+
@tool_cn_name("应用视图应用")
|
|
2332
|
+
def app_views_apply(
|
|
1651
2333
|
self,
|
|
1652
2334
|
*,
|
|
1653
2335
|
profile: str,
|
|
1654
2336
|
app_key: str,
|
|
1655
|
-
mode: str = "replace",
|
|
1656
2337
|
publish: bool = True,
|
|
1657
|
-
|
|
1658
|
-
|
|
2338
|
+
upsert_views: list[JSONObject],
|
|
2339
|
+
patch_views: list[JSONObject] | None = None,
|
|
2340
|
+
remove_views: list[str],
|
|
2341
|
+
apps: list[JSONObject] | None = None,
|
|
1659
2342
|
) -> JSONObject:
|
|
1660
|
-
"""
|
|
1661
|
-
|
|
1662
|
-
|
|
2343
|
+
"""执行应用相关逻辑。"""
|
|
2344
|
+
if apps:
|
|
2345
|
+
return self._facade._batch_write_apps(
|
|
1663
2346
|
profile=profile,
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
2347
|
+
apps=apps,
|
|
2348
|
+
single_writer=lambda profile, app_key, **kw: self.app_views_apply(
|
|
2349
|
+
profile=profile,
|
|
2350
|
+
app_key=app_key,
|
|
2351
|
+
publish=kw.get("publish", publish),
|
|
2352
|
+
upsert_views=kw.get("upsert_views", []),
|
|
2353
|
+
patch_views=kw.get("patch_views", []),
|
|
2354
|
+
remove_views=kw.get("remove_views", []),
|
|
2355
|
+
),
|
|
2356
|
+
tool_name="app_views_apply",
|
|
2357
|
+
)
|
|
2358
|
+
result = self._app_views_apply_once(
|
|
1670
2359
|
profile=profile,
|
|
2360
|
+
app_key=app_key,
|
|
1671
2361
|
publish=publish,
|
|
1672
|
-
|
|
1673
|
-
|
|
2362
|
+
upsert_views=upsert_views,
|
|
2363
|
+
patch_views=patch_views or [],
|
|
2364
|
+
remove_views=remove_views,
|
|
1674
2365
|
)
|
|
1675
|
-
|
|
1676
|
-
return plan_result
|
|
1677
|
-
plan_args = plan_result.get("normalized_args")
|
|
1678
|
-
if not isinstance(plan_args, dict):
|
|
1679
|
-
plan_args = {}
|
|
1680
|
-
try:
|
|
1681
|
-
request = FlowPlanRequest.model_validate(
|
|
1682
|
-
{
|
|
1683
|
-
"app_key": plan_args.get("app_key") or app_key,
|
|
1684
|
-
"mode": plan_args.get("mode") or mode,
|
|
1685
|
-
"nodes": plan_args.get("nodes") or [],
|
|
1686
|
-
"transitions": plan_args.get("transitions") or [],
|
|
1687
|
-
"preset": None,
|
|
1688
|
-
}
|
|
1689
|
-
)
|
|
1690
|
-
except ValidationError as exc:
|
|
1691
|
-
return _validation_failure(
|
|
1692
|
-
str(exc),
|
|
1693
|
-
tool_name="app_flow_apply",
|
|
1694
|
-
exc=exc,
|
|
1695
|
-
suggested_next_call={
|
|
1696
|
-
"tool_name": "app_flow_apply",
|
|
1697
|
-
"arguments": {
|
|
1698
|
-
"profile": profile,
|
|
1699
|
-
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1700
|
-
"mode": str(plan_args.get("mode") or "replace"),
|
|
1701
|
-
"publish": publish,
|
|
1702
|
-
"nodes": plan_args.get("nodes") or [{"id": "start", "type": "start", "name": "发起"}],
|
|
1703
|
-
"transitions": plan_args.get("transitions") or [],
|
|
1704
|
-
},
|
|
1705
|
-
},
|
|
1706
|
-
)
|
|
1707
|
-
normalized_args = {
|
|
1708
|
-
"app_key": request.app_key,
|
|
1709
|
-
"mode": request.mode,
|
|
1710
|
-
"publish": publish,
|
|
1711
|
-
"nodes": [node.model_dump(mode="json") for node in request.nodes],
|
|
1712
|
-
"transitions": [transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
|
|
1713
|
-
}
|
|
1714
|
-
return _safe_tool_call(
|
|
1715
|
-
lambda: self._facade.app_flow_apply(
|
|
1716
|
-
profile=profile,
|
|
1717
|
-
app_key=request.app_key,
|
|
1718
|
-
mode=request.mode,
|
|
1719
|
-
publish=publish,
|
|
1720
|
-
nodes=[node.model_dump(mode="json") for node in request.nodes],
|
|
1721
|
-
transitions=[transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
|
|
1722
|
-
),
|
|
1723
|
-
error_code="FLOW_APPLY_FAILED",
|
|
1724
|
-
normalized_args=normalized_args,
|
|
1725
|
-
suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1726
|
-
)
|
|
1727
|
-
|
|
1728
|
-
@tool_cn_name("应用视图应用")
|
|
1729
|
-
def app_views_apply(
|
|
1730
|
-
self,
|
|
1731
|
-
*,
|
|
1732
|
-
profile: str,
|
|
1733
|
-
app_key: str,
|
|
1734
|
-
publish: bool = True,
|
|
1735
|
-
upsert_views: list[JSONObject],
|
|
1736
|
-
remove_views: list[str],
|
|
1737
|
-
) -> JSONObject:
|
|
1738
|
-
"""执行应用相关逻辑。"""
|
|
1739
|
-
result = self._app_views_apply_once(
|
|
1740
|
-
profile=profile,
|
|
1741
|
-
app_key=app_key,
|
|
1742
|
-
publish=publish,
|
|
1743
|
-
upsert_views=upsert_views,
|
|
1744
|
-
remove_views=remove_views,
|
|
1745
|
-
)
|
|
1746
|
-
return self._retry_after_self_lock_release(
|
|
2366
|
+
result = self._retry_after_self_lock_release(
|
|
1747
2367
|
profile=profile,
|
|
1748
2368
|
result=result,
|
|
1749
2369
|
retry_call=lambda: self._app_views_apply_once(
|
|
@@ -1752,8 +2372,10 @@ class AiBuilderTools(ToolBase):
|
|
|
1752
2372
|
publish=publish,
|
|
1753
2373
|
remove_views=remove_views,
|
|
1754
2374
|
upsert_views=upsert_views,
|
|
2375
|
+
patch_views=patch_views or [],
|
|
1755
2376
|
),
|
|
1756
2377
|
)
|
|
2378
|
+
return _attach_builder_apply_envelope("app_views_apply", result)
|
|
1757
2379
|
|
|
1758
2380
|
def _app_views_apply_once(
|
|
1759
2381
|
self,
|
|
@@ -1762,9 +2384,53 @@ class AiBuilderTools(ToolBase):
|
|
|
1762
2384
|
app_key: str,
|
|
1763
2385
|
publish: bool = True,
|
|
1764
2386
|
upsert_views: list[JSONObject],
|
|
2387
|
+
patch_views: list[JSONObject],
|
|
1765
2388
|
remove_views: list[str],
|
|
1766
2389
|
) -> JSONObject:
|
|
1767
2390
|
"""执行内部辅助逻辑。"""
|
|
2391
|
+
if patch_views:
|
|
2392
|
+
try:
|
|
2393
|
+
parsed_views = [ViewUpsertPatch.model_validate(item) for item in (upsert_views or [])]
|
|
2394
|
+
parsed_patch_views = [ViewPartialPatch.model_validate(item) for item in patch_views]
|
|
2395
|
+
except ValidationError as exc:
|
|
2396
|
+
return _visibility_validation_failure(
|
|
2397
|
+
str(exc),
|
|
2398
|
+
tool_name="app_views_apply",
|
|
2399
|
+
exc=exc,
|
|
2400
|
+
suggested_next_call={
|
|
2401
|
+
"tool_name": "app_views_apply",
|
|
2402
|
+
"arguments": {
|
|
2403
|
+
"profile": profile,
|
|
2404
|
+
"app_key": app_key,
|
|
2405
|
+
"publish": publish,
|
|
2406
|
+
"upsert_views": upsert_views or [],
|
|
2407
|
+
"patch_views": [
|
|
2408
|
+
{"view_key": "VIEW_KEY", "set": {"query_conditions": {"enabled": True, "rows": [["字段A"]]}}},
|
|
2409
|
+
],
|
|
2410
|
+
"remove_views": remove_views or [],
|
|
2411
|
+
},
|
|
2412
|
+
},
|
|
2413
|
+
)
|
|
2414
|
+
normalized_args = {
|
|
2415
|
+
"app_key": app_key,
|
|
2416
|
+
"publish": publish,
|
|
2417
|
+
"upsert_views": [view.model_dump(mode="json") for view in parsed_views],
|
|
2418
|
+
"patch_views": [patch.model_dump(mode="json") for patch in parsed_patch_views],
|
|
2419
|
+
"remove_views": list(remove_views or []),
|
|
2420
|
+
}
|
|
2421
|
+
return _safe_tool_call(
|
|
2422
|
+
lambda: self._facade.app_views_apply(
|
|
2423
|
+
profile=profile,
|
|
2424
|
+
app_key=app_key,
|
|
2425
|
+
publish=publish,
|
|
2426
|
+
upsert_views=parsed_views,
|
|
2427
|
+
patch_views=parsed_patch_views,
|
|
2428
|
+
remove_views=list(remove_views or []),
|
|
2429
|
+
),
|
|
2430
|
+
error_code="VIEWS_APPLY_FAILED",
|
|
2431
|
+
normalized_args=normalized_args,
|
|
2432
|
+
suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
2433
|
+
)
|
|
1768
2434
|
plan_result = self._rewrite_plan_result_for_apply(
|
|
1769
2435
|
result=self.app_views_plan(
|
|
1770
2436
|
profile=profile,
|
|
@@ -1813,6 +2479,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1813
2479
|
app_key=str(plan_args.get("app_key") or app_key),
|
|
1814
2480
|
publish=publish,
|
|
1815
2481
|
upsert_views=parsed_views,
|
|
2482
|
+
patch_views=[],
|
|
1816
2483
|
remove_views=list(plan_args.get("remove_views") or remove_views),
|
|
1817
2484
|
),
|
|
1818
2485
|
error_code="VIEWS_APPLY_FAILED",
|
|
@@ -1827,6 +2494,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1827
2494
|
profile: str,
|
|
1828
2495
|
app_key: str,
|
|
1829
2496
|
upsert_charts: list[JSONObject],
|
|
2497
|
+
patch_charts: list[JSONObject] | None = None,
|
|
1830
2498
|
remove_chart_ids: list[str],
|
|
1831
2499
|
reorder_chart_ids: list[str],
|
|
1832
2500
|
) -> JSONObject:
|
|
@@ -1835,6 +2503,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1835
2503
|
profile=profile,
|
|
1836
2504
|
app_key=app_key,
|
|
1837
2505
|
upsert_charts=upsert_charts,
|
|
2506
|
+
patch_charts=patch_charts or [],
|
|
1838
2507
|
remove_chart_ids=remove_chart_ids,
|
|
1839
2508
|
reorder_chart_ids=reorder_chart_ids,
|
|
1840
2509
|
)
|
|
@@ -1848,19 +2517,36 @@ class AiBuilderTools(ToolBase):
|
|
|
1848
2517
|
upsert_charts: list[JSONObject],
|
|
1849
2518
|
remove_chart_ids: list[str],
|
|
1850
2519
|
reorder_chart_ids: list[str],
|
|
2520
|
+
patch_charts: list[JSONObject] | None = None,
|
|
2521
|
+
apps: list[JSONObject] | None = None,
|
|
1851
2522
|
) -> JSONObject:
|
|
1852
2523
|
"""执行应用相关逻辑。"""
|
|
2524
|
+
if apps:
|
|
2525
|
+
return self._facade._batch_write_apps(
|
|
2526
|
+
profile=profile,
|
|
2527
|
+
apps=apps,
|
|
2528
|
+
single_writer=lambda profile, app_key, **kw: self.app_charts_apply(
|
|
2529
|
+
profile=profile,
|
|
2530
|
+
app_key=app_key,
|
|
2531
|
+
upsert_charts=kw.get("upsert_charts", []),
|
|
2532
|
+
patch_charts=kw.get("patch_charts", []),
|
|
2533
|
+
remove_chart_ids=kw.get("remove_chart_ids", []),
|
|
2534
|
+
reorder_chart_ids=kw.get("reorder_chart_ids", []),
|
|
2535
|
+
),
|
|
2536
|
+
tool_name="app_charts_apply",
|
|
2537
|
+
)
|
|
1853
2538
|
try:
|
|
1854
2539
|
request = ChartApplyRequest.model_validate(
|
|
1855
2540
|
{
|
|
1856
2541
|
"app_key": app_key,
|
|
1857
2542
|
"upsert_charts": upsert_charts or [],
|
|
2543
|
+
"patch_charts": patch_charts or [],
|
|
1858
2544
|
"remove_chart_ids": remove_chart_ids or [],
|
|
1859
2545
|
"reorder_chart_ids": reorder_chart_ids or [],
|
|
1860
2546
|
}
|
|
1861
2547
|
)
|
|
1862
2548
|
except ValidationError as exc:
|
|
1863
|
-
return _visibility_validation_failure(
|
|
2549
|
+
return _attach_builder_apply_envelope("app_charts_apply", _visibility_validation_failure(
|
|
1864
2550
|
str(exc),
|
|
1865
2551
|
tool_name="app_charts_apply",
|
|
1866
2552
|
exc=exc,
|
|
@@ -1874,14 +2560,14 @@ class AiBuilderTools(ToolBase):
|
|
|
1874
2560
|
"reorder_chart_ids": [],
|
|
1875
2561
|
},
|
|
1876
2562
|
},
|
|
1877
|
-
)
|
|
2563
|
+
))
|
|
1878
2564
|
normalized_args = request.model_dump(mode="json")
|
|
1879
|
-
return _safe_tool_call(
|
|
2565
|
+
return _attach_builder_apply_envelope("app_charts_apply", _safe_tool_call(
|
|
1880
2566
|
lambda: self._facade.chart_apply(profile=profile, request=request),
|
|
1881
2567
|
error_code="CHART_APPLY_FAILED",
|
|
1882
2568
|
normalized_args=normalized_args,
|
|
1883
2569
|
suggested_next_call={"tool_name": "app_charts_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1884
|
-
)
|
|
2570
|
+
))
|
|
1885
2571
|
|
|
1886
2572
|
@tool_cn_name("门户配置应用")
|
|
1887
2573
|
def portal_apply(
|
|
@@ -1890,9 +2576,12 @@ class AiBuilderTools(ToolBase):
|
|
|
1890
2576
|
profile: str,
|
|
1891
2577
|
dash_key: str = "",
|
|
1892
2578
|
dash_name: str = "",
|
|
2579
|
+
name: str = "",
|
|
1893
2580
|
package_id: int | None = None,
|
|
1894
2581
|
publish: bool = True,
|
|
1895
2582
|
sections: list[JSONObject] | None = None,
|
|
2583
|
+
pages: list[JSONObject] | None = None,
|
|
2584
|
+
layout_preset: str = "",
|
|
1896
2585
|
visibility: JSONObject | None = None,
|
|
1897
2586
|
auth: JSONObject | None = None,
|
|
1898
2587
|
icon: str | None = None,
|
|
@@ -1900,27 +2589,66 @@ class AiBuilderTools(ToolBase):
|
|
|
1900
2589
|
hide_copyright: bool | None = None,
|
|
1901
2590
|
dash_global_config: JSONObject | None = None,
|
|
1902
2591
|
config: JSONObject | None = None,
|
|
2592
|
+
payload: JSONObject | None = None,
|
|
2593
|
+
patch_sections: list[JSONObject] | None = None,
|
|
1903
2594
|
) -> JSONObject:
|
|
1904
2595
|
"""执行门户相关逻辑。"""
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
"
|
|
1909
|
-
"
|
|
1910
|
-
"
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
}
|
|
2596
|
+
if patch_sections:
|
|
2597
|
+
if not dash_key:
|
|
2598
|
+
return _attach_builder_apply_envelope("portal_apply", _config_failure(
|
|
2599
|
+
tool_name="portal_apply",
|
|
2600
|
+
message="patch_sections requires dash_key to identify the portal",
|
|
2601
|
+
fix_hint="Provide dash_key from portal_list or portal_get",
|
|
2602
|
+
))
|
|
2603
|
+
result = _safe_tool_call(
|
|
2604
|
+
lambda: self._facade.portal_patch_sections(
|
|
2605
|
+
profile=profile,
|
|
2606
|
+
dash_key=dash_key,
|
|
2607
|
+
patch_sections=patch_sections,
|
|
2608
|
+
publish=publish,
|
|
2609
|
+
),
|
|
2610
|
+
error_code="PORTAL_APPLY_FAILED",
|
|
2611
|
+
normalized_args={"dash_key": dash_key, "patch_sections": patch_sections, "publish": publish},
|
|
2612
|
+
suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, "dash_key": dash_key, "patch_sections": patch_sections}},
|
|
1921
2613
|
)
|
|
2614
|
+
return _attach_builder_apply_envelope("portal_apply", result)
|
|
2615
|
+
request_payload: dict[str, Any] = dict(payload) if isinstance(payload, dict) else {}
|
|
2616
|
+
if dash_key:
|
|
2617
|
+
request_payload["dash_key"] = dash_key
|
|
2618
|
+
if dash_name:
|
|
2619
|
+
request_payload["dash_name"] = dash_name
|
|
2620
|
+
elif name:
|
|
2621
|
+
request_payload["name"] = name
|
|
2622
|
+
if package_id is not None:
|
|
2623
|
+
request_payload["package_id"] = package_id
|
|
2624
|
+
if "publish" not in request_payload or publish is False:
|
|
2625
|
+
request_payload["publish"] = publish
|
|
2626
|
+
if sections:
|
|
2627
|
+
request_payload["sections"] = sections
|
|
2628
|
+
if pages:
|
|
2629
|
+
request_payload["pages"] = pages
|
|
2630
|
+
if layout_preset:
|
|
2631
|
+
request_payload["layout_preset"] = layout_preset
|
|
2632
|
+
if visibility is not None:
|
|
2633
|
+
request_payload["visibility"] = visibility
|
|
2634
|
+
if auth is not None:
|
|
2635
|
+
request_payload["auth"] = auth
|
|
2636
|
+
if icon is not None:
|
|
2637
|
+
request_payload["icon"] = icon
|
|
2638
|
+
if color is not None:
|
|
2639
|
+
request_payload["color"] = color
|
|
2640
|
+
if hide_copyright is not None:
|
|
2641
|
+
request_payload["hide_copyright"] = hide_copyright
|
|
2642
|
+
if dash_global_config is not None:
|
|
2643
|
+
request_payload["dash_global_config"] = dash_global_config
|
|
2644
|
+
if config:
|
|
2645
|
+
merged_config = dict(request_payload.get("config") or {}) if isinstance(request_payload.get("config"), dict) else {}
|
|
2646
|
+
merged_config.update(config)
|
|
2647
|
+
request_payload["config"] = merged_config
|
|
2648
|
+
try:
|
|
2649
|
+
request = PortalApplyRequest.model_validate(request_payload)
|
|
1922
2650
|
except ValidationError as exc:
|
|
1923
|
-
return _visibility_validation_failure(
|
|
2651
|
+
return _attach_builder_apply_envelope("portal_apply", _visibility_validation_failure(
|
|
1924
2652
|
str(exc),
|
|
1925
2653
|
tool_name="portal_apply",
|
|
1926
2654
|
exc=exc,
|
|
@@ -1931,6 +2659,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1931
2659
|
"dash_name": dash_name or "业务门户",
|
|
1932
2660
|
"package_id": package_id or 1001,
|
|
1933
2661
|
"publish": True,
|
|
2662
|
+
"layout_preset": "dashboard_2col",
|
|
1934
2663
|
"sections": [
|
|
1935
2664
|
{
|
|
1936
2665
|
"title": "经营概览",
|
|
@@ -1940,25 +2669,43 @@ class AiBuilderTools(ToolBase):
|
|
|
1940
2669
|
],
|
|
1941
2670
|
},
|
|
1942
2671
|
},
|
|
1943
|
-
)
|
|
2672
|
+
))
|
|
1944
2673
|
normalized_args = request.model_dump(mode="json")
|
|
1945
2674
|
normalized_args["package_id"] = normalized_args.pop("package_tag_id", package_id)
|
|
1946
|
-
|
|
2675
|
+
icon_failure = _validate_workspace_icon_for_builder(
|
|
2676
|
+
tool_name="portal_apply",
|
|
2677
|
+
icon=str(request.icon or ""),
|
|
2678
|
+
color=str(request.color or ""),
|
|
2679
|
+
creating=not bool(str(request.dash_key or "").strip()),
|
|
2680
|
+
)
|
|
2681
|
+
if icon_failure is not None:
|
|
2682
|
+
return _attach_builder_apply_envelope("portal_apply", icon_failure)
|
|
2683
|
+
result = _publicize_package_fields(_safe_tool_call(
|
|
1947
2684
|
lambda: self._facade.portal_apply(profile=profile, request=request),
|
|
1948
2685
|
error_code="PORTAL_APPLY_FAILED",
|
|
1949
2686
|
normalized_args=normalized_args,
|
|
1950
2687
|
suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1951
2688
|
))
|
|
2689
|
+
return _attach_builder_apply_envelope("portal_apply", result)
|
|
1952
2690
|
|
|
1953
2691
|
@tool_cn_name("应用发布校验")
|
|
1954
2692
|
def app_publish_verify(
|
|
1955
2693
|
self,
|
|
1956
2694
|
*,
|
|
1957
2695
|
profile: str,
|
|
1958
|
-
app_key: str,
|
|
2696
|
+
app_key: str = "",
|
|
2697
|
+
app_keys: list[str] | None = None,
|
|
1959
2698
|
expected_package_id: int | None = None,
|
|
1960
2699
|
) -> JSONObject:
|
|
1961
2700
|
"""执行应用相关逻辑。"""
|
|
2701
|
+
if app_keys:
|
|
2702
|
+
return self._facade._batch_read_app_keys(
|
|
2703
|
+
profile=profile,
|
|
2704
|
+
app_keys=app_keys,
|
|
2705
|
+
single_reader=lambda profile, app_key: self._facade.app_publish_verify(profile=profile, app_key=app_key, expected_package_tag_id=expected_package_id),
|
|
2706
|
+
data_key="verification",
|
|
2707
|
+
tool_name="app_publish_verify",
|
|
2708
|
+
)
|
|
1962
2709
|
normalized_args = {"app_key": app_key, "expected_package_id": expected_package_id}
|
|
1963
2710
|
result = _publicize_package_fields(_safe_tool_call(
|
|
1964
2711
|
lambda: self._facade.app_publish_verify(profile=profile, app_key=app_key, expected_package_tag_id=expected_package_id),
|
|
@@ -1966,7 +2713,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1966
2713
|
normalized_args=normalized_args,
|
|
1967
2714
|
suggested_next_call={"tool_name": "app_publish_verify", "arguments": {"profile": profile, **normalized_args}},
|
|
1968
2715
|
))
|
|
1969
|
-
|
|
2716
|
+
result = _publicize_package_fields(self._retry_after_self_lock_release(
|
|
1970
2717
|
profile=profile,
|
|
1971
2718
|
result=result,
|
|
1972
2719
|
retry_call=lambda: self._facade.app_publish_verify(
|
|
@@ -1975,6 +2722,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1975
2722
|
expected_package_tag_id=expected_package_id,
|
|
1976
2723
|
),
|
|
1977
2724
|
))
|
|
2725
|
+
return _attach_builder_apply_envelope("app_publish_verify", result)
|
|
1978
2726
|
|
|
1979
2727
|
def _retry_after_self_lock_release(self, *, profile: str, result: JSONObject, retry_call) -> JSONObject:
|
|
1980
2728
|
"""执行内部辅助逻辑。"""
|
|
@@ -2093,6 +2841,128 @@ class AiBuilderTools(ToolBase):
|
|
|
2093
2841
|
return rewritten
|
|
2094
2842
|
|
|
2095
2843
|
|
|
2844
|
+
def _multi_app_item_failure(index: int, item: object, error_code: str, message: str) -> JSONObject:
|
|
2845
|
+
app_name = None
|
|
2846
|
+
client_key = None
|
|
2847
|
+
app_key = None
|
|
2848
|
+
if isinstance(item, dict):
|
|
2849
|
+
app_name = str(item.get("app_name") or item.get("appTitle") or item.get("app_title") or item.get("title") or "").strip() or None
|
|
2850
|
+
client_key = str(item.get("client_key") or item.get("clientKey") or "").strip() or None
|
|
2851
|
+
app_key = str(item.get("app_key") or item.get("appKey") or "").strip() or None
|
|
2852
|
+
return {
|
|
2853
|
+
"index": index,
|
|
2854
|
+
"row_number": index + 1,
|
|
2855
|
+
"client_key": client_key,
|
|
2856
|
+
"app_name": app_name,
|
|
2857
|
+
"app_key": app_key,
|
|
2858
|
+
"status": "failed",
|
|
2859
|
+
"stage": "validate_item",
|
|
2860
|
+
"error_code": error_code,
|
|
2861
|
+
"message": message,
|
|
2862
|
+
"safe_to_retry": True,
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
|
|
2866
|
+
def _compile_multi_app_schema_item_refs(item: JSONObject, client_key_to_app_key: dict[str, str]) -> JSONObject:
|
|
2867
|
+
compiled = deepcopy(item)
|
|
2868
|
+
|
|
2869
|
+
def visit(value):
|
|
2870
|
+
if isinstance(value, list):
|
|
2871
|
+
return [visit(entry) for entry in value]
|
|
2872
|
+
if not isinstance(value, dict):
|
|
2873
|
+
return value
|
|
2874
|
+
payload = {key: visit(entry) for key, entry in value.items()}
|
|
2875
|
+
ref = (
|
|
2876
|
+
payload.pop("target_app_ref", None)
|
|
2877
|
+
or payload.pop("targetAppRef", None)
|
|
2878
|
+
or payload.pop("target_app_client_key", None)
|
|
2879
|
+
or payload.pop("targetAppClientKey", None)
|
|
2880
|
+
)
|
|
2881
|
+
if ref is not None:
|
|
2882
|
+
ref_key = str(ref or "").strip()
|
|
2883
|
+
target_app_key = client_key_to_app_key.get(ref_key)
|
|
2884
|
+
if not target_app_key:
|
|
2885
|
+
raise ValueError(f"target_app_ref '{ref_key}' did not match any apps[].client_key")
|
|
2886
|
+
payload["target_app_key"] = target_app_key
|
|
2887
|
+
return payload
|
|
2888
|
+
|
|
2889
|
+
return visit(compiled)
|
|
2890
|
+
|
|
2891
|
+
|
|
2892
|
+
def _split_multi_app_initial_add_fields(item: JSONObject, *, is_new_app: bool) -> tuple[list[JSONObject], list[JSONObject]]:
|
|
2893
|
+
add_fields = _multi_app_list_value(item, "add_fields", "addFields")
|
|
2894
|
+
if not is_new_app:
|
|
2895
|
+
return [], add_fields
|
|
2896
|
+
initial: list[JSONObject] = []
|
|
2897
|
+
deferred: list[JSONObject] = []
|
|
2898
|
+
for field in add_fields:
|
|
2899
|
+
if _contains_multi_app_target_ref(field):
|
|
2900
|
+
deferred.append(field)
|
|
2901
|
+
else:
|
|
2902
|
+
initial.append(field)
|
|
2903
|
+
return initial, deferred
|
|
2904
|
+
|
|
2905
|
+
|
|
2906
|
+
def _compiled_multi_app_deferred_add_fields(compiled_item: JSONObject, existing_result: JSONObject) -> list[JSONObject]:
|
|
2907
|
+
deferred = existing_result.get("deferred_add_fields")
|
|
2908
|
+
if not isinstance(deferred, list):
|
|
2909
|
+
return list(compiled_item.get("add_fields") or [])
|
|
2910
|
+
deferred_names = {str(item.get("name") or item.get("title") or item.get("label") or "").strip() for item in deferred if isinstance(item, dict)}
|
|
2911
|
+
if not deferred_names:
|
|
2912
|
+
return []
|
|
2913
|
+
return [
|
|
2914
|
+
deepcopy(field)
|
|
2915
|
+
for field in list(compiled_item.get("add_fields") or [])
|
|
2916
|
+
if isinstance(field, dict)
|
|
2917
|
+
and str(field.get("name") or field.get("title") or field.get("label") or "").strip() in deferred_names
|
|
2918
|
+
]
|
|
2919
|
+
|
|
2920
|
+
|
|
2921
|
+
def _multi_app_list_value(item: JSONObject, *keys: str) -> list[JSONObject]:
|
|
2922
|
+
for key in keys:
|
|
2923
|
+
value = item.get(key)
|
|
2924
|
+
if isinstance(value, list):
|
|
2925
|
+
return [deepcopy(entry) for entry in value if isinstance(entry, dict)]
|
|
2926
|
+
return []
|
|
2927
|
+
|
|
2928
|
+
|
|
2929
|
+
def _contains_multi_app_target_ref(value: object) -> bool:
|
|
2930
|
+
if isinstance(value, list):
|
|
2931
|
+
return any(_contains_multi_app_target_ref(item) for item in value)
|
|
2932
|
+
if not isinstance(value, dict):
|
|
2933
|
+
return False
|
|
2934
|
+
for key, entry in value.items():
|
|
2935
|
+
if key in {"target_app_ref", "targetAppRef", "target_app_client_key", "targetAppClientKey"}:
|
|
2936
|
+
return True
|
|
2937
|
+
if _contains_multi_app_target_ref(entry):
|
|
2938
|
+
return True
|
|
2939
|
+
return False
|
|
2940
|
+
|
|
2941
|
+
|
|
2942
|
+
def _merge_schema_field_diffs(*diffs: object) -> JSONObject:
|
|
2943
|
+
merged: JSONObject = {"added": [], "updated": [], "removed": []}
|
|
2944
|
+
for diff in diffs:
|
|
2945
|
+
if not isinstance(diff, dict):
|
|
2946
|
+
continue
|
|
2947
|
+
for key in ("added", "updated", "removed"):
|
|
2948
|
+
values = diff.get(key)
|
|
2949
|
+
if not isinstance(values, list):
|
|
2950
|
+
continue
|
|
2951
|
+
for value in values:
|
|
2952
|
+
if value not in merged[key]:
|
|
2953
|
+
merged[key].append(value)
|
|
2954
|
+
return merged
|
|
2955
|
+
|
|
2956
|
+
|
|
2957
|
+
def _schema_apply_result_has_write(result: JSONObject) -> bool:
|
|
2958
|
+
if bool(result.get("created")) or bool(result.get("published")) or bool(result.get("app_base_updated")):
|
|
2959
|
+
return True
|
|
2960
|
+
field_diff = result.get("field_diff")
|
|
2961
|
+
if isinstance(field_diff, dict):
|
|
2962
|
+
return any(bool(field_diff.get(key)) for key in ("added", "updated", "removed"))
|
|
2963
|
+
return False
|
|
2964
|
+
|
|
2965
|
+
|
|
2096
2966
|
def _validation_failure(
|
|
2097
2967
|
detail: str,
|
|
2098
2968
|
*,
|
|
@@ -2173,20 +3043,34 @@ def _visibility_validation_failure(
|
|
|
2173
3043
|
return result
|
|
2174
3044
|
|
|
2175
3045
|
|
|
2176
|
-
def _config_failure(
|
|
3046
|
+
def _config_failure(
|
|
3047
|
+
*,
|
|
3048
|
+
tool_name: str,
|
|
3049
|
+
message: str,
|
|
3050
|
+
fix_hint: str,
|
|
3051
|
+
error_code: str = "CONFIG_ERROR",
|
|
3052
|
+
details: JSONObject | None = None,
|
|
3053
|
+
allowed_values: JSONObject | None = None,
|
|
3054
|
+
) -> JSONObject:
|
|
2177
3055
|
contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
|
|
3056
|
+
public_allowed_values = deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {}
|
|
3057
|
+
if allowed_values:
|
|
3058
|
+
public_allowed_values.update(deepcopy(allowed_values))
|
|
3059
|
+
public_details: JSONObject = {
|
|
3060
|
+
"fix_hint": fix_hint,
|
|
3061
|
+
"allowed_keys": deepcopy(contract.get("allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
3062
|
+
}
|
|
3063
|
+
if details:
|
|
3064
|
+
public_details.update(deepcopy(details))
|
|
2178
3065
|
return {
|
|
2179
3066
|
"status": "failed",
|
|
2180
|
-
"error_code":
|
|
3067
|
+
"error_code": error_code,
|
|
2181
3068
|
"recoverable": True,
|
|
2182
3069
|
"message": message,
|
|
2183
3070
|
"normalized_args": {},
|
|
2184
3071
|
"missing_fields": [],
|
|
2185
|
-
"allowed_values":
|
|
2186
|
-
"details":
|
|
2187
|
-
"fix_hint": fix_hint,
|
|
2188
|
-
"allowed_keys": deepcopy(contract.get("allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
2189
|
-
},
|
|
3072
|
+
"allowed_values": public_allowed_values,
|
|
3073
|
+
"details": public_details,
|
|
2190
3074
|
"suggested_next_call": None,
|
|
2191
3075
|
"request_id": None,
|
|
2192
3076
|
"backend_code": None,
|
|
@@ -2196,6 +3080,52 @@ def _config_failure(*, tool_name: str, message: str, fix_hint: str) -> JSONObjec
|
|
|
2196
3080
|
}
|
|
2197
3081
|
|
|
2198
3082
|
|
|
3083
|
+
def _workspace_icon_config_failure(
|
|
3084
|
+
*,
|
|
3085
|
+
tool_name: str,
|
|
3086
|
+
error_code: str,
|
|
3087
|
+
message: str,
|
|
3088
|
+
details: JSONObject,
|
|
3089
|
+
) -> JSONObject:
|
|
3090
|
+
return _config_failure(
|
|
3091
|
+
tool_name=tool_name,
|
|
3092
|
+
error_code=error_code,
|
|
3093
|
+
message=message,
|
|
3094
|
+
fix_hint="Call `qingflow --json builder icon catalog`, choose an explicit non-template icon and color, then retry.",
|
|
3095
|
+
details=details,
|
|
3096
|
+
allowed_values={
|
|
3097
|
+
"workspace_icon.icon_names": list(WORKSPACE_ICON_NAMES),
|
|
3098
|
+
"workspace_icon.icon_colors": list(WORKSPACE_ICON_COLORS),
|
|
3099
|
+
"workspace_icon.generic_icon_names": list(GENERIC_WORKSPACE_ICON_NAMES),
|
|
3100
|
+
},
|
|
3101
|
+
)
|
|
3102
|
+
|
|
3103
|
+
|
|
3104
|
+
def _validate_workspace_icon_for_builder(
|
|
3105
|
+
*,
|
|
3106
|
+
tool_name: str,
|
|
3107
|
+
icon: str | None,
|
|
3108
|
+
color: str | None,
|
|
3109
|
+
creating: bool,
|
|
3110
|
+
) -> JSONObject | None:
|
|
3111
|
+
if not creating and not (str(icon or "").strip() or str(color or "").strip()):
|
|
3112
|
+
return None
|
|
3113
|
+
ok, error_code, message, details = validate_workspace_icon_choice(
|
|
3114
|
+
icon=icon,
|
|
3115
|
+
color=color,
|
|
3116
|
+
require_explicit=creating,
|
|
3117
|
+
disallow_generic=creating,
|
|
3118
|
+
)
|
|
3119
|
+
if ok:
|
|
3120
|
+
return None
|
|
3121
|
+
return _workspace_icon_config_failure(
|
|
3122
|
+
tool_name=tool_name,
|
|
3123
|
+
error_code=error_code or "WORKSPACE_ICON_INVALID",
|
|
3124
|
+
message=message or "invalid workspace icon configuration",
|
|
3125
|
+
details=details,
|
|
3126
|
+
)
|
|
3127
|
+
|
|
3128
|
+
|
|
2199
3129
|
def _safe_tool_call(
|
|
2200
3130
|
call,
|
|
2201
3131
|
*,
|
|
@@ -2244,6 +3174,7 @@ def _publicize_package_fields(value):
|
|
|
2244
3174
|
"tag_ids_after": "package_ids_after",
|
|
2245
3175
|
"tag_name": "package_name",
|
|
2246
3176
|
"tag_icon": "icon",
|
|
3177
|
+
"iconConfig": "icon_config",
|
|
2247
3178
|
"package_tag_id": "package_id",
|
|
2248
3179
|
"package_tag_ids": "package_ids",
|
|
2249
3180
|
"expected_package_tag_id": "expected_package_id",
|
|
@@ -2255,6 +3186,742 @@ def _publicize_package_fields(value):
|
|
|
2255
3186
|
return public
|
|
2256
3187
|
|
|
2257
3188
|
|
|
3189
|
+
def _builder_contract_with_apply_output(tool_name: str, contract: JSONObject) -> JSONObject:
|
|
3190
|
+
public = deepcopy(contract)
|
|
3191
|
+
if tool_name not in BUILDER_APPLY_TOOL_NAMES:
|
|
3192
|
+
return public
|
|
3193
|
+
notes = public.setdefault("execution_notes", [])
|
|
3194
|
+
if isinstance(notes, list):
|
|
3195
|
+
note = "apply/write output includes schema_version, operation, summary, and resources[]; UI and agents should read resources[].id/key/name first and use legacy fields only for compatibility/debugging"
|
|
3196
|
+
if note not in notes:
|
|
3197
|
+
notes.append(note)
|
|
3198
|
+
public["output_contract"] = {
|
|
3199
|
+
"schema_version": BUILDER_APPLY_SCHEMA_VERSION,
|
|
3200
|
+
"preferred_ui_fields": ["operation", "summary", "resources"],
|
|
3201
|
+
"resource_fields": ["resource_type", "operation", "status", "id", "key", "name", "ids", "parent", "icon_config", "error_code", "message"],
|
|
3202
|
+
"legacy_fields_preserved": True,
|
|
3203
|
+
}
|
|
3204
|
+
return public
|
|
3205
|
+
|
|
3206
|
+
|
|
3207
|
+
def _attach_builder_apply_envelope(tool_name: str, payload: JSONObject) -> JSONObject:
|
|
3208
|
+
if not isinstance(payload, dict):
|
|
3209
|
+
return payload
|
|
3210
|
+
resources = _builder_apply_resources(tool_name, payload)
|
|
3211
|
+
payload["schema_version"] = BUILDER_APPLY_SCHEMA_VERSION
|
|
3212
|
+
payload["operation"] = tool_name
|
|
3213
|
+
payload["resources"] = resources
|
|
3214
|
+
payload["summary"] = _builder_apply_summary(payload, resources)
|
|
3215
|
+
return payload
|
|
3216
|
+
|
|
3217
|
+
|
|
3218
|
+
def _builder_apply_summary(payload: JSONObject, resources: list[JSONObject]) -> JSONObject:
|
|
3219
|
+
status = str(payload.get("status") or "")
|
|
3220
|
+
failed = sum(1 for item in resources if str(item.get("status") or "") == "failed")
|
|
3221
|
+
created = sum(1 for item in resources if str(item.get("operation") or "") == "created" and str(item.get("status") or "") != "failed")
|
|
3222
|
+
removed = sum(1 for item in resources if str(item.get("operation") or "") == "removed" and str(item.get("status") or "") != "failed")
|
|
3223
|
+
updated = sum(
|
|
3224
|
+
1
|
|
3225
|
+
for item in resources
|
|
3226
|
+
if str(item.get("status") or "") != "failed"
|
|
3227
|
+
and str(item.get("operation") or "") in {"updated", "layout_updated", "workflow_updated", "verified", "published"}
|
|
3228
|
+
)
|
|
3229
|
+
published_value = payload.get("published")
|
|
3230
|
+
if published_value is None:
|
|
3231
|
+
publish_requested = payload.get("publish_requested")
|
|
3232
|
+
published_value = bool(publish_requested) and status in {"success", "partial_success"}
|
|
3233
|
+
verified_value = payload.get("verified")
|
|
3234
|
+
if verified_value is None:
|
|
3235
|
+
verification = payload.get("verification")
|
|
3236
|
+
verified_value = status == "success" and failed == 0 and _builder_verification_truthy(verification)
|
|
3237
|
+
summary: JSONObject = {
|
|
3238
|
+
"total": len(resources),
|
|
3239
|
+
"created": created,
|
|
3240
|
+
"updated": updated,
|
|
3241
|
+
"removed": removed,
|
|
3242
|
+
"failed": failed,
|
|
3243
|
+
"published": bool(published_value),
|
|
3244
|
+
"verified": bool(verified_value),
|
|
3245
|
+
}
|
|
3246
|
+
if "write_executed" in payload:
|
|
3247
|
+
summary["write_executed"] = bool(payload.get("write_executed"))
|
|
3248
|
+
if "safe_to_retry" in payload:
|
|
3249
|
+
summary["safe_to_retry"] = bool(payload.get("safe_to_retry"))
|
|
3250
|
+
return summary
|
|
3251
|
+
|
|
3252
|
+
|
|
3253
|
+
def _builder_verification_truthy(value: object) -> bool:
|
|
3254
|
+
if value is None:
|
|
3255
|
+
return True
|
|
3256
|
+
if isinstance(value, bool):
|
|
3257
|
+
return value
|
|
3258
|
+
if isinstance(value, dict):
|
|
3259
|
+
booleans = [item for item in value.values() if isinstance(item, bool)]
|
|
3260
|
+
return all(booleans) if booleans else True
|
|
3261
|
+
return True
|
|
3262
|
+
|
|
3263
|
+
|
|
3264
|
+
def _builder_apply_resources(tool_name: str, payload: JSONObject) -> list[JSONObject]:
|
|
3265
|
+
resources: list[JSONObject]
|
|
3266
|
+
if tool_name == "package_apply":
|
|
3267
|
+
resources = _builder_package_resources(payload)
|
|
3268
|
+
elif tool_name == "app_schema_apply":
|
|
3269
|
+
resources = _builder_schema_resources(payload)
|
|
3270
|
+
elif tool_name == "app_layout_apply":
|
|
3271
|
+
resources = [_builder_app_resource(payload, operation="layout_updated")]
|
|
3272
|
+
elif tool_name == "app_flow_apply":
|
|
3273
|
+
resources = [_builder_app_resource(payload, operation="workflow_updated")]
|
|
3274
|
+
elif tool_name == "app_views_apply":
|
|
3275
|
+
resources = _builder_view_resources(payload)
|
|
3276
|
+
elif tool_name == "app_charts_apply":
|
|
3277
|
+
resources = _builder_chart_resources(payload)
|
|
3278
|
+
elif tool_name == "portal_apply":
|
|
3279
|
+
resources = _builder_portal_resources(payload)
|
|
3280
|
+
elif tool_name == "app_custom_buttons_apply":
|
|
3281
|
+
resources = _builder_button_resources(payload)
|
|
3282
|
+
elif tool_name == "app_associated_resources_apply":
|
|
3283
|
+
resources = _builder_associated_resource_resources(payload)
|
|
3284
|
+
elif tool_name == "app_publish_verify":
|
|
3285
|
+
resources = [_builder_app_resource(payload, operation="verified")]
|
|
3286
|
+
else:
|
|
3287
|
+
resources = []
|
|
3288
|
+
if not resources and _builder_status(payload, "") == "failed" and _builder_apply_tool_is_app_scoped(tool_name):
|
|
3289
|
+
app_key = _builder_payload_app_key(payload)
|
|
3290
|
+
if app_key not in (None, ""):
|
|
3291
|
+
resources = [_builder_app_resource(payload, operation="failed")]
|
|
3292
|
+
return resources
|
|
3293
|
+
|
|
3294
|
+
|
|
3295
|
+
def _builder_apply_tool_is_app_scoped(tool_name: str) -> bool:
|
|
3296
|
+
return tool_name in {
|
|
3297
|
+
"app_schema_apply",
|
|
3298
|
+
"app_layout_apply",
|
|
3299
|
+
"app_flow_apply",
|
|
3300
|
+
"app_views_apply",
|
|
3301
|
+
"app_custom_buttons_apply",
|
|
3302
|
+
"app_associated_resources_apply",
|
|
3303
|
+
"app_charts_apply",
|
|
3304
|
+
"app_publish_verify",
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
|
|
3308
|
+
def _builder_status(payload_or_item: JSONObject, fallback: str = "success") -> str:
|
|
3309
|
+
status = str(payload_or_item.get("status") or fallback or "success")
|
|
3310
|
+
return status
|
|
3311
|
+
|
|
3312
|
+
|
|
3313
|
+
def _builder_operation(value: object, fallback: str = "updated") -> str:
|
|
3314
|
+
raw = str(value or fallback or "updated").strip()
|
|
3315
|
+
mapping = {
|
|
3316
|
+
"create": "created",
|
|
3317
|
+
"created": "created",
|
|
3318
|
+
"add": "created",
|
|
3319
|
+
"update": "updated",
|
|
3320
|
+
"updated": "updated",
|
|
3321
|
+
"patch": "updated",
|
|
3322
|
+
"remove": "removed",
|
|
3323
|
+
"removed": "removed",
|
|
3324
|
+
"delete": "removed",
|
|
3325
|
+
"deleted": "removed",
|
|
3326
|
+
"unchanged": "unchanged",
|
|
3327
|
+
"failed": "failed",
|
|
3328
|
+
}
|
|
3329
|
+
return mapping.get(raw, raw)
|
|
3330
|
+
|
|
3331
|
+
|
|
3332
|
+
def _builder_parent(resource_type: str, *, key: object = None, name: object = None, id_value: object = None) -> JSONObject:
|
|
3333
|
+
return {
|
|
3334
|
+
"resource_type": resource_type,
|
|
3335
|
+
"id": id_value,
|
|
3336
|
+
"key": str(key) if key not in (None, "") else None,
|
|
3337
|
+
"name": str(name) if name not in (None, "") else None,
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
|
|
3341
|
+
def _builder_app_parent(payload: JSONObject) -> JSONObject | None:
|
|
3342
|
+
app_key = payload.get("app_key") or payload.get("appKey")
|
|
3343
|
+
app_name = payload.get("app_name_after") or payload.get("app_name") or payload.get("appTitle") or payload.get("app_title")
|
|
3344
|
+
if app_key in (None, "") and app_name in (None, ""):
|
|
3345
|
+
return None
|
|
3346
|
+
return _builder_parent("app", key=app_key, name=app_name)
|
|
3347
|
+
|
|
3348
|
+
|
|
3349
|
+
def _builder_icon_config(raw_icon: object = None, *, icon: object = None, color: object = None) -> JSONObject | None:
|
|
3350
|
+
raw = str(raw_icon).strip() if raw_icon not in (None, "") else ""
|
|
3351
|
+
explicit_icon = str(icon).strip() if icon not in (None, "") else ""
|
|
3352
|
+
explicit_color = str(color).strip() if color not in (None, "") else ""
|
|
3353
|
+
if raw:
|
|
3354
|
+
if raw.startswith("{") and raw.endswith("}"):
|
|
3355
|
+
config = workspace_icon_config(raw)
|
|
3356
|
+
else:
|
|
3357
|
+
config = {
|
|
3358
|
+
"icon_name": normalize_workspace_icon_name(raw),
|
|
3359
|
+
"icon_color": explicit_color or None,
|
|
3360
|
+
"icon_text": None,
|
|
3361
|
+
"raw": raw,
|
|
3362
|
+
}
|
|
3363
|
+
if any(config.get(key) for key in ("icon_name", "icon_color", "icon_text", "raw")):
|
|
3364
|
+
return config
|
|
3365
|
+
if explicit_icon or explicit_color:
|
|
3366
|
+
return {
|
|
3367
|
+
"icon_name": normalize_workspace_icon_name(explicit_icon) if explicit_icon else None,
|
|
3368
|
+
"icon_color": explicit_color or None,
|
|
3369
|
+
"icon_text": None,
|
|
3370
|
+
"raw": None,
|
|
3371
|
+
}
|
|
3372
|
+
return None
|
|
3373
|
+
|
|
3374
|
+
|
|
3375
|
+
def _builder_container_icon_config(container: object, *, raw_keys: tuple[str, ...], icon_keys: tuple[str, ...] = ("icon",), color_keys: tuple[str, ...] = ("color",)) -> JSONObject | None:
|
|
3376
|
+
if not isinstance(container, dict):
|
|
3377
|
+
return None
|
|
3378
|
+
raw_icon = next((container.get(key) for key in raw_keys if container.get(key) not in (None, "")), None)
|
|
3379
|
+
icon = next((container.get(key) for key in icon_keys if container.get(key) not in (None, "")), None)
|
|
3380
|
+
color = next((container.get(key) for key in color_keys if container.get(key) not in (None, "")), None)
|
|
3381
|
+
return _builder_icon_config(raw_icon, icon=icon, color=color)
|
|
3382
|
+
|
|
3383
|
+
|
|
3384
|
+
def _builder_resource(
|
|
3385
|
+
*,
|
|
3386
|
+
resource_type: str,
|
|
3387
|
+
operation: str,
|
|
3388
|
+
status: str,
|
|
3389
|
+
id_value: object = None,
|
|
3390
|
+
key: object = None,
|
|
3391
|
+
name: object = None,
|
|
3392
|
+
ids: JSONObject | None = None,
|
|
3393
|
+
parent: JSONObject | None = None,
|
|
3394
|
+
icon_config: JSONObject | None = None,
|
|
3395
|
+
error_code: object = None,
|
|
3396
|
+
message: object = None,
|
|
3397
|
+
) -> JSONObject:
|
|
3398
|
+
resource = {
|
|
3399
|
+
"resource_type": resource_type,
|
|
3400
|
+
"operation": operation,
|
|
3401
|
+
"status": status,
|
|
3402
|
+
"id": id_value,
|
|
3403
|
+
"key": str(key) if key not in (None, "") else None,
|
|
3404
|
+
"name": str(name) if name not in (None, "") else None,
|
|
3405
|
+
"ids": ids or {},
|
|
3406
|
+
"parent": parent,
|
|
3407
|
+
"error_code": str(error_code) if error_code not in (None, "") else None,
|
|
3408
|
+
"message": str(message) if message not in (None, "") else None,
|
|
3409
|
+
}
|
|
3410
|
+
if icon_config:
|
|
3411
|
+
resource["icon_config"] = icon_config
|
|
3412
|
+
return resource
|
|
3413
|
+
|
|
3414
|
+
|
|
3415
|
+
def _builder_app_resource(payload: JSONObject, *, operation: str) -> JSONObject:
|
|
3416
|
+
status = _builder_status(payload, "success")
|
|
3417
|
+
if status == "failed":
|
|
3418
|
+
operation = "failed"
|
|
3419
|
+
app_key = _builder_payload_app_key(payload)
|
|
3420
|
+
app_name = _builder_payload_app_name(payload)
|
|
3421
|
+
normalized_args = payload.get("normalized_args") if isinstance(payload.get("normalized_args"), dict) else {}
|
|
3422
|
+
icon_config = (
|
|
3423
|
+
_builder_container_icon_config(payload, raw_keys=("app_icon", "appIcon"))
|
|
3424
|
+
or _builder_container_icon_config(normalized_args, raw_keys=("app_icon", "appIcon", "icon"))
|
|
3425
|
+
)
|
|
3426
|
+
return _builder_resource(
|
|
3427
|
+
resource_type="app",
|
|
3428
|
+
operation=operation,
|
|
3429
|
+
status=status,
|
|
3430
|
+
key=app_key,
|
|
3431
|
+
name=app_name,
|
|
3432
|
+
ids={"app_key": app_key} if app_key not in (None, "") else {},
|
|
3433
|
+
icon_config=icon_config,
|
|
3434
|
+
error_code=payload.get("error_code"),
|
|
3435
|
+
message=payload.get("message") if status == "failed" else None,
|
|
3436
|
+
)
|
|
3437
|
+
|
|
3438
|
+
|
|
3439
|
+
def _builder_payload_app_key(payload: JSONObject) -> object:
|
|
3440
|
+
return _builder_payload_identity_value(payload, ("app_key", "appKey"))
|
|
3441
|
+
|
|
3442
|
+
|
|
3443
|
+
def _builder_payload_app_name(payload: JSONObject) -> object:
|
|
3444
|
+
return _builder_payload_identity_value(payload, ("app_name_after", "app_name", "appName", "appTitle", "app_title", "name", "title"))
|
|
3445
|
+
|
|
3446
|
+
|
|
3447
|
+
def _builder_payload_identity_value(payload: JSONObject, keys: tuple[str, ...]) -> object:
|
|
3448
|
+
for key in keys:
|
|
3449
|
+
value = payload.get(key)
|
|
3450
|
+
if value not in (None, ""):
|
|
3451
|
+
return value
|
|
3452
|
+
for container_key in ("normalized_args", "canonical_arguments"):
|
|
3453
|
+
container = payload.get(container_key)
|
|
3454
|
+
if isinstance(container, dict):
|
|
3455
|
+
for key in keys:
|
|
3456
|
+
value = container.get(key)
|
|
3457
|
+
if value not in (None, ""):
|
|
3458
|
+
return value
|
|
3459
|
+
details = payload.get("details")
|
|
3460
|
+
if isinstance(details, dict):
|
|
3461
|
+
for container_key in ("normalized_args", "canonical_arguments"):
|
|
3462
|
+
container = details.get(container_key)
|
|
3463
|
+
if isinstance(container, dict):
|
|
3464
|
+
for key in keys:
|
|
3465
|
+
value = container.get(key)
|
|
3466
|
+
if value not in (None, ""):
|
|
3467
|
+
return value
|
|
3468
|
+
return None
|
|
3469
|
+
|
|
3470
|
+
|
|
3471
|
+
def _builder_package_resources(payload: JSONObject) -> list[JSONObject]:
|
|
3472
|
+
package_id = payload.get("package_id") or payload.get("id")
|
|
3473
|
+
package_name = payload.get("package_name") or payload.get("name")
|
|
3474
|
+
status = _builder_status(payload, "success")
|
|
3475
|
+
operation = "failed" if status == "failed" else ("created" if bool(payload.get("created")) else "updated")
|
|
3476
|
+
normalized_args = payload.get("normalized_args") if isinstance(payload.get("normalized_args"), dict) else {}
|
|
3477
|
+
icon_config = (
|
|
3478
|
+
_builder_container_icon_config(payload, raw_keys=("icon", "tagIcon", "tag_icon"))
|
|
3479
|
+
or _builder_container_icon_config(normalized_args, raw_keys=("icon", "tagIcon", "tag_icon"))
|
|
3480
|
+
)
|
|
3481
|
+
return [
|
|
3482
|
+
_builder_resource(
|
|
3483
|
+
resource_type="package",
|
|
3484
|
+
operation=operation,
|
|
3485
|
+
status=status,
|
|
3486
|
+
id_value=package_id,
|
|
3487
|
+
key=str(package_id) if package_id not in (None, "") else None,
|
|
3488
|
+
name=package_name,
|
|
3489
|
+
ids={"package_id": package_id} if package_id not in (None, "") else {},
|
|
3490
|
+
icon_config=icon_config,
|
|
3491
|
+
error_code=payload.get("error_code"),
|
|
3492
|
+
message=payload.get("message") if status == "failed" else None,
|
|
3493
|
+
)
|
|
3494
|
+
]
|
|
3495
|
+
|
|
3496
|
+
|
|
3497
|
+
def _builder_schema_resources(payload: JSONObject) -> list[JSONObject]:
|
|
3498
|
+
if payload.get("mode") == "multi_app" and isinstance(payload.get("apps"), list):
|
|
3499
|
+
resources: list[JSONObject] = []
|
|
3500
|
+
package_id = payload.get("package_id")
|
|
3501
|
+
package_parent = _builder_parent("package", id_value=package_id, key=package_id) if package_id not in (None, "") else None
|
|
3502
|
+
for item in payload.get("apps") or []:
|
|
3503
|
+
if not isinstance(item, dict):
|
|
3504
|
+
continue
|
|
3505
|
+
status = _builder_status(item, "success")
|
|
3506
|
+
operation = "failed" if status == "failed" else ("created" if bool(item.get("created")) else "updated")
|
|
3507
|
+
parent = _builder_parent("app", key=item.get("app_key"), name=item.get("app_name"))
|
|
3508
|
+
icon_config = (
|
|
3509
|
+
_builder_container_icon_config(item, raw_keys=("app_icon", "appIcon", "icon"))
|
|
3510
|
+
or _builder_container_icon_config(item.get("shell_result"), raw_keys=("app_icon", "appIcon", "icon"))
|
|
3511
|
+
)
|
|
3512
|
+
resources.append(
|
|
3513
|
+
_builder_resource(
|
|
3514
|
+
resource_type="app",
|
|
3515
|
+
operation=operation,
|
|
3516
|
+
status=status,
|
|
3517
|
+
key=item.get("app_key"),
|
|
3518
|
+
name=item.get("app_name"),
|
|
3519
|
+
ids={
|
|
3520
|
+
**({"app_key": item.get("app_key")} if item.get("app_key") else {}),
|
|
3521
|
+
**({"package_id": package_id} if package_id not in (None, "") else {}),
|
|
3522
|
+
},
|
|
3523
|
+
parent=package_parent,
|
|
3524
|
+
icon_config=icon_config,
|
|
3525
|
+
error_code=item.get("error_code"),
|
|
3526
|
+
message=item.get("message") if status == "failed" else None,
|
|
3527
|
+
)
|
|
3528
|
+
)
|
|
3529
|
+
resources.extend(_builder_field_resources(item.get("field_diff_details") or item.get("field_diff"), parent=parent))
|
|
3530
|
+
return resources
|
|
3531
|
+
|
|
3532
|
+
status = _builder_status(payload, "success")
|
|
3533
|
+
operation = "failed" if status == "failed" else ("created" if bool(payload.get("created")) else "updated")
|
|
3534
|
+
app_key = payload.get("app_key")
|
|
3535
|
+
app_name = payload.get("app_name_after") or payload.get("app_name")
|
|
3536
|
+
parent = _builder_parent("app", key=app_key, name=app_name)
|
|
3537
|
+
normalized_args = payload.get("normalized_args") if isinstance(payload.get("normalized_args"), dict) else {}
|
|
3538
|
+
icon_config = (
|
|
3539
|
+
_builder_container_icon_config(payload, raw_keys=("app_icon", "appIcon", "icon"))
|
|
3540
|
+
or _builder_container_icon_config(normalized_args, raw_keys=("icon", "app_icon", "appIcon"))
|
|
3541
|
+
)
|
|
3542
|
+
resources = [
|
|
3543
|
+
_builder_resource(
|
|
3544
|
+
resource_type="app",
|
|
3545
|
+
operation=operation,
|
|
3546
|
+
status=status,
|
|
3547
|
+
key=app_key,
|
|
3548
|
+
name=app_name,
|
|
3549
|
+
ids={"app_key": app_key} if app_key else {},
|
|
3550
|
+
icon_config=icon_config,
|
|
3551
|
+
error_code=payload.get("error_code"),
|
|
3552
|
+
message=payload.get("message") if status == "failed" else None,
|
|
3553
|
+
)
|
|
3554
|
+
]
|
|
3555
|
+
resources.extend(_builder_field_resources(payload.get("field_diff_details") or payload.get("field_diff"), parent=parent))
|
|
3556
|
+
return resources
|
|
3557
|
+
|
|
3558
|
+
|
|
3559
|
+
def _builder_field_resources(field_diff: object, *, parent: JSONObject | None) -> list[JSONObject]:
|
|
3560
|
+
if not isinstance(field_diff, dict):
|
|
3561
|
+
return []
|
|
3562
|
+
resources: list[JSONObject] = []
|
|
3563
|
+
for key, operation in (("added", "created"), ("updated", "updated"), ("removed", "removed")):
|
|
3564
|
+
for field in field_diff.get(key) or []:
|
|
3565
|
+
if isinstance(field, dict):
|
|
3566
|
+
name = field.get("name") or field.get("title") or field.get("field_name") or field.get("queTitle")
|
|
3567
|
+
field_id = field.get("field_id") or field.get("queId")
|
|
3568
|
+
que_id = field.get("que_id") or field.get("queId")
|
|
3569
|
+
else:
|
|
3570
|
+
name = field
|
|
3571
|
+
field_id = None
|
|
3572
|
+
que_id = None
|
|
3573
|
+
resource_id = que_id if que_id not in (None, "") else field_id
|
|
3574
|
+
resources.append(
|
|
3575
|
+
_builder_resource(
|
|
3576
|
+
resource_type="field",
|
|
3577
|
+
operation=operation,
|
|
3578
|
+
status="success",
|
|
3579
|
+
id_value=resource_id,
|
|
3580
|
+
key=field_id if field_id not in (None, "") else name,
|
|
3581
|
+
name=name,
|
|
3582
|
+
ids={
|
|
3583
|
+
**({"field_id": field_id} if field_id not in (None, "") else {}),
|
|
3584
|
+
**({"que_id": que_id} if que_id not in (None, "") else {}),
|
|
3585
|
+
**({"app_key": parent.get("key")} if isinstance(parent, dict) and parent.get("key") else {}),
|
|
3586
|
+
},
|
|
3587
|
+
parent=parent,
|
|
3588
|
+
)
|
|
3589
|
+
)
|
|
3590
|
+
return resources
|
|
3591
|
+
|
|
3592
|
+
|
|
3593
|
+
def _builder_view_resources(payload: JSONObject) -> list[JSONObject]:
|
|
3594
|
+
diff = payload.get("views_diff") if isinstance(payload.get("views_diff"), dict) else {}
|
|
3595
|
+
verification_by_name = _builder_view_verification_by_name(payload)
|
|
3596
|
+
parent = _builder_app_parent(payload)
|
|
3597
|
+
resources: list[JSONObject] = []
|
|
3598
|
+
for key, operation in (("created", "created"), ("updated", "updated"), ("removed", "removed")):
|
|
3599
|
+
for item in diff.get(key) or []:
|
|
3600
|
+
name, view_key, status, error_code, message = _builder_view_identity(item, verification_by_name)
|
|
3601
|
+
resources.append(
|
|
3602
|
+
_builder_resource(
|
|
3603
|
+
resource_type="view",
|
|
3604
|
+
operation=operation,
|
|
3605
|
+
status=status,
|
|
3606
|
+
key=view_key,
|
|
3607
|
+
name=name,
|
|
3608
|
+
ids={
|
|
3609
|
+
**({"view_key": view_key} if view_key else {}),
|
|
3610
|
+
**({"app_key": payload.get("app_key")} if payload.get("app_key") else {}),
|
|
3611
|
+
},
|
|
3612
|
+
parent=parent,
|
|
3613
|
+
error_code=error_code,
|
|
3614
|
+
message=message,
|
|
3615
|
+
)
|
|
3616
|
+
)
|
|
3617
|
+
for item in diff.get("failed") or []:
|
|
3618
|
+
name, view_key, _status, error_code, message = _builder_view_identity(item, verification_by_name)
|
|
3619
|
+
resources.append(
|
|
3620
|
+
_builder_resource(
|
|
3621
|
+
resource_type="view",
|
|
3622
|
+
operation="failed",
|
|
3623
|
+
status="failed",
|
|
3624
|
+
key=view_key,
|
|
3625
|
+
name=name,
|
|
3626
|
+
ids={
|
|
3627
|
+
**({"view_key": view_key} if view_key else {}),
|
|
3628
|
+
**({"app_key": payload.get("app_key")} if payload.get("app_key") else {}),
|
|
3629
|
+
},
|
|
3630
|
+
parent=parent,
|
|
3631
|
+
error_code=error_code,
|
|
3632
|
+
message=message,
|
|
3633
|
+
)
|
|
3634
|
+
)
|
|
3635
|
+
return resources
|
|
3636
|
+
|
|
3637
|
+
|
|
3638
|
+
def _builder_view_verification_by_name(payload: JSONObject) -> dict[str, JSONObject]:
|
|
3639
|
+
verification = payload.get("verification")
|
|
3640
|
+
by_view = verification.get("by_view") if isinstance(verification, dict) else None
|
|
3641
|
+
result: dict[str, JSONObject] = {}
|
|
3642
|
+
if isinstance(by_view, list):
|
|
3643
|
+
for item in by_view:
|
|
3644
|
+
if isinstance(item, dict):
|
|
3645
|
+
name = str(item.get("name") or "").strip()
|
|
3646
|
+
if name:
|
|
3647
|
+
result[name] = item
|
|
3648
|
+
return result
|
|
3649
|
+
|
|
3650
|
+
|
|
3651
|
+
def _builder_view_identity(item: object, verification_by_name: dict[str, JSONObject]) -> tuple[str | None, str | None, str, object, object]:
|
|
3652
|
+
verification: JSONObject | None = None
|
|
3653
|
+
if isinstance(item, dict):
|
|
3654
|
+
name = item.get("name") or item.get("view_name") or item.get("viewName")
|
|
3655
|
+
view_key = item.get("view_key") or item.get("viewKey")
|
|
3656
|
+
status = str(item.get("status") or "success")
|
|
3657
|
+
error_code = item.get("error_code")
|
|
3658
|
+
message = item.get("message")
|
|
3659
|
+
else:
|
|
3660
|
+
name = str(item) if item not in (None, "") else None
|
|
3661
|
+
view_key = None
|
|
3662
|
+
status = "success"
|
|
3663
|
+
error_code = None
|
|
3664
|
+
message = None
|
|
3665
|
+
if name and not view_key:
|
|
3666
|
+
verification = verification_by_name.get(str(name))
|
|
3667
|
+
if isinstance(verification, dict):
|
|
3668
|
+
view_key = verification.get("view_key") or verification.get("viewKey")
|
|
3669
|
+
if not view_key:
|
|
3670
|
+
matching = verification.get("matching_view_keys")
|
|
3671
|
+
if isinstance(matching, list) and matching:
|
|
3672
|
+
view_key = matching[0]
|
|
3673
|
+
if name and verification is None:
|
|
3674
|
+
verification = verification_by_name.get(str(name))
|
|
3675
|
+
if isinstance(verification, dict):
|
|
3676
|
+
verification_status = str(verification.get("status") or "").strip()
|
|
3677
|
+
if verification_status in {"removed", "readback_pending"}:
|
|
3678
|
+
status = "readback_pending"
|
|
3679
|
+
if verification_status == "removed":
|
|
3680
|
+
status = "removed"
|
|
3681
|
+
error_code = error_code or verification.get("error_code")
|
|
3682
|
+
message = message or verification.get("message")
|
|
3683
|
+
return (
|
|
3684
|
+
str(name) if name not in (None, "") else None,
|
|
3685
|
+
str(view_key) if view_key not in (None, "") else None,
|
|
3686
|
+
status,
|
|
3687
|
+
error_code,
|
|
3688
|
+
message,
|
|
3689
|
+
)
|
|
3690
|
+
|
|
3691
|
+
|
|
3692
|
+
def _builder_chart_resources(payload: JSONObject) -> list[JSONObject]:
|
|
3693
|
+
parent = _builder_app_parent(payload)
|
|
3694
|
+
resources: list[JSONObject] = []
|
|
3695
|
+
for item in payload.get("chart_results") or []:
|
|
3696
|
+
if not isinstance(item, dict):
|
|
3697
|
+
continue
|
|
3698
|
+
status = str(item.get("status") or "success")
|
|
3699
|
+
operation = _builder_operation(item.get("operation") or status, fallback="updated")
|
|
3700
|
+
if status == "failed":
|
|
3701
|
+
operation = "failed"
|
|
3702
|
+
resource_status = "failed" if status == "failed" else ("readback_pending" if status == "readback_pending" else "success")
|
|
3703
|
+
chart_id = item.get("chart_id") or item.get("chartId")
|
|
3704
|
+
chart_key = item.get("chart_key") or item.get("chartKey")
|
|
3705
|
+
resources.append(
|
|
3706
|
+
_builder_resource(
|
|
3707
|
+
resource_type="chart",
|
|
3708
|
+
operation=operation,
|
|
3709
|
+
status=resource_status,
|
|
3710
|
+
id_value=chart_id,
|
|
3711
|
+
key=chart_key or chart_id,
|
|
3712
|
+
name=item.get("name") or item.get("chart_name") or item.get("chartName"),
|
|
3713
|
+
ids={
|
|
3714
|
+
**({"chart_id": chart_id} if chart_id not in (None, "") else {}),
|
|
3715
|
+
**({"chart_key": chart_key} if chart_key else {}),
|
|
3716
|
+
**({"app_key": payload.get("app_key")} if payload.get("app_key") else {}),
|
|
3717
|
+
**({"chart_type": item.get("chart_type")} if item.get("chart_type") else {}),
|
|
3718
|
+
},
|
|
3719
|
+
parent=parent,
|
|
3720
|
+
error_code=item.get("error_code"),
|
|
3721
|
+
message=item.get("message") if status in {"failed", "readback_pending"} else None,
|
|
3722
|
+
)
|
|
3723
|
+
)
|
|
3724
|
+
return resources
|
|
3725
|
+
|
|
3726
|
+
|
|
3727
|
+
def _builder_portal_resources(payload: JSONObject) -> list[JSONObject]:
|
|
3728
|
+
status = _builder_status(payload, "success")
|
|
3729
|
+
draft_result = payload.get("draft_result") if isinstance(payload.get("draft_result"), dict) else {}
|
|
3730
|
+
live_result = payload.get("live_result") if isinstance(payload.get("live_result"), dict) else {}
|
|
3731
|
+
normalized_args = payload.get("normalized_args") if isinstance(payload.get("normalized_args"), dict) else {}
|
|
3732
|
+
dash_key = (
|
|
3733
|
+
payload.get("dash_key")
|
|
3734
|
+
or payload.get("dashKey")
|
|
3735
|
+
or draft_result.get("dashKey")
|
|
3736
|
+
or draft_result.get("dash_key")
|
|
3737
|
+
or live_result.get("dashKey")
|
|
3738
|
+
or live_result.get("dash_key")
|
|
3739
|
+
)
|
|
3740
|
+
dash_name = (
|
|
3741
|
+
payload.get("dash_name")
|
|
3742
|
+
or payload.get("dashName")
|
|
3743
|
+
or payload.get("name")
|
|
3744
|
+
or draft_result.get("dashName")
|
|
3745
|
+
or draft_result.get("dash_name")
|
|
3746
|
+
or draft_result.get("name")
|
|
3747
|
+
or live_result.get("dashName")
|
|
3748
|
+
or live_result.get("dash_name")
|
|
3749
|
+
or live_result.get("name")
|
|
3750
|
+
or normalized_args.get("dash_name")
|
|
3751
|
+
or normalized_args.get("dashName")
|
|
3752
|
+
)
|
|
3753
|
+
package_id = payload.get("package_id") or normalized_args.get("package_id") or normalized_args.get("package_tag_id")
|
|
3754
|
+
if package_id in (None, "") and isinstance(draft_result.get("tags"), list) and draft_result.get("tags"):
|
|
3755
|
+
first_tag = draft_result.get("tags")[0]
|
|
3756
|
+
if isinstance(first_tag, dict):
|
|
3757
|
+
package_id = first_tag.get("tagId") or first_tag.get("tag_id") or first_tag.get("id")
|
|
3758
|
+
operation = "failed" if status == "failed" else ("created" if bool(payload.get("created")) else "updated")
|
|
3759
|
+
parent = None
|
|
3760
|
+
if package_id:
|
|
3761
|
+
parent = _builder_parent("package", id_value=package_id, key=package_id)
|
|
3762
|
+
icon_config = (
|
|
3763
|
+
_builder_container_icon_config(payload, raw_keys=("dash_icon", "dashIcon", "icon"))
|
|
3764
|
+
or _builder_container_icon_config(draft_result, raw_keys=("dashIcon", "dash_icon", "icon"))
|
|
3765
|
+
or _builder_container_icon_config(live_result, raw_keys=("dashIcon", "dash_icon", "icon"))
|
|
3766
|
+
or _builder_container_icon_config(normalized_args, raw_keys=("icon", "dash_icon", "dashIcon"))
|
|
3767
|
+
)
|
|
3768
|
+
return [
|
|
3769
|
+
_builder_resource(
|
|
3770
|
+
resource_type="portal",
|
|
3771
|
+
operation=operation,
|
|
3772
|
+
status=status,
|
|
3773
|
+
key=dash_key,
|
|
3774
|
+
name=dash_name,
|
|
3775
|
+
ids={
|
|
3776
|
+
**({"dash_key": dash_key} if dash_key else {}),
|
|
3777
|
+
**({"package_id": package_id} if package_id else {}),
|
|
3778
|
+
},
|
|
3779
|
+
parent=parent,
|
|
3780
|
+
icon_config=icon_config,
|
|
3781
|
+
error_code=payload.get("error_code"),
|
|
3782
|
+
message=payload.get("message") if status == "failed" else None,
|
|
3783
|
+
)
|
|
3784
|
+
]
|
|
3785
|
+
|
|
3786
|
+
|
|
3787
|
+
def _builder_button_resources(payload: JSONObject) -> list[JSONObject]:
|
|
3788
|
+
parent = _builder_app_parent(payload)
|
|
3789
|
+
resources: list[JSONObject] = []
|
|
3790
|
+
for key, operation in (("created", "created"), ("updated", "updated"), ("removed", "removed"), ("failed", "failed")):
|
|
3791
|
+
for item in payload.get(key) or []:
|
|
3792
|
+
if not isinstance(item, dict):
|
|
3793
|
+
continue
|
|
3794
|
+
status = "failed" if operation == "failed" or item.get("status") == "failed" else str(item.get("status") or "success")
|
|
3795
|
+
button_id = item.get("button_id") or item.get("buttonId")
|
|
3796
|
+
resources.append(
|
|
3797
|
+
_builder_resource(
|
|
3798
|
+
resource_type="button",
|
|
3799
|
+
operation=operation if operation != "failed" else "failed",
|
|
3800
|
+
status=status,
|
|
3801
|
+
id_value=button_id,
|
|
3802
|
+
key=button_id,
|
|
3803
|
+
name=item.get("button_text") or item.get("buttonText"),
|
|
3804
|
+
ids={
|
|
3805
|
+
**({"button_id": button_id} if button_id not in (None, "") else {}),
|
|
3806
|
+
**({"app_key": payload.get("app_key")} if payload.get("app_key") else {}),
|
|
3807
|
+
},
|
|
3808
|
+
parent=parent,
|
|
3809
|
+
error_code=item.get("error_code"),
|
|
3810
|
+
message=item.get("message") if status in {"failed", "readback_pending"} else None,
|
|
3811
|
+
)
|
|
3812
|
+
)
|
|
3813
|
+
for item in payload.get("view_configs") or []:
|
|
3814
|
+
if isinstance(item, dict):
|
|
3815
|
+
status = str(item.get("status") or "success")
|
|
3816
|
+
view_key = item.get("view_key") or item.get("viewKey")
|
|
3817
|
+
resources.append(
|
|
3818
|
+
_builder_resource(
|
|
3819
|
+
resource_type="button_binding",
|
|
3820
|
+
operation="updated" if status != "failed" else "failed",
|
|
3821
|
+
status=status,
|
|
3822
|
+
key=view_key,
|
|
3823
|
+
name=item.get("view_name") or item.get("viewName") or view_key,
|
|
3824
|
+
ids={
|
|
3825
|
+
**({"view_key": view_key} if view_key else {}),
|
|
3826
|
+
**({"app_key": payload.get("app_key")} if payload.get("app_key") else {}),
|
|
3827
|
+
},
|
|
3828
|
+
parent=parent,
|
|
3829
|
+
error_code=item.get("error_code"),
|
|
3830
|
+
message=item.get("message") if status in {"failed", "readback_pending"} else None,
|
|
3831
|
+
)
|
|
3832
|
+
)
|
|
3833
|
+
return resources
|
|
3834
|
+
|
|
3835
|
+
|
|
3836
|
+
def _builder_associated_resource_resources(payload: JSONObject) -> list[JSONObject]:
|
|
3837
|
+
parent = _builder_app_parent(payload)
|
|
3838
|
+
readback_by_id = _builder_associated_resource_readback_by_id(payload)
|
|
3839
|
+
resources: list[JSONObject] = []
|
|
3840
|
+
for key, operation in (
|
|
3841
|
+
("created", "created"),
|
|
3842
|
+
("updated", "updated"),
|
|
3843
|
+
("unchanged", "unchanged"),
|
|
3844
|
+
("removed", "removed"),
|
|
3845
|
+
("failed", "failed"),
|
|
3846
|
+
):
|
|
3847
|
+
for item in payload.get(key) or []:
|
|
3848
|
+
if not isinstance(item, dict):
|
|
3849
|
+
continue
|
|
3850
|
+
status = "failed" if operation == "failed" or item.get("status") == "failed" else str(item.get("status") or "success")
|
|
3851
|
+
associated_item_id = item.get("associated_item_id") or item.get("associatedItemId")
|
|
3852
|
+
readback = readback_by_id.get(str(associated_item_id)) if associated_item_id not in (None, "") else None
|
|
3853
|
+
view_key = item.get("view_key") or item.get("viewKey") or (readback or {}).get("view_key") or (readback or {}).get("viewKey")
|
|
3854
|
+
chart_key = item.get("chart_key") or item.get("chartKey") or (readback or {}).get("chart_key") or (readback or {}).get("chartKey")
|
|
3855
|
+
target_app_key = item.get("target_app_key") or (readback or {}).get("target_app_key")
|
|
3856
|
+
name = item.get("name") or item.get("resource_name") or (readback or {}).get("name") or view_key or chart_key
|
|
3857
|
+
resources.append(
|
|
3858
|
+
_builder_resource(
|
|
3859
|
+
resource_type="associated_resource",
|
|
3860
|
+
operation=operation if operation != "failed" else "failed",
|
|
3861
|
+
status=status,
|
|
3862
|
+
id_value=associated_item_id,
|
|
3863
|
+
key=view_key or chart_key or associated_item_id,
|
|
3864
|
+
name=name,
|
|
3865
|
+
ids={
|
|
3866
|
+
**({"associated_item_id": associated_item_id} if associated_item_id not in (None, "") else {}),
|
|
3867
|
+
**({"app_key": payload.get("app_key")} if payload.get("app_key") else {}),
|
|
3868
|
+
**({"target_app_key": target_app_key} if target_app_key else {}),
|
|
3869
|
+
**({"view_key": view_key} if view_key else {}),
|
|
3870
|
+
**({"chart_key": chart_key} if chart_key else {}),
|
|
3871
|
+
},
|
|
3872
|
+
parent=parent,
|
|
3873
|
+
error_code=item.get("error_code"),
|
|
3874
|
+
message=item.get("message") if status in {"failed", "readback_pending"} else None,
|
|
3875
|
+
)
|
|
3876
|
+
)
|
|
3877
|
+
for item in payload.get("view_configs") or []:
|
|
3878
|
+
if isinstance(item, dict):
|
|
3879
|
+
status = str(item.get("status") or "success")
|
|
3880
|
+
view_key = item.get("view_key") or item.get("viewKey")
|
|
3881
|
+
resources.append(
|
|
3882
|
+
_builder_resource(
|
|
3883
|
+
resource_type="associated_resource_binding",
|
|
3884
|
+
operation="updated" if status != "failed" else "failed",
|
|
3885
|
+
status=status,
|
|
3886
|
+
key=view_key,
|
|
3887
|
+
name=item.get("view_name") or item.get("viewName") or view_key,
|
|
3888
|
+
ids={
|
|
3889
|
+
**({"view_key": view_key} if view_key else {}),
|
|
3890
|
+
**({"app_key": payload.get("app_key")} if payload.get("app_key") else {}),
|
|
3891
|
+
},
|
|
3892
|
+
parent=parent,
|
|
3893
|
+
error_code=item.get("error_code"),
|
|
3894
|
+
message=item.get("message") if status == "failed" else None,
|
|
3895
|
+
)
|
|
3896
|
+
)
|
|
3897
|
+
return resources
|
|
3898
|
+
|
|
3899
|
+
|
|
3900
|
+
def _builder_associated_resource_readback_by_id(payload: JSONObject) -> dict[str, JSONObject]:
|
|
3901
|
+
result: dict[str, JSONObject] = {}
|
|
3902
|
+
|
|
3903
|
+
def collect(items: object) -> None:
|
|
3904
|
+
if not isinstance(items, list):
|
|
3905
|
+
return
|
|
3906
|
+
for item in items:
|
|
3907
|
+
if not isinstance(item, dict):
|
|
3908
|
+
continue
|
|
3909
|
+
associated_item_id = item.get("associated_item_id") or item.get("associatedItemId")
|
|
3910
|
+
if associated_item_id in (None, ""):
|
|
3911
|
+
continue
|
|
3912
|
+
result[str(associated_item_id)] = item
|
|
3913
|
+
|
|
3914
|
+
collect(payload.get("associated_resources"))
|
|
3915
|
+
for config in payload.get("view_configs") or []:
|
|
3916
|
+
if not isinstance(config, dict):
|
|
3917
|
+
continue
|
|
3918
|
+
for key in ("actual", "expected"):
|
|
3919
|
+
value = config.get(key)
|
|
3920
|
+
if isinstance(value, dict):
|
|
3921
|
+
collect(value.get("items"))
|
|
3922
|
+
return result
|
|
3923
|
+
|
|
3924
|
+
|
|
2258
3925
|
def _coerce_api_error(error: Exception) -> QingflowApiError:
|
|
2259
3926
|
if isinstance(error, QingflowApiError):
|
|
2260
3927
|
return error
|
|
@@ -2403,6 +4070,40 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2403
4070
|
"tool_name": "chart_get",
|
|
2404
4071
|
},
|
|
2405
4072
|
},
|
|
4073
|
+
"workspace_icon_catalog_get": {
|
|
4074
|
+
"allowed_keys": [],
|
|
4075
|
+
"aliases": {},
|
|
4076
|
+
"allowed_values": {
|
|
4077
|
+
"icon": list(WORKSPACE_ICON_NAMES),
|
|
4078
|
+
"color": list(WORKSPACE_ICON_COLORS),
|
|
4079
|
+
"generic_icon_names": list(GENERIC_WORKSPACE_ICON_NAMES),
|
|
4080
|
+
},
|
|
4081
|
+
"execution_notes": [
|
|
4082
|
+
"read this before creating app packages, apps, or portals when choosing supported workspace icons",
|
|
4083
|
+
"the CLI validates icon/color candidates but does not infer business defaults from resource names",
|
|
4084
|
+
"new app/package/portal creation requires explicit non-template icon + color",
|
|
4085
|
+
],
|
|
4086
|
+
"minimal_example": {
|
|
4087
|
+
"profile": "default",
|
|
4088
|
+
},
|
|
4089
|
+
},
|
|
4090
|
+
"package_list": {
|
|
4091
|
+
"allowed_keys": ["trial_status", "query"],
|
|
4092
|
+
"aliases": {"trialStatus": "trial_status", "keyword": "query"},
|
|
4093
|
+
"allowed_values": {"trial_status": ["all"]},
|
|
4094
|
+
"execution_notes": [
|
|
4095
|
+
"lists app packages visible to the current builder profile by calling backend GET /tag?trialStatus=...",
|
|
4096
|
+
"query is applied locally to package_id/tag_id/package_name/tag_name after /tag returns",
|
|
4097
|
+
"does not fall back to app list because app list cannot represent empty packages, duplicate package names, or package-level permissions",
|
|
4098
|
+
"returns package_id/package_name plus compatible tag_id/tag_name; use package_get for package detail before editing",
|
|
4099
|
+
"permission failures are returned as PACKAGE_LIST_FAILED with backend transport details",
|
|
4100
|
+
],
|
|
4101
|
+
"minimal_example": {
|
|
4102
|
+
"profile": "default",
|
|
4103
|
+
"trial_status": "all",
|
|
4104
|
+
"query": "产品研发",
|
|
4105
|
+
},
|
|
4106
|
+
},
|
|
2406
4107
|
"package_get": {
|
|
2407
4108
|
"allowed_keys": ["package_id"],
|
|
2408
4109
|
"aliases": {"packageId": "package_id"},
|
|
@@ -2427,9 +4128,18 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2427
4128
|
"iconColor": "color",
|
|
2428
4129
|
"allowDetach": "allow_detach",
|
|
2429
4130
|
},
|
|
2430
|
-
"allowed_values":
|
|
4131
|
+
"allowed_values": {
|
|
4132
|
+
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
4133
|
+
"workspace_icon.icon_names": list(WORKSPACE_ICON_NAMES),
|
|
4134
|
+
"workspace_icon.icon_colors": list(WORKSPACE_ICON_COLORS),
|
|
4135
|
+
"workspace_icon.generic_icon_names": list(GENERIC_WORKSPACE_ICON_NAMES),
|
|
4136
|
+
},
|
|
2431
4137
|
"execution_notes": [
|
|
2432
4138
|
"create or update package metadata, visibility, grouping, and ordering in one call",
|
|
4139
|
+
"creating a package requires explicit icon + color; icon=template is blocked because it is too generic",
|
|
4140
|
+
"updating a package preserves existing icon/color when omitted; explicit icon/color values are still validated",
|
|
4141
|
+
"call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
|
|
4142
|
+
"metadata keys omitted on update are preserved",
|
|
2433
4143
|
"package_id maps internally to backend tagId; do not use tag_id in public calls",
|
|
2434
4144
|
"items is a full package layout tree; omitting existing app/portal items is blocked unless allow_detach=true",
|
|
2435
4145
|
"item shapes: {type:'app', app_key}, {type:'portal', dash_key}, or {type:'group', group_id?, name, items:[...]}",
|
|
@@ -2452,7 +4162,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2452
4162
|
"profile": "default",
|
|
2453
4163
|
"package_name": "项目管理",
|
|
2454
4164
|
"create_if_missing": True,
|
|
2455
|
-
"icon": "
|
|
4165
|
+
"icon": "briefcase",
|
|
2456
4166
|
"color": "azure",
|
|
2457
4167
|
"visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
|
|
2458
4168
|
},
|
|
@@ -2565,122 +4275,287 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2565
4275
|
},
|
|
2566
4276
|
"execution_notes": [
|
|
2567
4277
|
"use this read-only tool before button writes when an agent needs a supported icon or color choice",
|
|
2568
|
-
"current frontend only supports
|
|
4278
|
+
"current frontend only supports button icons and button colors from this catalog",
|
|
2569
4279
|
"text/icon color is unified through text_color; there is no separate icon_color",
|
|
2570
4280
|
],
|
|
2571
4281
|
"minimal_example": {
|
|
2572
4282
|
"profile": "default",
|
|
2573
4283
|
},
|
|
2574
4284
|
},
|
|
2575
|
-
"
|
|
2576
|
-
"allowed_keys": [
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
"
|
|
2581
|
-
"
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
"
|
|
2590
|
-
"
|
|
2591
|
-
"
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
4285
|
+
"app_custom_buttons_apply": {
|
|
4286
|
+
"allowed_keys": [
|
|
4287
|
+
"app_key",
|
|
4288
|
+
"apps",
|
|
4289
|
+
"upsert_buttons",
|
|
4290
|
+
"patch_buttons",
|
|
4291
|
+
"remove_buttons",
|
|
4292
|
+
"view_configs",
|
|
4293
|
+
"upsert_buttons[].client_key",
|
|
4294
|
+
"upsert_buttons[].button_id",
|
|
4295
|
+
"upsert_buttons[].button_text",
|
|
4296
|
+
"upsert_buttons[].style_preset",
|
|
4297
|
+
"upsert_buttons[].background_color",
|
|
4298
|
+
"upsert_buttons[].text_color",
|
|
4299
|
+
"upsert_buttons[].button_icon",
|
|
4300
|
+
"upsert_buttons[].trigger_action",
|
|
4301
|
+
"upsert_buttons[].trigger_link_url",
|
|
4302
|
+
"upsert_buttons[].trigger_add_data_config",
|
|
4303
|
+
"upsert_buttons[].trigger_add_data_config.target_app_key",
|
|
4304
|
+
"upsert_buttons[].trigger_add_data_config.field_mappings",
|
|
4305
|
+
"upsert_buttons[].trigger_add_data_config.field_mappings[].source_field",
|
|
4306
|
+
"upsert_buttons[].trigger_add_data_config.field_mappings[].target_field",
|
|
4307
|
+
"upsert_buttons[].trigger_add_data_config.default_values",
|
|
4308
|
+
"patch_buttons[].button_id",
|
|
4309
|
+
"patch_buttons[].button_text",
|
|
4310
|
+
"patch_buttons[].set",
|
|
4311
|
+
"patch_buttons[].unset",
|
|
4312
|
+
"view_configs[].view_key",
|
|
4313
|
+
"view_configs[].mode",
|
|
4314
|
+
"view_configs[].buttons",
|
|
4315
|
+
"view_configs[].buttons[].button_ref",
|
|
4316
|
+
"view_configs[].buttons[].placement",
|
|
4317
|
+
"view_configs[].buttons[].primary",
|
|
4318
|
+
"view_configs[].buttons[].button_limit",
|
|
4319
|
+
"view_configs[].buttons[].button_formula",
|
|
4320
|
+
"view_configs[].buttons[].button_formula_type",
|
|
4321
|
+
"view_configs[].buttons[].print_tpls",
|
|
4322
|
+
"remove_buttons[].button_id",
|
|
4323
|
+
"remove_buttons[].button_text",
|
|
4324
|
+
],
|
|
2596
4325
|
"aliases": {
|
|
2597
|
-
"
|
|
2598
|
-
"
|
|
2599
|
-
"
|
|
2600
|
-
"
|
|
2601
|
-
"
|
|
2602
|
-
"
|
|
2603
|
-
"
|
|
2604
|
-
"
|
|
2605
|
-
"
|
|
2606
|
-
"
|
|
2607
|
-
"
|
|
4326
|
+
"upsertButtons": "upsert_buttons",
|
|
4327
|
+
"patchButtons": "patch_buttons",
|
|
4328
|
+
"removeButtons": "remove_buttons",
|
|
4329
|
+
"viewConfigs": "view_configs",
|
|
4330
|
+
"clientKey": "client_key",
|
|
4331
|
+
"buttonId": "button_id",
|
|
4332
|
+
"buttonText": "button_text",
|
|
4333
|
+
"stylePreset": "style_preset",
|
|
4334
|
+
"backgroundColor": "background_color",
|
|
4335
|
+
"textColor": "text_color",
|
|
4336
|
+
"buttonIcon": "button_icon",
|
|
4337
|
+
"triggerAction": "trigger_action",
|
|
4338
|
+
"triggerLinkUrl": "trigger_link_url",
|
|
4339
|
+
"triggerAddDataConfig": "trigger_add_data_config",
|
|
4340
|
+
"targetAppKey": "trigger_add_data_config.target_app_key",
|
|
4341
|
+
"fieldMappings": "trigger_add_data_config.field_mappings",
|
|
4342
|
+
"defaultValues": "trigger_add_data_config.default_values",
|
|
4343
|
+
"mode": "view_configs[].mode",
|
|
4344
|
+
"applyMode": "view_configs[].mode",
|
|
4345
|
+
"buttonRef": "view_configs[].buttons[].button_ref",
|
|
4346
|
+
"beingMain": "view_configs[].buttons[].primary",
|
|
4347
|
+
"buttonLimit": "view_configs[].buttons[].button_limit",
|
|
4348
|
+
"visibleWhen": "view_configs[].buttons[].button_limit",
|
|
4349
|
+
"buttonFormula": "view_configs[].buttons[].button_formula",
|
|
4350
|
+
"buttonFormulaType": "view_configs[].buttons[].button_formula_type",
|
|
4351
|
+
"printTpls": "view_configs[].buttons[].print_tpls",
|
|
4352
|
+
"externalQrobotConfig": "external_qrobot_config",
|
|
4353
|
+
"customButtonExternalQRobotRelationVO": "external_qrobot_config",
|
|
4354
|
+
"triggerWingsConfig": "trigger_wings_config",
|
|
2608
4355
|
},
|
|
2609
4356
|
"allowed_values": {
|
|
2610
|
-
"
|
|
2611
|
-
"
|
|
2612
|
-
"
|
|
2613
|
-
"
|
|
2614
|
-
"
|
|
4357
|
+
"upsert_buttons[].trigger_action": [member.value for member in PublicButtonTriggerAction],
|
|
4358
|
+
"upsert_buttons[].style_preset": [item["key"] for item in BUTTON_STYLE_PRESETS],
|
|
4359
|
+
"upsert_buttons[].button_icon": list(BUTTON_ICONS),
|
|
4360
|
+
"upsert_buttons[].background_color": list(BUTTON_BACKGROUND_COLORS),
|
|
4361
|
+
"upsert_buttons[].text_color": list(BUTTON_TEXT_COLORS),
|
|
4362
|
+
"view_configs[].mode": ["merge", "replace"],
|
|
4363
|
+
"view_configs[].buttons[].placement": [member.value for member in PublicButtonPlacement],
|
|
2615
4364
|
},
|
|
2616
4365
|
"execution_notes": [
|
|
2617
|
-
"
|
|
4366
|
+
"this is the default custom-button write path; old list/get/create/update/delete tools are hidden from the public agent surface",
|
|
4367
|
+
"use patch_buttons for partial parameter replacement on existing buttons; the tool reads current button detail, merges patch_buttons[].set/unset, then submits the backend full-save payload internally",
|
|
4368
|
+
"button_id targets an existing button; without button_id, button_text is used as an exact unique upsert key, otherwise a new button is created",
|
|
4369
|
+
"for addData buttons, use trigger_add_data_config.target_app_key plus field_mappings/default_values; field_mappings compiles to backend queRelation",
|
|
4370
|
+
"field_mappings.source_field accepts source schema fields and supported system fields: 数据ID/row_record_id/apply_id/_id maps to current record id (-17), 编号/record_number maps to visible record number (0)",
|
|
4371
|
+
"to fill a target relation field with the current source record, map source_field='数据ID' to the target relation field; default_values is for static constants, not dynamic current-record values",
|
|
4372
|
+
"do not write raw que_relation unless maintaining a legacy config; field_mappings/default_values and que_relation are mutually exclusive",
|
|
4373
|
+
"view_configs binds custom buttons into views in the same apply call; button_ref may be a same-call client_key, a button_id, or an exact unique existing button_text",
|
|
4374
|
+
"view_configs[].view_key is the raw builder view key from app_get.views[].view_key; do not pass record-data view_id values like custom:VIEW_KEY",
|
|
4375
|
+
"view_configs[].buttons is required in merge mode; omitting buttons is blocked to avoid no-op writes and accidental publish",
|
|
4376
|
+
"view_configs[].mode defaults to merge; use mode=replace or an explicit empty buttons list to replace/clear a view's custom button bindings",
|
|
4377
|
+
"advanced view button binding supports button_limit, button_formula, button_formula_type, and print_tpls when visibility or print-template behavior is required",
|
|
4378
|
+
"default placements are header and detail; header maps to frontend top buttons",
|
|
4379
|
+
"placement=list configures backend INSIDE row/list buttons; header maps to TOP and detail maps to DETAIL",
|
|
4380
|
+
"remove_buttons supports button_id or exact unique button_text",
|
|
4381
|
+
"after a remove_buttons DELETE is sent, the tool verifies deletion by single button_id readback; removed[] returns delete_executed, readback_status, and safe_to_retry_delete=false",
|
|
4382
|
+
"if a removed button returns readback_status=unavailable or still_exists, treat the result as readback pending and do not blindly repeat the delete",
|
|
4383
|
+
"all operations share one edit context and publish after at least one write succeeds; there is no draft-only mode for this tool",
|
|
2618
4384
|
"background_color and text_color cannot both be white",
|
|
2619
|
-
"
|
|
2620
|
-
"for addData buttons, put field mappings in payload.trigger_add_data_config.que_relation",
|
|
4385
|
+
"accepts apps[] for multi-app batch; each item is {app_key, upsert_buttons?, patch_buttons?, remove_buttons?, view_configs?}",
|
|
2621
4386
|
],
|
|
2622
4387
|
"minimal_example": {
|
|
2623
4388
|
"profile": "default",
|
|
2624
4389
|
"app_key": "APP_KEY",
|
|
2625
|
-
"
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
4390
|
+
"upsert_buttons": [
|
|
4391
|
+
{
|
|
4392
|
+
"button_text": "同步客户",
|
|
4393
|
+
"style_preset": "primary_blue",
|
|
4394
|
+
"button_icon": "ex-switch",
|
|
4395
|
+
"trigger_action": "link",
|
|
4396
|
+
"trigger_link_url": "https://example.com",
|
|
4397
|
+
}
|
|
4398
|
+
],
|
|
4399
|
+
"patch_buttons": [
|
|
4400
|
+
{
|
|
4401
|
+
"button_text": "同步客户",
|
|
4402
|
+
"set": {"trigger_link_url": "https://example.com/new"},
|
|
4403
|
+
}
|
|
4404
|
+
],
|
|
4405
|
+
"remove_buttons": [{"button_text": "旧按钮"}],
|
|
4406
|
+
"view_configs": [],
|
|
4407
|
+
},
|
|
4408
|
+
"add_data_example": {
|
|
4409
|
+
"profile": "default",
|
|
4410
|
+
"app_key": "APP_KEY",
|
|
4411
|
+
"upsert_buttons": [
|
|
4412
|
+
{
|
|
4413
|
+
"client_key": "add_order",
|
|
4414
|
+
"button_text": "新增订单",
|
|
4415
|
+
"style_preset": "neutral_outline",
|
|
4416
|
+
"button_icon": "ex-plus-circle",
|
|
4417
|
+
"trigger_action": "addData",
|
|
4418
|
+
"trigger_add_data_config": {
|
|
4419
|
+
"target_app_key": "TARGET_APP",
|
|
4420
|
+
"field_mappings": [{"source_field": "客户名称", "target_field": "客户"}],
|
|
4421
|
+
"default_values": {"状态": "待提交"},
|
|
4422
|
+
},
|
|
4423
|
+
}
|
|
4424
|
+
],
|
|
4425
|
+
"remove_buttons": [],
|
|
4426
|
+
"view_configs": [
|
|
4427
|
+
{
|
|
4428
|
+
"view_key": "VIEW_KEY",
|
|
4429
|
+
"buttons": [{"button_ref": "add_order", "placement": "detail", "primary": True}],
|
|
4430
|
+
}
|
|
4431
|
+
],
|
|
4432
|
+
},
|
|
4433
|
+
"relation_current_record_example": {
|
|
4434
|
+
"profile": "default",
|
|
4435
|
+
"app_key": "EMPLOYEE_APP",
|
|
4436
|
+
"upsert_buttons": [
|
|
4437
|
+
{
|
|
4438
|
+
"client_key": "add_worklog",
|
|
4439
|
+
"button_text": "快捷添加工时",
|
|
4440
|
+
"style_preset": "neutral_outline",
|
|
4441
|
+
"button_icon": "ex-plus-circle",
|
|
4442
|
+
"trigger_action": "addData",
|
|
4443
|
+
"trigger_add_data_config": {
|
|
4444
|
+
"target_app_key": "WORKLOG_APP",
|
|
4445
|
+
"field_mappings": [{"source_field": "数据ID", "target_field": "关联员工"}],
|
|
4446
|
+
"default_values": {"状态": "待提交"},
|
|
4447
|
+
},
|
|
4448
|
+
}
|
|
4449
|
+
],
|
|
4450
|
+
"view_configs": [
|
|
4451
|
+
{
|
|
4452
|
+
"view_key": "RAW_VIEW_KEY",
|
|
4453
|
+
"buttons": [{"button_ref": "add_worklog", "placement": "detail", "primary": True}],
|
|
4454
|
+
}
|
|
4455
|
+
],
|
|
2632
4456
|
},
|
|
2633
4457
|
},
|
|
2634
|
-
"
|
|
2635
|
-
"allowed_keys": [
|
|
4458
|
+
"app_associated_resources_apply": {
|
|
4459
|
+
"allowed_keys": [
|
|
4460
|
+
"app_key",
|
|
4461
|
+
"upsert_resources",
|
|
4462
|
+
"patch_resources",
|
|
4463
|
+
"remove_associated_item_ids",
|
|
4464
|
+
"reorder_associated_item_ids",
|
|
4465
|
+
"view_configs",
|
|
4466
|
+
"upsert_resources[].client_key",
|
|
4467
|
+
"upsert_resources[].associated_item_id",
|
|
4468
|
+
"upsert_resources[].graph_type",
|
|
4469
|
+
"upsert_resources[].target_app_key",
|
|
4470
|
+
"upsert_resources[].view_key",
|
|
4471
|
+
"upsert_resources[].chart_key",
|
|
4472
|
+
"upsert_resources[].report_source",
|
|
4473
|
+
"upsert_resources[].match_mappings",
|
|
4474
|
+
"upsert_resources[].match_mappings[].target_field",
|
|
4475
|
+
"upsert_resources[].match_mappings[].source_field",
|
|
4476
|
+
"upsert_resources[].match_mappings[].value",
|
|
4477
|
+
"upsert_resources[].match_mappings[].operator",
|
|
4478
|
+
"upsert_resources[].match_rules",
|
|
4479
|
+
"patch_resources[].associated_item_id",
|
|
4480
|
+
"patch_resources[].set",
|
|
4481
|
+
"patch_resources[].unset",
|
|
4482
|
+
"view_configs[].view_key",
|
|
4483
|
+
"view_configs[].visible",
|
|
4484
|
+
"view_configs[].limit_type",
|
|
4485
|
+
"view_configs[].associated_item_ids",
|
|
4486
|
+
"view_configs[].associated_item_refs",
|
|
4487
|
+
],
|
|
2636
4488
|
"aliases": {
|
|
2637
|
-
"
|
|
2638
|
-
"
|
|
2639
|
-
"
|
|
2640
|
-
"
|
|
2641
|
-
"
|
|
2642
|
-
"
|
|
2643
|
-
"
|
|
2644
|
-
"
|
|
2645
|
-
"
|
|
2646
|
-
"
|
|
2647
|
-
"
|
|
2648
|
-
"
|
|
4489
|
+
"upsertResources": "upsert_resources",
|
|
4490
|
+
"patchResources": "patch_resources",
|
|
4491
|
+
"resources": "upsert_resources",
|
|
4492
|
+
"removeAssociatedItemIds": "remove_associated_item_ids",
|
|
4493
|
+
"reorderAssociatedItemIds": "reorder_associated_item_ids",
|
|
4494
|
+
"viewConfigs": "view_configs",
|
|
4495
|
+
"associatedItemId": "associated_item_id",
|
|
4496
|
+
"graphType": "graph_type",
|
|
4497
|
+
"targetAppKey": "target_app_key",
|
|
4498
|
+
"chartKey": "chart_key",
|
|
4499
|
+
"chartId": "chart_key",
|
|
4500
|
+
"viewKey": "view_key",
|
|
4501
|
+
"viewgraphKey": "view_key",
|
|
4502
|
+
"reportSource": "report_source",
|
|
4503
|
+
"matchMappings": "match_mappings",
|
|
4504
|
+
"matchRules": "match_rules",
|
|
4505
|
+
"associatedItemRefs": "associated_item_refs",
|
|
4506
|
+
"associatedItemIds": "associated_item_ids",
|
|
2649
4507
|
},
|
|
2650
4508
|
"allowed_values": {
|
|
2651
|
-
"
|
|
2652
|
-
"
|
|
2653
|
-
"
|
|
2654
|
-
"payload.background_color": list(BUTTON_BACKGROUND_COLORS),
|
|
2655
|
-
"payload.text_color": list(BUTTON_TEXT_COLORS),
|
|
4509
|
+
"upsert_resources[].graph_type": ["chart", "view"],
|
|
4510
|
+
"upsert_resources[].report_source": ["app", "dataset"],
|
|
4511
|
+
"view_configs[].limit_type": ["all", "select"],
|
|
2656
4512
|
},
|
|
2657
4513
|
"execution_notes": [
|
|
2658
|
-
"
|
|
2659
|
-
"
|
|
2660
|
-
"
|
|
2661
|
-
"for
|
|
4514
|
+
"this tool manages Qingflow in-app associated report/view display; it does not create or edit QingBI report bodies/configs",
|
|
4515
|
+
"create or edit app-source BI report bodies first with app_charts_apply, then attach the resulting chart_id here with graph_type=chart; dataset BI reports can only be attached when they already exist",
|
|
4516
|
+
"this is the default associated report/view path; it manages both the app-level associated resource pool and per-view display config",
|
|
4517
|
+
"use patch_resources for partial parameter replacement on existing associated resources; the tool reads the current resource including backend-required raw fields, merges patch_resources[].set/unset, then submits the backend full-save payload internally",
|
|
4518
|
+
"associated_item_id is form_asos_chart.id from app_get.associated_resources[].associated_item_id; view_configs/remove/reorder may also pass an existing associated resource's chart_id/chart_key/view_key and the tool resolves it to the internal id",
|
|
4519
|
+
"before creating an associated resource, read app_get.associated_resources and reuse an existing item with patch_resources when target_app_key + view_key/chart_key already matches; repeated upsert_resources without associated_item_id can create duplicate associated items",
|
|
4520
|
+
"graph_type=view uses view_key and internally compiles to the Qingflow view source; graph_type=chart uses chart_key and defaults to report_source=app",
|
|
4521
|
+
"report_source=app maps to BI_QINGFLOW; report_source=dataset maps to BI_DATASET for associating an existing dataset report; do not pass raw backend sourceType",
|
|
4522
|
+
"use match_mappings for associated view/report filtering; dynamic conditions use source_field and static conditions use value",
|
|
4523
|
+
"match_mappings.source_field accepts source schema fields plus system fields 数据ID(-17) and 编号(0); match_mappings compiles to backend matchRules",
|
|
4524
|
+
"do not write raw match_rules unless preserving a legacy backend config; match_mappings and match_rules are mutually exclusive",
|
|
4525
|
+
"client_key only lets a view_config reference a resource created earlier in the same apply call through associated_item_refs; it is not persisted and cannot deduplicate later apply calls",
|
|
4526
|
+
"remove_associated_item_ids sends DELETE and verifies deletion with one associated-resource pool readback because the backend has no confirmed single-item GET; removed[] returns delete_executed, readback_status, and safe_to_retry_delete=false",
|
|
4527
|
+
"if an associated resource delete returns readback_status=unavailable or still_exists, treat the result as readback pending and do not blindly repeat the delete",
|
|
4528
|
+
"this tool publishes after at least one write succeeds; there is no draft-only mode",
|
|
4529
|
+
"visible=false hides the associated-resource area without clearing previous selected ids; visible=true with limit_type=all shows the whole app-level pool",
|
|
2662
4530
|
],
|
|
2663
4531
|
"minimal_example": {
|
|
2664
4532
|
"profile": "default",
|
|
2665
4533
|
"app_key": "APP_KEY",
|
|
2666
|
-
"
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
"
|
|
2682
|
-
"
|
|
2683
|
-
"
|
|
4534
|
+
"upsert_resources": [
|
|
4535
|
+
{
|
|
4536
|
+
"client_key": "customer_view",
|
|
4537
|
+
"graph_type": "view",
|
|
4538
|
+
"target_app_key": "TARGET_APP",
|
|
4539
|
+
"view_key": "VIEW_KEY",
|
|
4540
|
+
"match_mappings": [{"target_field": "关联员工", "source_field": "数据ID"}],
|
|
4541
|
+
}
|
|
4542
|
+
],
|
|
4543
|
+
"patch_resources": [
|
|
4544
|
+
{
|
|
4545
|
+
"associated_item_id": 123,
|
|
4546
|
+
"set": {"match_mappings": [{"target_field": "状态", "value": "待提交"}]},
|
|
4547
|
+
}
|
|
4548
|
+
],
|
|
4549
|
+
"remove_associated_item_ids": [],
|
|
4550
|
+
"reorder_associated_item_ids": [],
|
|
4551
|
+
"view_configs": [
|
|
4552
|
+
{
|
|
4553
|
+
"view_key": "MAIN_VIEW",
|
|
4554
|
+
"visible": True,
|
|
4555
|
+
"limit_type": "select",
|
|
4556
|
+
"associated_item_refs": ["customer_view"],
|
|
4557
|
+
}
|
|
4558
|
+
],
|
|
2684
4559
|
},
|
|
2685
4560
|
},
|
|
2686
4561
|
"app_schema_plan": {
|
|
@@ -2709,26 +4584,33 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2709
4584
|
"field.customBtnTextStatus": "field.custom_button_text_enabled",
|
|
2710
4585
|
"field.customBtnText": "field.custom_button_text",
|
|
2711
4586
|
"field.subfieldUpdates": "field.subfield_updates",
|
|
4587
|
+
"field.asDataTitle": "field.as_data_title",
|
|
4588
|
+
"field.asDataCover": "field.as_data_cover",
|
|
2712
4589
|
},
|
|
2713
4590
|
"allowed_values": {
|
|
2714
4591
|
"field.type": [member.value for member in PublicFieldType],
|
|
2715
4592
|
"field.relation_mode": [member.value for member in PublicRelationMode],
|
|
2716
4593
|
"field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
|
|
4594
|
+
"workspace_icon.icon_names": list(WORKSPACE_ICON_NAMES),
|
|
4595
|
+
"workspace_icon.icon_colors": list(WORKSPACE_ICON_COLORS),
|
|
4596
|
+
"workspace_icon.generic_icon_names": list(GENERIC_WORKSPACE_ICON_NAMES),
|
|
2717
4597
|
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
2718
4598
|
},
|
|
2719
4599
|
"execution_notes": [
|
|
2720
4600
|
"create mode may set visibility for the new app; edit mode may update visibility on an existing app",
|
|
4601
|
+
"create mode should include explicit non-template icon + color; apply mode enforces this before writing",
|
|
4602
|
+
"call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
|
|
2721
4603
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
2722
4604
|
],
|
|
2723
4605
|
"minimal_example": {
|
|
2724
4606
|
"profile": "default",
|
|
2725
4607
|
"app_name": "研发项目管理",
|
|
2726
4608
|
"package_id": 1001,
|
|
2727
|
-
"icon": "
|
|
4609
|
+
"icon": "briefcase",
|
|
2728
4610
|
"color": "emerald",
|
|
2729
4611
|
"visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
|
|
2730
4612
|
"create_if_missing": True,
|
|
2731
|
-
"add_fields": [{"name": "项目名称", "type": "text"}],
|
|
4613
|
+
"add_fields": [{"name": "项目名称", "type": "text", "as_data_title": True}],
|
|
2732
4614
|
"update_fields": [],
|
|
2733
4615
|
"remove_fields": [],
|
|
2734
4616
|
},
|
|
@@ -2750,11 +4632,40 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2750
4632
|
},
|
|
2751
4633
|
},
|
|
2752
4634
|
"app_schema_apply": {
|
|
2753
|
-
"allowed_keys": [
|
|
4635
|
+
"allowed_keys": [
|
|
4636
|
+
"app_key",
|
|
4637
|
+
"package_id",
|
|
4638
|
+
"app_name",
|
|
4639
|
+
"icon",
|
|
4640
|
+
"color",
|
|
4641
|
+
"visibility",
|
|
4642
|
+
"create_if_missing",
|
|
4643
|
+
"publish",
|
|
4644
|
+
"add_fields",
|
|
4645
|
+
"update_fields",
|
|
4646
|
+
"remove_fields",
|
|
4647
|
+
"apps",
|
|
4648
|
+
"apps[].client_key",
|
|
4649
|
+
"apps[].app_key",
|
|
4650
|
+
"apps[].app_name",
|
|
4651
|
+
"apps[].icon",
|
|
4652
|
+
"apps[].color",
|
|
4653
|
+
"apps[].visibility",
|
|
4654
|
+
"apps[].add_fields",
|
|
4655
|
+
"apps[].update_fields",
|
|
4656
|
+
"apps[].remove_fields",
|
|
4657
|
+
"apps[].add_fields[].target_app_ref",
|
|
4658
|
+
],
|
|
2754
4659
|
"aliases": {
|
|
2755
4660
|
"app_title": "app_name",
|
|
2756
4661
|
"title": "app_name",
|
|
2757
4662
|
"packageId": "package_id",
|
|
4663
|
+
"apps[].clientKey": "apps[].client_key",
|
|
4664
|
+
"apps[].appKey": "apps[].app_key",
|
|
4665
|
+
"apps[].appName": "apps[].app_name",
|
|
4666
|
+
"apps[].appTitle": "apps[].app_name",
|
|
4667
|
+
"field.targetAppRef": "field.target_app_ref",
|
|
4668
|
+
"field.targetAppClientKey": "field.target_app_ref",
|
|
2758
4669
|
"field.title": "field.name",
|
|
2759
4670
|
"field.label": "field.name",
|
|
2760
4671
|
"field.fields": "field.subfields",
|
|
@@ -2775,6 +4686,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2775
4686
|
"field.customBtnTextStatus": "field.custom_button_text_enabled",
|
|
2776
4687
|
"field.customBtnText": "field.custom_button_text",
|
|
2777
4688
|
"field.subfieldUpdates": "field.subfield_updates",
|
|
4689
|
+
"field.asDataTitle": "field.as_data_title",
|
|
4690
|
+
"field.asDataCover": "field.as_data_cover",
|
|
2778
4691
|
},
|
|
2779
4692
|
"allowed_values": {
|
|
2780
4693
|
"field.type": [member.value for member in PublicFieldType],
|
|
@@ -2789,8 +4702,16 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2789
4702
|
"use exactly one resource mode",
|
|
2790
4703
|
"edit mode: app_key, optional app_name to rename the existing app",
|
|
2791
4704
|
"create mode: package_id + app_name + create_if_missing=true",
|
|
4705
|
+
"multi-app mode: pass package_id + create_if_missing + apps[]; do not mix apps with top-level app_key/app_name/add_fields/update_fields/remove_fields",
|
|
4706
|
+
"multi-app relation fields may use target_app_ref to point at another apps[].client_key; the tool creates/resolves app shells first and compiles it to target_app_key",
|
|
4707
|
+
"multi-app mode is not transactional; read created_app_keys and apps[].status before retrying, and retry only failed app items",
|
|
2792
4708
|
"create mode defaults new app visibility to workspace/not when visibility is omitted; edit mode preserves current visibility when omitted",
|
|
4709
|
+
"create mode requires explicit icon + color; icon=template is blocked because it is too generic",
|
|
4710
|
+
"multi-app create mode requires each new app item to include a distinct non-template icon and a valid color",
|
|
4711
|
+
"edit mode preserves existing icon/color when omitted; explicit icon/color values are still validated",
|
|
4712
|
+
"call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
|
|
2793
4713
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
4714
|
+
"update_fields is the field-level partial update path; it reads current form schema and preserves untouched field config",
|
|
2794
4715
|
"multiple relation fields are backend-risky; read verification.relation_field_limit_verified and warnings before declaring the schema stable",
|
|
2795
4716
|
"backend 49614 is normalized to MULTIPLE_RELATION_FIELDS_UNSUPPORTED with a workaround message",
|
|
2796
4717
|
"relation_mode=multiple maps to referenceConfig.optionalDataNum=0",
|
|
@@ -2806,20 +4727,59 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2806
4727
|
"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 =",
|
|
2807
4728
|
"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",
|
|
2808
4729
|
"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",
|
|
4730
|
+
"data title is required: mark exactly one top-level field with as_data_title=true; use a readable name/number/date-like field",
|
|
4731
|
+
"data cover is optional: mark at most one top-level attachment field with as_data_cover=true",
|
|
2809
4732
|
],
|
|
2810
4733
|
"minimal_example": {
|
|
2811
4734
|
"profile": "default",
|
|
2812
4735
|
"app_name": "研发项目管理",
|
|
2813
4736
|
"package_id": 1001,
|
|
2814
|
-
"icon": "
|
|
4737
|
+
"icon": "briefcase",
|
|
2815
4738
|
"color": "emerald",
|
|
2816
4739
|
"visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
|
|
2817
4740
|
"create_if_missing": True,
|
|
2818
4741
|
"publish": True,
|
|
2819
|
-
"add_fields": [
|
|
4742
|
+
"add_fields": [
|
|
4743
|
+
{"name": "项目名称", "type": "text", "as_data_title": True},
|
|
4744
|
+
{"name": "项目封面", "type": "attachment", "as_data_cover": True},
|
|
4745
|
+
],
|
|
2820
4746
|
"update_fields": [],
|
|
2821
4747
|
"remove_fields": [],
|
|
2822
4748
|
},
|
|
4749
|
+
"multi_app_example": {
|
|
4750
|
+
"profile": "default",
|
|
4751
|
+
"package_id": 1001,
|
|
4752
|
+
"create_if_missing": True,
|
|
4753
|
+
"publish": True,
|
|
4754
|
+
"apps": [
|
|
4755
|
+
{
|
|
4756
|
+
"client_key": "employee",
|
|
4757
|
+
"app_name": "员工花名册",
|
|
4758
|
+
"icon": "business-personalcard",
|
|
4759
|
+
"color": "emerald",
|
|
4760
|
+
"add_fields": [
|
|
4761
|
+
{"name": "员工名称", "type": "text", "as_data_title": True},
|
|
4762
|
+
{"name": "员工照片", "type": "attachment", "as_data_cover": True},
|
|
4763
|
+
],
|
|
4764
|
+
},
|
|
4765
|
+
{
|
|
4766
|
+
"client_key": "worklog",
|
|
4767
|
+
"app_name": "工时表",
|
|
4768
|
+
"icon": "clock",
|
|
4769
|
+
"color": "blue",
|
|
4770
|
+
"add_fields": [
|
|
4771
|
+
{"name": "工时标题", "type": "text", "as_data_title": True},
|
|
4772
|
+
{
|
|
4773
|
+
"name": "关联员工",
|
|
4774
|
+
"type": "relation",
|
|
4775
|
+
"target_app_ref": "employee",
|
|
4776
|
+
"display_field": {"name": "员工名称"},
|
|
4777
|
+
"visible_fields": [{"name": "员工名称"}],
|
|
4778
|
+
},
|
|
4779
|
+
],
|
|
4780
|
+
},
|
|
4781
|
+
],
|
|
4782
|
+
},
|
|
2823
4783
|
"rename_example": {
|
|
2824
4784
|
"profile": "default",
|
|
2825
4785
|
"app_key": "APP_PROJECT",
|
|
@@ -2972,7 +4932,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2972
4932
|
"preset_example": {"profile": "default", "app_key": "APP_KEY", "mode": "merge", "preset": "balanced", "sections": []},
|
|
2973
4933
|
},
|
|
2974
4934
|
"app_layout_apply": {
|
|
2975
|
-
"allowed_keys": ["app_key", "mode", "publish", "sections"],
|
|
4935
|
+
"allowed_keys": ["app_key", "mode", "publish", "sections", "apps"],
|
|
2976
4936
|
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
2977
4937
|
"section_allowed_keys": ["type", "paragraph_id", "section_id", "title", "rows"],
|
|
2978
4938
|
"section_aliases": {
|
|
@@ -2985,8 +4945,11 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2985
4945
|
},
|
|
2986
4946
|
"allowed_values": {"mode": [member.value for member in LayoutApplyMode]},
|
|
2987
4947
|
"execution_notes": [
|
|
4948
|
+
"mode=merge is the layout partial update path: mentioned sections are merged and unmentioned fields are preserved",
|
|
4949
|
+
"mode=replace is full layout replacement and should be used only when intentionally rewriting all sections",
|
|
2988
4950
|
"layout verification is split into layout_verified and layout_summary_verified",
|
|
2989
4951
|
"LAYOUT_SUMMARY_UNVERIFIED means raw form readback is stronger than the compact summary",
|
|
4952
|
+
"accepts apps[] for multi-app batch; each item is {app_key, mode?, sections, publish?}; top-level publish is used as default for items that omit publish",
|
|
2990
4953
|
],
|
|
2991
4954
|
"minimal_section_example": {"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]},
|
|
2992
4955
|
"minimal_example": {
|
|
@@ -2996,96 +4959,95 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2996
4959
|
"publish": True,
|
|
2997
4960
|
"sections": [{"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["项目名称", "项目负责人", "项目阶段", "优先级"]]}],
|
|
2998
4961
|
},
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
"node.role_names": "node.assignees.role_names",
|
|
3007
|
-
"node.role_ids": "node.assignees.role_ids",
|
|
3008
|
-
"node.member_names": "node.assignees.member_names",
|
|
3009
|
-
"node.member_emails": "node.assignees.member_emails",
|
|
3010
|
-
"node.member_uids": "node.assignees.member_uids",
|
|
3011
|
-
"node.editable_fields": "node.permissions.editable_fields",
|
|
3012
|
-
"default_approval": "basic_approval",
|
|
3013
|
-
},
|
|
3014
|
-
"allowed_values": {
|
|
3015
|
-
"mode": ["replace"],
|
|
3016
|
-
"preset": [member.value for member in FlowPreset],
|
|
3017
|
-
"node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
|
|
4962
|
+
"batch_example": {
|
|
4963
|
+
"profile": "default",
|
|
4964
|
+
"publish": True,
|
|
4965
|
+
"apps": [
|
|
4966
|
+
{"app_key": "APP_KEY_1", "mode": "merge", "sections": [{"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B"]]}]},
|
|
4967
|
+
{"app_key": "APP_KEY_2", "mode": "merge", "sections": [{"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段C", "字段D"]]}]},
|
|
4968
|
+
],
|
|
3018
4969
|
},
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
4970
|
+
},
|
|
4971
|
+
"app_flow_get_schema": {
|
|
4972
|
+
"allowed_keys": ["schema_version"],
|
|
4973
|
+
"aliases": {"schemaVersion": "schema_version"},
|
|
4974
|
+
"allowed_values": {},
|
|
4975
|
+
"execution_notes": [
|
|
4976
|
+
"returns WorkflowSpec JSON Schema from /workflow/spec/schema",
|
|
4977
|
+
"call this before authoring a new spec or validating spec shape",
|
|
4978
|
+
"worksheet-level approval deduplication toggles (legacy global settings) are not yet part of WorkflowSpec",
|
|
3022
4979
|
],
|
|
4980
|
+
"minimal_example": {
|
|
4981
|
+
"profile": "default",
|
|
4982
|
+
"schema_version": "vnext-2026-06",
|
|
4983
|
+
},
|
|
4984
|
+
},
|
|
4985
|
+
"app_flow_get": {
|
|
4986
|
+
"allowed_keys": ["app_key", "version_id"],
|
|
4987
|
+
"aliases": {"versionId": "version_id"},
|
|
4988
|
+
"allowed_values": {},
|
|
3023
4989
|
"execution_notes": [
|
|
3024
|
-
"
|
|
3025
|
-
"
|
|
4990
|
+
"returns current WorkflowSpecDTO for one app via GET /workflow/spec",
|
|
4991
|
+
"use GET-first before patching and app_flow_apply",
|
|
3026
4992
|
],
|
|
3027
4993
|
"minimal_example": {
|
|
3028
4994
|
"profile": "default",
|
|
3029
4995
|
"app_key": "APP_KEY",
|
|
3030
|
-
"mode": "replace",
|
|
3031
|
-
"preset": "basic_approval",
|
|
3032
|
-
"nodes": [
|
|
3033
|
-
{
|
|
3034
|
-
"id": "approve_1",
|
|
3035
|
-
"type": "approve",
|
|
3036
|
-
"name": "部门审批",
|
|
3037
|
-
"assignees": {"role_names": ["项目经理"]},
|
|
3038
|
-
"permissions": {"editable_fields": ["状态", "审批意见"]},
|
|
3039
|
-
}
|
|
3040
|
-
],
|
|
3041
|
-
"transitions": [],
|
|
3042
4996
|
},
|
|
3043
4997
|
},
|
|
3044
4998
|
"app_flow_apply": {
|
|
3045
|
-
"allowed_keys": ["app_key", "
|
|
4999
|
+
"allowed_keys": ["app_key", "publish", "spec", "idempotency_key", "schema_version", "patch_nodes"],
|
|
3046
5000
|
"aliases": {
|
|
3047
|
-
"
|
|
3048
|
-
"
|
|
3049
|
-
"node.role_ids": "node.assignees.role_ids",
|
|
3050
|
-
"node.member_names": "node.assignees.member_names",
|
|
3051
|
-
"node.member_emails": "node.assignees.member_emails",
|
|
3052
|
-
"node.member_uids": "node.assignees.member_uids",
|
|
3053
|
-
"node.editable_fields": "node.permissions.editable_fields",
|
|
3054
|
-
},
|
|
3055
|
-
"allowed_values": {
|
|
3056
|
-
"mode": ["replace"],
|
|
3057
|
-
"node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
|
|
5001
|
+
"schemaVersion": "schema_version",
|
|
5002
|
+
"idempotencyKey": "idempotency_key",
|
|
3058
5003
|
},
|
|
5004
|
+
"allowed_values": {},
|
|
3059
5005
|
"dependency_hints": [
|
|
3060
|
-
"
|
|
3061
|
-
"
|
|
5006
|
+
"when using spec: must be a complete WorkflowSpecDTO object (replace-only apply); when using patch_nodes[]: spec is not required",
|
|
5007
|
+
"use patch_nodes[] instead of spec when updating only specific nodes; patch_nodes reads current flow, merges set/unset, then writes back — spec is not needed when patch_nodes is supplied",
|
|
3062
5008
|
],
|
|
3063
5009
|
"execution_notes": [
|
|
3064
|
-
"
|
|
3065
|
-
"
|
|
3066
|
-
"
|
|
5010
|
+
"posts to /workflow/spec:apply with appKey, idempotencyKey, schemaVersion, and spec",
|
|
5011
|
+
"verification uses appliedSpec, diffSummary, and semanticLint from the apply response",
|
|
5012
|
+
"publish=false keeps changes in draft when supported by the backend",
|
|
5013
|
+
"patch_nodes[] items are {id, set, unset}; id must match an existing node id from app_get_flow.spec.nodes; FLOW_NODE_NOT_FOUND is returned if id is missing",
|
|
3067
5014
|
],
|
|
3068
5015
|
"minimal_example": {
|
|
3069
5016
|
"profile": "default",
|
|
3070
5017
|
"app_key": "APP_KEY",
|
|
3071
|
-
"mode": "replace",
|
|
3072
5018
|
"publish": True,
|
|
3073
|
-
"
|
|
3074
|
-
{"id": "
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
],
|
|
3084
|
-
"transitions": [{"from": "start", "to": "approve_1"}, {"from": "approve_1", "to": "end"}],
|
|
5019
|
+
"spec": {
|
|
5020
|
+
"nodes": [{"id": "n1", "type": "APPLICANT", "name": "发起"}],
|
|
5021
|
+
"transitions": [],
|
|
5022
|
+
},
|
|
5023
|
+
},
|
|
5024
|
+
"patch_nodes_example": {
|
|
5025
|
+
"profile": "default",
|
|
5026
|
+
"app_key": "APP_KEY",
|
|
5027
|
+
"publish": True,
|
|
5028
|
+
"patch_nodes": [{"id": "approve_1", "set": {"name": "总监审批", "assignees": {"role_names": ["总监"]}}}],
|
|
3085
5029
|
},
|
|
3086
5030
|
},
|
|
3087
5031
|
"app_views_plan": {
|
|
3088
|
-
"allowed_keys": [
|
|
5032
|
+
"allowed_keys": [
|
|
5033
|
+
"app_key",
|
|
5034
|
+
"upsert_views",
|
|
5035
|
+
"patch_views",
|
|
5036
|
+
"remove_views",
|
|
5037
|
+
"preset",
|
|
5038
|
+
"upsert_views[].view_key",
|
|
5039
|
+
"upsert_views[].name",
|
|
5040
|
+
"upsert_views[].type",
|
|
5041
|
+
"upsert_views[].columns",
|
|
5042
|
+
"upsert_views[].filters",
|
|
5043
|
+
"upsert_views[].buttons",
|
|
5044
|
+
"upsert_views[].visibility",
|
|
5045
|
+
"upsert_views[].query_conditions",
|
|
5046
|
+
"patch_views[].view_key",
|
|
5047
|
+
"patch_views[].name",
|
|
5048
|
+
"patch_views[].set",
|
|
5049
|
+
"patch_views[].unset",
|
|
5050
|
+
],
|
|
3089
5051
|
"aliases": {
|
|
3090
5052
|
"fields": "columns",
|
|
3091
5053
|
"column_names": "columns",
|
|
@@ -3096,6 +5058,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3096
5058
|
"kanban": "board",
|
|
3097
5059
|
"filter_rules": "filters",
|
|
3098
5060
|
"filterRules": "filters",
|
|
5061
|
+
"queryConditions": "query_conditions",
|
|
5062
|
+
"query_condition": "query_conditions",
|
|
5063
|
+
"queryCondition": "query_conditions",
|
|
5064
|
+
"patchViews": "patch_views",
|
|
3099
5065
|
"startField": "start_field",
|
|
3100
5066
|
"endField": "end_field",
|
|
3101
5067
|
"titleField": "title_field",
|
|
@@ -3113,11 +5079,18 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3113
5079
|
"view.type": [member.value for member in PublicViewType],
|
|
3114
5080
|
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
3115
5081
|
"view.buttons.button_type": ["SYSTEM", "CUSTOM"],
|
|
3116
|
-
"view.buttons.config_type": ["TOP", "DETAIL"],
|
|
5082
|
+
"view.buttons.config_type": ["TOP", "DETAIL", "INSIDE"],
|
|
3117
5083
|
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
3118
5084
|
},
|
|
3119
5085
|
"execution_notes": [
|
|
3120
5086
|
"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",
|
|
5087
|
+
"filters are saved fixed filters that apply when the view opens; query_conditions configure the frontend query panel and only apply after a user enters query values",
|
|
5088
|
+
"upsert_views[].query_conditions.rows is a layout matrix of field names; it is compiled to backend queryCondition queIds",
|
|
5089
|
+
"use patch_views for partial parameter replacement on existing views; the tool reads current config, merges patch_views[].set/unset, then submits the backend full-save payload internally",
|
|
5090
|
+
"remove_views accepts a raw view_key or an exact unique view name; after DELETE the tool verifies deletion by single view_key readback, not by a full app view list",
|
|
5091
|
+
"deleted views return verification.by_view[].delete_executed, readback_status, and safe_to_retry_delete=false; if readback is pending, do not blindly repeat the delete",
|
|
5092
|
+
"new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/upsert_views explicitly changes associated_resources",
|
|
5093
|
+
"associated report/view resource pool and per-view selected resources are configured through app_associated_resources_apply; app_views_apply only keeps legacy associated_resources input compatible",
|
|
3121
5094
|
"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",
|
|
3122
5095
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
3123
5096
|
],
|
|
@@ -3127,6 +5100,41 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3127
5100
|
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
|
|
3128
5101
|
"remove_views": [],
|
|
3129
5102
|
},
|
|
5103
|
+
"query_conditions_example": {
|
|
5104
|
+
"profile": "default",
|
|
5105
|
+
"app_key": "APP_KEY",
|
|
5106
|
+
"patch_views": [
|
|
5107
|
+
{
|
|
5108
|
+
"view_key": "VIEW_KEY",
|
|
5109
|
+
"set": {
|
|
5110
|
+
"query_conditions": {
|
|
5111
|
+
"enabled": True,
|
|
5112
|
+
"rows": [["客户名称", "负责人"], ["创建时间"]],
|
|
5113
|
+
}
|
|
5114
|
+
},
|
|
5115
|
+
}
|
|
5116
|
+
],
|
|
5117
|
+
"remove_views": [],
|
|
5118
|
+
},
|
|
5119
|
+
"full_upsert_query_conditions_example": {
|
|
5120
|
+
"profile": "default",
|
|
5121
|
+
"app_key": "APP_KEY",
|
|
5122
|
+
"upsert_views": [
|
|
5123
|
+
{
|
|
5124
|
+
"name": "客户查询视图",
|
|
5125
|
+
"type": "table",
|
|
5126
|
+
"columns": ["客户名称", "负责人", "客户状态", "创建时间"],
|
|
5127
|
+
"filters": [{"field_name": "客户状态", "operator": "eq", "value": "有效"}],
|
|
5128
|
+
"query_conditions": {
|
|
5129
|
+
"enabled": True,
|
|
5130
|
+
"exact": False,
|
|
5131
|
+
"hide_before_query": False,
|
|
5132
|
+
"rows": [["客户名称", "负责人"], ["创建时间"]],
|
|
5133
|
+
},
|
|
5134
|
+
}
|
|
5135
|
+
],
|
|
5136
|
+
"remove_views": [],
|
|
5137
|
+
},
|
|
3130
5138
|
"gantt_example": {
|
|
3131
5139
|
"profile": "default",
|
|
3132
5140
|
"app_key": "APP_KEY",
|
|
@@ -3145,7 +5153,26 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3145
5153
|
},
|
|
3146
5154
|
},
|
|
3147
5155
|
"app_views_apply": {
|
|
3148
|
-
"allowed_keys": [
|
|
5156
|
+
"allowed_keys": [
|
|
5157
|
+
"app_key",
|
|
5158
|
+
"apps",
|
|
5159
|
+
"publish",
|
|
5160
|
+
"upsert_views",
|
|
5161
|
+
"patch_views",
|
|
5162
|
+
"remove_views",
|
|
5163
|
+
"upsert_views[].view_key",
|
|
5164
|
+
"upsert_views[].name",
|
|
5165
|
+
"upsert_views[].type",
|
|
5166
|
+
"upsert_views[].columns",
|
|
5167
|
+
"upsert_views[].filters",
|
|
5168
|
+
"upsert_views[].buttons",
|
|
5169
|
+
"upsert_views[].visibility",
|
|
5170
|
+
"upsert_views[].query_conditions",
|
|
5171
|
+
"patch_views[].view_key",
|
|
5172
|
+
"patch_views[].name",
|
|
5173
|
+
"patch_views[].set",
|
|
5174
|
+
"patch_views[].unset",
|
|
5175
|
+
],
|
|
3149
5176
|
"aliases": {
|
|
3150
5177
|
"fields": "columns",
|
|
3151
5178
|
"column_names": "columns",
|
|
@@ -3156,6 +5183,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3156
5183
|
"kanban": "board",
|
|
3157
5184
|
"filter_rules": "filters",
|
|
3158
5185
|
"filterRules": "filters",
|
|
5186
|
+
"queryConditions": "query_conditions",
|
|
5187
|
+
"query_condition": "query_conditions",
|
|
5188
|
+
"queryCondition": "query_conditions",
|
|
5189
|
+
"patchViews": "patch_views",
|
|
3159
5190
|
"startField": "start_field",
|
|
3160
5191
|
"endField": "end_field",
|
|
3161
5192
|
"titleField": "title_field",
|
|
@@ -3172,17 +5203,25 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3172
5203
|
"view.type": [member.value for member in PublicViewType],
|
|
3173
5204
|
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
3174
5205
|
"view.buttons.button_type": ["SYSTEM", "CUSTOM"],
|
|
3175
|
-
"view.buttons.config_type": ["TOP", "DETAIL"],
|
|
5206
|
+
"view.buttons.config_type": ["TOP", "DETAIL", "INSIDE"],
|
|
3176
5207
|
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
3177
5208
|
},
|
|
3178
5209
|
"execution_notes": [
|
|
3179
5210
|
"apply may return partial_success when some views land and others fail",
|
|
3180
5211
|
"when duplicate view names exist, supply view_key to target the exact view",
|
|
3181
|
-
"read back
|
|
5212
|
+
"read back app_get after any failed or partial view apply",
|
|
3182
5213
|
"view existence verification and saved-filter verification are separate; treat filters as unverified until verification.view_filters_verified is true",
|
|
3183
5214
|
"buttons omitted preserves existing button config; buttons=[] clears all buttons; buttons=[...] replaces the full button config",
|
|
3184
5215
|
"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",
|
|
5216
|
+
"filters are saved fixed filters that apply when the view opens; query_conditions configure the frontend query panel and only apply after a user enters query values",
|
|
5217
|
+
"upsert_views[].query_conditions.rows is a layout matrix of field names; it is compiled to backend queryCondition queIds",
|
|
5218
|
+
"use patch_views for partial parameter replacement on existing views; the public update mode is patch even though the backend save is still a full view payload",
|
|
5219
|
+
"remove_views accepts a raw view_key or an exact unique view name; after DELETE the tool verifies deletion by single view_key readback, not by a full app view list",
|
|
5220
|
+
"deleted views return verification.by_view[].delete_executed, readback_status, and safe_to_retry_delete=false; if readback is pending, do not blindly repeat the delete",
|
|
5221
|
+
"new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/upsert_views explicitly changes associated_resources",
|
|
5222
|
+
"associated report/view resource pool and per-view selected resources are configured through app_associated_resources_apply; app_views_apply keeps legacy associated_resources input compatible but it is no longer the recommended public contract",
|
|
3185
5223
|
"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",
|
|
5224
|
+
"accepts apps[] for multi-app batch; each item is {app_key, upsert_views?, patch_views?, remove_views?, publish?}; top-level publish is used as default for items that omit publish",
|
|
3186
5225
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
3187
5226
|
],
|
|
3188
5227
|
"minimal_example": {
|
|
@@ -3192,6 +5231,43 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3192
5231
|
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
|
|
3193
5232
|
"remove_views": [],
|
|
3194
5233
|
},
|
|
5234
|
+
"query_conditions_example": {
|
|
5235
|
+
"profile": "default",
|
|
5236
|
+
"app_key": "APP_KEY",
|
|
5237
|
+
"publish": True,
|
|
5238
|
+
"patch_views": [
|
|
5239
|
+
{
|
|
5240
|
+
"view_key": "VIEW_KEY",
|
|
5241
|
+
"set": {
|
|
5242
|
+
"query_conditions": {
|
|
5243
|
+
"enabled": True,
|
|
5244
|
+
"rows": [["客户名称", "负责人"], ["创建时间"]],
|
|
5245
|
+
}
|
|
5246
|
+
},
|
|
5247
|
+
}
|
|
5248
|
+
],
|
|
5249
|
+
"remove_views": [],
|
|
5250
|
+
},
|
|
5251
|
+
"full_upsert_query_conditions_example": {
|
|
5252
|
+
"profile": "default",
|
|
5253
|
+
"app_key": "APP_KEY",
|
|
5254
|
+
"publish": True,
|
|
5255
|
+
"upsert_views": [
|
|
5256
|
+
{
|
|
5257
|
+
"name": "客户查询视图",
|
|
5258
|
+
"type": "table",
|
|
5259
|
+
"columns": ["客户名称", "负责人", "客户状态", "创建时间"],
|
|
5260
|
+
"filters": [{"field_name": "客户状态", "operator": "eq", "value": "有效"}],
|
|
5261
|
+
"query_conditions": {
|
|
5262
|
+
"enabled": True,
|
|
5263
|
+
"exact": False,
|
|
5264
|
+
"hide_before_query": False,
|
|
5265
|
+
"rows": [["客户名称", "负责人"], ["创建时间"]],
|
|
5266
|
+
},
|
|
5267
|
+
}
|
|
5268
|
+
],
|
|
5269
|
+
"remove_views": [],
|
|
5270
|
+
},
|
|
3195
5271
|
"gantt_example": {
|
|
3196
5272
|
"profile": "default",
|
|
3197
5273
|
"app_key": "APP_KEY",
|
|
@@ -3215,12 +5291,14 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3215
5291
|
"aliases": {},
|
|
3216
5292
|
"allowed_values": {},
|
|
3217
5293
|
"execution_notes": [
|
|
3218
|
-
"returns builder-side app
|
|
3219
|
-
"use this as the default builder discovery read before
|
|
5294
|
+
"returns builder-side app map: base summary, editability, field/view/chart/button counts, compact views, compact charts, custom_buttons, and app-level associated_resources",
|
|
5295
|
+
"use this as the default builder discovery read before view_get/chart_get/apply detail work",
|
|
3220
5296
|
"editability is route-aware builder capability summary, not end-user data visibility",
|
|
3221
5297
|
"can_edit_app_base covers app base-info writes such as app_name, icon, and visibility",
|
|
3222
5298
|
"can_edit_form covers form/schema routes only and does not imply app base-info writes",
|
|
3223
5299
|
"returns normalized app visibility when backend auth is readable",
|
|
5300
|
+
"custom_buttons[].button_id is the id required by app_custom_buttons_apply view_configs[].buttons[].button_ref",
|
|
5301
|
+
"associated_resources[].associated_item_id is the internal id; app_associated_resources_apply.view_configs/remove/reorder may also pass an existing resource's chart_id/chart_key/view_key",
|
|
3224
5302
|
],
|
|
3225
5303
|
"minimal_example": {
|
|
3226
5304
|
"profile": "default",
|
|
@@ -3228,13 +5306,16 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3228
5306
|
},
|
|
3229
5307
|
},
|
|
3230
5308
|
"app_get_fields": {
|
|
3231
|
-
"allowed_keys": ["app_key"],
|
|
5309
|
+
"allowed_keys": ["app_key", "app_keys"],
|
|
3232
5310
|
"aliases": {},
|
|
3233
5311
|
"allowed_values": {},
|
|
3234
5312
|
"execution_notes": [
|
|
3235
5313
|
"returns compact current field configuration for one app",
|
|
3236
5314
|
"use this before app_schema_apply when you need exact field definitions",
|
|
5315
|
+
"also returns chart_fields from QingBI datasource fields; app_charts_apply field selectors should use chart_fields because record/schema-visible fields and QingBI fields are not the same schema",
|
|
5316
|
+
"chart_fields[].field_id supports field_<queId> selectors, while chart_fields[].bi_field_id is the raw QingBI fieldId accepted by report configs",
|
|
3237
5317
|
"subtable fields include nested subfields using the same compact field shape",
|
|
5318
|
+
"accepts app_keys[] for batch read; batch returns {status, apps[], errors[]} where each apps[] item has {app_key, fields}",
|
|
3238
5319
|
],
|
|
3239
5320
|
"minimal_example": {
|
|
3240
5321
|
"profile": "default",
|
|
@@ -3242,12 +5323,13 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3242
5323
|
},
|
|
3243
5324
|
},
|
|
3244
5325
|
"app_get_layout": {
|
|
3245
|
-
"allowed_keys": ["app_key"],
|
|
5326
|
+
"allowed_keys": ["app_key", "app_keys"],
|
|
3246
5327
|
"aliases": {},
|
|
3247
5328
|
"allowed_values": {},
|
|
3248
5329
|
"execution_notes": [
|
|
3249
5330
|
"returns compact current layout configuration for one app",
|
|
3250
5331
|
"use this before app_layout_apply when you need paragraph and row structure",
|
|
5332
|
+
"accepts app_keys[] for batch read; batch returns {status, apps[], errors[]} where each apps[] item has {app_key, sections}",
|
|
3251
5333
|
],
|
|
3252
5334
|
"minimal_example": {
|
|
3253
5335
|
"profile": "default",
|
|
@@ -3255,13 +5337,15 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3255
5337
|
},
|
|
3256
5338
|
},
|
|
3257
5339
|
"app_get_views": {
|
|
3258
|
-
"allowed_keys": ["app_key"],
|
|
5340
|
+
"allowed_keys": ["app_key", "app_keys"],
|
|
3259
5341
|
"aliases": {},
|
|
3260
5342
|
"allowed_values": {},
|
|
3261
5343
|
"execution_notes": [
|
|
3262
5344
|
"returns compact current view inventory for one app",
|
|
3263
|
-
"
|
|
5345
|
+
"compatibility/specialized inventory tool; default builder discovery should start with app_get",
|
|
5346
|
+
"use this before app_views_apply only when you need an exact current view inventory beyond app_get",
|
|
3264
5347
|
"view items include visibility_summary when backend view auth is readable",
|
|
5348
|
+
"accepts app_keys[] for batch read; batch returns {status, apps[], errors[]} where each apps[] item has {app_key, views}",
|
|
3265
5349
|
],
|
|
3266
5350
|
"minimal_example": {
|
|
3267
5351
|
"profile": "default",
|
|
@@ -3269,12 +5353,13 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3269
5353
|
},
|
|
3270
5354
|
},
|
|
3271
5355
|
"app_get_flow": {
|
|
3272
|
-
"allowed_keys": ["app_key"],
|
|
5356
|
+
"allowed_keys": ["app_key", "app_keys"],
|
|
3273
5357
|
"aliases": {},
|
|
3274
5358
|
"allowed_values": {},
|
|
3275
5359
|
"execution_notes": [
|
|
3276
|
-
"returns
|
|
3277
|
-
"use
|
|
5360
|
+
"returns WorkflowSpecDTO for one app (alias of app_flow_get)",
|
|
5361
|
+
"use app_flow_get_schema then app_flow_get before app_flow_apply",
|
|
5362
|
+
"accepts app_keys[] for batch read; batch returns {status, apps[], errors[]} where each apps[] item has {app_key, spec}",
|
|
3278
5363
|
],
|
|
3279
5364
|
"minimal_example": {
|
|
3280
5365
|
"profile": "default",
|
|
@@ -3282,14 +5367,46 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3282
5367
|
},
|
|
3283
5368
|
},
|
|
3284
5369
|
"app_get_charts": {
|
|
3285
|
-
"allowed_keys": ["app_key"],
|
|
5370
|
+
"allowed_keys": ["app_key", "app_keys"],
|
|
3286
5371
|
"aliases": {},
|
|
3287
5372
|
"allowed_values": {},
|
|
3288
5373
|
"execution_notes": [
|
|
3289
5374
|
"returns a compact current chart inventory for one app",
|
|
3290
|
-
"
|
|
5375
|
+
"compatibility/specialized inventory tool; default builder discovery should start with app_get",
|
|
5376
|
+
"use this before app_charts_apply when you need exact current chart_id values beyond the app_get summary",
|
|
3291
5377
|
"chart summaries do not include full qingbi config payloads",
|
|
3292
5378
|
"chart items include visibility_summary when QingBI base info is readable",
|
|
5379
|
+
"accepts app_keys[] for batch read; batch returns {status, apps[], errors[]} where each apps[] item has {app_key, charts}",
|
|
5380
|
+
],
|
|
5381
|
+
"minimal_example": {
|
|
5382
|
+
"profile": "default",
|
|
5383
|
+
"app_key": "APP_KEY",
|
|
5384
|
+
},
|
|
5385
|
+
},
|
|
5386
|
+
"app_get_buttons": {
|
|
5387
|
+
"allowed_keys": ["app_key", "app_keys"],
|
|
5388
|
+
"aliases": {},
|
|
5389
|
+
"allowed_values": {},
|
|
5390
|
+
"execution_notes": [
|
|
5391
|
+
"returns custom button list (draft state) for one app",
|
|
5392
|
+
"also returns view_configs read from view bindings, so app_custom_buttons_apply view_configs can be patched from the same read result",
|
|
5393
|
+
"accepts app_keys[] for batch read; batch returns {status, apps[], errors[]} where each apps[] item preserves the single-read fields such as {app_key, buttons, view_configs, warnings, verification}",
|
|
5394
|
+
"use before app_custom_buttons_apply when you need current button_id values",
|
|
5395
|
+
],
|
|
5396
|
+
"minimal_example": {
|
|
5397
|
+
"profile": "default",
|
|
5398
|
+
"app_key": "APP_KEY",
|
|
5399
|
+
},
|
|
5400
|
+
},
|
|
5401
|
+
"app_get_associated_resources": {
|
|
5402
|
+
"allowed_keys": ["app_key", "app_keys"],
|
|
5403
|
+
"aliases": {},
|
|
5404
|
+
"allowed_values": {},
|
|
5405
|
+
"execution_notes": [
|
|
5406
|
+
"returns associated resource pool (draft state) for one app",
|
|
5407
|
+
"also returns view_configs read from view bindings, so app_associated_resources_apply view_configs can be patched from the same read result",
|
|
5408
|
+
"accepts app_keys[] for batch read; batch returns {status, apps[], errors[]} where each apps[] item preserves the single-read fields such as {app_key, associated_resources, view_configs, warnings, verification}",
|
|
5409
|
+
"use before app_associated_resources_apply when you need current associated_item_id values",
|
|
3293
5410
|
],
|
|
3294
5411
|
"minimal_example": {
|
|
3295
5412
|
"profile": "default",
|
|
@@ -3326,8 +5443,9 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3326
5443
|
},
|
|
3327
5444
|
},
|
|
3328
5445
|
"app_charts_apply": {
|
|
3329
|
-
"allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids", "upsert_charts[].visibility"],
|
|
5446
|
+
"allowed_keys": ["app_key", "apps", "upsert_charts", "patch_charts", "remove_chart_ids", "reorder_chart_ids", "upsert_charts[].visibility", "patch_charts[].chart_id", "patch_charts[].name", "patch_charts[].set", "patch_charts[].unset"],
|
|
3330
5447
|
"aliases": {
|
|
5448
|
+
"patchCharts": "patch_charts",
|
|
3331
5449
|
"chart.id": "chart.chart_id",
|
|
3332
5450
|
"chart.type": "chart.chart_type",
|
|
3333
5451
|
"chart.dimension_fields": "chart.dimension_field_ids",
|
|
@@ -3341,18 +5459,31 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3341
5459
|
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
3342
5460
|
},
|
|
3343
5461
|
"execution_notes": [
|
|
5462
|
+
"this tool manages QingBI report bodies/configs; it does not attach reports to Qingflow app associated-resource display",
|
|
5463
|
+
"app_charts_apply creates/updates app-source QingBI reports only; generated payloads use dataSourceType=qingflow",
|
|
5464
|
+
"dataset BI reports are not created or edited by this tool yet; create them in QingBI first, then attach the existing report with app_associated_resources_apply report_source=dataset",
|
|
5465
|
+
"after creating or updating an app-source report body, use app_associated_resources_apply when the report should appear inside a Qingflow app/view",
|
|
3344
5466
|
"app_charts_apply is immediate-live and does not publish",
|
|
5467
|
+
"use patch_charts for partial parameter replacement on existing charts; the tool reads current chart base/config, merges patch_charts[].set/unset, then submits full QingBI base/config payloads internally",
|
|
3345
5468
|
"chart matching precedence is chart_id first, then exact unique chart name",
|
|
3346
5469
|
"when chart names are not unique, supply chart_id instead of guessing by name",
|
|
3347
5470
|
"successful create results must return a real backend chart_id",
|
|
3348
5471
|
"upsert_charts[].visibility compiles to QingBI base visibleAuth only",
|
|
3349
5472
|
"visibility-only updates keep the existing chart config and do not rewrite rawDataConfigDTO.authInfo",
|
|
5473
|
+
"chart dimension/metric/filter/query fields are resolved from app_get_fields.chart_fields (QingBI datasource fields), not record schema or form-only fields",
|
|
5474
|
+
"system fields such as 申请人/申请时间/编号 are usable only when they appear in chart_fields; otherwise app_charts_apply returns CHART_FIELD_NOT_IN_QINGBI_SCHEMA",
|
|
5475
|
+
"low-frequency chart types have local prevalidation: gauge requires 0 dimensions and 2 non-duplicated metrics; histogram requires at most 1 dimension and exactly 1 plain numeric metric",
|
|
5476
|
+
"chart rule failures return chart_results[].diagnostics with rule_code, expected, actual, offending_fields, and next_action; backend 81002/81005 are translated when possible",
|
|
5477
|
+
"remove_chart_ids deletes by chart_id and verifies each deleted chart with single chart_id readback; pure delete does not read the full chart list",
|
|
5478
|
+
"if delete readback is unavailable or still finds the chart, chart_results[] returns delete_executed=true, readback_status, and safe_to_retry_delete=false; do not blindly repeat delete",
|
|
5479
|
+
"accepts apps[] for multi-app batch; each item is {app_key, upsert_charts?, patch_charts?, remove_chart_ids?, reorder_chart_ids?}",
|
|
3350
5480
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
3351
5481
|
],
|
|
3352
5482
|
"minimal_example": {
|
|
3353
5483
|
"profile": "default",
|
|
3354
5484
|
"app_key": "APP_KEY",
|
|
3355
5485
|
"upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": [], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
|
|
5486
|
+
"patch_charts": [{"chart_id": "CHART_ID", "set": {"name": "数据总量-新版"}}],
|
|
3356
5487
|
"remove_chart_ids": [],
|
|
3357
5488
|
"reorder_chart_ids": [],
|
|
3358
5489
|
},
|
|
@@ -3364,6 +5495,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3364
5495
|
"execution_notes": [
|
|
3365
5496
|
"returns one builder-side view definition detail",
|
|
3366
5497
|
"does not return record data; use user-side view_get or record_list for runtime rows",
|
|
5498
|
+
"view_key is a raw builder view key; if a record-data custom:VIEW_KEY value is passed, the tool normalizes it to VIEW_KEY",
|
|
3367
5499
|
"use this after builder portal_get when a component references a view_ref.view_key",
|
|
3368
5500
|
"returns normalized view visibility when backend auth is readable",
|
|
3369
5501
|
],
|
|
@@ -3388,9 +5520,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3388
5520
|
},
|
|
3389
5521
|
},
|
|
3390
5522
|
"portal_apply": {
|
|
3391
|
-
"allowed_keys": ["dash_key", "dash_name", "package_id", "publish", "sections", "visibility", "auth", "icon", "color", "hide_copyright", "dash_global_config", "config"],
|
|
5523
|
+
"allowed_keys": ["dash_key", "dash_name", "name", "package_id", "publish", "sections", "patch_sections", "pages", "payload", "layout_preset", "visibility", "auth", "icon", "color", "hide_copyright", "dash_global_config", "config"],
|
|
3392
5524
|
"aliases": {
|
|
3393
5525
|
"packageId": "package_id",
|
|
5526
|
+
"name": "dash_name",
|
|
3394
5527
|
"sourceType": "source_type",
|
|
3395
5528
|
"chartRef": "chart_ref",
|
|
3396
5529
|
"viewRef": "view_ref",
|
|
@@ -3403,19 +5536,35 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3403
5536
|
"viewRef": "view_ref",
|
|
3404
5537
|
"dashStyleConfigBO": "dash_style_config",
|
|
3405
5538
|
},
|
|
3406
|
-
"allowed_values": {
|
|
3407
|
-
"
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
"
|
|
5539
|
+
"allowed_values": {
|
|
5540
|
+
"section.source_type": ["chart", "view", "grid", "filter", "text", "link"],
|
|
5541
|
+
"workspace_icon.icon_names": list(WORKSPACE_ICON_NAMES),
|
|
5542
|
+
"workspace_icon.icon_colors": list(WORKSPACE_ICON_COLORS),
|
|
5543
|
+
"workspace_icon.generic_icon_names": list(GENERIC_WORKSPACE_ICON_NAMES),
|
|
5544
|
+
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
5545
|
+
},
|
|
5546
|
+
"execution_notes": [
|
|
5547
|
+
"use exactly one resource mode",
|
|
5548
|
+
"update mode: dash_key",
|
|
5549
|
+
"create mode: package_id + dash_name",
|
|
5550
|
+
"create mode requires explicit icon + color; icon=template is blocked because it is too generic",
|
|
5551
|
+
"edit mode preserves existing icon/color when omitted; explicit icon/color values are still validated",
|
|
5552
|
+
"call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
|
|
5553
|
+
"portal_apply uses replace semantics for sections",
|
|
5554
|
+
"when editing an existing portal, sections may be omitted to update only base info such as visibility, icon, or package",
|
|
5555
|
+
"use patch_sections[] for targeted section updates without replacing all sections; each item needs one selector (chart_ref with chart_id/chart_key/chart_name, view_ref with view_key/view_name, or order as 0-based index) plus set/unset",
|
|
5556
|
+
"when sections[] is supplied without patch_sections[], it uses replace semantics for all sections",
|
|
5557
|
+
"remove a section by omitting it from the sections list (replace mode) or by unset in patch_sections (patch mode)",
|
|
5558
|
+
"package_id is required when creating a new portal",
|
|
5559
|
+
"publish=false only guarantees draft and base-info updates; it does not claim live has changed",
|
|
5560
|
+
"chart_ref resolves by chart_id/chart_key first, then exact unique chart_name",
|
|
3417
5561
|
"view_ref resolves by view_key first, then exact unique view_name",
|
|
5562
|
+
"pc layout uses a 24-column grid; mobile layout uses a 6-column grid",
|
|
5563
|
+
"if unsure about layout, omit position or use layout_preset=auto/dashboard_2col/dashboard_3col",
|
|
5564
|
+
"two-column pc layout should use x=0/12 with cols=12; three-column pc layout should use x=0/8/16 with cols=8",
|
|
5565
|
+
"x=0/6 with cols=6 only occupies the left half of the pc portal and triggers PORTAL_LAYOUT_HALF_WIDTH",
|
|
3418
5566
|
"position.pc/mobile is the canonical portal layout shape",
|
|
5567
|
+
"compat payload accepts name -> dash_name and single pages[0].components -> sections",
|
|
3419
5568
|
"visibility is the canonical public auth shape; auth is kept only as a deprecated compatibility alias",
|
|
3420
5569
|
"passing visibility and auth together is rejected as VISIBILITY_CONFLICT",
|
|
3421
5570
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
@@ -3424,7 +5573,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3424
5573
|
"profile": "default",
|
|
3425
5574
|
"dash_name": "经营门户",
|
|
3426
5575
|
"package_id": 1001,
|
|
5576
|
+
"icon": "view-grid",
|
|
5577
|
+
"color": "blue",
|
|
3427
5578
|
"publish": True,
|
|
5579
|
+
"layout_preset": "dashboard_2col",
|
|
3428
5580
|
"visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
|
|
3429
5581
|
"sections": [
|
|
3430
5582
|
{
|
|
@@ -3438,6 +5590,23 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3438
5590
|
}
|
|
3439
5591
|
],
|
|
3440
5592
|
},
|
|
5593
|
+
"compat_payload_example": {
|
|
5594
|
+
"name": "经营门户",
|
|
5595
|
+
"package_id": 1001,
|
|
5596
|
+
"layout_preset": "dashboard_2col",
|
|
5597
|
+
"pages": [
|
|
5598
|
+
{
|
|
5599
|
+
"title": "经营总览",
|
|
5600
|
+
"components": [
|
|
5601
|
+
{
|
|
5602
|
+
"title": "销售趋势",
|
|
5603
|
+
"source_type": "chart",
|
|
5604
|
+
"chart_ref": {"app_key": "APP_KEY", "chart_id": "CHART_ID"},
|
|
5605
|
+
}
|
|
5606
|
+
],
|
|
5607
|
+
}
|
|
5608
|
+
],
|
|
5609
|
+
},
|
|
3441
5610
|
"minimal_section_example": {
|
|
3442
5611
|
"title": "订单概览",
|
|
3443
5612
|
"source_type": "view",
|
|
@@ -3447,6 +5616,15 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3447
5616
|
"mobile": {"x": 0, "y": 0, "cols": 6, "rows": 8},
|
|
3448
5617
|
},
|
|
3449
5618
|
},
|
|
5619
|
+
"patch_sections_example": {
|
|
5620
|
+
"profile": "default",
|
|
5621
|
+
"dash_key": "DASH_KEY",
|
|
5622
|
+
"publish": True,
|
|
5623
|
+
"patch_sections": [
|
|
5624
|
+
{"chart_ref": {"chart_id": "CHART_ID"}, "set": {"title": "销售总览-新版"}},
|
|
5625
|
+
{"order": 2, "set": {"title": "任务列表-新版"}},
|
|
5626
|
+
],
|
|
5627
|
+
},
|
|
3450
5628
|
},
|
|
3451
5629
|
"app_publish_verify": {
|
|
3452
5630
|
"allowed_keys": ["app_key", "expected_package_id"],
|
|
@@ -3485,7 +5663,6 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
3485
5663
|
_PRIVATE_BUILDER_TOOL_CONTRACTS = {
|
|
3486
5664
|
"app_schema_plan",
|
|
3487
5665
|
"app_layout_plan",
|
|
3488
|
-
"app_flow_plan",
|
|
3489
5666
|
"app_views_plan",
|
|
3490
5667
|
}
|
|
3491
5668
|
|