@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.
Files changed (32) hide show
  1. package/README.md +12 -2
  2. package/npm/lib/runtime.mjs +37 -0
  3. package/npm/scripts/postinstall.mjs +5 -1
  4. package/package.json +3 -2
  5. package/pyproject.toml +1 -1
  6. package/skills/qingflow-app-user/SKILL.md +230 -0
  7. package/skills/qingflow-app-user/agents/openai.yaml +4 -0
  8. package/skills/qingflow-app-user/references/data-gotchas.md +49 -0
  9. package/skills/qingflow-app-user/references/environments.md +63 -0
  10. package/skills/qingflow-app-user/references/record-patterns.md +110 -0
  11. package/skills/qingflow-app-user/references/workflow-usage.md +26 -0
  12. package/skills/qingflow-record-analysis/SKILL.md +253 -0
  13. package/skills/qingflow-record-analysis/agents/openai.yaml +4 -0
  14. package/skills/qingflow-record-analysis/references/analysis-gotchas.md +141 -0
  15. package/skills/qingflow-record-analysis/references/analysis-patterns.md +113 -0
  16. package/skills/qingflow-record-analysis/references/confidence-reporting.md +92 -0
  17. package/src/qingflow_mcp/__init__.py +1 -1
  18. package/src/qingflow_mcp/builder_facade/models.py +294 -1
  19. package/src/qingflow_mcp/builder_facade/service.py +2727 -235
  20. package/src/qingflow_mcp/server.py +7 -5
  21. package/src/qingflow_mcp/server_app_builder.py +80 -4
  22. package/src/qingflow_mcp/server_app_user.py +8 -182
  23. package/src/qingflow_mcp/solution/compiler/form_compiler.py +1 -1
  24. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +21 -2
  25. package/src/qingflow_mcp/solution/executor.py +34 -7
  26. package/src/qingflow_mcp/tools/ai_builder_tools.py +1038 -30
  27. package/src/qingflow_mcp/tools/app_tools.py +1 -2
  28. package/src/qingflow_mcp/tools/approval_tools.py +357 -75
  29. package/src/qingflow_mcp/tools/directory_tools.py +158 -28
  30. package/src/qingflow_mcp/tools/record_tools.py +1954 -973
  31. package/src/qingflow_mcp/tools/task_tools.py +376 -225
  32. 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
- return self._facade.package_list(profile=profile, trial_status=trial_status)
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
- return self._facade.package_resolve(profile=profile, package_name=package_name)
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
- return self._facade.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key, app_title=app_title)
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
- return self._facade.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_tag_id=package_tag_id)
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
- return self._facade.app_read_summary(profile=profile, app_key=app_key)
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
- return self._facade.app_read_fields(profile=profile, app_key=app_key)
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
- return self._facade.app_read_layout_summary(profile=profile, app_key=app_key)
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
- return self._facade.app_read_views_summary(profile=profile, app_key=app_key)
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
- return self._facade.app_read_flow_summary(profile=profile, app_key=app_key)
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 self._facade.app_schema_plan(profile=profile, request=request)
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
- "preset": "balanced",
333
- "sections": [],
636
+ "sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
334
637
  },
335
638
  },
336
639
  )
337
- return self._facade.app_layout_plan(profile=profile, request=request)
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 self._facade.app_flow_plan(profile=profile, request=request)
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 self._facade.app_views_plan(profile=profile, request=request)
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
- return self._facade.app_schema_apply(
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
- return self._facade.app_layout_apply(profile=profile, app_key=app_key, mode=parsed_mode, sections=parsed_sections)
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
- return self._facade.app_flow_apply(
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
- app_key=request.app_key,
527
- mode=request.mode,
528
- nodes=[node.model_dump(mode="json") for node in request.nodes],
529
- transitions=[transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
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
- return self._facade.app_views_apply(profile=profile, app_key=app_key, upsert_views=parsed_views, remove_views=remove_views)
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
- return self._facade.app_publish_verify(profile=profile, app_key=app_key, expected_package_tag_id=expected_package_tag_id)
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(detail: str, *, suggested_next_call: JSONObject | None = None) -> JSONObject:
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": {"validation_detail": detail},
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
+ }