@josephyan/qingflow-cli 0.2.0-beta.66 → 0.2.0-beta.68
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/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +348 -2
- package/src/qingflow_mcp/builder_facade/service.py +2029 -135
- package/src/qingflow_mcp/cli/commands/builder.py +23 -0
- package/src/qingflow_mcp/cli/commands/record.py +2 -0
- package/src/qingflow_mcp/server.py +2 -1
- package/src/qingflow_mcp/server_app_builder.py +7 -2
- package/src/qingflow_mcp/server_app_user.py +2 -1
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +29 -0
- package/src/qingflow_mcp/solution/spec_models.py +2 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +162 -16
- package/src/qingflow_mcp/tools/approval_tools.py +31 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +91 -14
- package/src/qingflow_mcp/tools/package_tools.py +1 -0
- package/src/qingflow_mcp/tools/record_tools.py +549 -185
- package/src/qingflow_mcp/tools/task_context_tools.py +26 -1
|
@@ -33,6 +33,27 @@ SUPPORTED_CODE_BLOCK_ROLES = {1, 2, 3, 5}
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class CodeBlockTools(RecordTools):
|
|
36
|
+
def _get_code_block_relation_schema(
|
|
37
|
+
self,
|
|
38
|
+
profile: str,
|
|
39
|
+
context, # type: ignore[no-untyped-def]
|
|
40
|
+
app_key: str,
|
|
41
|
+
*,
|
|
42
|
+
force_refresh: bool,
|
|
43
|
+
) -> JSONObject:
|
|
44
|
+
cache_key = (profile, app_key, "code_block_relation_form", 1)
|
|
45
|
+
if not force_refresh and cache_key in self._form_cache:
|
|
46
|
+
return self._form_cache[cache_key]
|
|
47
|
+
schema = self.backend.request(
|
|
48
|
+
"GET",
|
|
49
|
+
context,
|
|
50
|
+
f"/app/{app_key}/form",
|
|
51
|
+
params={"type": 1},
|
|
52
|
+
)
|
|
53
|
+
normalized = schema if isinstance(schema, dict) else {}
|
|
54
|
+
self._form_cache[cache_key] = normalized
|
|
55
|
+
return normalized
|
|
56
|
+
|
|
36
57
|
def register(self, mcp: FastMCP) -> None:
|
|
37
58
|
super().register(mcp)
|
|
38
59
|
|
|
@@ -49,9 +70,10 @@ class CodeBlockTools(RecordTools):
|
|
|
49
70
|
|
|
50
71
|
@mcp.tool(
|
|
51
72
|
description=(
|
|
52
|
-
"Run a form code-block field against the current record data,
|
|
53
|
-
"relation-calculation chain to compute bound outputs and write them back
|
|
54
|
-
"Use record_code_block_schema_get first and choose an exact code-block field selector."
|
|
73
|
+
"Run a form code-block field against the current record data, parse alias results, and optionally "
|
|
74
|
+
"reuse Qingflow's existing relation-calculation chain to compute bound outputs and write them back. "
|
|
75
|
+
"Use record_code_block_schema_get first and choose an exact code-block field selector. "
|
|
76
|
+
"For safe debugging, pass apply_writeback=false to inspect parsed results without writing back."
|
|
55
77
|
)
|
|
56
78
|
)
|
|
57
79
|
def record_code_block_run(
|
|
@@ -64,6 +86,7 @@ class CodeBlockTools(RecordTools):
|
|
|
64
86
|
answers: list[JSONObject] | None = None,
|
|
65
87
|
fields: JSONObject | None = None,
|
|
66
88
|
manual: bool = True,
|
|
89
|
+
apply_writeback: bool = True,
|
|
67
90
|
verify_writeback: bool = True,
|
|
68
91
|
force_refresh_form: bool = False,
|
|
69
92
|
output_profile: str = "normal",
|
|
@@ -78,6 +101,7 @@ class CodeBlockTools(RecordTools):
|
|
|
78
101
|
answers=answers or [],
|
|
79
102
|
fields=fields or {},
|
|
80
103
|
manual=manual,
|
|
104
|
+
apply_writeback=apply_writeback,
|
|
81
105
|
verify_writeback=verify_writeback,
|
|
82
106
|
force_refresh_form=force_refresh_form,
|
|
83
107
|
output_profile=output_profile,
|
|
@@ -95,7 +119,7 @@ class CodeBlockTools(RecordTools):
|
|
|
95
119
|
normalized_output_profile = self._normalize_public_output_profile(output_profile)
|
|
96
120
|
|
|
97
121
|
def runner(session_profile, context):
|
|
98
|
-
|
|
122
|
+
relation_schema = self._get_code_block_relation_schema(profile, context, app_key, force_refresh=False)
|
|
99
123
|
index = self._get_applicant_top_level_field_index(profile, context, app_key, force_refresh=False)
|
|
100
124
|
input_fields = [
|
|
101
125
|
self._ready_schema_field_payload(
|
|
@@ -113,7 +137,7 @@ class CodeBlockTools(RecordTools):
|
|
|
113
137
|
if field.que_type != CODE_BLOCK_QUE_TYPE:
|
|
114
138
|
continue
|
|
115
139
|
targets = _collect_code_block_relation_targets(
|
|
116
|
-
_collect_question_relations(
|
|
140
|
+
_collect_question_relations(relation_schema),
|
|
117
141
|
code_block_que_id=field.que_id,
|
|
118
142
|
)
|
|
119
143
|
bound_output_fields = [
|
|
@@ -127,6 +151,7 @@ class CodeBlockTools(RecordTools):
|
|
|
127
151
|
"title": field.que_title,
|
|
128
152
|
"selector": field.que_title,
|
|
129
153
|
"bound_output_fields": bound_output_fields,
|
|
154
|
+
"configured_aliases": _extract_code_block_configured_aliases(field),
|
|
130
155
|
}
|
|
131
156
|
)
|
|
132
157
|
response: JSONObject = {
|
|
@@ -164,6 +189,7 @@ class CodeBlockTools(RecordTools):
|
|
|
164
189
|
answers: list[JSONObject] | None = None,
|
|
165
190
|
fields: JSONObject | None = None,
|
|
166
191
|
manual: bool = True,
|
|
192
|
+
apply_writeback: bool = True,
|
|
167
193
|
verify_writeback: bool = True,
|
|
168
194
|
force_refresh_form: bool = False,
|
|
169
195
|
output_profile: str = "normal",
|
|
@@ -178,7 +204,12 @@ class CodeBlockTools(RecordTools):
|
|
|
178
204
|
raise_tool_error(QingflowApiError.config_error("code_block_field is required"))
|
|
179
205
|
|
|
180
206
|
def runner(session_profile, context):
|
|
181
|
-
|
|
207
|
+
relation_schema = self._get_code_block_relation_schema(
|
|
208
|
+
profile,
|
|
209
|
+
context,
|
|
210
|
+
app_key,
|
|
211
|
+
force_refresh=force_refresh_form,
|
|
212
|
+
)
|
|
182
213
|
index = self._get_field_index(profile, context, app_key, force_refresh=force_refresh_form)
|
|
183
214
|
code_block = self._resolve_field_selector(code_block_field, index, location="code_block_field")
|
|
184
215
|
if code_block.que_type != CODE_BLOCK_QUE_TYPE:
|
|
@@ -233,8 +264,9 @@ class CodeBlockTools(RecordTools):
|
|
|
233
264
|
json_body=run_body,
|
|
234
265
|
)
|
|
235
266
|
alias_results = _normalize_code_block_alias_results(run_result)
|
|
267
|
+
configured_aliases = _extract_code_block_configured_aliases(code_block)
|
|
236
268
|
relation_target_fields = _collect_code_block_relation_targets(
|
|
237
|
-
_collect_question_relations(
|
|
269
|
+
_collect_question_relations(relation_schema),
|
|
238
270
|
code_block_que_id=code_block.que_id,
|
|
239
271
|
)
|
|
240
272
|
relation_errors: list[JSONObject] = []
|
|
@@ -277,7 +309,12 @@ class CodeBlockTools(RecordTools):
|
|
|
277
309
|
writeback_applied = False
|
|
278
310
|
status = "completed"
|
|
279
311
|
ok = True
|
|
280
|
-
if
|
|
312
|
+
if relation_errors:
|
|
313
|
+
status = "relation_failed"
|
|
314
|
+
ok = False
|
|
315
|
+
elif not apply_writeback:
|
|
316
|
+
status = "debug_completed"
|
|
317
|
+
elif relation_target_fields and calculated_answers:
|
|
281
318
|
write_body: JSONObject = {"role": role, "answers": calculated_answers}
|
|
282
319
|
if workflow_node_id is not None:
|
|
283
320
|
write_body["auditNodeId"] = workflow_node_id
|
|
@@ -305,9 +342,6 @@ class CodeBlockTools(RecordTools):
|
|
|
305
342
|
if not bool(verification.get("verified")):
|
|
306
343
|
status = "verification_failed"
|
|
307
344
|
ok = False
|
|
308
|
-
elif relation_errors:
|
|
309
|
-
status = "relation_failed"
|
|
310
|
-
ok = False
|
|
311
345
|
else:
|
|
312
346
|
status = "no_writeback"
|
|
313
347
|
response: JSONObject = {
|
|
@@ -325,9 +359,11 @@ class CodeBlockTools(RecordTools):
|
|
|
325
359
|
"role": role,
|
|
326
360
|
"workflow_node_id": workflow_node_id,
|
|
327
361
|
"manual": bool(manual),
|
|
362
|
+
"apply_writeback": bool(apply_writeback),
|
|
328
363
|
"result_count": len(alias_results),
|
|
329
364
|
},
|
|
330
365
|
"outputs": {
|
|
366
|
+
"configured_aliases": configured_aliases,
|
|
331
367
|
"alias_results": alias_results,
|
|
332
368
|
"alias_map": _build_alias_result_map(alias_results),
|
|
333
369
|
},
|
|
@@ -335,11 +371,14 @@ class CodeBlockTools(RecordTools):
|
|
|
335
371
|
"target_fields": relation_target_fields,
|
|
336
372
|
"result_item_count": len(relation_items),
|
|
337
373
|
"calculated_answer_count": len(calculated_answers),
|
|
374
|
+
"calculated_answers_preview": calculated_answers,
|
|
338
375
|
"errors": relation_errors,
|
|
339
376
|
},
|
|
340
377
|
"writeback": {
|
|
378
|
+
"enabled": bool(apply_writeback),
|
|
341
379
|
"attempted": writeback_attempted,
|
|
342
380
|
"applied": writeback_applied,
|
|
381
|
+
"skipped_reason": "apply_writeback_disabled" if not apply_writeback else None,
|
|
343
382
|
"verify_writeback": verify_writeback,
|
|
344
383
|
"write_verified": bool(verification.get("verified")) if verification is not None else None,
|
|
345
384
|
"result": write_result,
|
|
@@ -606,6 +645,38 @@ def _normalize_code_block_alias_results(payload: JSONValue) -> list[JSONObject]:
|
|
|
606
645
|
return results
|
|
607
646
|
|
|
608
647
|
|
|
648
|
+
def _extract_code_block_configured_aliases(field: FormField) -> list[JSONObject]:
|
|
649
|
+
raw_config = field.raw.get("codeBlockConfig")
|
|
650
|
+
if not isinstance(raw_config, dict):
|
|
651
|
+
return []
|
|
652
|
+
raw_aliases = raw_config.get("resultAliasPath")
|
|
653
|
+
if not isinstance(raw_aliases, list):
|
|
654
|
+
return []
|
|
655
|
+
return [item for item in (_normalize_code_block_alias_item(alias) for alias in raw_aliases) if item is not None]
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _normalize_code_block_alias_item(value: JSONValue) -> JSONObject | None:
|
|
659
|
+
if not isinstance(value, dict):
|
|
660
|
+
return None
|
|
661
|
+
alias_name = _normalize_optional_text(value.get("aliasName", value.get("alias_name")))
|
|
662
|
+
alias_path = _normalize_optional_text(value.get("aliasPath", value.get("alias_path")))
|
|
663
|
+
if alias_name is None and alias_path is None:
|
|
664
|
+
return None
|
|
665
|
+
raw_sub_alias = value.get("subAlias", value.get("sub_alias"))
|
|
666
|
+
sub_alias = (
|
|
667
|
+
[item for item in (_normalize_code_block_alias_item(entry) for entry in raw_sub_alias) if item is not None]
|
|
668
|
+
if isinstance(raw_sub_alias, list)
|
|
669
|
+
else []
|
|
670
|
+
)
|
|
671
|
+
return {
|
|
672
|
+
"alias_id": _coerce_count(value.get("aliasId", value.get("alias_id"))),
|
|
673
|
+
"alias_name": alias_name,
|
|
674
|
+
"alias_path": alias_path,
|
|
675
|
+
"alias_type": _coerce_count(value.get("aliasType", value.get("alias_type"))) or 1,
|
|
676
|
+
"sub_alias": sub_alias,
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
|
|
609
680
|
def _build_alias_result_map(alias_results: list[JSONObject]) -> JSONObject:
|
|
610
681
|
alias_map: JSONObject = {}
|
|
611
682
|
for item in alias_results:
|
|
@@ -625,7 +696,11 @@ def _collect_code_block_relation_targets(question_relations: list[JSONObject], *
|
|
|
625
696
|
for relation in question_relations:
|
|
626
697
|
if _coerce_count(relation.get("relationType")) != CODE_BLOCK_RELATION_TYPE:
|
|
627
698
|
continue
|
|
628
|
-
if
|
|
699
|
+
alias_config = relation.get("aliasConfig") if isinstance(relation.get("aliasConfig"), dict) else {}
|
|
700
|
+
relation_code_block_que_id = _coerce_count(relation.get("qlinkerQueId"))
|
|
701
|
+
if relation_code_block_que_id is None:
|
|
702
|
+
relation_code_block_que_id = _coerce_count(alias_config.get("queId"))
|
|
703
|
+
if relation_code_block_que_id != code_block_que_id:
|
|
629
704
|
continue
|
|
630
705
|
target_id = _coerce_count(
|
|
631
706
|
relation.get("queId", relation.get("targetQueId", relation.get("displayedQueId")))
|
|
@@ -636,8 +711,10 @@ def _collect_code_block_relation_targets(question_relations: list[JSONObject], *
|
|
|
636
711
|
targets.append(
|
|
637
712
|
{
|
|
638
713
|
"que_id": target_id,
|
|
639
|
-
"alias_id": _coerce_count(relation.get("aliasId")),
|
|
640
|
-
"
|
|
714
|
+
"alias_id": _coerce_count(relation.get("aliasId")) or _coerce_count(alias_config.get("aliasId")),
|
|
715
|
+
"alias_name": _normalize_optional_text(relation.get("qlinkerAlias"))
|
|
716
|
+
or _normalize_optional_text(alias_config.get("qlinkerAlias")),
|
|
717
|
+
"qlinker_que_id": relation_code_block_que_id,
|
|
641
718
|
}
|
|
642
719
|
)
|
|
643
720
|
return targets
|
|
@@ -200,6 +200,7 @@ class PackageTools(ToolBase):
|
|
|
200
200
|
compact = {
|
|
201
201
|
"tagId": result.get("tagId"),
|
|
202
202
|
"tagName": result.get("tagName"),
|
|
203
|
+
"tagIcon": result.get("tagIcon") if result.get("tagIcon") is not None else permission_source.get("tagIcon"),
|
|
203
204
|
"publishStatus": result.get("publishStatus") if result.get("publishStatus") is not None else permission_source.get("publishStatus"),
|
|
204
205
|
"beingTrial": result.get("beingTrial"),
|
|
205
206
|
"itemCount": len(tag_items),
|