@qingflow-tech/qingflow-cli 1.1.33 → 1.1.35
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 +2 -2
- package/docs/local-agent-install.md +10 -0
- package/npm/lib/runtime.mjs +100 -18
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +5 -5
- package/skills/qingflow-cli/reference/builder/90-workflow.md +6 -12
- package/skills/qingflow-cli/reference/builder/README.md +1 -1
- package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +3 -6
- package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +6 -5
- package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +1 -1
- package/skills/qingflow-cli/reference/builder/workflow/06-stage3-build-spec.md +33 -39
- package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +4 -10
- package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +2 -3
- package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +2 -1
- package/skills/qingflow-cli/reference/builder/workflow/README.md +3 -3
- package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +24 -2
- package/src/qingflow_mcp/builder_facade/service.py +41 -4
- package/src/qingflow_mcp/cli/commands/builder.py +15 -17
- package/src/qingflow_mcp/server_app_builder.py +1 -7
- package/src/qingflow_mcp/tools/ai_builder_tools.py +32 -51
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/README.md +0 -308
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-complete-system.md +0 -819
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-single-app.md +0 -836
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/complete-system-development-guide.md +0 -137
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/create-app.md +0 -203
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/environments.md +0 -63
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/flow-actors-and-permissions.md +0 -142
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/gotchas.md +0 -116
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/match-rules.md +0 -114
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/public-surface-sync.md +0 -75
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/single-app-development-guide.md +0 -69
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/solution-playbooks.md +0 -52
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/tool-selection.md +0 -113
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-flow.md +0 -7
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-layout.md +0 -7
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-schema.md +0 -7
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-views.md +0 -7
|
@@ -300,6 +300,25 @@ def check_orphan_nodes(nodes, edges):
|
|
|
300
300
|
return errors
|
|
301
301
|
|
|
302
302
|
|
|
303
|
+
def check_routing_entry_and_sync(nodes, edges):
|
|
304
|
+
"""检查入口出边和需要继续流转节点的 sync 标记。"""
|
|
305
|
+
errors = []
|
|
306
|
+
outgoing = {}
|
|
307
|
+
for edge in edges:
|
|
308
|
+
outgoing.setdefault(edge.get('from'), []).append(edge.get('to'))
|
|
309
|
+
|
|
310
|
+
for node in nodes:
|
|
311
|
+
node_id = node.get('id')
|
|
312
|
+
node_type = node.get('type')
|
|
313
|
+
node_label = f"节点 {node_id} ({node.get('name','')})"
|
|
314
|
+
has_outgoing = bool(outgoing.get(node_id))
|
|
315
|
+
if node_type == 'applicant' and not has_outgoing:
|
|
316
|
+
errors.append(f"申请人入口节点 {node_id} 没有出边,提交后流程不会进入下一节点")
|
|
317
|
+
if has_outgoing and node.get('sync') is not True:
|
|
318
|
+
errors.append(f"{node_label} 存在出边但未显式 sync=true,后端可能停止继续路由")
|
|
319
|
+
return errors
|
|
320
|
+
|
|
321
|
+
|
|
303
322
|
def check_minimal_modification(old_spec, new_spec):
|
|
304
323
|
"""更新模式下检查最小修改原则:ID 稳定性、无冗余删建、修改范围合理"""
|
|
305
324
|
warnings = []
|
|
@@ -560,7 +579,10 @@ def main():
|
|
|
560
579
|
# 11. 孤立节点检查
|
|
561
580
|
all_errors.extend(check_orphan_nodes(nodes, edges))
|
|
562
581
|
|
|
563
|
-
|
|
582
|
+
# 12. 入口出边与 sync 检查
|
|
583
|
+
all_errors.extend(check_routing_entry_and_sync(nodes, edges))
|
|
584
|
+
|
|
585
|
+
# 13. 更新模式:最小修改原则检查
|
|
564
586
|
if previous_file:
|
|
565
587
|
try:
|
|
566
588
|
prev_spec = load_json(previous_file)
|
|
@@ -602,4 +624,4 @@ def main():
|
|
|
602
624
|
|
|
603
625
|
|
|
604
626
|
if __name__ == '__main__':
|
|
605
|
-
main()
|
|
627
|
+
main()
|
|
@@ -8733,7 +8733,7 @@ class AiBuilderFacade:
|
|
|
8733
8733
|
if unsupported_nodes:
|
|
8734
8734
|
return _failed(
|
|
8735
8735
|
"FLOW_NODE_TYPE_UNSUPPORTED",
|
|
8736
|
-
"
|
|
8736
|
+
"public workflow writes use app_flow_get_schema/app_flow_get and app_flow_apply with WorkflowSpec spec; legacy nodes/transitions input cannot express this graph.",
|
|
8737
8737
|
normalized_args={
|
|
8738
8738
|
"app_key": request.app_key,
|
|
8739
8739
|
"mode": str(request.mode or "replace"),
|
|
@@ -10624,7 +10624,7 @@ class AiBuilderFacade:
|
|
|
10624
10624
|
flow_compile_ms += _duration_ms(flow_compile_started_at)
|
|
10625
10625
|
return finish(_failed(
|
|
10626
10626
|
"FLOW_NODE_TYPE_UNSUPPORTED",
|
|
10627
|
-
"
|
|
10627
|
+
"public workflow writes use app_flow_get_schema/app_flow_get and app_flow_apply with WorkflowSpec spec; legacy nodes/transitions input cannot express this graph.",
|
|
10628
10628
|
normalized_args=normalized_args,
|
|
10629
10629
|
details={
|
|
10630
10630
|
"unsupported_nodes": unsupported_nodes,
|
|
@@ -14848,7 +14848,14 @@ class AiBuilderFacade:
|
|
|
14848
14848
|
include_error: bool = False,
|
|
14849
14849
|
) -> tuple[Any, bool] | tuple[Any, bool, QingflowApiError | None]:
|
|
14850
14850
|
try:
|
|
14851
|
-
workflow = self.
|
|
14851
|
+
workflow = self._with_backend_context(
|
|
14852
|
+
profile,
|
|
14853
|
+
lambda context: workflow_spec_api.fetch_spec(
|
|
14854
|
+
self.apps.backend,
|
|
14855
|
+
context,
|
|
14856
|
+
app_key=app_key,
|
|
14857
|
+
),
|
|
14858
|
+
)
|
|
14852
14859
|
except (QingflowApiError, RuntimeError) as error:
|
|
14853
14860
|
api_error = _coerce_api_error(error)
|
|
14854
14861
|
if tolerate_404 and (
|
|
@@ -14857,7 +14864,7 @@ class AiBuilderFacade:
|
|
|
14857
14864
|
):
|
|
14858
14865
|
return ([], True, api_error) if include_error else ([], True)
|
|
14859
14866
|
raise
|
|
14860
|
-
result = workflow
|
|
14867
|
+
result = workflow if _looks_like_workflow_spec_payload(workflow) else []
|
|
14861
14868
|
return (result, False, None) if include_error else (result, False)
|
|
14862
14869
|
|
|
14863
14870
|
def _load_app_state(self, *, profile: str, app_key: str) -> dict[str, Any]:
|
|
@@ -25013,6 +25020,27 @@ def _apply_row_widths(row: list[dict[str, Any]]) -> None:
|
|
|
25013
25020
|
|
|
25014
25021
|
def _summarize_workflow_nodes(result: Any) -> list[dict[str, Any]]:
|
|
25015
25022
|
nodes = []
|
|
25023
|
+
spec: Any = result
|
|
25024
|
+
if isinstance(result, dict) and isinstance(result.get("spec"), dict):
|
|
25025
|
+
spec = result.get("spec")
|
|
25026
|
+
if isinstance(spec, dict) and isinstance(spec.get("nodes"), list):
|
|
25027
|
+
for item in spec.get("nodes") or []:
|
|
25028
|
+
if not isinstance(item, dict):
|
|
25029
|
+
continue
|
|
25030
|
+
node_id = item.get("id")
|
|
25031
|
+
name = item.get("name") or item.get("title")
|
|
25032
|
+
if node_id is None or not name:
|
|
25033
|
+
continue
|
|
25034
|
+
attrs = item.get("attrs") if isinstance(item.get("attrs"), dict) else {}
|
|
25035
|
+
nodes.append(
|
|
25036
|
+
{
|
|
25037
|
+
"id": str(node_id),
|
|
25038
|
+
"name": name,
|
|
25039
|
+
"type": item.get("type"),
|
|
25040
|
+
**({"sub_type": attrs.get("subType")} if attrs.get("subType") else {}),
|
|
25041
|
+
}
|
|
25042
|
+
)
|
|
25043
|
+
return nodes
|
|
25016
25044
|
if isinstance(result, dict):
|
|
25017
25045
|
iterable = result.values()
|
|
25018
25046
|
elif isinstance(result, list):
|
|
@@ -25030,6 +25058,15 @@ def _summarize_workflow_nodes(result: Any) -> list[dict[str, Any]]:
|
|
|
25030
25058
|
return nodes
|
|
25031
25059
|
|
|
25032
25060
|
|
|
25061
|
+
def _looks_like_workflow_spec_payload(payload: Any) -> bool:
|
|
25062
|
+
if not isinstance(payload, dict):
|
|
25063
|
+
return False
|
|
25064
|
+
spec = payload.get("spec")
|
|
25065
|
+
if isinstance(spec, dict):
|
|
25066
|
+
return True
|
|
25067
|
+
return any(key in payload for key in ("nodes", "edges", "transitions", "schemaVersion"))
|
|
25068
|
+
|
|
25069
|
+
|
|
25033
25070
|
def _workflow_node_iterable(result: Any) -> list[dict[str, Any]]:
|
|
25034
25071
|
if isinstance(result, dict):
|
|
25035
25072
|
iterable = result.values()
|
|
@@ -255,10 +255,10 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
255
255
|
|
|
256
256
|
flow_apply_apply = flow_apply_subparsers.add_parser("apply", help="执行流程变更")
|
|
257
257
|
flow_apply_apply.add_argument("--app-key", required=True)
|
|
258
|
-
flow_apply_apply.add_argument("--mode", choices=["replace"], default="replace")
|
|
258
|
+
flow_apply_apply.add_argument("--mode", choices=["replace"], default="replace", help=argparse.SUPPRESS)
|
|
259
259
|
flow_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
260
|
-
flow_apply_apply.add_argument("--nodes-file")
|
|
261
|
-
flow_apply_apply.add_argument("--transitions-file")
|
|
260
|
+
flow_apply_apply.add_argument("--nodes-file", help=argparse.SUPPRESS)
|
|
261
|
+
flow_apply_apply.add_argument("--transitions-file", help=argparse.SUPPRESS)
|
|
262
262
|
flow_apply_apply.add_argument("--spec-file")
|
|
263
263
|
flow_apply_apply.add_argument("--patch-nodes-file", help="节点局部更新 JSON 数组,每项含 id + set/unset")
|
|
264
264
|
flow_apply_apply.add_argument("--idempotency-key", default="")
|
|
@@ -855,11 +855,18 @@ def _handle_flow_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
855
855
|
patch_nodes = load_list_arg(getattr(args, "patch_nodes_file", None), option_name="--patch-nodes-file")
|
|
856
856
|
spec = load_object_arg(getattr(args, "spec_file", None), option_name="--spec-file")
|
|
857
857
|
has_legacy_nodes = bool(getattr(args, "nodes_file", None) or getattr(args, "transitions_file", None))
|
|
858
|
+
if has_legacy_nodes:
|
|
859
|
+
raise_config_error(
|
|
860
|
+
"builder flow apply no longer exposes legacy --nodes-file/--transitions-file input.",
|
|
861
|
+
fix_hint="Use --spec-file for a complete WorkflowSpec, or --patch-nodes-file for targeted edits after builder flow get.",
|
|
862
|
+
error_code="FLOW_LEGACY_INPUT_DISABLED",
|
|
863
|
+
details={"allowed_input_modes": ["--spec-file", "--patch-nodes-file"]},
|
|
864
|
+
)
|
|
858
865
|
selected_modes = sum(1 for selected in (bool(patch_nodes), bool(spec), has_legacy_nodes) if selected)
|
|
859
866
|
if selected_modes != 1:
|
|
860
867
|
raise_config_error(
|
|
861
|
-
"builder flow apply requires exactly one input mode: --spec-file
|
|
862
|
-
fix_hint="Use --
|
|
868
|
+
"builder flow apply requires exactly one input mode: --spec-file or --patch-nodes-file.",
|
|
869
|
+
fix_hint="Use --spec-file for a complete WorkflowSpec, or --patch-nodes-file for targeted edits after builder flow get.",
|
|
863
870
|
)
|
|
864
871
|
if patch_nodes:
|
|
865
872
|
return context.builder.app_flow_apply(
|
|
@@ -879,18 +886,9 @@ def _handle_flow_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
879
886
|
idempotency_key=args.idempotency_key,
|
|
880
887
|
schema_version=args.schema_version,
|
|
881
888
|
)
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
fix_hint="Pass both files, or use --spec-file / --patch-nodes-file.",
|
|
886
|
-
)
|
|
887
|
-
return context.builder.app_flow_apply(
|
|
888
|
-
profile=args.profile,
|
|
889
|
-
app_key=args.app_key,
|
|
890
|
-
mode=args.mode,
|
|
891
|
-
publish=bool(args.publish),
|
|
892
|
-
nodes=require_list_arg(args.nodes_file, option_name="--nodes-file"),
|
|
893
|
-
transitions=require_list_arg(args.transitions_file, option_name="--transitions-file"),
|
|
889
|
+
raise_config_error(
|
|
890
|
+
"builder flow apply requires --spec-file or --patch-nodes-file.",
|
|
891
|
+
fix_hint="Use --spec-file for a complete WorkflowSpec, or --patch-nodes-file for targeted edits after builder flow get.",
|
|
894
892
|
)
|
|
895
893
|
|
|
896
894
|
|
|
@@ -52,7 +52,7 @@ def build_builder_server() -> FastMCP:
|
|
|
52
52
|
"For associated reports/views, use app_associated_resources_apply for both the app-level associated_resources pool and per-view display config; associated_item_id is the app-level form_asos_chart.id, and view_configs/remove/reorder may also pass an existing resource's chart_id/chart_key/view_key because the tool resolves those to the internal id. Before creating an associated resource, read app_get.associated_resources and reuse an existing matching target_app_key + view_key/chart_key through patch_resources; client_key only works inside one apply call and is not persisted. Do not ask agents to pass backend raw sourceType: views infer the internal Qingflow view source, reports default to BI app reports, and dataset reports use report_source=dataset. "
|
|
53
53
|
"For code_block fields with output bindings, always use qf_output assignment rather than const/let qf_output, and use app_repair_code_blocks when an existing form hangs because output-bound fields stay loading. "
|
|
54
54
|
"Use package_apply to manage package metadata, visibility, grouping, and ordering, and app_publish_verify for explicit final publish verification. "
|
|
55
|
-
"For workflow edits, use app_flow_get_schema/app_flow_get when raw WorkflowSpec shape is needed, then app_flow_apply with spec for full replacement or patch_nodes for targeted node edits
|
|
55
|
+
"For workflow edits, use app_flow_get_schema/app_flow_get when raw WorkflowSpec shape is needed, then app_flow_apply with spec for full replacement or patch_nodes for targeted node edits. Declare node assignees and editable fields explicitly. "
|
|
56
56
|
"If builder writes are blocked by the current user's own edit lock, use app_release_edit_lock_if_mine with the lock owner details from the failed result. "
|
|
57
57
|
"Do not handcraft internal solution payloads or rely on build_id/stage/repair. "
|
|
58
58
|
"If the current MCP capability is unsupported, the workflow is awkward, or the user's need still cannot be satisfied after reasonable use, first summarize the gap, ask whether to submit feedback, and call feedback_submit only after explicit user confirmation."
|
|
@@ -513,10 +513,7 @@ def build_builder_server() -> FastMCP:
|
|
|
513
513
|
def app_flow_apply(
|
|
514
514
|
profile: str = DEFAULT_PROFILE,
|
|
515
515
|
app_key: str = "",
|
|
516
|
-
mode: str = "replace",
|
|
517
516
|
publish: bool = True,
|
|
518
|
-
nodes: list[dict] | None = None,
|
|
519
|
-
transitions: list[dict] | None = None,
|
|
520
517
|
spec: dict | None = None,
|
|
521
518
|
idempotency_key: str = "",
|
|
522
519
|
schema_version: str = "",
|
|
@@ -525,10 +522,7 @@ def build_builder_server() -> FastMCP:
|
|
|
525
522
|
return ai_builder.app_flow_apply(
|
|
526
523
|
profile=profile,
|
|
527
524
|
app_key=app_key,
|
|
528
|
-
mode=mode,
|
|
529
525
|
publish=publish,
|
|
530
|
-
nodes=nodes or [],
|
|
531
|
-
transitions=transitions or [],
|
|
532
526
|
spec=spec,
|
|
533
527
|
idempotency_key=idempotency_key or None,
|
|
534
528
|
schema_version=schema_version or None,
|
|
@@ -720,10 +720,7 @@ class AiBuilderTools(ToolBase):
|
|
|
720
720
|
def app_flow_apply(
|
|
721
721
|
profile: str = DEFAULT_PROFILE,
|
|
722
722
|
app_key: str = "",
|
|
723
|
-
mode: str = "replace",
|
|
724
723
|
publish: bool = True,
|
|
725
|
-
nodes: list[JSONObject] | None = None,
|
|
726
|
-
transitions: list[JSONObject] | None = None,
|
|
727
724
|
spec: JSONObject | None = None,
|
|
728
725
|
idempotency_key: str = "",
|
|
729
726
|
schema_version: str = "",
|
|
@@ -732,10 +729,7 @@ class AiBuilderTools(ToolBase):
|
|
|
732
729
|
return self.app_flow_apply(
|
|
733
730
|
profile=profile,
|
|
734
731
|
app_key=app_key,
|
|
735
|
-
mode=mode,
|
|
736
732
|
publish=publish,
|
|
737
|
-
nodes=nodes or [],
|
|
738
|
-
transitions=transitions or [],
|
|
739
733
|
spec=spec,
|
|
740
734
|
idempotency_key=idempotency_key or None,
|
|
741
735
|
schema_version=schema_version or None,
|
|
@@ -3573,7 +3567,7 @@ class AiBuilderTools(ToolBase):
|
|
|
3573
3567
|
*,
|
|
3574
3568
|
profile: str,
|
|
3575
3569
|
app_key: str,
|
|
3576
|
-
mode: str =
|
|
3570
|
+
mode: str | None = None,
|
|
3577
3571
|
publish: bool = True,
|
|
3578
3572
|
nodes: list[JSONObject] | None = None,
|
|
3579
3573
|
transitions: list[JSONObject] | None = None,
|
|
@@ -3625,29 +3619,37 @@ class AiBuilderTools(ToolBase):
|
|
|
3625
3619
|
normalized_args=normalized_args,
|
|
3626
3620
|
suggested_next_call={"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
3627
3621
|
))
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
app_key=app_key,
|
|
3633
|
-
mode=mode,
|
|
3634
|
-
publish=publish,
|
|
3635
|
-
nodes=effective_nodes,
|
|
3636
|
-
transitions=effective_transitions,
|
|
3637
|
-
)
|
|
3638
|
-
result = self._retry_after_self_lock_release(
|
|
3639
|
-
profile=profile,
|
|
3640
|
-
result=result,
|
|
3641
|
-
retry_call=lambda: self._app_flow_apply_once(
|
|
3622
|
+
if nodes or transitions:
|
|
3623
|
+
effective_nodes = nodes or []
|
|
3624
|
+
effective_transitions = transitions or []
|
|
3625
|
+
result = self._app_flow_apply_once(
|
|
3642
3626
|
profile=profile,
|
|
3643
3627
|
app_key=app_key,
|
|
3644
|
-
mode=mode,
|
|
3628
|
+
mode=mode or "replace",
|
|
3645
3629
|
publish=publish,
|
|
3646
3630
|
nodes=effective_nodes,
|
|
3647
3631
|
transitions=effective_transitions,
|
|
3648
|
-
)
|
|
3649
|
-
|
|
3650
|
-
|
|
3632
|
+
)
|
|
3633
|
+
result = self._retry_after_self_lock_release(
|
|
3634
|
+
profile=profile,
|
|
3635
|
+
result=result,
|
|
3636
|
+
retry_call=lambda: self._app_flow_apply_once(
|
|
3637
|
+
profile=profile,
|
|
3638
|
+
app_key=app_key,
|
|
3639
|
+
mode=mode or "replace",
|
|
3640
|
+
publish=publish,
|
|
3641
|
+
nodes=effective_nodes,
|
|
3642
|
+
transitions=effective_transitions,
|
|
3643
|
+
),
|
|
3644
|
+
)
|
|
3645
|
+
return _attach_builder_apply_envelope("app_flow_apply", result)
|
|
3646
|
+
normalized_args = {"app_key": app_key, "publish": publish}
|
|
3647
|
+
return _attach_builder_apply_envelope("app_flow_apply", _failed(
|
|
3648
|
+
"FLOW_SPEC_OR_PATCH_REQUIRED",
|
|
3649
|
+
"app_flow_apply requires spec for a complete WorkflowSpec write or patch_nodes for targeted edits.",
|
|
3650
|
+
normalized_args=normalized_args,
|
|
3651
|
+
suggested_next_call={"tool_name": "app_flow_get_schema", "arguments": {"profile": profile}},
|
|
3652
|
+
))
|
|
3651
3653
|
|
|
3652
3654
|
def _app_flow_apply_once(
|
|
3653
3655
|
self,
|
|
@@ -7731,7 +7733,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
7731
7733
|
"approve/fill/copy nodes require at least one assignee",
|
|
7732
7734
|
],
|
|
7733
7735
|
"execution_notes": [
|
|
7734
|
-
"app_flow_plan is
|
|
7736
|
+
"app_flow_plan is private compatibility plumbing, not an agent-facing workflow path; use app_flow_get/app_flow_get_schema plus app_flow_apply spec or patch_nodes",
|
|
7735
7737
|
"do not create platform system workflow fields such as 当前流程状态 / 当前处理人 / 当前处理节点 / 流程标题; use an explicit business status field instead",
|
|
7736
7738
|
],
|
|
7737
7739
|
"minimal_example": {
|
|
@@ -7759,35 +7761,22 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
7759
7761
|
"patch_nodes",
|
|
7760
7762
|
"idempotency_key",
|
|
7761
7763
|
"schema_version",
|
|
7762
|
-
"mode",
|
|
7763
|
-
"nodes",
|
|
7764
|
-
"transitions",
|
|
7765
7764
|
],
|
|
7766
7765
|
"aliases": {
|
|
7767
|
-
"overwrite": "replace",
|
|
7768
7766
|
"patchNodes": "patch_nodes",
|
|
7769
7767
|
"idempotencyKey": "idempotency_key",
|
|
7770
7768
|
"schemaVersion": "schema_version",
|
|
7771
|
-
"node.role_names": "node.assignees.role_names",
|
|
7772
|
-
"node.role_ids": "node.assignees.role_ids",
|
|
7773
|
-
"node.member_names": "node.assignees.member_names",
|
|
7774
|
-
"node.member_emails": "node.assignees.member_emails",
|
|
7775
|
-
"node.member_uids": "node.assignees.member_uids",
|
|
7776
|
-
"node.editable_fields": "node.permissions.editable_fields",
|
|
7777
|
-
},
|
|
7778
|
-
"allowed_values": {
|
|
7779
|
-
"mode": ["replace"],
|
|
7780
|
-
"node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
|
|
7781
7769
|
},
|
|
7770
|
+
"allowed_values": {},
|
|
7782
7771
|
"dependency_hints": [
|
|
7783
7772
|
"approval-style workflows require an explicit business status select field before app_flow_apply, such as 状态 / 处理状态 / 审批状态 / 工单状态",
|
|
7784
|
-
"
|
|
7773
|
+
"WorkflowSpec nodes should declare sync=true for routing nodes and explicit assignees for approval/filling/cc nodes",
|
|
7785
7774
|
],
|
|
7786
7775
|
"execution_notes": [
|
|
7787
7776
|
"preferred full workflow path is app_flow_get_schema/app_flow_get -> app_flow_apply with spec",
|
|
7788
7777
|
"use patch_nodes for targeted existing-node edits; each item uses id plus set/unset",
|
|
7789
|
-
"
|
|
7790
|
-
"do not mix spec
|
|
7778
|
+
"public workflow writes use only spec or patch_nodes",
|
|
7779
|
+
"do not mix spec and patch_nodes in one call",
|
|
7791
7780
|
"do not create platform system workflow fields such as 当前流程状态 / 当前处理人 / 当前处理节点 / 流程标题; use an explicit business status field instead",
|
|
7792
7781
|
"workflow readback is app_flow_get; use app_flow_get_schema when raw spec shape is unclear",
|
|
7793
7782
|
],
|
|
@@ -7803,14 +7792,6 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
7803
7792
|
"publish": True,
|
|
7804
7793
|
"patch_nodes": [{"id": "approve_1", "set": {"name": "主管审批"}}],
|
|
7805
7794
|
},
|
|
7806
|
-
"legacy_linear_example": {
|
|
7807
|
-
"profile": "default",
|
|
7808
|
-
"app_key": "APP_KEY",
|
|
7809
|
-
"mode": "replace",
|
|
7810
|
-
"publish": True,
|
|
7811
|
-
"nodes": [{"id": "start", "type": "start", "name": "发起"}, {"id": "end", "type": "end", "name": "结束"}],
|
|
7812
|
-
"transitions": [{"from": "start", "to": "end"}],
|
|
7813
|
-
},
|
|
7814
7795
|
},
|
|
7815
7796
|
"app_views_plan": {
|
|
7816
7797
|
"allowed_keys": [
|