@josephyan/qingflow-app-builder-mcp 0.2.0-beta.4 → 0.2.0-beta.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +1 -1
- package/pyproject.toml +3 -1
- package/skills/qingflow-app-builder/SKILL.md +154 -22
- package/skills/qingflow-app-builder/references/create-app.md +51 -21
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
- package/skills/qingflow-app-builder/references/gotchas.md +28 -1
- package/skills/qingflow-app-builder/references/solution-playbooks.md +14 -12
- package/skills/qingflow-app-builder/references/tool-selection.md +45 -17
- package/skills/qingflow-app-builder/references/update-flow.md +112 -25
- package/skills/qingflow-app-builder/references/update-layout.md +11 -24
- package/skills/qingflow-app-builder/references/update-schema.md +1 -23
- package/skills/qingflow-app-builder/references/update-views.md +87 -21
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +189 -0
- package/src/qingflow_mcp/builder_facade/models.py +584 -1
- package/src/qingflow_mcp/builder_facade/service.py +4698 -262
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/list_type_labels.py +24 -0
- package/src/qingflow_mcp/server.py +131 -16
- package/src/qingflow_mcp/server_app_builder.py +132 -72
- package/src/qingflow_mcp/server_app_user.py +143 -187
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +14 -4
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
- package/src/qingflow_mcp/solution/executor.py +44 -7
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1567 -144
- package/src/qingflow_mcp/tools/app_tools.py +243 -14
- package/src/qingflow_mcp/tools/approval_tools.py +411 -76
- package/src/qingflow_mcp/tools/directory_tools.py +203 -31
- package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
- package/src/qingflow_mcp/tools/file_tools.py +1 -0
- package/src/qingflow_mcp/tools/import_tools.py +1164 -0
- package/src/qingflow_mcp/tools/portal_tools.py +31 -0
- package/src/qingflow_mcp/tools/record_tools.py +4943 -1025
- package/src/qingflow_mcp/tools/task_context_tools.py +1335 -0
- package/src/qingflow_mcp/tools/task_tools.py +376 -225
- package/src/qingflow_mcp/tools/workflow_tools.py +78 -4
|
@@ -1,31 +1,52 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
|
|
3
7
|
from pydantic import ValidationError
|
|
4
8
|
|
|
5
9
|
from ..config import DEFAULT_PROFILE
|
|
10
|
+
from ..errors import QingflowApiError
|
|
6
11
|
from ..json_types import JSONObject
|
|
7
12
|
from ..builder_facade.models import (
|
|
13
|
+
ChartApplyRequest,
|
|
14
|
+
FIELD_TYPE_ID_ALIASES,
|
|
8
15
|
FieldPatch,
|
|
9
16
|
FieldRemovePatch,
|
|
10
17
|
FieldUpdatePatch,
|
|
18
|
+
FlowPreset,
|
|
11
19
|
FlowNodePatch,
|
|
12
20
|
FlowPlanRequest,
|
|
13
21
|
FlowTransitionPatch,
|
|
14
22
|
LayoutApplyMode,
|
|
15
23
|
LayoutPlanRequest,
|
|
24
|
+
LayoutPreset,
|
|
16
25
|
LayoutSectionPatch,
|
|
26
|
+
PortalApplyRequest,
|
|
27
|
+
PublicFieldType,
|
|
28
|
+
PublicChartType,
|
|
29
|
+
PublicViewType,
|
|
17
30
|
SchemaPlanRequest,
|
|
31
|
+
ViewFilterOperator,
|
|
18
32
|
ViewUpsertPatch,
|
|
33
|
+
ViewsPreset,
|
|
19
34
|
ViewsPlanRequest,
|
|
20
35
|
)
|
|
21
36
|
from ..builder_facade.service import AiBuilderFacade
|
|
22
37
|
from .app_tools import AppTools
|
|
23
38
|
from .base import ToolBase
|
|
39
|
+
from .directory_tools import DirectoryTools
|
|
24
40
|
from .package_tools import PackageTools
|
|
41
|
+
from .portal_tools import PortalTools
|
|
42
|
+
from .qingbi_report_tools import QingbiReportTools
|
|
43
|
+
from .role_tools import RoleTools
|
|
25
44
|
from .solution_tools import SolutionTools
|
|
26
45
|
from .view_tools import ViewTools
|
|
27
46
|
from .workflow_tools import WorkflowTools
|
|
28
47
|
|
|
48
|
+
PUBLIC_STABLE_FLOW_NODE_TYPES = ["start", "approve", "fill", "copy", "webhook", "end"]
|
|
49
|
+
|
|
29
50
|
|
|
30
51
|
class AiBuilderTools(ToolBase):
|
|
31
52
|
def __init__(self, sessions, backend) -> None:
|
|
@@ -35,6 +56,10 @@ class AiBuilderTools(ToolBase):
|
|
|
35
56
|
packages=PackageTools(sessions, backend),
|
|
36
57
|
views=ViewTools(sessions, backend),
|
|
37
58
|
workflows=WorkflowTools(sessions, backend),
|
|
59
|
+
portals=PortalTools(sessions, backend),
|
|
60
|
+
charts=QingbiReportTools(sessions, backend),
|
|
61
|
+
roles=RoleTools(sessions, backend),
|
|
62
|
+
directory=DirectoryTools(sessions, backend),
|
|
38
63
|
solutions=SolutionTools(sessions, backend),
|
|
39
64
|
)
|
|
40
65
|
|
|
@@ -47,6 +72,57 @@ class AiBuilderTools(ToolBase):
|
|
|
47
72
|
def package_resolve(profile: str = DEFAULT_PROFILE, package_name: str = "") -> JSONObject:
|
|
48
73
|
return self.package_resolve(profile=profile, package_name=package_name)
|
|
49
74
|
|
|
75
|
+
@mcp.tool()
|
|
76
|
+
def builder_tool_contract(tool_name: str = "") -> JSONObject:
|
|
77
|
+
return self.builder_tool_contract(tool_name=tool_name)
|
|
78
|
+
|
|
79
|
+
@mcp.tool()
|
|
80
|
+
def package_create(profile: str = DEFAULT_PROFILE, package_name: str = "") -> JSONObject:
|
|
81
|
+
return self.package_create(profile=profile, package_name=package_name)
|
|
82
|
+
|
|
83
|
+
@mcp.tool()
|
|
84
|
+
def member_search(
|
|
85
|
+
profile: str = DEFAULT_PROFILE,
|
|
86
|
+
query: str = "",
|
|
87
|
+
page_num: int = 1,
|
|
88
|
+
page_size: int = 20,
|
|
89
|
+
contain_disable: bool = False,
|
|
90
|
+
) -> JSONObject:
|
|
91
|
+
return self.member_search(
|
|
92
|
+
profile=profile,
|
|
93
|
+
query=query,
|
|
94
|
+
page_num=page_num,
|
|
95
|
+
page_size=page_size,
|
|
96
|
+
contain_disable=contain_disable,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
@mcp.tool()
|
|
100
|
+
def role_search(
|
|
101
|
+
profile: str = DEFAULT_PROFILE,
|
|
102
|
+
keyword: str = "",
|
|
103
|
+
page_num: int = 1,
|
|
104
|
+
page_size: int = 20,
|
|
105
|
+
) -> JSONObject:
|
|
106
|
+
return self.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size)
|
|
107
|
+
|
|
108
|
+
@mcp.tool()
|
|
109
|
+
def role_create(
|
|
110
|
+
profile: str = DEFAULT_PROFILE,
|
|
111
|
+
role_name: str = "",
|
|
112
|
+
member_uids: list[int] | None = None,
|
|
113
|
+
member_emails: list[str] | None = None,
|
|
114
|
+
member_names: list[str] | None = None,
|
|
115
|
+
role_icon: str = "ex-user-outlined",
|
|
116
|
+
) -> JSONObject:
|
|
117
|
+
return self.role_create(
|
|
118
|
+
profile=profile,
|
|
119
|
+
role_name=role_name,
|
|
120
|
+
member_uids=member_uids or [],
|
|
121
|
+
member_emails=member_emails or [],
|
|
122
|
+
member_names=member_names or [],
|
|
123
|
+
role_icon=role_icon,
|
|
124
|
+
)
|
|
125
|
+
|
|
50
126
|
@mcp.tool()
|
|
51
127
|
def package_attach_app(
|
|
52
128
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -56,6 +132,20 @@ class AiBuilderTools(ToolBase):
|
|
|
56
132
|
) -> JSONObject:
|
|
57
133
|
return self.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key, app_title=app_title)
|
|
58
134
|
|
|
135
|
+
@mcp.tool()
|
|
136
|
+
def app_release_edit_lock_if_mine(
|
|
137
|
+
profile: str = DEFAULT_PROFILE,
|
|
138
|
+
app_key: str = "",
|
|
139
|
+
lock_owner_email: str = "",
|
|
140
|
+
lock_owner_name: str = "",
|
|
141
|
+
) -> JSONObject:
|
|
142
|
+
return self.app_release_edit_lock_if_mine(
|
|
143
|
+
profile=profile,
|
|
144
|
+
app_key=app_key,
|
|
145
|
+
lock_owner_email=lock_owner_email,
|
|
146
|
+
lock_owner_name=lock_owner_name,
|
|
147
|
+
)
|
|
148
|
+
|
|
59
149
|
@mcp.tool()
|
|
60
150
|
def app_resolve(
|
|
61
151
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -86,76 +176,16 @@ class AiBuilderTools(ToolBase):
|
|
|
86
176
|
return self.app_read_flow_summary(profile=profile, app_key=app_key)
|
|
87
177
|
|
|
88
178
|
@mcp.tool()
|
|
89
|
-
def
|
|
90
|
-
profile
|
|
91
|
-
app_key: str = "",
|
|
92
|
-
package_tag_id: int | None = None,
|
|
93
|
-
app_name: str = "",
|
|
94
|
-
create_if_missing: bool = False,
|
|
95
|
-
add_fields: list[JSONObject] | None = None,
|
|
96
|
-
update_fields: list[JSONObject] | None = None,
|
|
97
|
-
remove_fields: list[JSONObject] | None = None,
|
|
98
|
-
) -> JSONObject:
|
|
99
|
-
return self.app_schema_plan(
|
|
100
|
-
profile=profile,
|
|
101
|
-
app_key=app_key,
|
|
102
|
-
package_tag_id=package_tag_id,
|
|
103
|
-
app_name=app_name,
|
|
104
|
-
create_if_missing=create_if_missing,
|
|
105
|
-
add_fields=add_fields or [],
|
|
106
|
-
update_fields=update_fields or [],
|
|
107
|
-
remove_fields=remove_fields or [],
|
|
108
|
-
)
|
|
179
|
+
def app_read_charts_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
180
|
+
return self.app_read_charts_summary(profile=profile, app_key=app_key)
|
|
109
181
|
|
|
110
182
|
@mcp.tool()
|
|
111
|
-
def
|
|
183
|
+
def portal_read_summary(
|
|
112
184
|
profile: str = DEFAULT_PROFILE,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
sections: list[JSONObject] | None = None,
|
|
116
|
-
preset: str | None = None,
|
|
185
|
+
dash_key: str = "",
|
|
186
|
+
being_draft: bool = True,
|
|
117
187
|
) -> JSONObject:
|
|
118
|
-
return self.
|
|
119
|
-
profile=profile,
|
|
120
|
-
app_key=app_key,
|
|
121
|
-
mode=mode,
|
|
122
|
-
sections=sections or [],
|
|
123
|
-
preset=preset,
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
@mcp.tool()
|
|
127
|
-
def app_flow_plan(
|
|
128
|
-
profile: str = DEFAULT_PROFILE,
|
|
129
|
-
app_key: str = "",
|
|
130
|
-
mode: str = "replace",
|
|
131
|
-
nodes: list[JSONObject] | None = None,
|
|
132
|
-
transitions: list[JSONObject] | None = None,
|
|
133
|
-
preset: str | None = None,
|
|
134
|
-
) -> JSONObject:
|
|
135
|
-
return self.app_flow_plan(
|
|
136
|
-
profile=profile,
|
|
137
|
-
app_key=app_key,
|
|
138
|
-
mode=mode,
|
|
139
|
-
nodes=nodes or [],
|
|
140
|
-
transitions=transitions or [],
|
|
141
|
-
preset=preset,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
@mcp.tool()
|
|
145
|
-
def app_views_plan(
|
|
146
|
-
profile: str = DEFAULT_PROFILE,
|
|
147
|
-
app_key: str = "",
|
|
148
|
-
upsert_views: list[JSONObject] | None = None,
|
|
149
|
-
remove_views: list[str] | None = None,
|
|
150
|
-
preset: str | None = None,
|
|
151
|
-
) -> JSONObject:
|
|
152
|
-
return self.app_views_plan(
|
|
153
|
-
profile=profile,
|
|
154
|
-
app_key=app_key,
|
|
155
|
-
upsert_views=upsert_views or [],
|
|
156
|
-
remove_views=remove_views or [],
|
|
157
|
-
preset=preset,
|
|
158
|
-
)
|
|
188
|
+
return self.portal_read_summary(profile=profile, dash_key=dash_key, being_draft=being_draft)
|
|
159
189
|
|
|
160
190
|
@mcp.tool()
|
|
161
191
|
def app_schema_apply(
|
|
@@ -227,6 +257,52 @@ class AiBuilderTools(ToolBase):
|
|
|
227
257
|
remove_views=remove_views or [],
|
|
228
258
|
)
|
|
229
259
|
|
|
260
|
+
@mcp.tool()
|
|
261
|
+
def app_charts_apply(
|
|
262
|
+
profile: str = DEFAULT_PROFILE,
|
|
263
|
+
app_key: str = "",
|
|
264
|
+
upsert_charts: list[JSONObject] | None = None,
|
|
265
|
+
remove_chart_ids: list[str] | None = None,
|
|
266
|
+
reorder_chart_ids: list[str] | None = None,
|
|
267
|
+
) -> JSONObject:
|
|
268
|
+
return self.app_charts_apply(
|
|
269
|
+
profile=profile,
|
|
270
|
+
app_key=app_key,
|
|
271
|
+
upsert_charts=upsert_charts or [],
|
|
272
|
+
remove_chart_ids=remove_chart_ids or [],
|
|
273
|
+
reorder_chart_ids=reorder_chart_ids or [],
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
@mcp.tool()
|
|
277
|
+
def portal_apply(
|
|
278
|
+
profile: str = DEFAULT_PROFILE,
|
|
279
|
+
dash_key: str = "",
|
|
280
|
+
dash_name: str = "",
|
|
281
|
+
package_tag_id: int | None = None,
|
|
282
|
+
publish: bool = True,
|
|
283
|
+
sections: list[JSONObject] | None = None,
|
|
284
|
+
auth: JSONObject | None = None,
|
|
285
|
+
icon: str | None = None,
|
|
286
|
+
color: str | None = None,
|
|
287
|
+
hide_copyright: bool | None = None,
|
|
288
|
+
dash_global_config: JSONObject | None = None,
|
|
289
|
+
config: JSONObject | None = None,
|
|
290
|
+
) -> JSONObject:
|
|
291
|
+
return self.portal_apply(
|
|
292
|
+
profile=profile,
|
|
293
|
+
dash_key=dash_key,
|
|
294
|
+
dash_name=dash_name,
|
|
295
|
+
package_tag_id=package_tag_id,
|
|
296
|
+
publish=publish,
|
|
297
|
+
sections=sections or [],
|
|
298
|
+
auth=auth,
|
|
299
|
+
icon=icon,
|
|
300
|
+
color=color,
|
|
301
|
+
hide_copyright=hide_copyright,
|
|
302
|
+
dash_global_config=dash_global_config,
|
|
303
|
+
config=config or {},
|
|
304
|
+
)
|
|
305
|
+
|
|
230
306
|
@mcp.tool()
|
|
231
307
|
def app_publish_verify(
|
|
232
308
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -240,31 +316,269 @@ class AiBuilderTools(ToolBase):
|
|
|
240
316
|
)
|
|
241
317
|
|
|
242
318
|
def package_list(self, *, profile: str, trial_status: str = "all") -> JSONObject:
|
|
243
|
-
|
|
319
|
+
normalized_args = {"trial_status": trial_status}
|
|
320
|
+
return _safe_tool_call(
|
|
321
|
+
lambda: self._facade.package_list(profile=profile, trial_status=trial_status),
|
|
322
|
+
error_code="PACKAGE_LIST_FAILED",
|
|
323
|
+
normalized_args=normalized_args,
|
|
324
|
+
suggested_next_call={"tool_name": "package_list", "arguments": {"profile": profile, "trial_status": trial_status}},
|
|
325
|
+
)
|
|
244
326
|
|
|
245
327
|
def package_resolve(self, *, profile: str, package_name: str) -> JSONObject:
|
|
246
|
-
|
|
328
|
+
normalized_args = {"package_name": package_name}
|
|
329
|
+
return _safe_tool_call(
|
|
330
|
+
lambda: self._facade.package_resolve(profile=profile, package_name=package_name),
|
|
331
|
+
error_code="PACKAGE_RESOLVE_FAILED",
|
|
332
|
+
normalized_args=normalized_args,
|
|
333
|
+
suggested_next_call={"tool_name": "package_resolve", "arguments": {"profile": profile, "package_name": package_name}},
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def builder_tool_contract(self, *, tool_name: str) -> JSONObject:
|
|
337
|
+
requested = str(tool_name or "").strip()
|
|
338
|
+
public_tool_names = sorted(
|
|
339
|
+
name
|
|
340
|
+
for name in _BUILDER_TOOL_CONTRACTS.keys()
|
|
341
|
+
if name not in _PRIVATE_BUILDER_TOOL_CONTRACTS and name not in _BUILDER_TOOL_CONTRACT_ALIASES
|
|
342
|
+
)
|
|
343
|
+
if requested in _PRIVATE_BUILDER_TOOL_CONTRACTS:
|
|
344
|
+
lookup_name = ""
|
|
345
|
+
elif requested in _BUILDER_TOOL_CONTRACTS:
|
|
346
|
+
lookup_name = requested
|
|
347
|
+
else:
|
|
348
|
+
lookup_name = _BUILDER_TOOL_CONTRACT_ALIASES.get(requested, requested)
|
|
349
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(lookup_name)
|
|
350
|
+
if contract is None:
|
|
351
|
+
return {
|
|
352
|
+
"status": "failed",
|
|
353
|
+
"error_code": "TOOL_CONTRACT_NOT_FOUND",
|
|
354
|
+
"recoverable": True,
|
|
355
|
+
"message": "tool contract is not defined for the requested public builder tool",
|
|
356
|
+
"normalized_args": {"tool_name": requested},
|
|
357
|
+
"missing_fields": [],
|
|
358
|
+
"allowed_values": {"tool_name": public_tool_names},
|
|
359
|
+
"details": {"reason_path": "tool_name"},
|
|
360
|
+
"suggested_next_call": None,
|
|
361
|
+
"request_id": None,
|
|
362
|
+
"backend_code": None,
|
|
363
|
+
"http_status": None,
|
|
364
|
+
"noop": False,
|
|
365
|
+
"warnings": [],
|
|
366
|
+
"verification": {},
|
|
367
|
+
"verified": False,
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
"status": "success",
|
|
371
|
+
"error_code": None,
|
|
372
|
+
"recoverable": False,
|
|
373
|
+
"message": "loaded builder tool contract",
|
|
374
|
+
"normalized_args": {"tool_name": requested},
|
|
375
|
+
"missing_fields": [],
|
|
376
|
+
"allowed_values": {},
|
|
377
|
+
"details": {},
|
|
378
|
+
"suggested_next_call": None,
|
|
379
|
+
"request_id": None,
|
|
380
|
+
"backend_code": None,
|
|
381
|
+
"http_status": None,
|
|
382
|
+
"noop": False,
|
|
383
|
+
"warnings": [],
|
|
384
|
+
"verification": {},
|
|
385
|
+
"verified": True,
|
|
386
|
+
"tool_name": requested,
|
|
387
|
+
"contract": contract,
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
def package_create(self, *, profile: str, package_name: str) -> JSONObject:
|
|
391
|
+
normalized_args = {"package_name": package_name}
|
|
392
|
+
return _safe_tool_call(
|
|
393
|
+
lambda: self._facade.package_create(profile=profile, package_name=package_name),
|
|
394
|
+
error_code="PACKAGE_CREATE_FAILED",
|
|
395
|
+
normalized_args=normalized_args,
|
|
396
|
+
suggested_next_call={"tool_name": "package_create", "arguments": {"profile": profile, "package_name": package_name}},
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
def member_search(
|
|
400
|
+
self,
|
|
401
|
+
*,
|
|
402
|
+
profile: str,
|
|
403
|
+
query: str,
|
|
404
|
+
page_num: int = 1,
|
|
405
|
+
page_size: int = 20,
|
|
406
|
+
contain_disable: bool = False,
|
|
407
|
+
) -> JSONObject:
|
|
408
|
+
normalized_args = {
|
|
409
|
+
"query": query,
|
|
410
|
+
"page_num": page_num,
|
|
411
|
+
"page_size": page_size,
|
|
412
|
+
"contain_disable": contain_disable,
|
|
413
|
+
}
|
|
414
|
+
return _safe_tool_call(
|
|
415
|
+
lambda: self._facade.member_search(
|
|
416
|
+
profile=profile,
|
|
417
|
+
query=query,
|
|
418
|
+
page_num=page_num,
|
|
419
|
+
page_size=page_size,
|
|
420
|
+
contain_disable=contain_disable,
|
|
421
|
+
),
|
|
422
|
+
error_code="MEMBER_SEARCH_FAILED",
|
|
423
|
+
normalized_args=normalized_args,
|
|
424
|
+
suggested_next_call={"tool_name": "member_search", "arguments": {"profile": profile, **normalized_args}},
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def role_search(self, *, profile: str, keyword: str, page_num: int = 1, page_size: int = 20) -> JSONObject:
|
|
428
|
+
normalized_args = {"keyword": keyword, "page_num": page_num, "page_size": page_size}
|
|
429
|
+
return _safe_tool_call(
|
|
430
|
+
lambda: self._facade.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size),
|
|
431
|
+
error_code="ROLE_SEARCH_FAILED",
|
|
432
|
+
normalized_args=normalized_args,
|
|
433
|
+
suggested_next_call={"tool_name": "role_search", "arguments": {"profile": profile, **normalized_args}},
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
def role_create(
|
|
437
|
+
self,
|
|
438
|
+
*,
|
|
439
|
+
profile: str,
|
|
440
|
+
role_name: str,
|
|
441
|
+
member_uids: list[int],
|
|
442
|
+
member_emails: list[str],
|
|
443
|
+
member_names: list[str],
|
|
444
|
+
role_icon: str = "ex-user-outlined",
|
|
445
|
+
) -> JSONObject:
|
|
446
|
+
normalized_args = {
|
|
447
|
+
"role_name": role_name,
|
|
448
|
+
"member_uids": member_uids,
|
|
449
|
+
"member_emails": member_emails,
|
|
450
|
+
"member_names": member_names,
|
|
451
|
+
"role_icon": role_icon,
|
|
452
|
+
}
|
|
453
|
+
return _safe_tool_call(
|
|
454
|
+
lambda: self._facade.role_create(
|
|
455
|
+
profile=profile,
|
|
456
|
+
role_name=role_name,
|
|
457
|
+
member_uids=member_uids,
|
|
458
|
+
member_emails=member_emails,
|
|
459
|
+
member_names=member_names,
|
|
460
|
+
role_icon=role_icon,
|
|
461
|
+
),
|
|
462
|
+
error_code="ROLE_CREATE_FAILED",
|
|
463
|
+
normalized_args=normalized_args,
|
|
464
|
+
suggested_next_call={"tool_name": "role_create", "arguments": {"profile": profile, **normalized_args}},
|
|
465
|
+
)
|
|
247
466
|
|
|
248
467
|
def package_attach_app(self, *, profile: str, tag_id: int, app_key: str, app_title: str = "") -> JSONObject:
|
|
249
|
-
|
|
468
|
+
normalized_args = {"tag_id": tag_id, "app_key": app_key, "app_title": app_title}
|
|
469
|
+
result = _safe_tool_call(
|
|
470
|
+
lambda: self._facade.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key, app_title=app_title),
|
|
471
|
+
error_code="PACKAGE_ATTACH_FAILED",
|
|
472
|
+
normalized_args=normalized_args,
|
|
473
|
+
suggested_next_call={"tool_name": "package_attach_app", "arguments": {"profile": profile, **normalized_args}},
|
|
474
|
+
)
|
|
475
|
+
return self._retry_after_self_lock_release(
|
|
476
|
+
profile=profile,
|
|
477
|
+
result=result,
|
|
478
|
+
retry_call=lambda: self._facade.package_attach_app(
|
|
479
|
+
profile=profile,
|
|
480
|
+
tag_id=tag_id,
|
|
481
|
+
app_key=app_key,
|
|
482
|
+
app_title=app_title,
|
|
483
|
+
),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
def app_release_edit_lock_if_mine(
|
|
487
|
+
self,
|
|
488
|
+
*,
|
|
489
|
+
profile: str,
|
|
490
|
+
app_key: str,
|
|
491
|
+
lock_owner_email: str = "",
|
|
492
|
+
lock_owner_name: str = "",
|
|
493
|
+
) -> JSONObject:
|
|
494
|
+
normalized_args = {
|
|
495
|
+
"app_key": app_key,
|
|
496
|
+
"lock_owner_email": lock_owner_email,
|
|
497
|
+
"lock_owner_name": lock_owner_name,
|
|
498
|
+
}
|
|
499
|
+
return _safe_tool_call(
|
|
500
|
+
lambda: self._facade.app_release_edit_lock_if_mine(
|
|
501
|
+
profile=profile,
|
|
502
|
+
app_key=app_key,
|
|
503
|
+
lock_owner_email=lock_owner_email,
|
|
504
|
+
lock_owner_name=lock_owner_name,
|
|
505
|
+
),
|
|
506
|
+
error_code="EDIT_LOCK_RELEASE_FAILED",
|
|
507
|
+
normalized_args=normalized_args,
|
|
508
|
+
suggested_next_call={"tool_name": "app_release_edit_lock_if_mine", "arguments": {"profile": profile, **normalized_args}},
|
|
509
|
+
)
|
|
250
510
|
|
|
251
511
|
def app_resolve(self, *, profile: str, app_key: str = "", app_name: str = "", package_tag_id: int | None = None) -> JSONObject:
|
|
252
|
-
|
|
512
|
+
normalized_args = {"app_key": app_key, "app_name": app_name, "package_tag_id": package_tag_id}
|
|
513
|
+
return _safe_tool_call(
|
|
514
|
+
lambda: self._facade.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_tag_id=package_tag_id),
|
|
515
|
+
error_code="APP_RESOLVE_FAILED",
|
|
516
|
+
normalized_args=normalized_args,
|
|
517
|
+
suggested_next_call={"tool_name": "app_resolve", "arguments": {"profile": profile, **normalized_args}},
|
|
518
|
+
)
|
|
253
519
|
|
|
254
520
|
def app_read_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
255
|
-
|
|
521
|
+
normalized_args = {"app_key": app_key}
|
|
522
|
+
return _safe_tool_call(
|
|
523
|
+
lambda: self._facade.app_read_summary(profile=profile, app_key=app_key),
|
|
524
|
+
error_code="APP_READ_FAILED",
|
|
525
|
+
normalized_args=normalized_args,
|
|
526
|
+
suggested_next_call={"tool_name": "app_read_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
527
|
+
)
|
|
256
528
|
|
|
257
529
|
def app_read_fields(self, *, profile: str, app_key: str) -> JSONObject:
|
|
258
|
-
|
|
530
|
+
normalized_args = {"app_key": app_key}
|
|
531
|
+
return _safe_tool_call(
|
|
532
|
+
lambda: self._facade.app_read_fields(profile=profile, app_key=app_key),
|
|
533
|
+
error_code="FIELDS_READ_FAILED",
|
|
534
|
+
normalized_args=normalized_args,
|
|
535
|
+
suggested_next_call={"tool_name": "app_read_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
536
|
+
)
|
|
259
537
|
|
|
260
538
|
def app_read_layout_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
261
|
-
|
|
539
|
+
normalized_args = {"app_key": app_key}
|
|
540
|
+
return _safe_tool_call(
|
|
541
|
+
lambda: self._facade.app_read_layout_summary(profile=profile, app_key=app_key),
|
|
542
|
+
error_code="LAYOUT_READ_FAILED",
|
|
543
|
+
normalized_args=normalized_args,
|
|
544
|
+
suggested_next_call={"tool_name": "app_read_layout_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
545
|
+
)
|
|
262
546
|
|
|
263
547
|
def app_read_views_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
264
|
-
|
|
548
|
+
normalized_args = {"app_key": app_key}
|
|
549
|
+
return _safe_tool_call(
|
|
550
|
+
lambda: self._facade.app_read_views_summary(profile=profile, app_key=app_key),
|
|
551
|
+
error_code="VIEWS_READ_FAILED",
|
|
552
|
+
normalized_args=normalized_args,
|
|
553
|
+
suggested_next_call={"tool_name": "app_read_views_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
554
|
+
)
|
|
265
555
|
|
|
266
556
|
def app_read_flow_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
267
|
-
|
|
557
|
+
normalized_args = {"app_key": app_key}
|
|
558
|
+
return _safe_tool_call(
|
|
559
|
+
lambda: self._facade.app_read_flow_summary(profile=profile, app_key=app_key),
|
|
560
|
+
error_code="FLOW_READ_FAILED",
|
|
561
|
+
normalized_args=normalized_args,
|
|
562
|
+
suggested_next_call={"tool_name": "app_read_flow_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
def app_read_charts_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
566
|
+
normalized_args = {"app_key": app_key}
|
|
567
|
+
return _safe_tool_call(
|
|
568
|
+
lambda: self._facade.app_read_charts_summary(profile=profile, app_key=app_key),
|
|
569
|
+
error_code="CHARTS_READ_FAILED",
|
|
570
|
+
normalized_args=normalized_args,
|
|
571
|
+
suggested_next_call={"tool_name": "app_read_charts_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
def portal_read_summary(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
|
|
575
|
+
normalized_args = {"dash_key": dash_key, "being_draft": being_draft}
|
|
576
|
+
return _safe_tool_call(
|
|
577
|
+
lambda: self._facade.portal_read_summary(profile=profile, dash_key=dash_key, being_draft=being_draft),
|
|
578
|
+
error_code="PORTAL_READ_FAILED",
|
|
579
|
+
normalized_args=normalized_args,
|
|
580
|
+
suggested_next_call={"tool_name": "portal_read_summary", "arguments": {"profile": profile, **normalized_args}},
|
|
581
|
+
)
|
|
268
582
|
|
|
269
583
|
def app_schema_plan(
|
|
270
584
|
self,
|
|
@@ -293,6 +607,8 @@ class AiBuilderTools(ToolBase):
|
|
|
293
607
|
except ValidationError as exc:
|
|
294
608
|
return _validation_failure(
|
|
295
609
|
str(exc),
|
|
610
|
+
tool_name="app_schema_plan",
|
|
611
|
+
exc=exc,
|
|
296
612
|
suggested_next_call={
|
|
297
613
|
"tool_name": "app_schema_plan",
|
|
298
614
|
"arguments": {
|
|
@@ -307,7 +623,12 @@ class AiBuilderTools(ToolBase):
|
|
|
307
623
|
},
|
|
308
624
|
},
|
|
309
625
|
)
|
|
310
|
-
return
|
|
626
|
+
return _safe_tool_call(
|
|
627
|
+
lambda: self._facade.app_schema_plan(profile=profile, request=request),
|
|
628
|
+
error_code="SCHEMA_PLAN_FAILED",
|
|
629
|
+
normalized_args=request.model_dump(mode="json"),
|
|
630
|
+
suggested_next_call={"tool_name": "app_schema_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
631
|
+
)
|
|
311
632
|
|
|
312
633
|
def app_layout_plan(
|
|
313
634
|
self,
|
|
@@ -330,18 +651,24 @@ class AiBuilderTools(ToolBase):
|
|
|
330
651
|
except ValidationError as exc:
|
|
331
652
|
return _validation_failure(
|
|
332
653
|
str(exc),
|
|
654
|
+
tool_name="app_layout_plan",
|
|
655
|
+
exc=exc,
|
|
333
656
|
suggested_next_call={
|
|
334
657
|
"tool_name": "app_layout_plan",
|
|
335
658
|
"arguments": {
|
|
336
659
|
"profile": profile,
|
|
337
660
|
"app_key": app_key,
|
|
338
661
|
"mode": "merge",
|
|
339
|
-
"
|
|
340
|
-
"sections": [],
|
|
662
|
+
"sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
341
663
|
},
|
|
342
664
|
},
|
|
343
665
|
)
|
|
344
|
-
return
|
|
666
|
+
return _safe_tool_call(
|
|
667
|
+
lambda: self._facade.app_layout_plan(profile=profile, request=request),
|
|
668
|
+
error_code="LAYOUT_PLAN_FAILED",
|
|
669
|
+
normalized_args=request.model_dump(mode="json"),
|
|
670
|
+
suggested_next_call={"tool_name": "app_layout_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
671
|
+
)
|
|
345
672
|
|
|
346
673
|
def app_flow_plan(
|
|
347
674
|
self,
|
|
@@ -366,6 +693,8 @@ class AiBuilderTools(ToolBase):
|
|
|
366
693
|
except ValidationError as exc:
|
|
367
694
|
return _validation_failure(
|
|
368
695
|
str(exc),
|
|
696
|
+
tool_name="app_flow_plan",
|
|
697
|
+
exc=exc,
|
|
369
698
|
suggested_next_call={
|
|
370
699
|
"tool_name": "app_flow_plan",
|
|
371
700
|
"arguments": {
|
|
@@ -378,7 +707,12 @@ class AiBuilderTools(ToolBase):
|
|
|
378
707
|
},
|
|
379
708
|
},
|
|
380
709
|
)
|
|
381
|
-
return
|
|
710
|
+
return _safe_tool_call(
|
|
711
|
+
lambda: self._facade.app_flow_plan(profile=profile, request=request),
|
|
712
|
+
error_code="FLOW_PLAN_FAILED",
|
|
713
|
+
normalized_args=request.model_dump(mode="json"),
|
|
714
|
+
suggested_next_call={"tool_name": "app_flow_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
715
|
+
)
|
|
382
716
|
|
|
383
717
|
def app_views_plan(
|
|
384
718
|
self,
|
|
@@ -401,6 +735,8 @@ class AiBuilderTools(ToolBase):
|
|
|
401
735
|
except ValidationError as exc:
|
|
402
736
|
return _validation_failure(
|
|
403
737
|
str(exc),
|
|
738
|
+
tool_name="app_views_plan",
|
|
739
|
+
exc=exc,
|
|
404
740
|
suggested_next_call={
|
|
405
741
|
"tool_name": "app_views_plan",
|
|
406
742
|
"arguments": {
|
|
@@ -412,7 +748,12 @@ class AiBuilderTools(ToolBase):
|
|
|
412
748
|
},
|
|
413
749
|
},
|
|
414
750
|
)
|
|
415
|
-
return
|
|
751
|
+
return _safe_tool_call(
|
|
752
|
+
lambda: self._facade.app_views_plan(profile=profile, request=request),
|
|
753
|
+
error_code="VIEWS_PLAN_FAILED",
|
|
754
|
+
normalized_args=request.model_dump(mode="json"),
|
|
755
|
+
suggested_next_call={"tool_name": "app_views_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
756
|
+
)
|
|
416
757
|
|
|
417
758
|
def app_schema_apply(
|
|
418
759
|
self,
|
|
@@ -427,75 +768,200 @@ class AiBuilderTools(ToolBase):
|
|
|
427
768
|
add_fields: list[JSONObject],
|
|
428
769
|
update_fields: list[JSONObject],
|
|
429
770
|
remove_fields: list[JSONObject],
|
|
771
|
+
) -> JSONObject:
|
|
772
|
+
result = self._app_schema_apply_once(
|
|
773
|
+
profile=profile,
|
|
774
|
+
app_key=app_key,
|
|
775
|
+
package_tag_id=package_tag_id,
|
|
776
|
+
app_name=app_name,
|
|
777
|
+
app_title=app_title,
|
|
778
|
+
create_if_missing=create_if_missing,
|
|
779
|
+
publish=publish,
|
|
780
|
+
add_fields=add_fields,
|
|
781
|
+
update_fields=update_fields,
|
|
782
|
+
remove_fields=remove_fields,
|
|
783
|
+
)
|
|
784
|
+
return self._retry_after_self_lock_release(
|
|
785
|
+
profile=profile,
|
|
786
|
+
result=result,
|
|
787
|
+
retry_call=lambda: self._app_schema_apply_once(
|
|
788
|
+
profile=profile,
|
|
789
|
+
app_key=app_key,
|
|
790
|
+
package_tag_id=package_tag_id,
|
|
791
|
+
app_name=app_name,
|
|
792
|
+
app_title=app_title,
|
|
793
|
+
create_if_missing=create_if_missing,
|
|
794
|
+
publish=publish,
|
|
795
|
+
add_fields=add_fields,
|
|
796
|
+
update_fields=update_fields,
|
|
797
|
+
remove_fields=remove_fields,
|
|
798
|
+
),
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
def _app_schema_apply_once(
|
|
802
|
+
self,
|
|
803
|
+
*,
|
|
804
|
+
profile: str,
|
|
805
|
+
app_key: str = "",
|
|
806
|
+
package_tag_id: int | None = None,
|
|
807
|
+
app_name: str = "",
|
|
808
|
+
app_title: str = "",
|
|
809
|
+
create_if_missing: bool = False,
|
|
810
|
+
publish: bool = True,
|
|
811
|
+
add_fields: list[JSONObject],
|
|
812
|
+
update_fields: list[JSONObject],
|
|
813
|
+
remove_fields: list[JSONObject],
|
|
430
814
|
) -> JSONObject:
|
|
431
815
|
effective_app_name = app_name or app_title
|
|
816
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
817
|
+
result=self.app_schema_plan(
|
|
818
|
+
profile=profile,
|
|
819
|
+
app_key=app_key,
|
|
820
|
+
package_tag_id=package_tag_id,
|
|
821
|
+
app_name=effective_app_name,
|
|
822
|
+
create_if_missing=create_if_missing,
|
|
823
|
+
add_fields=add_fields,
|
|
824
|
+
update_fields=update_fields,
|
|
825
|
+
remove_fields=remove_fields,
|
|
826
|
+
),
|
|
827
|
+
profile=profile,
|
|
828
|
+
publish=publish,
|
|
829
|
+
plan_tool_name="app_schema_plan",
|
|
830
|
+
apply_tool_name="app_schema_apply",
|
|
831
|
+
)
|
|
832
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
833
|
+
return plan_result
|
|
834
|
+
plan_args = plan_result.get("normalized_args")
|
|
835
|
+
if not isinstance(plan_args, dict):
|
|
836
|
+
plan_args = {}
|
|
432
837
|
try:
|
|
433
|
-
parsed_add = [FieldPatch.model_validate(item) for item in add_fields]
|
|
434
|
-
parsed_update = [FieldUpdatePatch.model_validate(item) for item in update_fields]
|
|
435
|
-
parsed_remove = [FieldRemovePatch.model_validate(item) for item in remove_fields]
|
|
838
|
+
parsed_add = [FieldPatch.model_validate(item) for item in plan_args.get("add_fields") or []]
|
|
839
|
+
parsed_update = [FieldUpdatePatch.model_validate(item) for item in plan_args.get("update_fields") or []]
|
|
840
|
+
parsed_remove = [FieldRemovePatch.model_validate(item) for item in plan_args.get("remove_fields") or []]
|
|
436
841
|
except ValidationError as exc:
|
|
437
842
|
return _validation_failure(
|
|
438
843
|
str(exc),
|
|
844
|
+
tool_name="app_schema_apply",
|
|
845
|
+
exc=exc,
|
|
439
846
|
suggested_next_call={
|
|
440
847
|
"tool_name": "app_schema_apply",
|
|
441
848
|
"arguments": {
|
|
442
849
|
"profile": profile,
|
|
443
|
-
"app_key": app_key,
|
|
444
|
-
"package_tag_id": package_tag_id,
|
|
445
|
-
"app_name": effective_app_name,
|
|
446
|
-
"create_if_missing": create_if_missing,
|
|
447
|
-
"
|
|
448
|
-
"
|
|
449
|
-
"
|
|
850
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
851
|
+
"package_tag_id": plan_args.get("package_tag_id", package_tag_id),
|
|
852
|
+
"app_name": str(plan_args.get("app_name") or effective_app_name),
|
|
853
|
+
"create_if_missing": bool(plan_args.get("create_if_missing", create_if_missing)),
|
|
854
|
+
"publish": publish,
|
|
855
|
+
"add_fields": plan_args.get("add_fields") or [{"name": "字段名称", "type": "text", "required": False}],
|
|
856
|
+
"update_fields": plan_args.get("update_fields") or [],
|
|
857
|
+
"remove_fields": plan_args.get("remove_fields") or [],
|
|
450
858
|
},
|
|
451
859
|
},
|
|
452
860
|
)
|
|
453
|
-
|
|
861
|
+
normalized_args = {
|
|
862
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
863
|
+
"package_tag_id": plan_args.get("package_tag_id", package_tag_id),
|
|
864
|
+
"app_name": str(plan_args.get("app_name") or effective_app_name),
|
|
865
|
+
"create_if_missing": bool(plan_args.get("create_if_missing", create_if_missing)),
|
|
866
|
+
"publish": publish,
|
|
867
|
+
"add_fields": [patch.model_dump(mode="json") for patch in parsed_add],
|
|
868
|
+
"update_fields": [patch.model_dump(mode="json") for patch in parsed_update],
|
|
869
|
+
"remove_fields": [patch.model_dump(mode="json") for patch in parsed_remove],
|
|
870
|
+
}
|
|
871
|
+
result = _safe_tool_call(
|
|
872
|
+
lambda: self._facade.app_schema_apply(
|
|
873
|
+
profile=profile,
|
|
874
|
+
app_key=str(plan_args.get("app_key") or app_key),
|
|
875
|
+
package_tag_id=plan_args.get("package_tag_id", package_tag_id),
|
|
876
|
+
app_name=str(plan_args.get("app_name") or effective_app_name),
|
|
877
|
+
create_if_missing=bool(plan_args.get("create_if_missing", create_if_missing)),
|
|
878
|
+
publish=publish,
|
|
879
|
+
add_fields=parsed_add,
|
|
880
|
+
update_fields=parsed_update,
|
|
881
|
+
remove_fields=parsed_remove,
|
|
882
|
+
),
|
|
883
|
+
error_code="SCHEMA_APPLY_FAILED",
|
|
884
|
+
normalized_args=normalized_args,
|
|
885
|
+
suggested_next_call={"tool_name": "app_schema_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
886
|
+
)
|
|
887
|
+
return result
|
|
888
|
+
|
|
889
|
+
def app_layout_apply(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
890
|
+
result = self._app_layout_apply_once(
|
|
454
891
|
profile=profile,
|
|
455
892
|
app_key=app_key,
|
|
456
|
-
|
|
457
|
-
app_name=effective_app_name,
|
|
458
|
-
create_if_missing=create_if_missing,
|
|
893
|
+
mode=mode,
|
|
459
894
|
publish=publish,
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
895
|
+
sections=sections,
|
|
896
|
+
)
|
|
897
|
+
return self._retry_after_self_lock_release(
|
|
898
|
+
profile=profile,
|
|
899
|
+
result=result,
|
|
900
|
+
retry_call=lambda: self._app_layout_apply_once(
|
|
901
|
+
profile=profile,
|
|
902
|
+
app_key=app_key,
|
|
903
|
+
mode=mode,
|
|
904
|
+
publish=publish,
|
|
905
|
+
sections=sections,
|
|
906
|
+
),
|
|
463
907
|
)
|
|
464
908
|
|
|
465
|
-
def
|
|
909
|
+
def _app_layout_apply_once(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
910
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
911
|
+
result=self.app_layout_plan(
|
|
912
|
+
profile=profile,
|
|
913
|
+
app_key=app_key,
|
|
914
|
+
mode=mode,
|
|
915
|
+
sections=sections,
|
|
916
|
+
preset=None,
|
|
917
|
+
),
|
|
918
|
+
profile=profile,
|
|
919
|
+
publish=publish,
|
|
920
|
+
plan_tool_name="app_layout_plan",
|
|
921
|
+
apply_tool_name="app_layout_apply",
|
|
922
|
+
)
|
|
923
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
924
|
+
return plan_result
|
|
925
|
+
plan_args = plan_result.get("normalized_args")
|
|
926
|
+
if not isinstance(plan_args, dict):
|
|
927
|
+
plan_args = {}
|
|
466
928
|
try:
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
parsed_mode = LayoutApplyMode(normalized_mode)
|
|
471
|
-
parsed_sections = [LayoutSectionPatch.model_validate(item) for item in sections]
|
|
472
|
-
except ValueError:
|
|
473
|
-
return _validation_failure(
|
|
474
|
-
"mode must be one of: merge, replace",
|
|
475
|
-
suggested_next_call={
|
|
476
|
-
"tool_name": "app_layout_apply",
|
|
477
|
-
"arguments": {
|
|
478
|
-
"profile": profile,
|
|
479
|
-
"app_key": app_key,
|
|
480
|
-
"mode": "merge",
|
|
481
|
-
"sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
482
|
-
},
|
|
483
|
-
},
|
|
484
|
-
)
|
|
485
|
-
except ValidationError as exc:
|
|
929
|
+
parsed_mode = LayoutApplyMode(str(plan_args.get("mode") or mode))
|
|
930
|
+
parsed_sections = [LayoutSectionPatch.model_validate(item) for item in plan_args.get("sections") or []]
|
|
931
|
+
except (ValueError, ValidationError) as exc:
|
|
486
932
|
return _validation_failure(
|
|
487
933
|
str(exc),
|
|
934
|
+
tool_name="app_layout_apply",
|
|
935
|
+
exc=exc if isinstance(exc, ValidationError) else None,
|
|
488
936
|
suggested_next_call={
|
|
489
937
|
"tool_name": "app_layout_apply",
|
|
490
938
|
"arguments": {
|
|
491
939
|
"profile": profile,
|
|
492
|
-
"app_key": app_key,
|
|
493
|
-
"mode": "merge",
|
|
494
|
-
"
|
|
940
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
941
|
+
"mode": str(plan_args.get("mode") or "merge"),
|
|
942
|
+
"publish": publish,
|
|
943
|
+
"sections": plan_args.get("sections") or [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
495
944
|
},
|
|
496
945
|
},
|
|
497
946
|
)
|
|
498
|
-
|
|
947
|
+
normalized_args = {
|
|
948
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
949
|
+
"mode": parsed_mode.value,
|
|
950
|
+
"publish": publish,
|
|
951
|
+
"sections": [section.model_dump(mode="json") for section in parsed_sections],
|
|
952
|
+
}
|
|
953
|
+
return _safe_tool_call(
|
|
954
|
+
lambda: self._facade.app_layout_apply(
|
|
955
|
+
profile=profile,
|
|
956
|
+
app_key=str(plan_args.get("app_key") or app_key),
|
|
957
|
+
mode=parsed_mode,
|
|
958
|
+
publish=publish,
|
|
959
|
+
sections=parsed_sections,
|
|
960
|
+
),
|
|
961
|
+
error_code="LAYOUT_APPLY_FAILED",
|
|
962
|
+
normalized_args=normalized_args,
|
|
963
|
+
suggested_next_call={"tool_name": "app_layout_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
964
|
+
)
|
|
499
965
|
|
|
500
966
|
def app_flow_apply(
|
|
501
967
|
self,
|
|
@@ -507,40 +973,133 @@ class AiBuilderTools(ToolBase):
|
|
|
507
973
|
nodes: list[JSONObject],
|
|
508
974
|
transitions: list[JSONObject],
|
|
509
975
|
) -> JSONObject:
|
|
976
|
+
result = self._app_flow_apply_once(
|
|
977
|
+
profile=profile,
|
|
978
|
+
app_key=app_key,
|
|
979
|
+
mode=mode,
|
|
980
|
+
publish=publish,
|
|
981
|
+
nodes=nodes,
|
|
982
|
+
transitions=transitions,
|
|
983
|
+
)
|
|
984
|
+
return self._retry_after_self_lock_release(
|
|
985
|
+
profile=profile,
|
|
986
|
+
result=result,
|
|
987
|
+
retry_call=lambda: self._app_flow_apply_once(
|
|
988
|
+
profile=profile,
|
|
989
|
+
app_key=app_key,
|
|
990
|
+
mode=mode,
|
|
991
|
+
publish=publish,
|
|
992
|
+
nodes=nodes,
|
|
993
|
+
transitions=transitions,
|
|
994
|
+
),
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
def _app_flow_apply_once(
|
|
998
|
+
self,
|
|
999
|
+
*,
|
|
1000
|
+
profile: str,
|
|
1001
|
+
app_key: str,
|
|
1002
|
+
mode: str = "replace",
|
|
1003
|
+
publish: bool = True,
|
|
1004
|
+
nodes: list[JSONObject],
|
|
1005
|
+
transitions: list[JSONObject],
|
|
1006
|
+
) -> JSONObject:
|
|
1007
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
1008
|
+
result=self.app_flow_plan(
|
|
1009
|
+
profile=profile,
|
|
1010
|
+
app_key=app_key,
|
|
1011
|
+
mode=mode,
|
|
1012
|
+
nodes=nodes,
|
|
1013
|
+
transitions=transitions,
|
|
1014
|
+
preset=None,
|
|
1015
|
+
),
|
|
1016
|
+
profile=profile,
|
|
1017
|
+
publish=publish,
|
|
1018
|
+
plan_tool_name="app_flow_plan",
|
|
1019
|
+
apply_tool_name="app_flow_apply",
|
|
1020
|
+
)
|
|
1021
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
1022
|
+
return plan_result
|
|
1023
|
+
plan_args = plan_result.get("normalized_args")
|
|
1024
|
+
if not isinstance(plan_args, dict):
|
|
1025
|
+
plan_args = {}
|
|
510
1026
|
try:
|
|
511
1027
|
request = FlowPlanRequest.model_validate(
|
|
512
1028
|
{
|
|
513
|
-
"app_key": app_key,
|
|
514
|
-
"mode": mode,
|
|
515
|
-
"nodes": nodes,
|
|
516
|
-
"transitions": transitions,
|
|
1029
|
+
"app_key": plan_args.get("app_key") or app_key,
|
|
1030
|
+
"mode": plan_args.get("mode") or mode,
|
|
1031
|
+
"nodes": plan_args.get("nodes") or [],
|
|
1032
|
+
"transitions": plan_args.get("transitions") or [],
|
|
517
1033
|
"preset": None,
|
|
518
1034
|
}
|
|
519
1035
|
)
|
|
520
1036
|
except ValidationError as exc:
|
|
521
1037
|
return _validation_failure(
|
|
522
1038
|
str(exc),
|
|
1039
|
+
tool_name="app_flow_apply",
|
|
1040
|
+
exc=exc,
|
|
523
1041
|
suggested_next_call={
|
|
524
1042
|
"tool_name": "app_flow_apply",
|
|
525
1043
|
"arguments": {
|
|
526
1044
|
"profile": profile,
|
|
527
|
-
"app_key": app_key,
|
|
528
|
-
"mode": "replace",
|
|
529
|
-
"
|
|
530
|
-
"
|
|
1045
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1046
|
+
"mode": str(plan_args.get("mode") or "replace"),
|
|
1047
|
+
"publish": publish,
|
|
1048
|
+
"nodes": plan_args.get("nodes") or [{"id": "start", "type": "start", "name": "发起"}],
|
|
1049
|
+
"transitions": plan_args.get("transitions") or [],
|
|
531
1050
|
},
|
|
532
1051
|
},
|
|
533
1052
|
)
|
|
534
|
-
|
|
1053
|
+
normalized_args = {
|
|
1054
|
+
"app_key": request.app_key,
|
|
1055
|
+
"mode": request.mode,
|
|
1056
|
+
"publish": publish,
|
|
1057
|
+
"nodes": [node.model_dump(mode="json") for node in request.nodes],
|
|
1058
|
+
"transitions": [transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
|
|
1059
|
+
}
|
|
1060
|
+
return _safe_tool_call(
|
|
1061
|
+
lambda: self._facade.app_flow_apply(
|
|
1062
|
+
profile=profile,
|
|
1063
|
+
app_key=request.app_key,
|
|
1064
|
+
mode=request.mode,
|
|
1065
|
+
publish=publish,
|
|
1066
|
+
nodes=[node.model_dump(mode="json") for node in request.nodes],
|
|
1067
|
+
transitions=[transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
|
|
1068
|
+
),
|
|
1069
|
+
error_code="FLOW_APPLY_FAILED",
|
|
1070
|
+
normalized_args=normalized_args,
|
|
1071
|
+
suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
def app_views_apply(
|
|
1075
|
+
self,
|
|
1076
|
+
*,
|
|
1077
|
+
profile: str,
|
|
1078
|
+
app_key: str,
|
|
1079
|
+
publish: bool = True,
|
|
1080
|
+
upsert_views: list[JSONObject],
|
|
1081
|
+
remove_views: list[str],
|
|
1082
|
+
) -> JSONObject:
|
|
1083
|
+
result = self._app_views_apply_once(
|
|
535
1084
|
profile=profile,
|
|
536
|
-
app_key=
|
|
537
|
-
mode=request.mode,
|
|
1085
|
+
app_key=app_key,
|
|
538
1086
|
publish=publish,
|
|
539
|
-
|
|
540
|
-
|
|
1087
|
+
upsert_views=upsert_views,
|
|
1088
|
+
remove_views=remove_views,
|
|
1089
|
+
)
|
|
1090
|
+
return self._retry_after_self_lock_release(
|
|
1091
|
+
profile=profile,
|
|
1092
|
+
result=result,
|
|
1093
|
+
retry_call=lambda: self._app_views_apply_once(
|
|
1094
|
+
profile=profile,
|
|
1095
|
+
app_key=app_key,
|
|
1096
|
+
publish=publish,
|
|
1097
|
+
remove_views=remove_views,
|
|
1098
|
+
upsert_views=upsert_views,
|
|
1099
|
+
),
|
|
541
1100
|
)
|
|
542
1101
|
|
|
543
|
-
def
|
|
1102
|
+
def _app_views_apply_once(
|
|
544
1103
|
self,
|
|
545
1104
|
*,
|
|
546
1105
|
profile: str,
|
|
@@ -549,28 +1108,335 @@ class AiBuilderTools(ToolBase):
|
|
|
549
1108
|
upsert_views: list[JSONObject],
|
|
550
1109
|
remove_views: list[str],
|
|
551
1110
|
) -> JSONObject:
|
|
1111
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
1112
|
+
result=self.app_views_plan(
|
|
1113
|
+
profile=profile,
|
|
1114
|
+
app_key=app_key,
|
|
1115
|
+
upsert_views=upsert_views,
|
|
1116
|
+
remove_views=remove_views,
|
|
1117
|
+
preset=None,
|
|
1118
|
+
),
|
|
1119
|
+
profile=profile,
|
|
1120
|
+
publish=publish,
|
|
1121
|
+
plan_tool_name="app_views_plan",
|
|
1122
|
+
apply_tool_name="app_views_apply",
|
|
1123
|
+
)
|
|
1124
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
1125
|
+
return plan_result
|
|
1126
|
+
plan_args = plan_result.get("normalized_args")
|
|
1127
|
+
if not isinstance(plan_args, dict):
|
|
1128
|
+
plan_args = {}
|
|
552
1129
|
try:
|
|
553
|
-
parsed_views = [ViewUpsertPatch.model_validate(item) for item in upsert_views]
|
|
1130
|
+
parsed_views = [ViewUpsertPatch.model_validate(item) for item in plan_args.get("upsert_views") or []]
|
|
554
1131
|
except ValidationError as exc:
|
|
555
1132
|
return _validation_failure(
|
|
556
1133
|
str(exc),
|
|
1134
|
+
tool_name="app_views_apply",
|
|
1135
|
+
exc=exc,
|
|
557
1136
|
suggested_next_call={
|
|
558
1137
|
"tool_name": "app_views_apply",
|
|
1138
|
+
"arguments": {
|
|
1139
|
+
"profile": profile,
|
|
1140
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1141
|
+
"publish": publish,
|
|
1142
|
+
"upsert_views": plan_args.get("upsert_views") or [{"name": "全部数据", "type": "table", "columns": ["字段A"]}],
|
|
1143
|
+
"remove_views": plan_args.get("remove_views") or [],
|
|
1144
|
+
},
|
|
1145
|
+
},
|
|
1146
|
+
)
|
|
1147
|
+
normalized_args = {
|
|
1148
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1149
|
+
"publish": publish,
|
|
1150
|
+
"upsert_views": [view.model_dump(mode="json") for view in parsed_views],
|
|
1151
|
+
"remove_views": list(plan_args.get("remove_views") or remove_views),
|
|
1152
|
+
}
|
|
1153
|
+
return _safe_tool_call(
|
|
1154
|
+
lambda: self._facade.app_views_apply(
|
|
1155
|
+
profile=profile,
|
|
1156
|
+
app_key=str(plan_args.get("app_key") or app_key),
|
|
1157
|
+
publish=publish,
|
|
1158
|
+
upsert_views=parsed_views,
|
|
1159
|
+
remove_views=list(plan_args.get("remove_views") or remove_views),
|
|
1160
|
+
),
|
|
1161
|
+
error_code="VIEWS_APPLY_FAILED",
|
|
1162
|
+
normalized_args=normalized_args,
|
|
1163
|
+
suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1164
|
+
)
|
|
1165
|
+
|
|
1166
|
+
def chart_apply(
|
|
1167
|
+
self,
|
|
1168
|
+
*,
|
|
1169
|
+
profile: str,
|
|
1170
|
+
app_key: str,
|
|
1171
|
+
upsert_charts: list[JSONObject],
|
|
1172
|
+
remove_chart_ids: list[str],
|
|
1173
|
+
reorder_chart_ids: list[str],
|
|
1174
|
+
) -> JSONObject:
|
|
1175
|
+
return self.app_charts_apply(
|
|
1176
|
+
profile=profile,
|
|
1177
|
+
app_key=app_key,
|
|
1178
|
+
upsert_charts=upsert_charts,
|
|
1179
|
+
remove_chart_ids=remove_chart_ids,
|
|
1180
|
+
reorder_chart_ids=reorder_chart_ids,
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1183
|
+
def app_charts_apply(
|
|
1184
|
+
self,
|
|
1185
|
+
*,
|
|
1186
|
+
profile: str,
|
|
1187
|
+
app_key: str,
|
|
1188
|
+
upsert_charts: list[JSONObject],
|
|
1189
|
+
remove_chart_ids: list[str],
|
|
1190
|
+
reorder_chart_ids: list[str],
|
|
1191
|
+
) -> JSONObject:
|
|
1192
|
+
try:
|
|
1193
|
+
request = ChartApplyRequest.model_validate(
|
|
1194
|
+
{
|
|
1195
|
+
"app_key": app_key,
|
|
1196
|
+
"upsert_charts": upsert_charts or [],
|
|
1197
|
+
"remove_chart_ids": remove_chart_ids or [],
|
|
1198
|
+
"reorder_chart_ids": reorder_chart_ids or [],
|
|
1199
|
+
}
|
|
1200
|
+
)
|
|
1201
|
+
except ValidationError as exc:
|
|
1202
|
+
return _validation_failure(
|
|
1203
|
+
str(exc),
|
|
1204
|
+
tool_name="app_charts_apply",
|
|
1205
|
+
exc=exc,
|
|
1206
|
+
suggested_next_call={
|
|
1207
|
+
"tool_name": "app_charts_apply",
|
|
559
1208
|
"arguments": {
|
|
560
1209
|
"profile": profile,
|
|
561
1210
|
"app_key": app_key,
|
|
562
|
-
"
|
|
563
|
-
"
|
|
1211
|
+
"upsert_charts": [{"name": "销售总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
1212
|
+
"remove_chart_ids": [],
|
|
1213
|
+
"reorder_chart_ids": [],
|
|
564
1214
|
},
|
|
565
1215
|
},
|
|
566
1216
|
)
|
|
567
|
-
|
|
1217
|
+
normalized_args = request.model_dump(mode="json")
|
|
1218
|
+
return _safe_tool_call(
|
|
1219
|
+
lambda: self._facade.chart_apply(profile=profile, request=request),
|
|
1220
|
+
error_code="CHART_APPLY_FAILED",
|
|
1221
|
+
normalized_args=normalized_args,
|
|
1222
|
+
suggested_next_call={"tool_name": "app_charts_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1223
|
+
)
|
|
1224
|
+
|
|
1225
|
+
def portal_apply(
|
|
1226
|
+
self,
|
|
1227
|
+
*,
|
|
1228
|
+
profile: str,
|
|
1229
|
+
dash_key: str = "",
|
|
1230
|
+
dash_name: str = "",
|
|
1231
|
+
package_tag_id: int | None = None,
|
|
1232
|
+
publish: bool = True,
|
|
1233
|
+
sections: list[JSONObject],
|
|
1234
|
+
auth: JSONObject | None = None,
|
|
1235
|
+
icon: str | None = None,
|
|
1236
|
+
color: str | None = None,
|
|
1237
|
+
hide_copyright: bool | None = None,
|
|
1238
|
+
dash_global_config: JSONObject | None = None,
|
|
1239
|
+
config: JSONObject | None = None,
|
|
1240
|
+
) -> JSONObject:
|
|
1241
|
+
try:
|
|
1242
|
+
request = PortalApplyRequest.model_validate(
|
|
1243
|
+
{
|
|
1244
|
+
"dash_key": dash_key or None,
|
|
1245
|
+
"dash_name": dash_name or None,
|
|
1246
|
+
"package_tag_id": package_tag_id,
|
|
1247
|
+
"publish": publish,
|
|
1248
|
+
"sections": sections or [],
|
|
1249
|
+
"auth": auth,
|
|
1250
|
+
"icon": icon,
|
|
1251
|
+
"color": color,
|
|
1252
|
+
"hide_copyright": hide_copyright,
|
|
1253
|
+
"dash_global_config": dash_global_config,
|
|
1254
|
+
"config": config or {},
|
|
1255
|
+
}
|
|
1256
|
+
)
|
|
1257
|
+
except ValidationError as exc:
|
|
1258
|
+
return _validation_failure(
|
|
1259
|
+
str(exc),
|
|
1260
|
+
tool_name="portal_apply",
|
|
1261
|
+
exc=exc,
|
|
1262
|
+
suggested_next_call={
|
|
1263
|
+
"tool_name": "portal_apply",
|
|
1264
|
+
"arguments": {
|
|
1265
|
+
"profile": profile,
|
|
1266
|
+
"dash_name": dash_name or "业务门户",
|
|
1267
|
+
"package_tag_id": package_tag_id or 1001,
|
|
1268
|
+
"publish": True,
|
|
1269
|
+
"sections": [
|
|
1270
|
+
{
|
|
1271
|
+
"title": "经营概览",
|
|
1272
|
+
"source_type": "text",
|
|
1273
|
+
"text": "欢迎使用业务门户",
|
|
1274
|
+
}
|
|
1275
|
+
],
|
|
1276
|
+
},
|
|
1277
|
+
},
|
|
1278
|
+
)
|
|
1279
|
+
normalized_args = request.model_dump(mode="json")
|
|
1280
|
+
return _safe_tool_call(
|
|
1281
|
+
lambda: self._facade.portal_apply(profile=profile, request=request),
|
|
1282
|
+
error_code="PORTAL_APPLY_FAILED",
|
|
1283
|
+
normalized_args=normalized_args,
|
|
1284
|
+
suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1285
|
+
)
|
|
568
1286
|
|
|
569
1287
|
def app_publish_verify(self, *, profile: str, app_key: str, expected_package_tag_id: int | None = None) -> JSONObject:
|
|
570
|
-
|
|
1288
|
+
normalized_args = {"app_key": app_key, "expected_package_tag_id": expected_package_tag_id}
|
|
1289
|
+
result = _safe_tool_call(
|
|
1290
|
+
lambda: self._facade.app_publish_verify(profile=profile, app_key=app_key, expected_package_tag_id=expected_package_tag_id),
|
|
1291
|
+
error_code="PUBLISH_VERIFY_FAILED",
|
|
1292
|
+
normalized_args=normalized_args,
|
|
1293
|
+
suggested_next_call={"tool_name": "app_publish_verify", "arguments": {"profile": profile, **normalized_args}},
|
|
1294
|
+
)
|
|
1295
|
+
return self._retry_after_self_lock_release(
|
|
1296
|
+
profile=profile,
|
|
1297
|
+
result=result,
|
|
1298
|
+
retry_call=lambda: self._facade.app_publish_verify(
|
|
1299
|
+
profile=profile,
|
|
1300
|
+
app_key=app_key,
|
|
1301
|
+
expected_package_tag_id=expected_package_tag_id,
|
|
1302
|
+
),
|
|
1303
|
+
)
|
|
571
1304
|
|
|
1305
|
+
def _retry_after_self_lock_release(self, *, profile: str, result: JSONObject, retry_call) -> JSONObject:
|
|
1306
|
+
if not isinstance(result, dict) or result.get("status") != "failed" or result.get("error_code") != "APP_EDIT_LOCKED":
|
|
1307
|
+
return result
|
|
1308
|
+
suggested = result.get("suggested_next_call")
|
|
1309
|
+
if not isinstance(suggested, dict) or suggested.get("tool_name") != "app_release_edit_lock_if_mine":
|
|
1310
|
+
return result
|
|
1311
|
+
arguments = suggested.get("arguments")
|
|
1312
|
+
if not isinstance(arguments, dict):
|
|
1313
|
+
return result
|
|
1314
|
+
app_key = str(arguments.get("app_key") or "")
|
|
1315
|
+
lock_owner_email = str(arguments.get("lock_owner_email") or "")
|
|
1316
|
+
lock_owner_name = str(arguments.get("lock_owner_name") or "")
|
|
1317
|
+
release_attempts: list[JSONObject] = []
|
|
1318
|
+
retried: JSONObject = result
|
|
1319
|
+
for _ in range(3):
|
|
1320
|
+
release_result = self.app_release_edit_lock_if_mine(
|
|
1321
|
+
profile=profile,
|
|
1322
|
+
app_key=app_key,
|
|
1323
|
+
lock_owner_email=lock_owner_email,
|
|
1324
|
+
lock_owner_name=lock_owner_name,
|
|
1325
|
+
)
|
|
1326
|
+
release_attempts.append(release_result)
|
|
1327
|
+
if not isinstance(release_result, dict) or release_result.get("status") != "success":
|
|
1328
|
+
result.setdefault("details", {})
|
|
1329
|
+
if isinstance(result["details"], dict):
|
|
1330
|
+
result["details"]["edit_lock_release_result"] = release_result
|
|
1331
|
+
result["details"]["edit_lock_release_attempts"] = release_attempts
|
|
1332
|
+
return result
|
|
1333
|
+
retried = retry_call()
|
|
1334
|
+
if not (
|
|
1335
|
+
isinstance(retried, dict)
|
|
1336
|
+
and retried.get("status") == "failed"
|
|
1337
|
+
and retried.get("error_code") == "APP_EDIT_LOCKED"
|
|
1338
|
+
):
|
|
1339
|
+
break
|
|
1340
|
+
time.sleep(0.2)
|
|
1341
|
+
if (
|
|
1342
|
+
isinstance(retried, dict)
|
|
1343
|
+
and retried.get("status") == "failed"
|
|
1344
|
+
and retried.get("error_code") == "APP_EDIT_LOCKED"
|
|
1345
|
+
):
|
|
1346
|
+
retried = {
|
|
1347
|
+
**retried,
|
|
1348
|
+
"error_code": "PERSISTENT_SELF_LOCK",
|
|
1349
|
+
"message": "app remains locked by the current user's active editor session after repeated forced release attempts",
|
|
1350
|
+
"recoverable": True,
|
|
1351
|
+
"suggested_next_call": None,
|
|
1352
|
+
}
|
|
1353
|
+
if isinstance(retried, dict):
|
|
1354
|
+
retried.setdefault("details", {})
|
|
1355
|
+
if isinstance(retried["details"], dict):
|
|
1356
|
+
retried["details"]["edit_lock_release_result"] = release_attempts[-1] if release_attempts else None
|
|
1357
|
+
retried["details"]["edit_lock_release_attempts"] = release_attempts
|
|
1358
|
+
retried["edit_lock_released"] = bool(release_attempts)
|
|
1359
|
+
retried["retried_after_edit_lock_release"] = True
|
|
1360
|
+
return retried
|
|
572
1361
|
|
|
573
|
-
def
|
|
1362
|
+
def _rewrite_plan_result_for_apply(
|
|
1363
|
+
self,
|
|
1364
|
+
*,
|
|
1365
|
+
result: JSONObject,
|
|
1366
|
+
profile: str,
|
|
1367
|
+
publish: bool,
|
|
1368
|
+
plan_tool_name: str,
|
|
1369
|
+
apply_tool_name: str,
|
|
1370
|
+
) -> JSONObject:
|
|
1371
|
+
if not isinstance(result, dict):
|
|
1372
|
+
return result
|
|
1373
|
+
rewritten = dict(result)
|
|
1374
|
+
if rewritten.get("error_code") == "VALIDATION_ERROR":
|
|
1375
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(apply_tool_name)
|
|
1376
|
+
details = rewritten.get("details")
|
|
1377
|
+
if not isinstance(details, dict):
|
|
1378
|
+
details = {}
|
|
1379
|
+
rewritten["details"] = details
|
|
1380
|
+
if isinstance(contract, dict):
|
|
1381
|
+
rewritten["allowed_values"] = deepcopy(contract.get("allowed_values", {}))
|
|
1382
|
+
details["allowed_keys"] = deepcopy(contract.get("allowed_keys", []))
|
|
1383
|
+
details["section_allowed_keys"] = deepcopy(contract.get("section_allowed_keys", []))
|
|
1384
|
+
details["section_aliases"] = deepcopy(contract.get("section_aliases", {}))
|
|
1385
|
+
details["minimal_section_example"] = deepcopy(contract.get("minimal_section_example"))
|
|
1386
|
+
suggested_next_call = rewritten.get("suggested_next_call")
|
|
1387
|
+
if isinstance(suggested_next_call, dict):
|
|
1388
|
+
if suggested_next_call.get("tool_name") == plan_tool_name:
|
|
1389
|
+
arguments = suggested_next_call.get("arguments")
|
|
1390
|
+
normalized_arguments = dict(arguments) if isinstance(arguments, dict) else {}
|
|
1391
|
+
normalized_arguments.setdefault("profile", profile)
|
|
1392
|
+
normalized_arguments["publish"] = publish
|
|
1393
|
+
rewritten["suggested_next_call"] = {
|
|
1394
|
+
**suggested_next_call,
|
|
1395
|
+
"tool_name": apply_tool_name,
|
|
1396
|
+
"arguments": normalized_arguments,
|
|
1397
|
+
}
|
|
1398
|
+
return rewritten
|
|
1399
|
+
if rewritten.get("error_code") == "VALIDATION_ERROR" and suggested_next_call.get("tool_name") == apply_tool_name:
|
|
1400
|
+
arguments = suggested_next_call.get("arguments")
|
|
1401
|
+
normalized_arguments = dict(arguments) if isinstance(arguments, dict) else {}
|
|
1402
|
+
normalized_arguments.setdefault("profile", profile)
|
|
1403
|
+
normalized_arguments["publish"] = publish
|
|
1404
|
+
if isinstance(details, dict):
|
|
1405
|
+
details["canonical_arguments"] = normalized_arguments
|
|
1406
|
+
rewritten["suggested_next_call"] = {
|
|
1407
|
+
**suggested_next_call,
|
|
1408
|
+
"arguments": normalized_arguments,
|
|
1409
|
+
}
|
|
1410
|
+
if rewritten.get("status") == "success":
|
|
1411
|
+
normalized_args = rewritten.get("normalized_args")
|
|
1412
|
+
if isinstance(normalized_args, dict):
|
|
1413
|
+
rewritten["suggested_next_call"] = {
|
|
1414
|
+
"tool_name": apply_tool_name,
|
|
1415
|
+
"arguments": {"profile": profile, **normalized_args, "publish": publish},
|
|
1416
|
+
}
|
|
1417
|
+
return rewritten
|
|
1418
|
+
|
|
1419
|
+
|
|
1420
|
+
def _validation_failure(
|
|
1421
|
+
detail: str,
|
|
1422
|
+
*,
|
|
1423
|
+
tool_name: str | None = None,
|
|
1424
|
+
exc: ValidationError | None = None,
|
|
1425
|
+
suggested_next_call: JSONObject | None = None,
|
|
1426
|
+
) -> JSONObject:
|
|
1427
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
|
|
1428
|
+
reason_path = None
|
|
1429
|
+
if exc is not None:
|
|
1430
|
+
errors = exc.errors()
|
|
1431
|
+
if errors:
|
|
1432
|
+
loc = errors[0].get("loc")
|
|
1433
|
+
if isinstance(loc, (tuple, list)):
|
|
1434
|
+
reason_path = ".".join(str(part) for part in loc)
|
|
1435
|
+
canonical_arguments = None
|
|
1436
|
+
if isinstance(suggested_next_call, dict):
|
|
1437
|
+
arguments = suggested_next_call.get("arguments")
|
|
1438
|
+
if isinstance(arguments, dict):
|
|
1439
|
+
canonical_arguments = arguments
|
|
574
1440
|
return {
|
|
575
1441
|
"status": "failed",
|
|
576
1442
|
"error_code": "VALIDATION_ERROR",
|
|
@@ -578,8 +1444,16 @@ def _validation_failure(detail: str, *, suggested_next_call: JSONObject | None =
|
|
|
578
1444
|
"message": detail,
|
|
579
1445
|
"normalized_args": {},
|
|
580
1446
|
"missing_fields": [],
|
|
581
|
-
"allowed_values": {},
|
|
582
|
-
"details": {
|
|
1447
|
+
"allowed_values": deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {},
|
|
1448
|
+
"details": {
|
|
1449
|
+
"validation_detail": detail,
|
|
1450
|
+
"reason_path": reason_path,
|
|
1451
|
+
"allowed_keys": deepcopy(contract.get("allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
1452
|
+
"canonical_arguments": canonical_arguments,
|
|
1453
|
+
"section_allowed_keys": deepcopy(contract.get("section_allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
1454
|
+
"section_aliases": deepcopy(contract.get("section_aliases", {})) if isinstance(contract, dict) else {},
|
|
1455
|
+
"minimal_section_example": deepcopy(contract.get("minimal_section_example")) if isinstance(contract, dict) else None,
|
|
1456
|
+
},
|
|
583
1457
|
"suggested_next_call": suggested_next_call,
|
|
584
1458
|
"request_id": None,
|
|
585
1459
|
"backend_code": None,
|
|
@@ -587,3 +1461,552 @@ def _validation_failure(detail: str, *, suggested_next_call: JSONObject | None =
|
|
|
587
1461
|
"noop": False,
|
|
588
1462
|
"verification": {},
|
|
589
1463
|
}
|
|
1464
|
+
|
|
1465
|
+
|
|
1466
|
+
def _safe_tool_call(
|
|
1467
|
+
call,
|
|
1468
|
+
*,
|
|
1469
|
+
error_code: str,
|
|
1470
|
+
normalized_args: JSONObject,
|
|
1471
|
+
suggested_next_call: JSONObject | None,
|
|
1472
|
+
) -> JSONObject:
|
|
1473
|
+
try:
|
|
1474
|
+
return call()
|
|
1475
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
1476
|
+
api_error = _coerce_api_error(error)
|
|
1477
|
+
public_http_status = None if api_error.http_status == 404 else api_error.http_status
|
|
1478
|
+
return {
|
|
1479
|
+
"status": "failed",
|
|
1480
|
+
"error_code": error_code,
|
|
1481
|
+
"recoverable": True,
|
|
1482
|
+
"message": _public_error_message(error_code, api_error),
|
|
1483
|
+
"normalized_args": normalized_args,
|
|
1484
|
+
"missing_fields": [],
|
|
1485
|
+
"allowed_values": {},
|
|
1486
|
+
"details": {
|
|
1487
|
+
"transport_error": {
|
|
1488
|
+
"http_status": api_error.http_status,
|
|
1489
|
+
"backend_code": api_error.backend_code,
|
|
1490
|
+
"category": api_error.category,
|
|
1491
|
+
}
|
|
1492
|
+
},
|
|
1493
|
+
"suggested_next_call": suggested_next_call,
|
|
1494
|
+
"request_id": api_error.request_id,
|
|
1495
|
+
"backend_code": api_error.backend_code,
|
|
1496
|
+
"http_status": public_http_status,
|
|
1497
|
+
"noop": False,
|
|
1498
|
+
"verification": {},
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
def _coerce_api_error(error: Exception) -> QingflowApiError:
|
|
1503
|
+
if isinstance(error, QingflowApiError):
|
|
1504
|
+
return error
|
|
1505
|
+
if isinstance(error, RuntimeError):
|
|
1506
|
+
try:
|
|
1507
|
+
payload = json.loads(str(error))
|
|
1508
|
+
except json.JSONDecodeError:
|
|
1509
|
+
payload = None
|
|
1510
|
+
if isinstance(payload, dict) and payload.get("category") and payload.get("message"):
|
|
1511
|
+
details = payload.get("details")
|
|
1512
|
+
return QingflowApiError(
|
|
1513
|
+
category=str(payload.get("category")),
|
|
1514
|
+
message=str(payload.get("message")),
|
|
1515
|
+
backend_code=payload.get("backend_code"),
|
|
1516
|
+
request_id=payload.get("request_id"),
|
|
1517
|
+
http_status=payload.get("http_status"),
|
|
1518
|
+
details=details if isinstance(details, dict) else None,
|
|
1519
|
+
)
|
|
1520
|
+
return QingflowApiError(category="runtime", message=str(error))
|
|
1521
|
+
|
|
1522
|
+
|
|
1523
|
+
def _public_error_message(error_code: str, error: QingflowApiError) -> str:
|
|
1524
|
+
if error.backend_code == 40074 or error_code == "APP_EDIT_LOCKED":
|
|
1525
|
+
return "app is currently locked by another active editor session"
|
|
1526
|
+
if error.http_status != 404:
|
|
1527
|
+
return error.message
|
|
1528
|
+
mapping = {
|
|
1529
|
+
"PACKAGE_LIST_FAILED": "package list is unavailable in the current route",
|
|
1530
|
+
"PACKAGE_RESOLVE_FAILED": "package resolution is unavailable in the current route",
|
|
1531
|
+
"PACKAGE_ATTACH_FAILED": "package attachment could not be verified in the current route",
|
|
1532
|
+
"APP_RESOLVE_FAILED": "app resolution is unavailable in the current route",
|
|
1533
|
+
"APP_READ_FAILED": "app base or schema is unavailable in the current route",
|
|
1534
|
+
"FIELDS_READ_FAILED": "app fields are unavailable in the current route",
|
|
1535
|
+
"LAYOUT_READ_FAILED": "layout resource is unavailable for this app in the current route",
|
|
1536
|
+
"VIEWS_READ_FAILED": "views resource is unavailable for this app in the current route",
|
|
1537
|
+
"FLOW_READ_FAILED": "workflow resource is unavailable for this app in the current route",
|
|
1538
|
+
"SCHEMA_PLAN_FAILED": "schema planning could not load the required app state in the current route",
|
|
1539
|
+
"LAYOUT_PLAN_FAILED": "layout planning could not load the required app state in the current route",
|
|
1540
|
+
"FLOW_PLAN_FAILED": "flow planning could not load the required app state in the current route",
|
|
1541
|
+
"VIEWS_PLAN_FAILED": "views planning could not load the required app state in the current route",
|
|
1542
|
+
"SCHEMA_APPLY_FAILED": "schema apply could not complete because the app route or readback is unavailable",
|
|
1543
|
+
"LAYOUT_APPLY_FAILED": "layout apply could not complete because the layout route or readback is unavailable",
|
|
1544
|
+
"FLOW_APPLY_FAILED": "flow apply could not complete because the workflow route or readback is unavailable",
|
|
1545
|
+
"VIEWS_APPLY_FAILED": "views apply could not complete because the views route or readback is unavailable",
|
|
1546
|
+
"CHART_APPLY_FAILED": "chart apply could not complete because the QingBI route or readback is unavailable",
|
|
1547
|
+
"PORTAL_APPLY_FAILED": "portal apply could not complete because the portal route or readback is unavailable",
|
|
1548
|
+
"PUBLISH_VERIFY_FAILED": "publish verification is unavailable in the current route",
|
|
1549
|
+
}
|
|
1550
|
+
return mapping.get(error_code, "requested builder resource is unavailable in the current route")
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
_BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
1554
|
+
"member_search": {
|
|
1555
|
+
"allowed_keys": ["query", "page_num", "page_size", "contain_disable"],
|
|
1556
|
+
"aliases": {},
|
|
1557
|
+
"allowed_values": {},
|
|
1558
|
+
"minimal_example": {
|
|
1559
|
+
"profile": "default",
|
|
1560
|
+
"query": "严琪东",
|
|
1561
|
+
"page_num": 1,
|
|
1562
|
+
"page_size": 20,
|
|
1563
|
+
"contain_disable": False,
|
|
1564
|
+
},
|
|
1565
|
+
},
|
|
1566
|
+
"role_search": {
|
|
1567
|
+
"allowed_keys": ["keyword", "page_num", "page_size"],
|
|
1568
|
+
"aliases": {},
|
|
1569
|
+
"allowed_values": {},
|
|
1570
|
+
"minimal_example": {
|
|
1571
|
+
"profile": "default",
|
|
1572
|
+
"keyword": "项目经理",
|
|
1573
|
+
"page_num": 1,
|
|
1574
|
+
"page_size": 20,
|
|
1575
|
+
},
|
|
1576
|
+
},
|
|
1577
|
+
"role_create": {
|
|
1578
|
+
"allowed_keys": ["role_name", "member_uids", "member_emails", "member_names", "role_icon"],
|
|
1579
|
+
"aliases": {},
|
|
1580
|
+
"allowed_values": {},
|
|
1581
|
+
"minimal_example": {
|
|
1582
|
+
"profile": "default",
|
|
1583
|
+
"role_name": "研发负责人",
|
|
1584
|
+
"member_names": ["严琪东"],
|
|
1585
|
+
"member_uids": [],
|
|
1586
|
+
"member_emails": [],
|
|
1587
|
+
"role_icon": "ex-user-outlined",
|
|
1588
|
+
},
|
|
1589
|
+
},
|
|
1590
|
+
"app_schema_plan": {
|
|
1591
|
+
"allowed_keys": ["app_key", "package_tag_id", "app_name", "create_if_missing", "add_fields", "update_fields", "remove_fields"],
|
|
1592
|
+
"aliases": {
|
|
1593
|
+
"app_title": "app_name",
|
|
1594
|
+
"title": "app_name",
|
|
1595
|
+
"field.title": "field.name",
|
|
1596
|
+
"field.label": "field.name",
|
|
1597
|
+
"field.fields": "field.subfields",
|
|
1598
|
+
"field.type_id": "field.type",
|
|
1599
|
+
},
|
|
1600
|
+
"allowed_values": {
|
|
1601
|
+
"field.type": [member.value for member in PublicFieldType],
|
|
1602
|
+
"field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
|
|
1603
|
+
},
|
|
1604
|
+
"minimal_example": {
|
|
1605
|
+
"profile": "default",
|
|
1606
|
+
"app_name": "研发项目管理",
|
|
1607
|
+
"package_tag_id": 1001,
|
|
1608
|
+
"create_if_missing": True,
|
|
1609
|
+
"add_fields": [{"name": "项目名称", "type": "text"}],
|
|
1610
|
+
"update_fields": [],
|
|
1611
|
+
"remove_fields": [],
|
|
1612
|
+
},
|
|
1613
|
+
},
|
|
1614
|
+
"app_schema_apply": {
|
|
1615
|
+
"allowed_keys": ["app_key", "package_tag_id", "app_name", "create_if_missing", "publish", "add_fields", "update_fields", "remove_fields"],
|
|
1616
|
+
"aliases": {
|
|
1617
|
+
"app_title": "app_name",
|
|
1618
|
+
"title": "app_name",
|
|
1619
|
+
"field.title": "field.name",
|
|
1620
|
+
"field.label": "field.name",
|
|
1621
|
+
"field.fields": "field.subfields",
|
|
1622
|
+
"field.type_id": "field.type",
|
|
1623
|
+
},
|
|
1624
|
+
"allowed_values": {
|
|
1625
|
+
"field.type": [member.value for member in PublicFieldType],
|
|
1626
|
+
"field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
|
|
1627
|
+
},
|
|
1628
|
+
"execution_notes": [
|
|
1629
|
+
"multiple relation fields are backend-risky; read verification.relation_field_limit_verified and warnings before declaring the schema stable",
|
|
1630
|
+
"backend 49614 is normalized to MULTIPLE_RELATION_FIELDS_UNSUPPORTED with a workaround message",
|
|
1631
|
+
],
|
|
1632
|
+
"minimal_example": {
|
|
1633
|
+
"profile": "default",
|
|
1634
|
+
"app_name": "研发项目管理",
|
|
1635
|
+
"package_tag_id": 1001,
|
|
1636
|
+
"create_if_missing": True,
|
|
1637
|
+
"publish": True,
|
|
1638
|
+
"add_fields": [{"name": "项目名称", "type": "text"}],
|
|
1639
|
+
"update_fields": [],
|
|
1640
|
+
"remove_fields": [],
|
|
1641
|
+
},
|
|
1642
|
+
},
|
|
1643
|
+
"app_layout_plan": {
|
|
1644
|
+
"allowed_keys": ["app_key", "mode", "sections", "preset"],
|
|
1645
|
+
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
1646
|
+
"section_allowed_keys": ["section_id", "title", "rows"],
|
|
1647
|
+
"section_aliases": {
|
|
1648
|
+
"name": "title",
|
|
1649
|
+
"sectionId": "section_id",
|
|
1650
|
+
"fields": "rows",
|
|
1651
|
+
"field_ids": "rows",
|
|
1652
|
+
"columns": "rows_chunk_size",
|
|
1653
|
+
},
|
|
1654
|
+
"allowed_values": {"mode": [member.value for member in LayoutApplyMode], "preset": [member.value for member in LayoutPreset]},
|
|
1655
|
+
"minimal_section_example": {"title": "基础信息", "rows": [["字段A", "字段B"]]},
|
|
1656
|
+
"minimal_example": {
|
|
1657
|
+
"profile": "default",
|
|
1658
|
+
"app_key": "APP_KEY",
|
|
1659
|
+
"mode": "merge",
|
|
1660
|
+
"sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
1661
|
+
},
|
|
1662
|
+
"preset_example": {"profile": "default", "app_key": "APP_KEY", "mode": "merge", "preset": "balanced", "sections": []},
|
|
1663
|
+
},
|
|
1664
|
+
"app_layout_apply": {
|
|
1665
|
+
"allowed_keys": ["app_key", "mode", "publish", "sections"],
|
|
1666
|
+
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
1667
|
+
"section_allowed_keys": ["section_id", "title", "rows"],
|
|
1668
|
+
"section_aliases": {
|
|
1669
|
+
"name": "title",
|
|
1670
|
+
"sectionId": "section_id",
|
|
1671
|
+
"fields": "rows",
|
|
1672
|
+
"field_ids": "rows",
|
|
1673
|
+
"columns": "rows_chunk_size",
|
|
1674
|
+
},
|
|
1675
|
+
"allowed_values": {"mode": [member.value for member in LayoutApplyMode]},
|
|
1676
|
+
"execution_notes": [
|
|
1677
|
+
"layout verification is split into layout_verified and layout_summary_verified",
|
|
1678
|
+
"LAYOUT_SUMMARY_UNVERIFIED means raw form readback is stronger than the compact summary",
|
|
1679
|
+
],
|
|
1680
|
+
"minimal_section_example": {"title": "基础信息", "rows": [["字段A", "字段B"]]},
|
|
1681
|
+
"minimal_example": {
|
|
1682
|
+
"profile": "default",
|
|
1683
|
+
"app_key": "APP_KEY",
|
|
1684
|
+
"mode": "merge",
|
|
1685
|
+
"publish": True,
|
|
1686
|
+
"sections": [{"title": "基础信息", "rows": [["项目名称", "项目负责人"]]}],
|
|
1687
|
+
},
|
|
1688
|
+
},
|
|
1689
|
+
"app_flow_plan": {
|
|
1690
|
+
"allowed_keys": ["app_key", "mode", "nodes", "transitions", "preset"],
|
|
1691
|
+
"aliases": {
|
|
1692
|
+
"overwrite": "replace",
|
|
1693
|
+
"base_preset": "preset",
|
|
1694
|
+
"default_approval": "basic_approval",
|
|
1695
|
+
"node.role_names": "node.assignees.role_names",
|
|
1696
|
+
"node.role_ids": "node.assignees.role_ids",
|
|
1697
|
+
"node.member_names": "node.assignees.member_names",
|
|
1698
|
+
"node.member_emails": "node.assignees.member_emails",
|
|
1699
|
+
"node.member_uids": "node.assignees.member_uids",
|
|
1700
|
+
"node.editable_fields": "node.permissions.editable_fields",
|
|
1701
|
+
"default_approval": "basic_approval",
|
|
1702
|
+
},
|
|
1703
|
+
"allowed_values": {
|
|
1704
|
+
"mode": ["replace"],
|
|
1705
|
+
"preset": [member.value for member in FlowPreset],
|
|
1706
|
+
"node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
|
|
1707
|
+
},
|
|
1708
|
+
"dependency_hints": [
|
|
1709
|
+
"approval-style workflows require an explicit status field",
|
|
1710
|
+
"approve/fill/copy nodes require at least one assignee",
|
|
1711
|
+
],
|
|
1712
|
+
"execution_notes": [
|
|
1713
|
+
"public flow building is intentionally limited to linear workflows",
|
|
1714
|
+
"branch and condition nodes are disabled because the backend workflow route is not front-end stable for these node types",
|
|
1715
|
+
],
|
|
1716
|
+
"minimal_example": {
|
|
1717
|
+
"profile": "default",
|
|
1718
|
+
"app_key": "APP_KEY",
|
|
1719
|
+
"mode": "replace",
|
|
1720
|
+
"preset": "basic_approval",
|
|
1721
|
+
"nodes": [
|
|
1722
|
+
{
|
|
1723
|
+
"id": "approve_1",
|
|
1724
|
+
"type": "approve",
|
|
1725
|
+
"name": "部门审批",
|
|
1726
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
1727
|
+
"permissions": {"editable_fields": ["状态", "审批意见"]},
|
|
1728
|
+
}
|
|
1729
|
+
],
|
|
1730
|
+
"transitions": [],
|
|
1731
|
+
},
|
|
1732
|
+
},
|
|
1733
|
+
"app_flow_apply": {
|
|
1734
|
+
"allowed_keys": ["app_key", "mode", "publish", "nodes", "transitions"],
|
|
1735
|
+
"aliases": {
|
|
1736
|
+
"overwrite": "replace",
|
|
1737
|
+
"node.role_names": "node.assignees.role_names",
|
|
1738
|
+
"node.role_ids": "node.assignees.role_ids",
|
|
1739
|
+
"node.member_names": "node.assignees.member_names",
|
|
1740
|
+
"node.member_emails": "node.assignees.member_emails",
|
|
1741
|
+
"node.member_uids": "node.assignees.member_uids",
|
|
1742
|
+
"node.editable_fields": "node.permissions.editable_fields",
|
|
1743
|
+
},
|
|
1744
|
+
"allowed_values": {
|
|
1745
|
+
"mode": ["replace"],
|
|
1746
|
+
"node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
|
|
1747
|
+
},
|
|
1748
|
+
"dependency_hints": [
|
|
1749
|
+
"approval-style workflows require an explicit status field",
|
|
1750
|
+
"approve/fill/copy nodes require at least one assignee",
|
|
1751
|
+
],
|
|
1752
|
+
"execution_notes": [
|
|
1753
|
+
"public flow building is intentionally limited to linear workflows",
|
|
1754
|
+
"branch and condition nodes are disabled because the backend workflow route is not front-end stable for these node types",
|
|
1755
|
+
"workflow verification only covers linear node structure in the public tool surface",
|
|
1756
|
+
],
|
|
1757
|
+
"minimal_example": {
|
|
1758
|
+
"profile": "default",
|
|
1759
|
+
"app_key": "APP_KEY",
|
|
1760
|
+
"mode": "replace",
|
|
1761
|
+
"publish": True,
|
|
1762
|
+
"nodes": [
|
|
1763
|
+
{"id": "start", "type": "start", "name": "发起"},
|
|
1764
|
+
{
|
|
1765
|
+
"id": "approve_1",
|
|
1766
|
+
"type": "approve",
|
|
1767
|
+
"name": "部门审批",
|
|
1768
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
1769
|
+
"permissions": {"editable_fields": ["状态", "审批意见"]},
|
|
1770
|
+
},
|
|
1771
|
+
{"id": "end", "type": "end", "name": "结束"},
|
|
1772
|
+
],
|
|
1773
|
+
"transitions": [{"from": "start", "to": "approve_1"}, {"from": "approve_1", "to": "end"}],
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
"app_views_plan": {
|
|
1777
|
+
"allowed_keys": ["app_key", "upsert_views", "remove_views", "preset", "upsert_views[].view_key"],
|
|
1778
|
+
"aliases": {
|
|
1779
|
+
"fields": "columns",
|
|
1780
|
+
"column_names": "columns",
|
|
1781
|
+
"columnNames": "columns",
|
|
1782
|
+
"viewKey": "view_key",
|
|
1783
|
+
"tableView": "table",
|
|
1784
|
+
"cardView": "card",
|
|
1785
|
+
"kanban": "board",
|
|
1786
|
+
"filter_rules": "filters",
|
|
1787
|
+
"filterRules": "filters",
|
|
1788
|
+
"startField": "start_field",
|
|
1789
|
+
"endField": "end_field",
|
|
1790
|
+
"titleField": "title_field",
|
|
1791
|
+
},
|
|
1792
|
+
"allowed_values": {
|
|
1793
|
+
"preset": [member.value for member in ViewsPreset],
|
|
1794
|
+
"view.type": [member.value for member in PublicViewType],
|
|
1795
|
+
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1796
|
+
},
|
|
1797
|
+
"minimal_example": {
|
|
1798
|
+
"profile": "default",
|
|
1799
|
+
"app_key": "APP_KEY",
|
|
1800
|
+
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"]}],
|
|
1801
|
+
"remove_views": [],
|
|
1802
|
+
},
|
|
1803
|
+
"gantt_example": {
|
|
1804
|
+
"profile": "default",
|
|
1805
|
+
"app_key": "APP_KEY",
|
|
1806
|
+
"upsert_views": [
|
|
1807
|
+
{
|
|
1808
|
+
"name": "项目甘特图",
|
|
1809
|
+
"type": "gantt",
|
|
1810
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
1811
|
+
"start_field": "开始日期",
|
|
1812
|
+
"end_field": "结束日期",
|
|
1813
|
+
"title_field": "项目名称",
|
|
1814
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
1815
|
+
}
|
|
1816
|
+
],
|
|
1817
|
+
"remove_views": [],
|
|
1818
|
+
},
|
|
1819
|
+
},
|
|
1820
|
+
"app_views_apply": {
|
|
1821
|
+
"allowed_keys": ["app_key", "publish", "upsert_views", "remove_views", "upsert_views[].view_key"],
|
|
1822
|
+
"aliases": {
|
|
1823
|
+
"fields": "columns",
|
|
1824
|
+
"column_names": "columns",
|
|
1825
|
+
"columnNames": "columns",
|
|
1826
|
+
"viewKey": "view_key",
|
|
1827
|
+
"tableView": "table",
|
|
1828
|
+
"cardView": "card",
|
|
1829
|
+
"kanban": "board",
|
|
1830
|
+
"filter_rules": "filters",
|
|
1831
|
+
"filterRules": "filters",
|
|
1832
|
+
"startField": "start_field",
|
|
1833
|
+
"endField": "end_field",
|
|
1834
|
+
"titleField": "title_field",
|
|
1835
|
+
},
|
|
1836
|
+
"allowed_values": {
|
|
1837
|
+
"view.type": [member.value for member in PublicViewType],
|
|
1838
|
+
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1839
|
+
},
|
|
1840
|
+
"execution_notes": [
|
|
1841
|
+
"apply may return partial_success when some views land and others fail",
|
|
1842
|
+
"when duplicate view names exist, supply view_key to target the exact view",
|
|
1843
|
+
"read back app_read_views_summary after any failed or partial view apply",
|
|
1844
|
+
"view existence verification and saved-filter verification are separate; treat filters as unverified until verification.view_filters_verified is true",
|
|
1845
|
+
],
|
|
1846
|
+
"minimal_example": {
|
|
1847
|
+
"profile": "default",
|
|
1848
|
+
"app_key": "APP_KEY",
|
|
1849
|
+
"publish": True,
|
|
1850
|
+
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"]}],
|
|
1851
|
+
"remove_views": [],
|
|
1852
|
+
},
|
|
1853
|
+
"gantt_example": {
|
|
1854
|
+
"profile": "default",
|
|
1855
|
+
"app_key": "APP_KEY",
|
|
1856
|
+
"publish": True,
|
|
1857
|
+
"upsert_views": [
|
|
1858
|
+
{
|
|
1859
|
+
"name": "项目甘特图",
|
|
1860
|
+
"type": "gantt",
|
|
1861
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
1862
|
+
"start_field": "开始日期",
|
|
1863
|
+
"end_field": "结束日期",
|
|
1864
|
+
"title_field": "项目名称",
|
|
1865
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
1866
|
+
}
|
|
1867
|
+
],
|
|
1868
|
+
"remove_views": [],
|
|
1869
|
+
},
|
|
1870
|
+
},
|
|
1871
|
+
"app_read_charts_summary": {
|
|
1872
|
+
"allowed_keys": ["app_key"],
|
|
1873
|
+
"aliases": {},
|
|
1874
|
+
"allowed_values": {},
|
|
1875
|
+
"execution_notes": [
|
|
1876
|
+
"returns a compact current chart inventory for one app",
|
|
1877
|
+
"use this before app_charts_apply when you need exact current chart_id values",
|
|
1878
|
+
"chart summaries do not include full qingbi config payloads",
|
|
1879
|
+
],
|
|
1880
|
+
"minimal_example": {
|
|
1881
|
+
"profile": "default",
|
|
1882
|
+
"app_key": "APP_KEY",
|
|
1883
|
+
},
|
|
1884
|
+
},
|
|
1885
|
+
"app_charts_apply": {
|
|
1886
|
+
"allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids"],
|
|
1887
|
+
"aliases": {
|
|
1888
|
+
"chart.id": "chart.chart_id",
|
|
1889
|
+
"chart.type": "chart.chart_type",
|
|
1890
|
+
"chart.dimension_fields": "chart.dimension_field_ids",
|
|
1891
|
+
"chart.indicator_fields": "chart.indicator_field_ids",
|
|
1892
|
+
"chart.metric_field_ids": "chart.indicator_field_ids",
|
|
1893
|
+
"chart.filter.op": "chart.filter.operator",
|
|
1894
|
+
},
|
|
1895
|
+
"allowed_values": {
|
|
1896
|
+
"chart.chart_type": [member.value for member in PublicChartType],
|
|
1897
|
+
"chart.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1898
|
+
},
|
|
1899
|
+
"execution_notes": [
|
|
1900
|
+
"app_charts_apply is immediate-live and does not publish",
|
|
1901
|
+
"chart matching precedence is chart_id first, then exact unique chart name",
|
|
1902
|
+
"when chart names are not unique, supply chart_id instead of guessing by name",
|
|
1903
|
+
"successful create results must return a real backend chart_id",
|
|
1904
|
+
],
|
|
1905
|
+
"minimal_example": {
|
|
1906
|
+
"profile": "default",
|
|
1907
|
+
"app_key": "APP_KEY",
|
|
1908
|
+
"upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
1909
|
+
"remove_chart_ids": [],
|
|
1910
|
+
"reorder_chart_ids": [],
|
|
1911
|
+
},
|
|
1912
|
+
},
|
|
1913
|
+
"chart_apply": {
|
|
1914
|
+
"allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids"],
|
|
1915
|
+
"aliases": {
|
|
1916
|
+
"legacy_tool_name": "app_charts_apply",
|
|
1917
|
+
},
|
|
1918
|
+
"allowed_values": {
|
|
1919
|
+
"chart.chart_type": [member.value for member in PublicChartType],
|
|
1920
|
+
"chart.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1921
|
+
},
|
|
1922
|
+
"execution_notes": [
|
|
1923
|
+
"legacy compatibility alias; prefer app_charts_apply in new builder flows",
|
|
1924
|
+
"behavior matches app_charts_apply exactly",
|
|
1925
|
+
],
|
|
1926
|
+
"minimal_example": {
|
|
1927
|
+
"profile": "default",
|
|
1928
|
+
"app_key": "APP_KEY",
|
|
1929
|
+
"upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
1930
|
+
"remove_chart_ids": [],
|
|
1931
|
+
"reorder_chart_ids": [],
|
|
1932
|
+
},
|
|
1933
|
+
},
|
|
1934
|
+
"portal_read_summary": {
|
|
1935
|
+
"allowed_keys": ["dash_key", "being_draft"],
|
|
1936
|
+
"aliases": {"beingDraft": "being_draft"},
|
|
1937
|
+
"allowed_values": {},
|
|
1938
|
+
"execution_notes": [
|
|
1939
|
+
"returns a compact portal summary instead of the raw dash payload",
|
|
1940
|
+
"being_draft=true reads the current draft view; being_draft=false reads live",
|
|
1941
|
+
"use this before portal_apply when you need the current section inventory or target dash metadata",
|
|
1942
|
+
],
|
|
1943
|
+
"minimal_example": {
|
|
1944
|
+
"profile": "default",
|
|
1945
|
+
"dash_key": "DASH_KEY",
|
|
1946
|
+
"being_draft": True,
|
|
1947
|
+
},
|
|
1948
|
+
},
|
|
1949
|
+
"portal_apply": {
|
|
1950
|
+
"allowed_keys": ["dash_key", "dash_name", "package_tag_id", "publish", "sections", "auth", "icon", "color", "hide_copyright", "dash_global_config", "config"],
|
|
1951
|
+
"aliases": {
|
|
1952
|
+
"sourceType": "source_type",
|
|
1953
|
+
"chartRef": "chart_ref",
|
|
1954
|
+
"viewRef": "view_ref",
|
|
1955
|
+
"dashStyleConfigBO": "dash_style_config",
|
|
1956
|
+
},
|
|
1957
|
+
"section_allowed_keys": ["title", "source_type", "position", "dash_style_config", "config", "chart_ref", "view_ref", "text", "url"],
|
|
1958
|
+
"section_aliases": {
|
|
1959
|
+
"sourceType": "source_type",
|
|
1960
|
+
"chartRef": "chart_ref",
|
|
1961
|
+
"viewRef": "view_ref",
|
|
1962
|
+
"dashStyleConfigBO": "dash_style_config",
|
|
1963
|
+
},
|
|
1964
|
+
"allowed_values": {"section.source_type": ["chart", "view", "grid", "filter", "text", "link"]},
|
|
1965
|
+
"execution_notes": [
|
|
1966
|
+
"portal_apply uses replace semantics for sections",
|
|
1967
|
+
"remove a section by omitting it from the new sections list",
|
|
1968
|
+
"package_tag_id is required when creating a new portal",
|
|
1969
|
+
"publish=false only guarantees draft and base-info updates; it does not claim live has changed",
|
|
1970
|
+
"chart_ref resolves by chart_id first, then exact unique chart_name",
|
|
1971
|
+
"view_ref resolves by view_key first, then exact unique view_name",
|
|
1972
|
+
"position.pc/mobile is the canonical portal layout shape",
|
|
1973
|
+
],
|
|
1974
|
+
"minimal_example": {
|
|
1975
|
+
"profile": "default",
|
|
1976
|
+
"dash_name": "经营门户",
|
|
1977
|
+
"package_tag_id": 1001,
|
|
1978
|
+
"publish": True,
|
|
1979
|
+
"sections": [
|
|
1980
|
+
{
|
|
1981
|
+
"title": "经营概览",
|
|
1982
|
+
"source_type": "chart",
|
|
1983
|
+
"chart_ref": {"app_key": "APP_KEY", "chart_name": "数据总量"},
|
|
1984
|
+
"position": {
|
|
1985
|
+
"pc": {"x": 0, "y": 0, "cols": 12, "rows": 6},
|
|
1986
|
+
"mobile": {"x": 0, "y": 0, "cols": 6, "rows": 6},
|
|
1987
|
+
},
|
|
1988
|
+
}
|
|
1989
|
+
],
|
|
1990
|
+
},
|
|
1991
|
+
"minimal_section_example": {
|
|
1992
|
+
"title": "订单概览",
|
|
1993
|
+
"source_type": "view",
|
|
1994
|
+
"view_ref": {"app_key": "APP_KEY", "view_key": "VIEW_KEY"},
|
|
1995
|
+
"position": {
|
|
1996
|
+
"pc": {"x": 0, "y": 0, "cols": 24, "rows": 8},
|
|
1997
|
+
"mobile": {"x": 0, "y": 0, "cols": 6, "rows": 8},
|
|
1998
|
+
},
|
|
1999
|
+
},
|
|
2000
|
+
},
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
_PRIVATE_BUILDER_TOOL_CONTRACTS = {
|
|
2004
|
+
"app_schema_plan",
|
|
2005
|
+
"app_layout_plan",
|
|
2006
|
+
"app_flow_plan",
|
|
2007
|
+
"app_views_plan",
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
_BUILDER_TOOL_CONTRACT_ALIASES = {
|
|
2011
|
+
"chart_apply": "app_charts_apply",
|
|
2012
|
+
}
|