@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.
- package/README.md +4 -2
- package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
- package/npm/lib/runtime.mjs +43 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +1 -1
- package/skills/qingflow-mcp-setup/SKILL.md +115 -0
- package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
- package/skills/qingflow-mcp-setup/references/environments.md +62 -0
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
- package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
- package/skills/qingflow-workflow-builder/SKILL.md +98 -0
- package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
- package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
- package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
- package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
- package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
- package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
- package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
- package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
- package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +0 -39
- package/src/qingflow_mcp/builder_facade/service.py +262 -862
- package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
- package/src/qingflow_mcp/cli/commands/builder.py +44 -12
- package/src/qingflow_mcp/public_surface.py +2 -0
- package/src/qingflow_mcp/server_app_builder.py +16 -8
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/executor.py +3 -133
- package/src/qingflow_mcp/tools/ai_builder_tools.py +92 -233
- package/src/qingflow_mcp/tools/solution_tools.py +30 -2
- package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
- 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}"
|