@josephyan/qingflow-app-user-mcp 0.2.0-beta.2 → 0.2.0-beta.21
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 +12 -2
- package/npm/lib/runtime.mjs +37 -0
- package/npm/scripts/postinstall.mjs +5 -1
- package/package.json +3 -2
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-user/SKILL.md +230 -0
- package/skills/qingflow-app-user/agents/openai.yaml +4 -0
- package/skills/qingflow-app-user/references/data-gotchas.md +49 -0
- package/skills/qingflow-app-user/references/environments.md +63 -0
- package/skills/qingflow-app-user/references/record-patterns.md +110 -0
- package/skills/qingflow-app-user/references/workflow-usage.md +26 -0
- package/skills/qingflow-record-analysis/SKILL.md +253 -0
- package/skills/qingflow-record-analysis/agents/openai.yaml +4 -0
- package/skills/qingflow-record-analysis/references/analysis-gotchas.md +141 -0
- package/skills/qingflow-record-analysis/references/analysis-patterns.md +113 -0
- package/skills/qingflow-record-analysis/references/confidence-reporting.md +92 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +294 -1
- package/src/qingflow_mcp/builder_facade/service.py +2727 -235
- package/src/qingflow_mcp/server.py +7 -5
- package/src/qingflow_mcp/server_app_builder.py +80 -4
- package/src/qingflow_mcp/server_app_user.py +8 -182
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +1 -1
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +21 -2
- package/src/qingflow_mcp/solution/executor.py +34 -7
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1038 -30
- package/src/qingflow_mcp/tools/app_tools.py +1 -2
- package/src/qingflow_mcp/tools/approval_tools.py +357 -75
- package/src/qingflow_mcp/tools/directory_tools.py +158 -28
- package/src/qingflow_mcp/tools/record_tools.py +1954 -973
- package/src/qingflow_mcp/tools/task_tools.py +376 -225
- package/src/qingflow_mcp/tools/workflow_tools.py +78 -4
|
@@ -1,27 +1,43 @@
|
|
|
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
|
+
FIELD_TYPE_ID_ALIASES,
|
|
8
14
|
FieldPatch,
|
|
9
15
|
FieldRemovePatch,
|
|
10
16
|
FieldUpdatePatch,
|
|
17
|
+
FlowConditionOperator,
|
|
18
|
+
FlowPreset,
|
|
11
19
|
FlowNodePatch,
|
|
12
20
|
FlowPlanRequest,
|
|
13
21
|
FlowTransitionPatch,
|
|
14
22
|
LayoutApplyMode,
|
|
15
23
|
LayoutPlanRequest,
|
|
24
|
+
LayoutPreset,
|
|
16
25
|
LayoutSectionPatch,
|
|
26
|
+
PublicFieldType,
|
|
27
|
+
PublicFlowNodeType,
|
|
28
|
+
PublicViewType,
|
|
17
29
|
SchemaPlanRequest,
|
|
30
|
+
ViewFilterOperator,
|
|
18
31
|
ViewUpsertPatch,
|
|
32
|
+
ViewsPreset,
|
|
19
33
|
ViewsPlanRequest,
|
|
20
34
|
)
|
|
21
35
|
from ..builder_facade.service import AiBuilderFacade
|
|
22
36
|
from .app_tools import AppTools
|
|
23
37
|
from .base import ToolBase
|
|
38
|
+
from .directory_tools import DirectoryTools
|
|
24
39
|
from .package_tools import PackageTools
|
|
40
|
+
from .role_tools import RoleTools
|
|
25
41
|
from .solution_tools import SolutionTools
|
|
26
42
|
from .view_tools import ViewTools
|
|
27
43
|
from .workflow_tools import WorkflowTools
|
|
@@ -35,6 +51,8 @@ class AiBuilderTools(ToolBase):
|
|
|
35
51
|
packages=PackageTools(sessions, backend),
|
|
36
52
|
views=ViewTools(sessions, backend),
|
|
37
53
|
workflows=WorkflowTools(sessions, backend),
|
|
54
|
+
roles=RoleTools(sessions, backend),
|
|
55
|
+
directory=DirectoryTools(sessions, backend),
|
|
38
56
|
solutions=SolutionTools(sessions, backend),
|
|
39
57
|
)
|
|
40
58
|
|
|
@@ -47,6 +65,57 @@ class AiBuilderTools(ToolBase):
|
|
|
47
65
|
def package_resolve(profile: str = DEFAULT_PROFILE, package_name: str = "") -> JSONObject:
|
|
48
66
|
return self.package_resolve(profile=profile, package_name=package_name)
|
|
49
67
|
|
|
68
|
+
@mcp.tool()
|
|
69
|
+
def builder_tool_contract(tool_name: str = "") -> JSONObject:
|
|
70
|
+
return self.builder_tool_contract(tool_name=tool_name)
|
|
71
|
+
|
|
72
|
+
@mcp.tool()
|
|
73
|
+
def package_create(profile: str = DEFAULT_PROFILE, package_name: str = "") -> JSONObject:
|
|
74
|
+
return self.package_create(profile=profile, package_name=package_name)
|
|
75
|
+
|
|
76
|
+
@mcp.tool()
|
|
77
|
+
def member_search(
|
|
78
|
+
profile: str = DEFAULT_PROFILE,
|
|
79
|
+
query: str = "",
|
|
80
|
+
page_num: int = 1,
|
|
81
|
+
page_size: int = 20,
|
|
82
|
+
contain_disable: bool = False,
|
|
83
|
+
) -> JSONObject:
|
|
84
|
+
return self.member_search(
|
|
85
|
+
profile=profile,
|
|
86
|
+
query=query,
|
|
87
|
+
page_num=page_num,
|
|
88
|
+
page_size=page_size,
|
|
89
|
+
contain_disable=contain_disable,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@mcp.tool()
|
|
93
|
+
def role_search(
|
|
94
|
+
profile: str = DEFAULT_PROFILE,
|
|
95
|
+
keyword: str = "",
|
|
96
|
+
page_num: int = 1,
|
|
97
|
+
page_size: int = 20,
|
|
98
|
+
) -> JSONObject:
|
|
99
|
+
return self.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size)
|
|
100
|
+
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
def role_create(
|
|
103
|
+
profile: str = DEFAULT_PROFILE,
|
|
104
|
+
role_name: str = "",
|
|
105
|
+
member_uids: list[int] | None = None,
|
|
106
|
+
member_emails: list[str] | None = None,
|
|
107
|
+
member_names: list[str] | None = None,
|
|
108
|
+
role_icon: str = "ex-user-outlined",
|
|
109
|
+
) -> JSONObject:
|
|
110
|
+
return self.role_create(
|
|
111
|
+
profile=profile,
|
|
112
|
+
role_name=role_name,
|
|
113
|
+
member_uids=member_uids or [],
|
|
114
|
+
member_emails=member_emails or [],
|
|
115
|
+
member_names=member_names or [],
|
|
116
|
+
role_icon=role_icon,
|
|
117
|
+
)
|
|
118
|
+
|
|
50
119
|
@mcp.tool()
|
|
51
120
|
def package_attach_app(
|
|
52
121
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -56,6 +125,20 @@ class AiBuilderTools(ToolBase):
|
|
|
56
125
|
) -> JSONObject:
|
|
57
126
|
return self.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key, app_title=app_title)
|
|
58
127
|
|
|
128
|
+
@mcp.tool()
|
|
129
|
+
def app_release_edit_lock_if_mine(
|
|
130
|
+
profile: str = DEFAULT_PROFILE,
|
|
131
|
+
app_key: str = "",
|
|
132
|
+
lock_owner_email: str = "",
|
|
133
|
+
lock_owner_name: str = "",
|
|
134
|
+
) -> JSONObject:
|
|
135
|
+
return self.app_release_edit_lock_if_mine(
|
|
136
|
+
profile=profile,
|
|
137
|
+
app_key=app_key,
|
|
138
|
+
lock_owner_email=lock_owner_email,
|
|
139
|
+
lock_owner_name=lock_owner_name,
|
|
140
|
+
)
|
|
141
|
+
|
|
59
142
|
@mcp.tool()
|
|
60
143
|
def app_resolve(
|
|
61
144
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -165,6 +248,7 @@ class AiBuilderTools(ToolBase):
|
|
|
165
248
|
app_name: str = "",
|
|
166
249
|
app_title: str = "",
|
|
167
250
|
create_if_missing: bool = False,
|
|
251
|
+
publish: bool = True,
|
|
168
252
|
add_fields: list[JSONObject] | None = None,
|
|
169
253
|
update_fields: list[JSONObject] | None = None,
|
|
170
254
|
remove_fields: list[JSONObject] | None = None,
|
|
@@ -176,6 +260,7 @@ class AiBuilderTools(ToolBase):
|
|
|
176
260
|
app_name=app_name,
|
|
177
261
|
app_title=app_title,
|
|
178
262
|
create_if_missing=create_if_missing,
|
|
263
|
+
publish=publish,
|
|
179
264
|
add_fields=add_fields or [],
|
|
180
265
|
update_fields=update_fields or [],
|
|
181
266
|
remove_fields=remove_fields or [],
|
|
@@ -186,15 +271,17 @@ class AiBuilderTools(ToolBase):
|
|
|
186
271
|
profile: str = DEFAULT_PROFILE,
|
|
187
272
|
app_key: str = "",
|
|
188
273
|
mode: str = "merge",
|
|
274
|
+
publish: bool = True,
|
|
189
275
|
sections: list[JSONObject] | None = None,
|
|
190
276
|
) -> JSONObject:
|
|
191
|
-
return self.app_layout_apply(profile=profile, app_key=app_key, mode=mode, sections=sections or [])
|
|
277
|
+
return self.app_layout_apply(profile=profile, app_key=app_key, mode=mode, publish=publish, sections=sections or [])
|
|
192
278
|
|
|
193
279
|
@mcp.tool()
|
|
194
280
|
def app_flow_apply(
|
|
195
281
|
profile: str = DEFAULT_PROFILE,
|
|
196
282
|
app_key: str = "",
|
|
197
283
|
mode: str = "replace",
|
|
284
|
+
publish: bool = True,
|
|
198
285
|
nodes: list[JSONObject] | None = None,
|
|
199
286
|
transitions: list[JSONObject] | None = None,
|
|
200
287
|
) -> JSONObject:
|
|
@@ -202,6 +289,7 @@ class AiBuilderTools(ToolBase):
|
|
|
202
289
|
profile=profile,
|
|
203
290
|
app_key=app_key,
|
|
204
291
|
mode=mode,
|
|
292
|
+
publish=publish,
|
|
205
293
|
nodes=nodes or [],
|
|
206
294
|
transitions=transitions or [],
|
|
207
295
|
)
|
|
@@ -210,12 +298,14 @@ class AiBuilderTools(ToolBase):
|
|
|
210
298
|
def app_views_apply(
|
|
211
299
|
profile: str = DEFAULT_PROFILE,
|
|
212
300
|
app_key: str = "",
|
|
301
|
+
publish: bool = True,
|
|
213
302
|
upsert_views: list[JSONObject] | None = None,
|
|
214
303
|
remove_views: list[str] | None = None,
|
|
215
304
|
) -> JSONObject:
|
|
216
305
|
return self.app_views_apply(
|
|
217
306
|
profile=profile,
|
|
218
307
|
app_key=app_key,
|
|
308
|
+
publish=publish,
|
|
219
309
|
upsert_views=upsert_views or [],
|
|
220
310
|
remove_views=remove_views or [],
|
|
221
311
|
)
|
|
@@ -233,31 +323,236 @@ class AiBuilderTools(ToolBase):
|
|
|
233
323
|
)
|
|
234
324
|
|
|
235
325
|
def package_list(self, *, profile: str, trial_status: str = "all") -> JSONObject:
|
|
236
|
-
|
|
326
|
+
normalized_args = {"trial_status": trial_status}
|
|
327
|
+
return _safe_tool_call(
|
|
328
|
+
lambda: self._facade.package_list(profile=profile, trial_status=trial_status),
|
|
329
|
+
error_code="PACKAGE_LIST_FAILED",
|
|
330
|
+
normalized_args=normalized_args,
|
|
331
|
+
suggested_next_call={"tool_name": "package_list", "arguments": {"profile": profile, "trial_status": trial_status}},
|
|
332
|
+
)
|
|
237
333
|
|
|
238
334
|
def package_resolve(self, *, profile: str, package_name: str) -> JSONObject:
|
|
239
|
-
|
|
335
|
+
normalized_args = {"package_name": package_name}
|
|
336
|
+
return _safe_tool_call(
|
|
337
|
+
lambda: self._facade.package_resolve(profile=profile, package_name=package_name),
|
|
338
|
+
error_code="PACKAGE_RESOLVE_FAILED",
|
|
339
|
+
normalized_args=normalized_args,
|
|
340
|
+
suggested_next_call={"tool_name": "package_resolve", "arguments": {"profile": profile, "package_name": package_name}},
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def builder_tool_contract(self, *, tool_name: str) -> JSONObject:
|
|
344
|
+
requested = str(tool_name or "").strip()
|
|
345
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(requested)
|
|
346
|
+
if contract is None:
|
|
347
|
+
return {
|
|
348
|
+
"status": "failed",
|
|
349
|
+
"error_code": "TOOL_CONTRACT_NOT_FOUND",
|
|
350
|
+
"recoverable": True,
|
|
351
|
+
"message": "tool contract is not defined for the requested public builder tool",
|
|
352
|
+
"normalized_args": {"tool_name": requested},
|
|
353
|
+
"missing_fields": [],
|
|
354
|
+
"allowed_values": {"tool_name": sorted(_BUILDER_TOOL_CONTRACTS.keys())},
|
|
355
|
+
"details": {"reason_path": "tool_name"},
|
|
356
|
+
"suggested_next_call": None,
|
|
357
|
+
"request_id": None,
|
|
358
|
+
"backend_code": None,
|
|
359
|
+
"http_status": None,
|
|
360
|
+
"noop": False,
|
|
361
|
+
"verification": {},
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
"status": "success",
|
|
365
|
+
"error_code": None,
|
|
366
|
+
"recoverable": False,
|
|
367
|
+
"message": "loaded builder tool contract",
|
|
368
|
+
"normalized_args": {"tool_name": requested},
|
|
369
|
+
"missing_fields": [],
|
|
370
|
+
"allowed_values": {},
|
|
371
|
+
"details": {},
|
|
372
|
+
"suggested_next_call": None,
|
|
373
|
+
"request_id": None,
|
|
374
|
+
"backend_code": None,
|
|
375
|
+
"http_status": None,
|
|
376
|
+
"noop": False,
|
|
377
|
+
"verification": {},
|
|
378
|
+
"tool_name": requested,
|
|
379
|
+
"contract": contract,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
def package_create(self, *, profile: str, package_name: str) -> JSONObject:
|
|
383
|
+
normalized_args = {"package_name": package_name}
|
|
384
|
+
return _safe_tool_call(
|
|
385
|
+
lambda: self._facade.package_create(profile=profile, package_name=package_name),
|
|
386
|
+
error_code="PACKAGE_CREATE_FAILED",
|
|
387
|
+
normalized_args=normalized_args,
|
|
388
|
+
suggested_next_call={"tool_name": "package_create", "arguments": {"profile": profile, "package_name": package_name}},
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def member_search(
|
|
392
|
+
self,
|
|
393
|
+
*,
|
|
394
|
+
profile: str,
|
|
395
|
+
query: str,
|
|
396
|
+
page_num: int = 1,
|
|
397
|
+
page_size: int = 20,
|
|
398
|
+
contain_disable: bool = False,
|
|
399
|
+
) -> JSONObject:
|
|
400
|
+
normalized_args = {
|
|
401
|
+
"query": query,
|
|
402
|
+
"page_num": page_num,
|
|
403
|
+
"page_size": page_size,
|
|
404
|
+
"contain_disable": contain_disable,
|
|
405
|
+
}
|
|
406
|
+
return _safe_tool_call(
|
|
407
|
+
lambda: self._facade.member_search(
|
|
408
|
+
profile=profile,
|
|
409
|
+
query=query,
|
|
410
|
+
page_num=page_num,
|
|
411
|
+
page_size=page_size,
|
|
412
|
+
contain_disable=contain_disable,
|
|
413
|
+
),
|
|
414
|
+
error_code="MEMBER_SEARCH_FAILED",
|
|
415
|
+
normalized_args=normalized_args,
|
|
416
|
+
suggested_next_call={"tool_name": "member_search", "arguments": {"profile": profile, **normalized_args}},
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
def role_search(self, *, profile: str, keyword: str, page_num: int = 1, page_size: int = 20) -> JSONObject:
|
|
420
|
+
normalized_args = {"keyword": keyword, "page_num": page_num, "page_size": page_size}
|
|
421
|
+
return _safe_tool_call(
|
|
422
|
+
lambda: self._facade.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size),
|
|
423
|
+
error_code="ROLE_SEARCH_FAILED",
|
|
424
|
+
normalized_args=normalized_args,
|
|
425
|
+
suggested_next_call={"tool_name": "role_search", "arguments": {"profile": profile, **normalized_args}},
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def role_create(
|
|
429
|
+
self,
|
|
430
|
+
*,
|
|
431
|
+
profile: str,
|
|
432
|
+
role_name: str,
|
|
433
|
+
member_uids: list[int],
|
|
434
|
+
member_emails: list[str],
|
|
435
|
+
member_names: list[str],
|
|
436
|
+
role_icon: str = "ex-user-outlined",
|
|
437
|
+
) -> JSONObject:
|
|
438
|
+
normalized_args = {
|
|
439
|
+
"role_name": role_name,
|
|
440
|
+
"member_uids": member_uids,
|
|
441
|
+
"member_emails": member_emails,
|
|
442
|
+
"member_names": member_names,
|
|
443
|
+
"role_icon": role_icon,
|
|
444
|
+
}
|
|
445
|
+
return _safe_tool_call(
|
|
446
|
+
lambda: self._facade.role_create(
|
|
447
|
+
profile=profile,
|
|
448
|
+
role_name=role_name,
|
|
449
|
+
member_uids=member_uids,
|
|
450
|
+
member_emails=member_emails,
|
|
451
|
+
member_names=member_names,
|
|
452
|
+
role_icon=role_icon,
|
|
453
|
+
),
|
|
454
|
+
error_code="ROLE_CREATE_FAILED",
|
|
455
|
+
normalized_args=normalized_args,
|
|
456
|
+
suggested_next_call={"tool_name": "role_create", "arguments": {"profile": profile, **normalized_args}},
|
|
457
|
+
)
|
|
240
458
|
|
|
241
459
|
def package_attach_app(self, *, profile: str, tag_id: int, app_key: str, app_title: str = "") -> JSONObject:
|
|
242
|
-
|
|
460
|
+
normalized_args = {"tag_id": tag_id, "app_key": app_key, "app_title": app_title}
|
|
461
|
+
result = _safe_tool_call(
|
|
462
|
+
lambda: self._facade.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key, app_title=app_title),
|
|
463
|
+
error_code="PACKAGE_ATTACH_FAILED",
|
|
464
|
+
normalized_args=normalized_args,
|
|
465
|
+
suggested_next_call={"tool_name": "package_attach_app", "arguments": {"profile": profile, **normalized_args}},
|
|
466
|
+
)
|
|
467
|
+
return self._retry_after_self_lock_release(
|
|
468
|
+
profile=profile,
|
|
469
|
+
result=result,
|
|
470
|
+
retry_call=lambda: self._facade.package_attach_app(
|
|
471
|
+
profile=profile,
|
|
472
|
+
tag_id=tag_id,
|
|
473
|
+
app_key=app_key,
|
|
474
|
+
app_title=app_title,
|
|
475
|
+
),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
def app_release_edit_lock_if_mine(
|
|
479
|
+
self,
|
|
480
|
+
*,
|
|
481
|
+
profile: str,
|
|
482
|
+
app_key: str,
|
|
483
|
+
lock_owner_email: str = "",
|
|
484
|
+
lock_owner_name: str = "",
|
|
485
|
+
) -> JSONObject:
|
|
486
|
+
normalized_args = {
|
|
487
|
+
"app_key": app_key,
|
|
488
|
+
"lock_owner_email": lock_owner_email,
|
|
489
|
+
"lock_owner_name": lock_owner_name,
|
|
490
|
+
}
|
|
491
|
+
return _safe_tool_call(
|
|
492
|
+
lambda: self._facade.app_release_edit_lock_if_mine(
|
|
493
|
+
profile=profile,
|
|
494
|
+
app_key=app_key,
|
|
495
|
+
lock_owner_email=lock_owner_email,
|
|
496
|
+
lock_owner_name=lock_owner_name,
|
|
497
|
+
),
|
|
498
|
+
error_code="EDIT_LOCK_RELEASE_FAILED",
|
|
499
|
+
normalized_args=normalized_args,
|
|
500
|
+
suggested_next_call={"tool_name": "app_release_edit_lock_if_mine", "arguments": {"profile": profile, **normalized_args}},
|
|
501
|
+
)
|
|
243
502
|
|
|
244
503
|
def app_resolve(self, *, profile: str, app_key: str = "", app_name: str = "", package_tag_id: int | None = None) -> JSONObject:
|
|
245
|
-
|
|
504
|
+
normalized_args = {"app_key": app_key, "app_name": app_name, "package_tag_id": package_tag_id}
|
|
505
|
+
return _safe_tool_call(
|
|
506
|
+
lambda: self._facade.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_tag_id=package_tag_id),
|
|
507
|
+
error_code="APP_RESOLVE_FAILED",
|
|
508
|
+
normalized_args=normalized_args,
|
|
509
|
+
suggested_next_call={"tool_name": "app_resolve", "arguments": {"profile": profile, **normalized_args}},
|
|
510
|
+
)
|
|
246
511
|
|
|
247
512
|
def app_read_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
248
|
-
|
|
513
|
+
normalized_args = {"app_key": app_key}
|
|
514
|
+
return _safe_tool_call(
|
|
515
|
+
lambda: self._facade.app_read_summary(profile=profile, app_key=app_key),
|
|
516
|
+
error_code="APP_READ_FAILED",
|
|
517
|
+
normalized_args=normalized_args,
|
|
518
|
+
suggested_next_call={"tool_name": "app_read_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
519
|
+
)
|
|
249
520
|
|
|
250
521
|
def app_read_fields(self, *, profile: str, app_key: str) -> JSONObject:
|
|
251
|
-
|
|
522
|
+
normalized_args = {"app_key": app_key}
|
|
523
|
+
return _safe_tool_call(
|
|
524
|
+
lambda: self._facade.app_read_fields(profile=profile, app_key=app_key),
|
|
525
|
+
error_code="FIELDS_READ_FAILED",
|
|
526
|
+
normalized_args=normalized_args,
|
|
527
|
+
suggested_next_call={"tool_name": "app_read_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
528
|
+
)
|
|
252
529
|
|
|
253
530
|
def app_read_layout_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
254
|
-
|
|
531
|
+
normalized_args = {"app_key": app_key}
|
|
532
|
+
return _safe_tool_call(
|
|
533
|
+
lambda: self._facade.app_read_layout_summary(profile=profile, app_key=app_key),
|
|
534
|
+
error_code="LAYOUT_READ_FAILED",
|
|
535
|
+
normalized_args=normalized_args,
|
|
536
|
+
suggested_next_call={"tool_name": "app_read_layout_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
537
|
+
)
|
|
255
538
|
|
|
256
539
|
def app_read_views_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
257
|
-
|
|
540
|
+
normalized_args = {"app_key": app_key}
|
|
541
|
+
return _safe_tool_call(
|
|
542
|
+
lambda: self._facade.app_read_views_summary(profile=profile, app_key=app_key),
|
|
543
|
+
error_code="VIEWS_READ_FAILED",
|
|
544
|
+
normalized_args=normalized_args,
|
|
545
|
+
suggested_next_call={"tool_name": "app_read_views_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
546
|
+
)
|
|
258
547
|
|
|
259
548
|
def app_read_flow_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
260
|
-
|
|
549
|
+
normalized_args = {"app_key": app_key}
|
|
550
|
+
return _safe_tool_call(
|
|
551
|
+
lambda: self._facade.app_read_flow_summary(profile=profile, app_key=app_key),
|
|
552
|
+
error_code="FLOW_READ_FAILED",
|
|
553
|
+
normalized_args=normalized_args,
|
|
554
|
+
suggested_next_call={"tool_name": "app_read_flow_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
555
|
+
)
|
|
261
556
|
|
|
262
557
|
def app_schema_plan(
|
|
263
558
|
self,
|
|
@@ -286,6 +581,8 @@ class AiBuilderTools(ToolBase):
|
|
|
286
581
|
except ValidationError as exc:
|
|
287
582
|
return _validation_failure(
|
|
288
583
|
str(exc),
|
|
584
|
+
tool_name="app_schema_plan",
|
|
585
|
+
exc=exc,
|
|
289
586
|
suggested_next_call={
|
|
290
587
|
"tool_name": "app_schema_plan",
|
|
291
588
|
"arguments": {
|
|
@@ -300,7 +597,12 @@ class AiBuilderTools(ToolBase):
|
|
|
300
597
|
},
|
|
301
598
|
},
|
|
302
599
|
)
|
|
303
|
-
return
|
|
600
|
+
return _safe_tool_call(
|
|
601
|
+
lambda: self._facade.app_schema_plan(profile=profile, request=request),
|
|
602
|
+
error_code="SCHEMA_PLAN_FAILED",
|
|
603
|
+
normalized_args=request.model_dump(mode="json"),
|
|
604
|
+
suggested_next_call={"tool_name": "app_schema_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
605
|
+
)
|
|
304
606
|
|
|
305
607
|
def app_layout_plan(
|
|
306
608
|
self,
|
|
@@ -323,18 +625,24 @@ class AiBuilderTools(ToolBase):
|
|
|
323
625
|
except ValidationError as exc:
|
|
324
626
|
return _validation_failure(
|
|
325
627
|
str(exc),
|
|
628
|
+
tool_name="app_layout_plan",
|
|
629
|
+
exc=exc,
|
|
326
630
|
suggested_next_call={
|
|
327
631
|
"tool_name": "app_layout_plan",
|
|
328
632
|
"arguments": {
|
|
329
633
|
"profile": profile,
|
|
330
634
|
"app_key": app_key,
|
|
331
635
|
"mode": "merge",
|
|
332
|
-
"
|
|
333
|
-
"sections": [],
|
|
636
|
+
"sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
334
637
|
},
|
|
335
638
|
},
|
|
336
639
|
)
|
|
337
|
-
return
|
|
640
|
+
return _safe_tool_call(
|
|
641
|
+
lambda: self._facade.app_layout_plan(profile=profile, request=request),
|
|
642
|
+
error_code="LAYOUT_PLAN_FAILED",
|
|
643
|
+
normalized_args=request.model_dump(mode="json"),
|
|
644
|
+
suggested_next_call={"tool_name": "app_layout_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
645
|
+
)
|
|
338
646
|
|
|
339
647
|
def app_flow_plan(
|
|
340
648
|
self,
|
|
@@ -359,6 +667,8 @@ class AiBuilderTools(ToolBase):
|
|
|
359
667
|
except ValidationError as exc:
|
|
360
668
|
return _validation_failure(
|
|
361
669
|
str(exc),
|
|
670
|
+
tool_name="app_flow_plan",
|
|
671
|
+
exc=exc,
|
|
362
672
|
suggested_next_call={
|
|
363
673
|
"tool_name": "app_flow_plan",
|
|
364
674
|
"arguments": {
|
|
@@ -371,7 +681,12 @@ class AiBuilderTools(ToolBase):
|
|
|
371
681
|
},
|
|
372
682
|
},
|
|
373
683
|
)
|
|
374
|
-
return
|
|
684
|
+
return _safe_tool_call(
|
|
685
|
+
lambda: self._facade.app_flow_plan(profile=profile, request=request),
|
|
686
|
+
error_code="FLOW_PLAN_FAILED",
|
|
687
|
+
normalized_args=request.model_dump(mode="json"),
|
|
688
|
+
suggested_next_call={"tool_name": "app_flow_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
689
|
+
)
|
|
375
690
|
|
|
376
691
|
def app_views_plan(
|
|
377
692
|
self,
|
|
@@ -394,6 +709,8 @@ class AiBuilderTools(ToolBase):
|
|
|
394
709
|
except ValidationError as exc:
|
|
395
710
|
return _validation_failure(
|
|
396
711
|
str(exc),
|
|
712
|
+
tool_name="app_views_plan",
|
|
713
|
+
exc=exc,
|
|
397
714
|
suggested_next_call={
|
|
398
715
|
"tool_name": "app_views_plan",
|
|
399
716
|
"arguments": {
|
|
@@ -405,7 +722,12 @@ class AiBuilderTools(ToolBase):
|
|
|
405
722
|
},
|
|
406
723
|
},
|
|
407
724
|
)
|
|
408
|
-
return
|
|
725
|
+
return _safe_tool_call(
|
|
726
|
+
lambda: self._facade.app_views_plan(profile=profile, request=request),
|
|
727
|
+
error_code="VIEWS_PLAN_FAILED",
|
|
728
|
+
normalized_args=request.model_dump(mode="json"),
|
|
729
|
+
suggested_next_call={"tool_name": "app_views_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
730
|
+
)
|
|
409
731
|
|
|
410
732
|
def app_schema_apply(
|
|
411
733
|
self,
|
|
@@ -416,6 +738,7 @@ class AiBuilderTools(ToolBase):
|
|
|
416
738
|
app_name: str = "",
|
|
417
739
|
app_title: str = "",
|
|
418
740
|
create_if_missing: bool = False,
|
|
741
|
+
publish: bool = True,
|
|
419
742
|
add_fields: list[JSONObject],
|
|
420
743
|
update_fields: list[JSONObject],
|
|
421
744
|
remove_fields: list[JSONObject],
|
|
@@ -428,6 +751,8 @@ class AiBuilderTools(ToolBase):
|
|
|
428
751
|
except ValidationError as exc:
|
|
429
752
|
return _validation_failure(
|
|
430
753
|
str(exc),
|
|
754
|
+
tool_name="app_schema_apply",
|
|
755
|
+
exc=exc,
|
|
431
756
|
suggested_next_call={
|
|
432
757
|
"tool_name": "app_schema_apply",
|
|
433
758
|
"arguments": {
|
|
@@ -442,18 +767,45 @@ class AiBuilderTools(ToolBase):
|
|
|
442
767
|
},
|
|
443
768
|
},
|
|
444
769
|
)
|
|
445
|
-
|
|
770
|
+
normalized_args = {
|
|
771
|
+
"app_key": app_key,
|
|
772
|
+
"package_tag_id": package_tag_id,
|
|
773
|
+
"app_name": effective_app_name,
|
|
774
|
+
"create_if_missing": create_if_missing,
|
|
775
|
+
"publish": publish,
|
|
776
|
+
"add_fields": [patch.model_dump(mode="json") for patch in parsed_add],
|
|
777
|
+
"update_fields": [patch.model_dump(mode="json") for patch in parsed_update],
|
|
778
|
+
"remove_fields": [patch.model_dump(mode="json") for patch in parsed_remove],
|
|
779
|
+
}
|
|
780
|
+
result = _safe_tool_call(
|
|
781
|
+
lambda: self._facade.app_schema_apply(
|
|
782
|
+
profile=profile,
|
|
783
|
+
app_key=app_key,
|
|
784
|
+
package_tag_id=package_tag_id,
|
|
785
|
+
app_name=effective_app_name,
|
|
786
|
+
create_if_missing=create_if_missing,
|
|
787
|
+
publish=publish,
|
|
788
|
+
add_fields=parsed_add,
|
|
789
|
+
update_fields=parsed_update,
|
|
790
|
+
remove_fields=parsed_remove,
|
|
791
|
+
),
|
|
792
|
+
error_code="SCHEMA_APPLY_FAILED",
|
|
793
|
+
normalized_args=normalized_args,
|
|
794
|
+
suggested_next_call={"tool_name": "app_schema_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
795
|
+
)
|
|
796
|
+
return self._retry_after_self_lock_release(profile=profile, result=result, retry_call=lambda: self._facade.app_schema_apply(
|
|
446
797
|
profile=profile,
|
|
447
798
|
app_key=app_key,
|
|
448
799
|
package_tag_id=package_tag_id,
|
|
449
800
|
app_name=effective_app_name,
|
|
450
801
|
create_if_missing=create_if_missing,
|
|
802
|
+
publish=publish,
|
|
451
803
|
add_fields=parsed_add,
|
|
452
804
|
update_fields=parsed_update,
|
|
453
805
|
remove_fields=parsed_remove,
|
|
454
|
-
)
|
|
806
|
+
))
|
|
455
807
|
|
|
456
|
-
def app_layout_apply(self, *, profile: str, app_key: str, mode: str = "merge", sections: list[JSONObject]) -> JSONObject:
|
|
808
|
+
def app_layout_apply(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
457
809
|
try:
|
|
458
810
|
normalized_mode = str(mode or "merge").strip().lower()
|
|
459
811
|
if normalized_mode in {"overwrite"}:
|
|
@@ -463,6 +815,7 @@ class AiBuilderTools(ToolBase):
|
|
|
463
815
|
except ValueError:
|
|
464
816
|
return _validation_failure(
|
|
465
817
|
"mode must be one of: merge, replace",
|
|
818
|
+
tool_name="app_layout_apply",
|
|
466
819
|
suggested_next_call={
|
|
467
820
|
"tool_name": "app_layout_apply",
|
|
468
821
|
"arguments": {
|
|
@@ -476,6 +829,8 @@ class AiBuilderTools(ToolBase):
|
|
|
476
829
|
except ValidationError as exc:
|
|
477
830
|
return _validation_failure(
|
|
478
831
|
str(exc),
|
|
832
|
+
tool_name="app_layout_apply",
|
|
833
|
+
exc=exc,
|
|
479
834
|
suggested_next_call={
|
|
480
835
|
"tool_name": "app_layout_apply",
|
|
481
836
|
"arguments": {
|
|
@@ -486,7 +841,29 @@ class AiBuilderTools(ToolBase):
|
|
|
486
841
|
},
|
|
487
842
|
},
|
|
488
843
|
)
|
|
489
|
-
|
|
844
|
+
normalized_args = {
|
|
845
|
+
"app_key": app_key,
|
|
846
|
+
"mode": parsed_mode.value,
|
|
847
|
+
"publish": publish,
|
|
848
|
+
"sections": [section.model_dump(mode="json") for section in parsed_sections],
|
|
849
|
+
}
|
|
850
|
+
result = _safe_tool_call(
|
|
851
|
+
lambda: self._facade.app_layout_apply(profile=profile, app_key=app_key, mode=parsed_mode, publish=publish, sections=parsed_sections),
|
|
852
|
+
error_code="LAYOUT_APPLY_FAILED",
|
|
853
|
+
normalized_args=normalized_args,
|
|
854
|
+
suggested_next_call={"tool_name": "app_layout_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
855
|
+
)
|
|
856
|
+
return self._retry_after_self_lock_release(
|
|
857
|
+
profile=profile,
|
|
858
|
+
result=result,
|
|
859
|
+
retry_call=lambda: self._facade.app_layout_apply(
|
|
860
|
+
profile=profile,
|
|
861
|
+
app_key=app_key,
|
|
862
|
+
mode=parsed_mode,
|
|
863
|
+
publish=publish,
|
|
864
|
+
sections=parsed_sections,
|
|
865
|
+
),
|
|
866
|
+
)
|
|
490
867
|
|
|
491
868
|
def app_flow_apply(
|
|
492
869
|
self,
|
|
@@ -494,6 +871,7 @@ class AiBuilderTools(ToolBase):
|
|
|
494
871
|
profile: str,
|
|
495
872
|
app_key: str,
|
|
496
873
|
mode: str = "replace",
|
|
874
|
+
publish: bool = True,
|
|
497
875
|
nodes: list[JSONObject],
|
|
498
876
|
transitions: list[JSONObject],
|
|
499
877
|
) -> JSONObject:
|
|
@@ -510,6 +888,8 @@ class AiBuilderTools(ToolBase):
|
|
|
510
888
|
except ValidationError as exc:
|
|
511
889
|
return _validation_failure(
|
|
512
890
|
str(exc),
|
|
891
|
+
tool_name="app_flow_apply",
|
|
892
|
+
exc=exc,
|
|
513
893
|
suggested_next_call={
|
|
514
894
|
"tool_name": "app_flow_apply",
|
|
515
895
|
"arguments": {
|
|
@@ -521,12 +901,37 @@ class AiBuilderTools(ToolBase):
|
|
|
521
901
|
},
|
|
522
902
|
},
|
|
523
903
|
)
|
|
524
|
-
|
|
904
|
+
normalized_args = {
|
|
905
|
+
"app_key": request.app_key,
|
|
906
|
+
"mode": request.mode,
|
|
907
|
+
"publish": publish,
|
|
908
|
+
"nodes": [node.model_dump(mode="json") for node in request.nodes],
|
|
909
|
+
"transitions": [transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
|
|
910
|
+
}
|
|
911
|
+
result = _safe_tool_call(
|
|
912
|
+
lambda: self._facade.app_flow_apply(
|
|
913
|
+
profile=profile,
|
|
914
|
+
app_key=request.app_key,
|
|
915
|
+
mode=request.mode,
|
|
916
|
+
publish=publish,
|
|
917
|
+
nodes=[node.model_dump(mode="json") for node in request.nodes],
|
|
918
|
+
transitions=[transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
|
|
919
|
+
),
|
|
920
|
+
error_code="FLOW_APPLY_FAILED",
|
|
921
|
+
normalized_args=normalized_args,
|
|
922
|
+
suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
923
|
+
)
|
|
924
|
+
return self._retry_after_self_lock_release(
|
|
525
925
|
profile=profile,
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
926
|
+
result=result,
|
|
927
|
+
retry_call=lambda: self._facade.app_flow_apply(
|
|
928
|
+
profile=profile,
|
|
929
|
+
app_key=request.app_key,
|
|
930
|
+
mode=request.mode,
|
|
931
|
+
publish=publish,
|
|
932
|
+
nodes=[node.model_dump(mode="json") for node in request.nodes],
|
|
933
|
+
transitions=[transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
|
|
934
|
+
),
|
|
530
935
|
)
|
|
531
936
|
|
|
532
937
|
def app_views_apply(
|
|
@@ -534,6 +939,7 @@ class AiBuilderTools(ToolBase):
|
|
|
534
939
|
*,
|
|
535
940
|
profile: str,
|
|
536
941
|
app_key: str,
|
|
942
|
+
publish: bool = True,
|
|
537
943
|
upsert_views: list[JSONObject],
|
|
538
944
|
remove_views: list[str],
|
|
539
945
|
) -> JSONObject:
|
|
@@ -542,6 +948,8 @@ class AiBuilderTools(ToolBase):
|
|
|
542
948
|
except ValidationError as exc:
|
|
543
949
|
return _validation_failure(
|
|
544
950
|
str(exc),
|
|
951
|
+
tool_name="app_views_apply",
|
|
952
|
+
exc=exc,
|
|
545
953
|
suggested_next_call={
|
|
546
954
|
"tool_name": "app_views_apply",
|
|
547
955
|
"arguments": {
|
|
@@ -552,13 +960,126 @@ class AiBuilderTools(ToolBase):
|
|
|
552
960
|
},
|
|
553
961
|
},
|
|
554
962
|
)
|
|
555
|
-
|
|
963
|
+
normalized_args = {
|
|
964
|
+
"app_key": app_key,
|
|
965
|
+
"publish": publish,
|
|
966
|
+
"upsert_views": [view.model_dump(mode="json") for view in parsed_views],
|
|
967
|
+
"remove_views": list(remove_views),
|
|
968
|
+
}
|
|
969
|
+
result = _safe_tool_call(
|
|
970
|
+
lambda: self._facade.app_views_apply(profile=profile, app_key=app_key, publish=publish, upsert_views=parsed_views, remove_views=remove_views),
|
|
971
|
+
error_code="VIEWS_APPLY_FAILED",
|
|
972
|
+
normalized_args=normalized_args,
|
|
973
|
+
suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
974
|
+
)
|
|
975
|
+
return self._retry_after_self_lock_release(
|
|
976
|
+
profile=profile,
|
|
977
|
+
result=result,
|
|
978
|
+
retry_call=lambda: self._facade.app_views_apply(
|
|
979
|
+
profile=profile,
|
|
980
|
+
app_key=app_key,
|
|
981
|
+
publish=publish,
|
|
982
|
+
upsert_views=parsed_views,
|
|
983
|
+
remove_views=remove_views,
|
|
984
|
+
),
|
|
985
|
+
)
|
|
556
986
|
|
|
557
987
|
def app_publish_verify(self, *, profile: str, app_key: str, expected_package_tag_id: int | None = None) -> JSONObject:
|
|
558
|
-
|
|
988
|
+
normalized_args = {"app_key": app_key, "expected_package_tag_id": expected_package_tag_id}
|
|
989
|
+
result = _safe_tool_call(
|
|
990
|
+
lambda: self._facade.app_publish_verify(profile=profile, app_key=app_key, expected_package_tag_id=expected_package_tag_id),
|
|
991
|
+
error_code="PUBLISH_VERIFY_FAILED",
|
|
992
|
+
normalized_args=normalized_args,
|
|
993
|
+
suggested_next_call={"tool_name": "app_publish_verify", "arguments": {"profile": profile, **normalized_args}},
|
|
994
|
+
)
|
|
995
|
+
return self._retry_after_self_lock_release(
|
|
996
|
+
profile=profile,
|
|
997
|
+
result=result,
|
|
998
|
+
retry_call=lambda: self._facade.app_publish_verify(
|
|
999
|
+
profile=profile,
|
|
1000
|
+
app_key=app_key,
|
|
1001
|
+
expected_package_tag_id=expected_package_tag_id,
|
|
1002
|
+
),
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
def _retry_after_self_lock_release(self, *, profile: str, result: JSONObject, retry_call) -> JSONObject:
|
|
1006
|
+
if not isinstance(result, dict) or result.get("status") != "failed" or result.get("error_code") != "APP_EDIT_LOCKED":
|
|
1007
|
+
return result
|
|
1008
|
+
suggested = result.get("suggested_next_call")
|
|
1009
|
+
if not isinstance(suggested, dict) or suggested.get("tool_name") != "app_release_edit_lock_if_mine":
|
|
1010
|
+
return result
|
|
1011
|
+
arguments = suggested.get("arguments")
|
|
1012
|
+
if not isinstance(arguments, dict):
|
|
1013
|
+
return result
|
|
1014
|
+
app_key = str(arguments.get("app_key") or "")
|
|
1015
|
+
lock_owner_email = str(arguments.get("lock_owner_email") or "")
|
|
1016
|
+
lock_owner_name = str(arguments.get("lock_owner_name") or "")
|
|
1017
|
+
release_attempts: list[JSONObject] = []
|
|
1018
|
+
retried: JSONObject = result
|
|
1019
|
+
for _ in range(3):
|
|
1020
|
+
release_result = self.app_release_edit_lock_if_mine(
|
|
1021
|
+
profile=profile,
|
|
1022
|
+
app_key=app_key,
|
|
1023
|
+
lock_owner_email=lock_owner_email,
|
|
1024
|
+
lock_owner_name=lock_owner_name,
|
|
1025
|
+
)
|
|
1026
|
+
release_attempts.append(release_result)
|
|
1027
|
+
if not isinstance(release_result, dict) or release_result.get("status") != "success":
|
|
1028
|
+
result.setdefault("details", {})
|
|
1029
|
+
if isinstance(result["details"], dict):
|
|
1030
|
+
result["details"]["edit_lock_release_result"] = release_result
|
|
1031
|
+
result["details"]["edit_lock_release_attempts"] = release_attempts
|
|
1032
|
+
return result
|
|
1033
|
+
retried = retry_call()
|
|
1034
|
+
if not (
|
|
1035
|
+
isinstance(retried, dict)
|
|
1036
|
+
and retried.get("status") == "failed"
|
|
1037
|
+
and retried.get("error_code") == "APP_EDIT_LOCKED"
|
|
1038
|
+
):
|
|
1039
|
+
break
|
|
1040
|
+
time.sleep(0.2)
|
|
1041
|
+
if (
|
|
1042
|
+
isinstance(retried, dict)
|
|
1043
|
+
and retried.get("status") == "failed"
|
|
1044
|
+
and retried.get("error_code") == "APP_EDIT_LOCKED"
|
|
1045
|
+
):
|
|
1046
|
+
retried = {
|
|
1047
|
+
**retried,
|
|
1048
|
+
"error_code": "PERSISTENT_SELF_LOCK",
|
|
1049
|
+
"message": "app remains locked by the current user's active editor session after repeated forced release attempts",
|
|
1050
|
+
"recoverable": True,
|
|
1051
|
+
"suggested_next_call": None,
|
|
1052
|
+
}
|
|
1053
|
+
if isinstance(retried, dict):
|
|
1054
|
+
retried.setdefault("details", {})
|
|
1055
|
+
if isinstance(retried["details"], dict):
|
|
1056
|
+
retried["details"]["edit_lock_release_result"] = release_attempts[-1] if release_attempts else None
|
|
1057
|
+
retried["details"]["edit_lock_release_attempts"] = release_attempts
|
|
1058
|
+
retried["edit_lock_released"] = bool(release_attempts)
|
|
1059
|
+
retried["retried_after_edit_lock_release"] = True
|
|
1060
|
+
return retried
|
|
559
1061
|
|
|
560
1062
|
|
|
561
|
-
def _validation_failure(
|
|
1063
|
+
def _validation_failure(
|
|
1064
|
+
detail: str,
|
|
1065
|
+
*,
|
|
1066
|
+
tool_name: str | None = None,
|
|
1067
|
+
exc: ValidationError | None = None,
|
|
1068
|
+
suggested_next_call: JSONObject | None = None,
|
|
1069
|
+
) -> JSONObject:
|
|
1070
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
|
|
1071
|
+
reason_path = None
|
|
1072
|
+
if exc is not None:
|
|
1073
|
+
errors = exc.errors()
|
|
1074
|
+
if errors:
|
|
1075
|
+
loc = errors[0].get("loc")
|
|
1076
|
+
if isinstance(loc, (tuple, list)):
|
|
1077
|
+
reason_path = ".".join(str(part) for part in loc)
|
|
1078
|
+
canonical_arguments = None
|
|
1079
|
+
if isinstance(suggested_next_call, dict):
|
|
1080
|
+
arguments = suggested_next_call.get("arguments")
|
|
1081
|
+
if isinstance(arguments, dict):
|
|
1082
|
+
canonical_arguments = arguments
|
|
562
1083
|
return {
|
|
563
1084
|
"status": "failed",
|
|
564
1085
|
"error_code": "VALIDATION_ERROR",
|
|
@@ -566,8 +1087,16 @@ def _validation_failure(detail: str, *, suggested_next_call: JSONObject | None =
|
|
|
566
1087
|
"message": detail,
|
|
567
1088
|
"normalized_args": {},
|
|
568
1089
|
"missing_fields": [],
|
|
569
|
-
"allowed_values": {},
|
|
570
|
-
"details": {
|
|
1090
|
+
"allowed_values": deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {},
|
|
1091
|
+
"details": {
|
|
1092
|
+
"validation_detail": detail,
|
|
1093
|
+
"reason_path": reason_path,
|
|
1094
|
+
"allowed_keys": deepcopy(contract.get("allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
1095
|
+
"canonical_arguments": canonical_arguments,
|
|
1096
|
+
"section_allowed_keys": deepcopy(contract.get("section_allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
1097
|
+
"section_aliases": deepcopy(contract.get("section_aliases", {})) if isinstance(contract, dict) else {},
|
|
1098
|
+
"minimal_section_example": deepcopy(contract.get("minimal_section_example")) if isinstance(contract, dict) else None,
|
|
1099
|
+
},
|
|
571
1100
|
"suggested_next_call": suggested_next_call,
|
|
572
1101
|
"request_id": None,
|
|
573
1102
|
"backend_code": None,
|
|
@@ -575,3 +1104,482 @@ def _validation_failure(detail: str, *, suggested_next_call: JSONObject | None =
|
|
|
575
1104
|
"noop": False,
|
|
576
1105
|
"verification": {},
|
|
577
1106
|
}
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
def _safe_tool_call(
|
|
1110
|
+
call,
|
|
1111
|
+
*,
|
|
1112
|
+
error_code: str,
|
|
1113
|
+
normalized_args: JSONObject,
|
|
1114
|
+
suggested_next_call: JSONObject | None,
|
|
1115
|
+
) -> JSONObject:
|
|
1116
|
+
try:
|
|
1117
|
+
return call()
|
|
1118
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
1119
|
+
api_error = _coerce_api_error(error)
|
|
1120
|
+
public_http_status = None if api_error.http_status == 404 else api_error.http_status
|
|
1121
|
+
return {
|
|
1122
|
+
"status": "failed",
|
|
1123
|
+
"error_code": error_code,
|
|
1124
|
+
"recoverable": True,
|
|
1125
|
+
"message": _public_error_message(error_code, api_error),
|
|
1126
|
+
"normalized_args": normalized_args,
|
|
1127
|
+
"missing_fields": [],
|
|
1128
|
+
"allowed_values": {},
|
|
1129
|
+
"details": {
|
|
1130
|
+
"transport_error": {
|
|
1131
|
+
"http_status": api_error.http_status,
|
|
1132
|
+
"backend_code": api_error.backend_code,
|
|
1133
|
+
"category": api_error.category,
|
|
1134
|
+
}
|
|
1135
|
+
},
|
|
1136
|
+
"suggested_next_call": suggested_next_call,
|
|
1137
|
+
"request_id": api_error.request_id,
|
|
1138
|
+
"backend_code": api_error.backend_code,
|
|
1139
|
+
"http_status": public_http_status,
|
|
1140
|
+
"noop": False,
|
|
1141
|
+
"verification": {},
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
def _coerce_api_error(error: Exception) -> QingflowApiError:
|
|
1146
|
+
if isinstance(error, QingflowApiError):
|
|
1147
|
+
return error
|
|
1148
|
+
if isinstance(error, RuntimeError):
|
|
1149
|
+
try:
|
|
1150
|
+
payload = json.loads(str(error))
|
|
1151
|
+
except json.JSONDecodeError:
|
|
1152
|
+
payload = None
|
|
1153
|
+
if isinstance(payload, dict) and payload.get("category") and payload.get("message"):
|
|
1154
|
+
details = payload.get("details")
|
|
1155
|
+
return QingflowApiError(
|
|
1156
|
+
category=str(payload.get("category")),
|
|
1157
|
+
message=str(payload.get("message")),
|
|
1158
|
+
backend_code=payload.get("backend_code"),
|
|
1159
|
+
request_id=payload.get("request_id"),
|
|
1160
|
+
http_status=payload.get("http_status"),
|
|
1161
|
+
details=details if isinstance(details, dict) else None,
|
|
1162
|
+
)
|
|
1163
|
+
return QingflowApiError(category="runtime", message=str(error))
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
def _public_error_message(error_code: str, error: QingflowApiError) -> str:
|
|
1167
|
+
if error.backend_code == 40074 or error_code == "APP_EDIT_LOCKED":
|
|
1168
|
+
return "app is currently locked by another active editor session"
|
|
1169
|
+
if error.http_status != 404:
|
|
1170
|
+
return error.message
|
|
1171
|
+
mapping = {
|
|
1172
|
+
"PACKAGE_LIST_FAILED": "package list is unavailable in the current route",
|
|
1173
|
+
"PACKAGE_RESOLVE_FAILED": "package resolution is unavailable in the current route",
|
|
1174
|
+
"PACKAGE_ATTACH_FAILED": "package attachment could not be verified in the current route",
|
|
1175
|
+
"APP_RESOLVE_FAILED": "app resolution is unavailable in the current route",
|
|
1176
|
+
"APP_READ_FAILED": "app base or schema is unavailable in the current route",
|
|
1177
|
+
"FIELDS_READ_FAILED": "app fields are unavailable in the current route",
|
|
1178
|
+
"LAYOUT_READ_FAILED": "layout resource is unavailable for this app in the current route",
|
|
1179
|
+
"VIEWS_READ_FAILED": "views resource is unavailable for this app in the current route",
|
|
1180
|
+
"FLOW_READ_FAILED": "workflow resource is unavailable for this app in the current route",
|
|
1181
|
+
"SCHEMA_PLAN_FAILED": "schema planning could not load the required app state in the current route",
|
|
1182
|
+
"LAYOUT_PLAN_FAILED": "layout planning could not load the required app state in the current route",
|
|
1183
|
+
"FLOW_PLAN_FAILED": "flow planning could not load the required app state in the current route",
|
|
1184
|
+
"VIEWS_PLAN_FAILED": "views planning could not load the required app state in the current route",
|
|
1185
|
+
"SCHEMA_APPLY_FAILED": "schema apply could not complete because the app route or readback is unavailable",
|
|
1186
|
+
"LAYOUT_APPLY_FAILED": "layout apply could not complete because the layout route or readback is unavailable",
|
|
1187
|
+
"FLOW_APPLY_FAILED": "flow apply could not complete because the workflow route or readback is unavailable",
|
|
1188
|
+
"VIEWS_APPLY_FAILED": "views apply could not complete because the views route or readback is unavailable",
|
|
1189
|
+
"PUBLISH_VERIFY_FAILED": "publish verification is unavailable in the current route",
|
|
1190
|
+
}
|
|
1191
|
+
return mapping.get(error_code, "requested builder resource is unavailable in the current route")
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
_BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
1195
|
+
"member_search": {
|
|
1196
|
+
"allowed_keys": ["query", "page_num", "page_size", "contain_disable"],
|
|
1197
|
+
"aliases": {},
|
|
1198
|
+
"allowed_values": {},
|
|
1199
|
+
"minimal_example": {
|
|
1200
|
+
"profile": "default",
|
|
1201
|
+
"query": "严琪东",
|
|
1202
|
+
"page_num": 1,
|
|
1203
|
+
"page_size": 20,
|
|
1204
|
+
"contain_disable": False,
|
|
1205
|
+
},
|
|
1206
|
+
},
|
|
1207
|
+
"role_search": {
|
|
1208
|
+
"allowed_keys": ["keyword", "page_num", "page_size"],
|
|
1209
|
+
"aliases": {},
|
|
1210
|
+
"allowed_values": {},
|
|
1211
|
+
"minimal_example": {
|
|
1212
|
+
"profile": "default",
|
|
1213
|
+
"keyword": "项目经理",
|
|
1214
|
+
"page_num": 1,
|
|
1215
|
+
"page_size": 20,
|
|
1216
|
+
},
|
|
1217
|
+
},
|
|
1218
|
+
"role_create": {
|
|
1219
|
+
"allowed_keys": ["role_name", "member_uids", "member_emails", "member_names", "role_icon"],
|
|
1220
|
+
"aliases": {},
|
|
1221
|
+
"allowed_values": {},
|
|
1222
|
+
"minimal_example": {
|
|
1223
|
+
"profile": "default",
|
|
1224
|
+
"role_name": "研发负责人",
|
|
1225
|
+
"member_names": ["严琪东"],
|
|
1226
|
+
"member_uids": [],
|
|
1227
|
+
"member_emails": [],
|
|
1228
|
+
"role_icon": "ex-user-outlined",
|
|
1229
|
+
},
|
|
1230
|
+
},
|
|
1231
|
+
"app_schema_plan": {
|
|
1232
|
+
"allowed_keys": ["app_key", "package_tag_id", "app_name", "create_if_missing", "add_fields", "update_fields", "remove_fields"],
|
|
1233
|
+
"aliases": {
|
|
1234
|
+
"app_title": "app_name",
|
|
1235
|
+
"title": "app_name",
|
|
1236
|
+
"field.title": "field.name",
|
|
1237
|
+
"field.label": "field.name",
|
|
1238
|
+
"field.fields": "field.subfields",
|
|
1239
|
+
"field.type_id": "field.type",
|
|
1240
|
+
},
|
|
1241
|
+
"allowed_values": {
|
|
1242
|
+
"field.type": [member.value for member in PublicFieldType],
|
|
1243
|
+
"field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
|
|
1244
|
+
},
|
|
1245
|
+
"minimal_example": {
|
|
1246
|
+
"profile": "default",
|
|
1247
|
+
"app_name": "研发项目管理",
|
|
1248
|
+
"package_tag_id": 1001,
|
|
1249
|
+
"create_if_missing": True,
|
|
1250
|
+
"add_fields": [{"name": "项目名称", "type": "text"}],
|
|
1251
|
+
"update_fields": [],
|
|
1252
|
+
"remove_fields": [],
|
|
1253
|
+
},
|
|
1254
|
+
},
|
|
1255
|
+
"app_schema_apply": {
|
|
1256
|
+
"allowed_keys": ["app_key", "package_tag_id", "app_name", "create_if_missing", "publish", "add_fields", "update_fields", "remove_fields"],
|
|
1257
|
+
"aliases": {
|
|
1258
|
+
"app_title": "app_name",
|
|
1259
|
+
"title": "app_name",
|
|
1260
|
+
"field.title": "field.name",
|
|
1261
|
+
"field.label": "field.name",
|
|
1262
|
+
"field.fields": "field.subfields",
|
|
1263
|
+
"field.type_id": "field.type",
|
|
1264
|
+
},
|
|
1265
|
+
"allowed_values": {
|
|
1266
|
+
"field.type": [member.value for member in PublicFieldType],
|
|
1267
|
+
"field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
|
|
1268
|
+
},
|
|
1269
|
+
"minimal_example": {
|
|
1270
|
+
"profile": "default",
|
|
1271
|
+
"app_name": "研发项目管理",
|
|
1272
|
+
"package_tag_id": 1001,
|
|
1273
|
+
"create_if_missing": True,
|
|
1274
|
+
"publish": True,
|
|
1275
|
+
"add_fields": [{"name": "项目名称", "type": "text"}],
|
|
1276
|
+
"update_fields": [],
|
|
1277
|
+
"remove_fields": [],
|
|
1278
|
+
},
|
|
1279
|
+
},
|
|
1280
|
+
"app_layout_plan": {
|
|
1281
|
+
"allowed_keys": ["app_key", "mode", "sections", "preset"],
|
|
1282
|
+
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
1283
|
+
"section_allowed_keys": ["section_id", "title", "rows"],
|
|
1284
|
+
"section_aliases": {
|
|
1285
|
+
"name": "title",
|
|
1286
|
+
"sectionId": "section_id",
|
|
1287
|
+
"fields": "rows",
|
|
1288
|
+
"field_ids": "rows",
|
|
1289
|
+
"columns": "rows_chunk_size",
|
|
1290
|
+
},
|
|
1291
|
+
"allowed_values": {"mode": [member.value for member in LayoutApplyMode], "preset": [member.value for member in LayoutPreset]},
|
|
1292
|
+
"minimal_section_example": {"title": "基础信息", "rows": [["字段A", "字段B"]]},
|
|
1293
|
+
"minimal_example": {
|
|
1294
|
+
"profile": "default",
|
|
1295
|
+
"app_key": "APP_KEY",
|
|
1296
|
+
"mode": "merge",
|
|
1297
|
+
"sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
1298
|
+
},
|
|
1299
|
+
"preset_example": {"profile": "default", "app_key": "APP_KEY", "mode": "merge", "preset": "balanced", "sections": []},
|
|
1300
|
+
},
|
|
1301
|
+
"app_layout_apply": {
|
|
1302
|
+
"allowed_keys": ["app_key", "mode", "publish", "sections"],
|
|
1303
|
+
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
1304
|
+
"section_allowed_keys": ["section_id", "title", "rows"],
|
|
1305
|
+
"section_aliases": {
|
|
1306
|
+
"name": "title",
|
|
1307
|
+
"sectionId": "section_id",
|
|
1308
|
+
"fields": "rows",
|
|
1309
|
+
"field_ids": "rows",
|
|
1310
|
+
"columns": "rows_chunk_size",
|
|
1311
|
+
},
|
|
1312
|
+
"allowed_values": {"mode": [member.value for member in LayoutApplyMode]},
|
|
1313
|
+
"minimal_section_example": {"title": "基础信息", "rows": [["字段A", "字段B"]]},
|
|
1314
|
+
"minimal_example": {
|
|
1315
|
+
"profile": "default",
|
|
1316
|
+
"app_key": "APP_KEY",
|
|
1317
|
+
"mode": "merge",
|
|
1318
|
+
"publish": True,
|
|
1319
|
+
"sections": [{"title": "基础信息", "rows": [["项目名称", "项目负责人"]]}],
|
|
1320
|
+
},
|
|
1321
|
+
},
|
|
1322
|
+
"app_flow_plan": {
|
|
1323
|
+
"allowed_keys": ["app_key", "mode", "nodes", "transitions", "preset"],
|
|
1324
|
+
"aliases": {
|
|
1325
|
+
"overwrite": "replace",
|
|
1326
|
+
"base_preset": "preset",
|
|
1327
|
+
"default_approval": "basic_approval",
|
|
1328
|
+
"node.role_names": "node.assignees.role_names",
|
|
1329
|
+
"node.role_ids": "node.assignees.role_ids",
|
|
1330
|
+
"node.member_names": "node.assignees.member_names",
|
|
1331
|
+
"node.member_emails": "node.assignees.member_emails",
|
|
1332
|
+
"node.member_uids": "node.assignees.member_uids",
|
|
1333
|
+
"node.editable_fields": "node.permissions.editable_fields",
|
|
1334
|
+
"node.filters": "node.conditions",
|
|
1335
|
+
"node.rules": "node.conditions",
|
|
1336
|
+
"node.conditionRules": "node.condition_groups",
|
|
1337
|
+
"node.condition.field": "node.conditions[].field_name",
|
|
1338
|
+
"node.condition.name": "node.conditions[].field_name",
|
|
1339
|
+
"node.condition.op": "node.conditions[].operator",
|
|
1340
|
+
"default_approval": "basic_approval",
|
|
1341
|
+
},
|
|
1342
|
+
"allowed_values": {
|
|
1343
|
+
"mode": ["replace"],
|
|
1344
|
+
"preset": [member.value for member in FlowPreset],
|
|
1345
|
+
"node.type": [member.value for member in PublicFlowNodeType],
|
|
1346
|
+
"node.condition.operator": [member.value for member in FlowConditionOperator],
|
|
1347
|
+
},
|
|
1348
|
+
"dependency_hints": [
|
|
1349
|
+
"approval-style workflows require an explicit status field",
|
|
1350
|
+
"approve/fill/copy nodes require at least one assignee",
|
|
1351
|
+
],
|
|
1352
|
+
"minimal_example": {
|
|
1353
|
+
"profile": "default",
|
|
1354
|
+
"app_key": "APP_KEY",
|
|
1355
|
+
"mode": "replace",
|
|
1356
|
+
"preset": "basic_approval",
|
|
1357
|
+
"nodes": [
|
|
1358
|
+
{
|
|
1359
|
+
"id": "approve_1",
|
|
1360
|
+
"type": "approve",
|
|
1361
|
+
"name": "部门审批",
|
|
1362
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
1363
|
+
"permissions": {"editable_fields": ["状态", "审批意见"]},
|
|
1364
|
+
}
|
|
1365
|
+
],
|
|
1366
|
+
"transitions": [],
|
|
1367
|
+
},
|
|
1368
|
+
"branch_example": {
|
|
1369
|
+
"profile": "default",
|
|
1370
|
+
"app_key": "APP_KEY",
|
|
1371
|
+
"mode": "replace",
|
|
1372
|
+
"nodes": [
|
|
1373
|
+
{"id": "start", "type": "start", "name": "发起"},
|
|
1374
|
+
{"id": "route", "type": "branch", "name": "金额分支"},
|
|
1375
|
+
{
|
|
1376
|
+
"id": "high_amount",
|
|
1377
|
+
"type": "condition",
|
|
1378
|
+
"name": "金额大于等于一万",
|
|
1379
|
+
"conditions": [{"field_name": "预计金额", "operator": "gte", "value": 10000}],
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
"id": "approve_finance",
|
|
1383
|
+
"type": "approve",
|
|
1384
|
+
"name": "财务审批",
|
|
1385
|
+
"assignees": {"role_names": ["财务负责人"]},
|
|
1386
|
+
},
|
|
1387
|
+
{"id": "default_lane", "type": "condition", "name": "其他情况"},
|
|
1388
|
+
{
|
|
1389
|
+
"id": "approve_manager",
|
|
1390
|
+
"type": "approve",
|
|
1391
|
+
"name": "部门审批",
|
|
1392
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
1393
|
+
},
|
|
1394
|
+
{"id": "end", "type": "end", "name": "结束"},
|
|
1395
|
+
],
|
|
1396
|
+
"transitions": [
|
|
1397
|
+
{"from": "start", "to": "route"},
|
|
1398
|
+
{"from": "route", "to": "high_amount"},
|
|
1399
|
+
{"from": "high_amount", "to": "approve_finance"},
|
|
1400
|
+
{"from": "route", "to": "default_lane"},
|
|
1401
|
+
{"from": "default_lane", "to": "approve_manager"},
|
|
1402
|
+
{"from": "approve_finance", "to": "end"},
|
|
1403
|
+
{"from": "approve_manager", "to": "end"},
|
|
1404
|
+
],
|
|
1405
|
+
},
|
|
1406
|
+
},
|
|
1407
|
+
"app_flow_apply": {
|
|
1408
|
+
"allowed_keys": ["app_key", "mode", "publish", "nodes", "transitions"],
|
|
1409
|
+
"aliases": {
|
|
1410
|
+
"overwrite": "replace",
|
|
1411
|
+
"node.role_names": "node.assignees.role_names",
|
|
1412
|
+
"node.role_ids": "node.assignees.role_ids",
|
|
1413
|
+
"node.member_names": "node.assignees.member_names",
|
|
1414
|
+
"node.member_emails": "node.assignees.member_emails",
|
|
1415
|
+
"node.member_uids": "node.assignees.member_uids",
|
|
1416
|
+
"node.editable_fields": "node.permissions.editable_fields",
|
|
1417
|
+
"node.filters": "node.conditions",
|
|
1418
|
+
"node.rules": "node.conditions",
|
|
1419
|
+
"node.conditionRules": "node.condition_groups",
|
|
1420
|
+
"node.condition.field": "node.conditions[].field_name",
|
|
1421
|
+
"node.condition.name": "node.conditions[].field_name",
|
|
1422
|
+
"node.condition.op": "node.conditions[].operator",
|
|
1423
|
+
},
|
|
1424
|
+
"allowed_values": {
|
|
1425
|
+
"mode": ["replace"],
|
|
1426
|
+
"node.type": [member.value for member in PublicFlowNodeType],
|
|
1427
|
+
"node.condition.operator": [member.value for member in FlowConditionOperator],
|
|
1428
|
+
},
|
|
1429
|
+
"dependency_hints": [
|
|
1430
|
+
"approval-style workflows require an explicit status field",
|
|
1431
|
+
"approve/fill/copy nodes require at least one assignee",
|
|
1432
|
+
],
|
|
1433
|
+
"minimal_example": {
|
|
1434
|
+
"profile": "default",
|
|
1435
|
+
"app_key": "APP_KEY",
|
|
1436
|
+
"mode": "replace",
|
|
1437
|
+
"publish": True,
|
|
1438
|
+
"nodes": [
|
|
1439
|
+
{"id": "start", "type": "start", "name": "发起"},
|
|
1440
|
+
{
|
|
1441
|
+
"id": "approve_1",
|
|
1442
|
+
"type": "approve",
|
|
1443
|
+
"name": "部门审批",
|
|
1444
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
1445
|
+
"permissions": {"editable_fields": ["状态", "审批意见"]},
|
|
1446
|
+
},
|
|
1447
|
+
{"id": "end", "type": "end", "name": "结束"},
|
|
1448
|
+
],
|
|
1449
|
+
"transitions": [{"from": "start", "to": "approve_1"}, {"from": "approve_1", "to": "end"}],
|
|
1450
|
+
},
|
|
1451
|
+
"branch_example": {
|
|
1452
|
+
"profile": "default",
|
|
1453
|
+
"app_key": "APP_KEY",
|
|
1454
|
+
"mode": "replace",
|
|
1455
|
+
"publish": True,
|
|
1456
|
+
"nodes": [
|
|
1457
|
+
{"id": "start", "type": "start", "name": "发起"},
|
|
1458
|
+
{"id": "route", "type": "branch", "name": "金额分支"},
|
|
1459
|
+
{
|
|
1460
|
+
"id": "high_amount",
|
|
1461
|
+
"type": "condition",
|
|
1462
|
+
"name": "金额大于等于一万",
|
|
1463
|
+
"conditions": [{"field_name": "预计金额", "operator": "gte", "value": 10000}],
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
"id": "approve_finance",
|
|
1467
|
+
"type": "approve",
|
|
1468
|
+
"name": "财务审批",
|
|
1469
|
+
"assignees": {"role_names": ["财务负责人"]},
|
|
1470
|
+
},
|
|
1471
|
+
{"id": "default_lane", "type": "condition", "name": "其他情况"},
|
|
1472
|
+
{
|
|
1473
|
+
"id": "approve_manager",
|
|
1474
|
+
"type": "approve",
|
|
1475
|
+
"name": "部门审批",
|
|
1476
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
1477
|
+
},
|
|
1478
|
+
{"id": "end", "type": "end", "name": "结束"},
|
|
1479
|
+
],
|
|
1480
|
+
"transitions": [
|
|
1481
|
+
{"from": "start", "to": "route"},
|
|
1482
|
+
{"from": "route", "to": "high_amount"},
|
|
1483
|
+
{"from": "high_amount", "to": "approve_finance"},
|
|
1484
|
+
{"from": "route", "to": "default_lane"},
|
|
1485
|
+
{"from": "default_lane", "to": "approve_manager"},
|
|
1486
|
+
{"from": "approve_finance", "to": "end"},
|
|
1487
|
+
{"from": "approve_manager", "to": "end"},
|
|
1488
|
+
],
|
|
1489
|
+
},
|
|
1490
|
+
},
|
|
1491
|
+
"app_views_plan": {
|
|
1492
|
+
"allowed_keys": ["app_key", "upsert_views", "remove_views", "preset", "upsert_views[].view_key"],
|
|
1493
|
+
"aliases": {
|
|
1494
|
+
"fields": "columns",
|
|
1495
|
+
"column_names": "columns",
|
|
1496
|
+
"columnNames": "columns",
|
|
1497
|
+
"viewKey": "view_key",
|
|
1498
|
+
"tableView": "table",
|
|
1499
|
+
"cardView": "card",
|
|
1500
|
+
"kanban": "board",
|
|
1501
|
+
"filter_rules": "filters",
|
|
1502
|
+
"filterRules": "filters",
|
|
1503
|
+
"startField": "start_field",
|
|
1504
|
+
"endField": "end_field",
|
|
1505
|
+
"titleField": "title_field",
|
|
1506
|
+
},
|
|
1507
|
+
"allowed_values": {
|
|
1508
|
+
"preset": [member.value for member in ViewsPreset],
|
|
1509
|
+
"view.type": [member.value for member in PublicViewType],
|
|
1510
|
+
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1511
|
+
},
|
|
1512
|
+
"minimal_example": {
|
|
1513
|
+
"profile": "default",
|
|
1514
|
+
"app_key": "APP_KEY",
|
|
1515
|
+
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"]}],
|
|
1516
|
+
"remove_views": [],
|
|
1517
|
+
},
|
|
1518
|
+
"gantt_example": {
|
|
1519
|
+
"profile": "default",
|
|
1520
|
+
"app_key": "APP_KEY",
|
|
1521
|
+
"upsert_views": [
|
|
1522
|
+
{
|
|
1523
|
+
"name": "项目甘特图",
|
|
1524
|
+
"type": "gantt",
|
|
1525
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
1526
|
+
"start_field": "开始日期",
|
|
1527
|
+
"end_field": "结束日期",
|
|
1528
|
+
"title_field": "项目名称",
|
|
1529
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
1530
|
+
}
|
|
1531
|
+
],
|
|
1532
|
+
"remove_views": [],
|
|
1533
|
+
},
|
|
1534
|
+
},
|
|
1535
|
+
"app_views_apply": {
|
|
1536
|
+
"allowed_keys": ["app_key", "publish", "upsert_views", "remove_views", "upsert_views[].view_key"],
|
|
1537
|
+
"aliases": {
|
|
1538
|
+
"fields": "columns",
|
|
1539
|
+
"column_names": "columns",
|
|
1540
|
+
"columnNames": "columns",
|
|
1541
|
+
"viewKey": "view_key",
|
|
1542
|
+
"tableView": "table",
|
|
1543
|
+
"cardView": "card",
|
|
1544
|
+
"kanban": "board",
|
|
1545
|
+
"filter_rules": "filters",
|
|
1546
|
+
"filterRules": "filters",
|
|
1547
|
+
"startField": "start_field",
|
|
1548
|
+
"endField": "end_field",
|
|
1549
|
+
"titleField": "title_field",
|
|
1550
|
+
},
|
|
1551
|
+
"allowed_values": {
|
|
1552
|
+
"view.type": [member.value for member in PublicViewType],
|
|
1553
|
+
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1554
|
+
},
|
|
1555
|
+
"execution_notes": [
|
|
1556
|
+
"apply may return partial_success when some views land and others fail",
|
|
1557
|
+
"when duplicate view names exist, supply view_key to target the exact view",
|
|
1558
|
+
"read back app_read_views_summary after any failed or partial view apply",
|
|
1559
|
+
],
|
|
1560
|
+
"minimal_example": {
|
|
1561
|
+
"profile": "default",
|
|
1562
|
+
"app_key": "APP_KEY",
|
|
1563
|
+
"publish": True,
|
|
1564
|
+
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"]}],
|
|
1565
|
+
"remove_views": [],
|
|
1566
|
+
},
|
|
1567
|
+
"gantt_example": {
|
|
1568
|
+
"profile": "default",
|
|
1569
|
+
"app_key": "APP_KEY",
|
|
1570
|
+
"publish": True,
|
|
1571
|
+
"upsert_views": [
|
|
1572
|
+
{
|
|
1573
|
+
"name": "项目甘特图",
|
|
1574
|
+
"type": "gantt",
|
|
1575
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
1576
|
+
"start_field": "开始日期",
|
|
1577
|
+
"end_field": "结束日期",
|
|
1578
|
+
"title_field": "项目名称",
|
|
1579
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
1580
|
+
}
|
|
1581
|
+
],
|
|
1582
|
+
"remove_views": [],
|
|
1583
|
+
},
|
|
1584
|
+
},
|
|
1585
|
+
}
|