@qingflow-tech/qingflow-app-builder-mcp 1.0.44 → 1.1.1

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 (40) hide show
  1. package/README.md +4 -2
  2. package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
  3. package/npm/lib/runtime.mjs +43 -2
  4. package/package.json +1 -1
  5. package/pyproject.toml +1 -1
  6. package/skills/qingflow-app-builder-code-integrations/SKILL.md +1 -1
  7. package/skills/qingflow-mcp-setup/SKILL.md +115 -0
  8. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  9. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  10. package/skills/qingflow-mcp-setup/references/environments.md +62 -0
  11. package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
  12. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  13. package/skills/qingflow-workflow-builder/SKILL.md +98 -0
  14. package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
  15. package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
  16. package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
  17. package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
  18. package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
  19. package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
  20. package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
  21. package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
  22. package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
  23. package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
  24. package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
  25. package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
  26. package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
  27. package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
  28. package/src/qingflow_mcp/__init__.py +1 -1
  29. package/src/qingflow_mcp/builder_facade/models.py +0 -39
  30. package/src/qingflow_mcp/builder_facade/service.py +262 -862
  31. package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
  32. package/src/qingflow_mcp/cli/commands/builder.py +44 -12
  33. package/src/qingflow_mcp/public_surface.py +2 -0
  34. package/src/qingflow_mcp/server_app_builder.py +16 -8
  35. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  36. package/src/qingflow_mcp/solution/executor.py +3 -133
  37. package/src/qingflow_mcp/tools/ai_builder_tools.py +92 -233
  38. package/src/qingflow_mcp/tools/solution_tools.py +30 -2
  39. package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
  40. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
@@ -1,173 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- from ..spec_models import EntitySpec, WorkflowNodeType
6
-
7
-
8
- WORKFLOW_TYPE_MAP = {
9
- WorkflowNodeType.start: {"type": 0, "dealType": 3},
10
- WorkflowNodeType.branch: {"type": 1, "dealType": None},
11
- WorkflowNodeType.audit: {"type": 0, "dealType": 0},
12
- WorkflowNodeType.fill: {"type": 0, "dealType": 1},
13
- WorkflowNodeType.copy: {"type": 0, "dealType": 2},
14
- WorkflowNodeType.webhook: {"type": 3, "dealType": 10},
15
- WorkflowNodeType.condition: {"type": 2, "dealType": None},
16
- }
17
-
18
-
19
- def _default_audit_user_infos() -> dict[str, Any]:
20
- return {
21
- "member": [],
22
- "depart": [],
23
- "role": [],
24
- "dynamic": [],
25
- "includeSubDeparts": None,
26
- "externalMemberList": [],
27
- "externalDepartList": [],
28
- "role_refs": [],
29
- }
30
-
31
-
32
- def compile_workflow(entity: EntitySpec) -> dict[str, Any] | None:
33
- workflow = entity.workflow
34
- if workflow is None or not workflow.enabled:
35
- return None
36
- actions: list[dict[str, Any]] = []
37
- seen_node_ids: set[str] = set()
38
- created_extra_branch_lanes: set[str] = set()
39
- start_node_ids = {
40
- node.node_id
41
- for node in workflow.nodes
42
- if node.node_type == WorkflowNodeType.start
43
- }
44
- for node in workflow.nodes:
45
- if node.node_type == WorkflowNodeType.start:
46
- seen_node_ids.add(node.node_id)
47
- continue
48
- if node.parent_node_id and node.parent_node_id not in seen_node_ids:
49
- raise ValueError(f"workflow node '{node.node_id}' must appear after parent node '{node.parent_node_id}'")
50
- if node.branch_parent_id and node.branch_parent_id not in seen_node_ids:
51
- raise ValueError(f"workflow node '{node.node_id}' must appear after branch node '{node.branch_parent_id}'")
52
- branch_index = _branch_index(node)
53
- branch_lane_ref = _branch_lane_ref(node.branch_parent_id, branch_index) if node.branch_parent_id else None
54
- if branch_lane_ref and branch_index > 2 and branch_lane_ref not in created_extra_branch_lanes:
55
- actions.append(
56
- {
57
- "action": "create_sub_branch",
58
- "node_id": branch_lane_ref,
59
- "payload": {
60
- "editVersionNo": 1,
61
- "auditNodeRef": node.branch_parent_id,
62
- },
63
- }
64
- )
65
- created_extra_branch_lanes.add(branch_lane_ref)
66
- lane_only = bool((node.config or {}).get("__lane_only__")) and node.node_type == WorkflowNodeType.condition and branch_lane_ref
67
- if lane_only:
68
- lane_payload = {key: value for key, value in node.config.items() if key != "__lane_only__"}
69
- actions.append(
70
- {
71
- "action": "update_node",
72
- "node_id": branch_lane_ref,
73
- "node_name": node.name,
74
- "node_type": node.node_type.value,
75
- "payload": {
76
- "editVersionNo": 1,
77
- "auditNodeName": node.name,
78
- "type": WORKFLOW_TYPE_MAP[node.node_type]["type"],
79
- "dealType": WORKFLOW_TYPE_MAP[node.node_type]["dealType"],
80
- **lane_payload,
81
- },
82
- }
83
- )
84
- seen_node_ids.add(node.node_id)
85
- continue
86
- actions.append(
87
- {
88
- "action": "add_node",
89
- "node_id": node.node_id,
90
- "node_name": node.name,
91
- "node_type": node.node_type.value,
92
- "payload": {
93
- "editVersionNo": 1,
94
- "auditNodeName": node.name,
95
- "type": WORKFLOW_TYPE_MAP[node.node_type]["type"],
96
- "dealType": WORKFLOW_TYPE_MAP[node.node_type]["dealType"],
97
- "prevNodeRef": _prev_node_ref(node, branch_lane_ref, start_node_ids),
98
- "auditUserInfos": _build_audit_user_infos(node)
99
- if node.node_type in {WorkflowNodeType.audit, WorkflowNodeType.fill, WorkflowNodeType.copy}
100
- else None,
101
- **node.config,
102
- },
103
- }
104
- )
105
- seen_node_ids.add(node.node_id)
106
- return {
107
- "global_settings": {
108
- "editVersionNo": 1,
109
- **workflow.global_settings,
110
- },
111
- "actions": actions,
112
- }
113
-
114
-
115
- def _build_audit_user_infos(node) -> dict[str, Any]:
116
- audit_user_infos = _default_audit_user_infos()
117
- assignees = node.assignees or {}
118
- member_uids = assignees.get("member_uids") or []
119
- if member_uids:
120
- audit_user_infos["member"] = [
121
- {"uid": uid, "beingFrontendConfig": True}
122
- for uid in member_uids
123
- if isinstance(uid, int) and uid > 0
124
- ]
125
- role_refs = assignees.get("role_refs") or []
126
- if role_refs:
127
- audit_user_infos["role_refs"] = [role_ref for role_ref in role_refs if role_ref]
128
- role_entries = assignees.get("role_entries") or []
129
- if role_entries:
130
- audit_user_infos["role"] = [
131
- {
132
- "roleId": int(entry.get("roleId") or entry.get("role_id")),
133
- "roleName": entry.get("roleName") or entry.get("role_name") or str(entry.get("roleId") or entry.get("role_id")),
134
- "roleIcon": entry.get("roleIcon") or entry.get("role_icon") or "ex-user-outlined",
135
- "beingFrontendConfig": True,
136
- }
137
- for entry in role_entries
138
- if isinstance(entry, dict) and isinstance(entry.get("roleId") or entry.get("role_id"), int) and int(entry.get("roleId") or entry.get("role_id")) > 0
139
- ]
140
- include_sub_departs = assignees.get("include_sub_departs")
141
- if include_sub_departs is not None:
142
- audit_user_infos["includeSubDeparts"] = bool(include_sub_departs)
143
- return audit_user_infos
144
-
145
-
146
- def _prev_node_ref(node, branch_lane_ref: str | None, start_node_ids: set[str]) -> str:
147
- if branch_lane_ref:
148
- if node.parent_node_id and node.parent_node_id != node.branch_parent_id:
149
- return node.parent_node_id
150
- return branch_lane_ref
151
- if node.parent_node_id in start_node_ids:
152
- return "__applicant__"
153
- return node.parent_node_id or "__applicant__"
154
-
155
-
156
- def _branch_index(node) -> int:
157
- config = node.config or {}
158
- raw_value = getattr(node, "branch_index", None)
159
- if raw_value is None:
160
- raw_value = config.get("branch_index", config.get("branchIndex", config.get("lane_index", config.get("laneIndex", 1))))
161
- try:
162
- branch_index = int(raw_value)
163
- except (TypeError, ValueError) as exc:
164
- raise ValueError(f"workflow node '{node.node_id}' has invalid branch index '{raw_value}'") from exc
165
- if branch_index <= 0:
166
- raise ValueError(f"workflow node '{node.node_id}' branch index must be positive")
167
- return branch_index
168
-
169
-
170
- def _branch_lane_ref(branch_parent_id: str | None, branch_index: int) -> str:
171
- if not branch_parent_id:
172
- raise ValueError("branch_parent_id is required for branch lane references")
173
- return f"__branch_lane__{branch_parent_id}__{branch_index}"