@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
|
@@ -12,10 +12,20 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
12
12
|
|
|
13
13
|
package = builder_subparsers.add_parser("package", help="应用包")
|
|
14
14
|
package_subparsers = package.add_subparsers(dest="builder_package_command", required=True)
|
|
15
|
+
package_list = package_subparsers.add_parser("list", help="列出应用包")
|
|
16
|
+
package_list.add_argument("--trial-status", default="all")
|
|
17
|
+
package_list.set_defaults(handler=_handle_package_list, format_hint="builder_summary")
|
|
18
|
+
|
|
15
19
|
package_resolve = package_subparsers.add_parser("resolve", help="解析应用包")
|
|
16
20
|
package_resolve.add_argument("--package-name", required=True)
|
|
17
21
|
package_resolve.set_defaults(handler=_handle_package_resolve, format_hint="builder_summary")
|
|
18
22
|
|
|
23
|
+
package_create = package_subparsers.add_parser("create", help="创建应用包")
|
|
24
|
+
package_create.add_argument("--package-name", required=True)
|
|
25
|
+
package_create.add_argument("--icon")
|
|
26
|
+
package_create.add_argument("--color")
|
|
27
|
+
package_create.set_defaults(handler=_handle_package_create, format_hint="builder_summary")
|
|
28
|
+
|
|
19
29
|
app = builder_subparsers.add_parser("app", help="应用")
|
|
20
30
|
app_subparsers = app.add_subparsers(dest="builder_app_command", required=True)
|
|
21
31
|
|
|
@@ -119,10 +129,23 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
119
129
|
publish_verify_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_summary")
|
|
120
130
|
|
|
121
131
|
|
|
132
|
+
def _handle_package_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
133
|
+
return context.builder.package_list(profile=args.profile, trial_status=args.trial_status)
|
|
134
|
+
|
|
135
|
+
|
|
122
136
|
def _handle_package_resolve(args: argparse.Namespace, context: CliContext) -> dict:
|
|
123
137
|
return context.builder.package_resolve(profile=args.profile, package_name=args.package_name)
|
|
124
138
|
|
|
125
139
|
|
|
140
|
+
def _handle_package_create(args: argparse.Namespace, context: CliContext) -> dict:
|
|
141
|
+
return context.builder.package_create(
|
|
142
|
+
profile=args.profile,
|
|
143
|
+
package_name=args.package_name,
|
|
144
|
+
icon=args.icon,
|
|
145
|
+
color=args.color,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
126
149
|
def _handle_app_resolve(args: argparse.Namespace, context: CliContext) -> dict:
|
|
127
150
|
return context.builder.app_resolve(
|
|
128
151
|
profile=args.profile,
|
|
@@ -77,6 +77,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
77
77
|
code_block.add_argument("--answers-file")
|
|
78
78
|
code_block.add_argument("--fields-file")
|
|
79
79
|
code_block.add_argument("--manual", action=argparse.BooleanOptionalAction, default=True)
|
|
80
|
+
code_block.add_argument("--apply-writeback", action=argparse.BooleanOptionalAction, default=True)
|
|
80
81
|
code_block.add_argument("--verify-writeback", action=argparse.BooleanOptionalAction, default=True)
|
|
81
82
|
code_block.add_argument("--force-refresh-form", action="store_true")
|
|
82
83
|
code_block.set_defaults(handler=_handle_code_block_run, format_hint="")
|
|
@@ -197,6 +198,7 @@ def _handle_code_block_run(args: argparse.Namespace, context: CliContext) -> dic
|
|
|
197
198
|
answers=load_list_arg(args.answers_file, option_name="--answers-file"),
|
|
198
199
|
fields=load_object_arg(args.fields_file, option_name="--fields-file") or {},
|
|
199
200
|
manual=bool(args.manual),
|
|
201
|
+
apply_writeback=bool(args.apply_writeback),
|
|
200
202
|
verify_writeback=bool(args.verify_writeback),
|
|
201
203
|
force_refresh_form=bool(args.force_refresh_form),
|
|
202
204
|
)
|
|
@@ -132,8 +132,9 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
|
|
|
132
132
|
- Always resolve the exact code-block field from `record_code_block_schema_get` first.
|
|
133
133
|
- Treat code-block execution as write-capable, not read-only.
|
|
134
134
|
- If the code block is bound to relation outputs, Qingflow may calculate target answers and write them back automatically.
|
|
135
|
+
- For safe debugging, pass `apply_writeback=false` and inspect the parsed alias results plus `relation.calculated_answers_preview` before allowing any writeback.
|
|
135
136
|
- In workflow context, pass `role=3` and the exact `workflow_node_id`.
|
|
136
|
-
- After execution, inspect `outputs.alias_results`, `relation.target_fields`, and `writeback.verification` before claiming success.
|
|
137
|
+
- After execution, inspect `outputs.configured_aliases`, `outputs.alias_results`, `outputs.alias_map`, `relation.target_fields`, and `writeback.verification` before claiming success.
|
|
137
138
|
|
|
138
139
|
## Import Path
|
|
139
140
|
|
|
@@ -138,8 +138,13 @@ def build_builder_server() -> FastMCP:
|
|
|
138
138
|
return ai_builder.builder_tool_contract(tool_name=tool_name)
|
|
139
139
|
|
|
140
140
|
@server.tool()
|
|
141
|
-
def package_create(
|
|
142
|
-
|
|
141
|
+
def package_create(
|
|
142
|
+
profile: str = DEFAULT_PROFILE,
|
|
143
|
+
package_name: str = "",
|
|
144
|
+
icon: str | None = None,
|
|
145
|
+
color: str | None = None,
|
|
146
|
+
) -> dict:
|
|
147
|
+
return ai_builder.package_create(profile=profile, package_name=package_name, icon=icon, color=color)
|
|
143
148
|
|
|
144
149
|
@server.tool()
|
|
145
150
|
def member_search(
|
|
@@ -121,8 +121,9 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
|
|
|
121
121
|
- Always resolve the exact code-block field from `record_code_block_schema_get` first.
|
|
122
122
|
- Treat code-block execution as write-capable, not read-only.
|
|
123
123
|
- If the code block is bound to relation outputs, Qingflow may calculate target answers and write them back automatically.
|
|
124
|
+
- For safe debugging, pass `apply_writeback=false` and inspect the parsed alias results plus `relation.calculated_answers_preview` before allowing any writeback.
|
|
124
125
|
- In workflow context, pass `role=3` and the exact `workflow_node_id`.
|
|
125
|
-
- After execution, inspect `outputs.alias_results`, `relation.target_fields`, and `writeback.verification` before claiming success.
|
|
126
|
+
- After execution, inspect `outputs.configured_aliases`, `outputs.alias_results`, `outputs.alias_map`, `relation.target_fields`, and `writeback.verification` before claiming success.
|
|
126
127
|
|
|
127
128
|
## Import Path
|
|
128
129
|
|
|
@@ -23,6 +23,8 @@ QUESTION_TYPE_MAP = {
|
|
|
23
23
|
FieldType.address: 21,
|
|
24
24
|
FieldType.attachment: 13,
|
|
25
25
|
FieldType.boolean: 10,
|
|
26
|
+
FieldType.q_linker: 20,
|
|
27
|
+
FieldType.code_block: 26,
|
|
26
28
|
FieldType.relation: 25,
|
|
27
29
|
FieldType.subtable: 18,
|
|
28
30
|
}
|
|
@@ -243,6 +245,33 @@ def build_question(field: dict[str, Any], temp_id: int) -> tuple[dict[str, Any],
|
|
|
243
245
|
"queDefaultType": 2,
|
|
244
246
|
}
|
|
245
247
|
)
|
|
248
|
+
if field_type == FieldType.code_block:
|
|
249
|
+
question.update(
|
|
250
|
+
{
|
|
251
|
+
"minOpts": -1,
|
|
252
|
+
"maxOpts": -1,
|
|
253
|
+
"codeBlockConfig": {
|
|
254
|
+
"configMode": int(config.get("config_mode") or 1),
|
|
255
|
+
"codeContent": str(config.get("code_content") or ""),
|
|
256
|
+
"resultAliasPath": deepcopy(config.get("result_alias_path") or []),
|
|
257
|
+
"beingHideOnForm": bool(config.get("being_hide_on_form", False)),
|
|
258
|
+
},
|
|
259
|
+
"autoTrigger": bool(config.get("auto_trigger", False)),
|
|
260
|
+
"customBtnTextStatus": bool(config.get("custom_button_text_enabled", False)),
|
|
261
|
+
"customBtnText": str(config.get("custom_button_text") or ""),
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
if field_type == FieldType.q_linker:
|
|
265
|
+
question.update(
|
|
266
|
+
{
|
|
267
|
+
"minOpts": -1,
|
|
268
|
+
"maxOpts": -1,
|
|
269
|
+
"remoteLookupConfig": deepcopy(config.get("remote_lookup_config") or config),
|
|
270
|
+
"autoTrigger": bool(config.get("auto_trigger", False)),
|
|
271
|
+
"customBtnTextStatus": bool(config.get("custom_button_text_enabled", False)),
|
|
272
|
+
"customBtnText": str(config.get("custom_button_text") or ""),
|
|
273
|
+
}
|
|
274
|
+
)
|
|
246
275
|
if field_type == FieldType.subtable:
|
|
247
276
|
sub_questions: list[dict[str, Any]] = []
|
|
248
277
|
for subfield in field.get("subfields", []):
|
|
@@ -82,8 +82,13 @@ class AiBuilderTools(ToolBase):
|
|
|
82
82
|
return self.builder_tool_contract(tool_name=tool_name)
|
|
83
83
|
|
|
84
84
|
@mcp.tool()
|
|
85
|
-
def package_create(
|
|
86
|
-
|
|
85
|
+
def package_create(
|
|
86
|
+
profile: str = DEFAULT_PROFILE,
|
|
87
|
+
package_name: str = "",
|
|
88
|
+
icon: str | None = None,
|
|
89
|
+
color: str | None = None,
|
|
90
|
+
) -> JSONObject:
|
|
91
|
+
return self.package_create(profile=profile, package_name=package_name, icon=icon, color=color)
|
|
87
92
|
|
|
88
93
|
@mcp.tool()
|
|
89
94
|
def member_search(
|
|
@@ -425,13 +430,32 @@ class AiBuilderTools(ToolBase):
|
|
|
425
430
|
"contract": contract,
|
|
426
431
|
}
|
|
427
432
|
|
|
428
|
-
def package_create(
|
|
429
|
-
|
|
433
|
+
def package_create(
|
|
434
|
+
self,
|
|
435
|
+
*,
|
|
436
|
+
profile: str,
|
|
437
|
+
package_name: str,
|
|
438
|
+
icon: str | None = None,
|
|
439
|
+
color: str | None = None,
|
|
440
|
+
) -> JSONObject:
|
|
441
|
+
normalized_args = {
|
|
442
|
+
"package_name": package_name,
|
|
443
|
+
**({"icon": icon} if icon else {}),
|
|
444
|
+
**({"color": color} if color else {}),
|
|
445
|
+
}
|
|
430
446
|
return _safe_tool_call(
|
|
431
|
-
lambda: self._facade.package_create(profile=profile, package_name=package_name),
|
|
447
|
+
lambda: self._facade.package_create(profile=profile, package_name=package_name, icon=icon, color=color),
|
|
432
448
|
error_code="PACKAGE_CREATE_FAILED",
|
|
433
449
|
normalized_args=normalized_args,
|
|
434
|
-
suggested_next_call={
|
|
450
|
+
suggested_next_call={
|
|
451
|
+
"tool_name": "package_create",
|
|
452
|
+
"arguments": {
|
|
453
|
+
"profile": profile,
|
|
454
|
+
"package_name": package_name,
|
|
455
|
+
**({"icon": icon} if icon else {}),
|
|
456
|
+
**({"color": color} if color else {}),
|
|
457
|
+
},
|
|
458
|
+
},
|
|
435
459
|
)
|
|
436
460
|
|
|
437
461
|
def member_search(
|
|
@@ -795,15 +819,21 @@ class AiBuilderTools(ToolBase):
|
|
|
795
819
|
"profile": profile,
|
|
796
820
|
"app_key": app_key,
|
|
797
821
|
"mode": "merge",
|
|
798
|
-
"sections": [{
|
|
822
|
+
"sections": [{
|
|
823
|
+
"type": "paragraph",
|
|
824
|
+
"paragraph_id": "basic",
|
|
825
|
+
"title": "基础信息",
|
|
826
|
+
"rows": [["字段A", "字段B", "字段C", "字段D"]],
|
|
827
|
+
}],
|
|
799
828
|
},
|
|
800
829
|
},
|
|
801
830
|
)
|
|
831
|
+
normalized_request = request.model_dump(mode="json", exclude_none=True)
|
|
802
832
|
return _safe_tool_call(
|
|
803
833
|
lambda: self._facade.app_layout_plan(profile=profile, request=request),
|
|
804
834
|
error_code="LAYOUT_PLAN_FAILED",
|
|
805
|
-
normalized_args=
|
|
806
|
-
suggested_next_call={"tool_name": "app_layout_plan", "arguments": {"profile": profile, **
|
|
835
|
+
normalized_args=normalized_request,
|
|
836
|
+
suggested_next_call={"tool_name": "app_layout_plan", "arguments": {"profile": profile, **normalized_request}},
|
|
807
837
|
)
|
|
808
838
|
|
|
809
839
|
def app_flow_plan(
|
|
@@ -1100,7 +1130,7 @@ class AiBuilderTools(ToolBase):
|
|
|
1100
1130
|
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1101
1131
|
"mode": parsed_mode.value,
|
|
1102
1132
|
"publish": publish,
|
|
1103
|
-
"sections": [section.model_dump(mode="json") for section in parsed_sections],
|
|
1133
|
+
"sections": [section.model_dump(mode="json", exclude_none=True) for section in parsed_sections],
|
|
1104
1134
|
}
|
|
1105
1135
|
return _safe_tool_call(
|
|
1106
1136
|
lambda: self._facade.app_layout_apply(
|
|
@@ -1708,6 +1738,39 @@ def _public_error_message(error_code: str, error: QingflowApiError) -> str:
|
|
|
1708
1738
|
|
|
1709
1739
|
|
|
1710
1740
|
_BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
1741
|
+
"package_list": {
|
|
1742
|
+
"allowed_keys": ["trial_status"],
|
|
1743
|
+
"aliases": {"trialStatus": "trial_status"},
|
|
1744
|
+
"allowed_values": {"trial_status": ["all", "trial", "formal"]},
|
|
1745
|
+
"minimal_example": {
|
|
1746
|
+
"profile": "default",
|
|
1747
|
+
"trial_status": "all",
|
|
1748
|
+
},
|
|
1749
|
+
},
|
|
1750
|
+
"package_resolve": {
|
|
1751
|
+
"allowed_keys": ["package_name"],
|
|
1752
|
+
"aliases": {"packageName": "package_name"},
|
|
1753
|
+
"allowed_values": {},
|
|
1754
|
+
"minimal_example": {
|
|
1755
|
+
"profile": "default",
|
|
1756
|
+
"package_name": "PLM(备用,施工中)",
|
|
1757
|
+
},
|
|
1758
|
+
},
|
|
1759
|
+
"package_create": {
|
|
1760
|
+
"allowed_keys": ["package_name", "icon", "color"],
|
|
1761
|
+
"aliases": {
|
|
1762
|
+
"packageName": "package_name",
|
|
1763
|
+
"iconName": "icon",
|
|
1764
|
+
"iconColor": "color",
|
|
1765
|
+
},
|
|
1766
|
+
"allowed_values": {},
|
|
1767
|
+
"minimal_example": {
|
|
1768
|
+
"profile": "default",
|
|
1769
|
+
"package_name": "项目管理",
|
|
1770
|
+
"icon": "files-folder",
|
|
1771
|
+
"color": "azure",
|
|
1772
|
+
},
|
|
1773
|
+
},
|
|
1711
1774
|
"member_search": {
|
|
1712
1775
|
"allowed_keys": ["query", "page_num", "page_size", "contain_disable"],
|
|
1713
1776
|
"aliases": {},
|
|
@@ -1855,6 +1918,13 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
1855
1918
|
"field.allow_multiple": "field.relation_mode",
|
|
1856
1919
|
"field.optional_data_num": "field.relation_mode",
|
|
1857
1920
|
"field.optionalDataNum": "field.relation_mode",
|
|
1921
|
+
"field.remoteLookupConfig": "field.remote_lookup_config",
|
|
1922
|
+
"field.qLinkerBinding": "field.q_linker_binding",
|
|
1923
|
+
"field.codeBlockConfig": "field.code_block_config",
|
|
1924
|
+
"field.codeBlockBinding": "field.code_block_binding",
|
|
1925
|
+
"field.autoTrigger": "field.auto_trigger",
|
|
1926
|
+
"field.customBtnTextStatus": "field.custom_button_text_enabled",
|
|
1927
|
+
"field.customBtnText": "field.custom_button_text",
|
|
1858
1928
|
},
|
|
1859
1929
|
"allowed_values": {
|
|
1860
1930
|
"field.type": [member.value for member in PublicFieldType],
|
|
@@ -1905,6 +1975,13 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
1905
1975
|
"field.allow_multiple": "field.relation_mode",
|
|
1906
1976
|
"field.optional_data_num": "field.relation_mode",
|
|
1907
1977
|
"field.optionalDataNum": "field.relation_mode",
|
|
1978
|
+
"field.remoteLookupConfig": "field.remote_lookup_config",
|
|
1979
|
+
"field.qLinkerBinding": "field.q_linker_binding",
|
|
1980
|
+
"field.codeBlockConfig": "field.code_block_config",
|
|
1981
|
+
"field.codeBlockBinding": "field.code_block_binding",
|
|
1982
|
+
"field.autoTrigger": "field.auto_trigger",
|
|
1983
|
+
"field.customBtnTextStatus": "field.custom_button_text_enabled",
|
|
1984
|
+
"field.customBtnText": "field.custom_button_text",
|
|
1908
1985
|
},
|
|
1909
1986
|
"allowed_values": {
|
|
1910
1987
|
"field.type": [member.value for member in PublicFieldType],
|
|
@@ -1915,6 +1992,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
1915
1992
|
"multiple relation fields are backend-risky; read verification.relation_field_limit_verified and warnings before declaring the schema stable",
|
|
1916
1993
|
"backend 49614 is normalized to MULTIPLE_RELATION_FIELDS_UNSUPPORTED with a workaround message",
|
|
1917
1994
|
"relation_mode=multiple maps to referenceConfig.optionalDataNum=0",
|
|
1995
|
+
"if relation target metadata lookup is blocked by 40161/40002/40027, explicit display_field.name and visible_fields[].name let builder degrade verification and still continue schema write",
|
|
1996
|
+
"q_linker_binding lets you declare request config, dynamic inputs, alias parsing, and target-field bindings in one step; builder writes remoteLookupConfig plus the existing backend relation-default and questionRelations structures",
|
|
1997
|
+
"code_block_binding lets you declare inputs, code, alias parsing, and target-field bindings in one step; builder writes codeBlockConfig plus the existing backend relation-default and questionRelations structures",
|
|
1998
|
+
"builder configures code blocks only; it does not execute or trigger code blocks",
|
|
1918
1999
|
],
|
|
1919
2000
|
"minimal_example": {
|
|
1920
2001
|
"profile": "default",
|
|
@@ -1945,34 +2026,99 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
1945
2026
|
"update_fields": [],
|
|
1946
2027
|
"remove_fields": [],
|
|
1947
2028
|
},
|
|
2029
|
+
"code_block_example": {
|
|
2030
|
+
"profile": "default",
|
|
2031
|
+
"app_key": "APP_SCRIPT",
|
|
2032
|
+
"publish": True,
|
|
2033
|
+
"add_fields": [
|
|
2034
|
+
{
|
|
2035
|
+
"name": "查询代码块",
|
|
2036
|
+
"type": "code_block",
|
|
2037
|
+
"code_block_binding": {
|
|
2038
|
+
"inputs": [
|
|
2039
|
+
{"field": {"name": "客户名称"}, "var": "customerName"},
|
|
2040
|
+
{"field": {"name": "预算金额"}, "var": "budget"},
|
|
2041
|
+
],
|
|
2042
|
+
"code": "const qf_output = {}; qf_output.customerLevel = budget > 100000 ? 'A' : 'B'; return qf_output;",
|
|
2043
|
+
"auto_trigger": True,
|
|
2044
|
+
"custom_button_text_enabled": True,
|
|
2045
|
+
"custom_button_text": "评估客户",
|
|
2046
|
+
"outputs": [
|
|
2047
|
+
{"alias": "customerLevel", "path": "$.customerLevel"},
|
|
2048
|
+
],
|
|
2049
|
+
},
|
|
2050
|
+
}
|
|
2051
|
+
],
|
|
2052
|
+
"update_fields": [],
|
|
2053
|
+
"remove_fields": [],
|
|
2054
|
+
},
|
|
2055
|
+
"q_linker_example": {
|
|
2056
|
+
"profile": "default",
|
|
2057
|
+
"app_key": "APP_CUSTOMER",
|
|
2058
|
+
"publish": True,
|
|
2059
|
+
"add_fields": [
|
|
2060
|
+
{"name": "客户名称", "type": "text"},
|
|
2061
|
+
{"name": "企业名称", "type": "text"},
|
|
2062
|
+
{"name": "统一社会信用代码", "type": "text"},
|
|
2063
|
+
{
|
|
2064
|
+
"name": "企业信息查询",
|
|
2065
|
+
"type": "q_linker",
|
|
2066
|
+
"q_linker_binding": {
|
|
2067
|
+
"inputs": [
|
|
2068
|
+
{"field": {"name": "客户名称"}, "key": "keyword", "source": "query_param"},
|
|
2069
|
+
],
|
|
2070
|
+
"request": {
|
|
2071
|
+
"url": "https://example.com/company/search",
|
|
2072
|
+
"method": "GET",
|
|
2073
|
+
"headers": [],
|
|
2074
|
+
"query_params": [],
|
|
2075
|
+
"body_type": 1,
|
|
2076
|
+
"url_encoded_value": [],
|
|
2077
|
+
"result_type": 1,
|
|
2078
|
+
"auto_trigger": True,
|
|
2079
|
+
"custom_button_text_enabled": True,
|
|
2080
|
+
"custom_button_text": "查询企业信息",
|
|
2081
|
+
},
|
|
2082
|
+
"outputs": [
|
|
2083
|
+
{"alias": "company_name", "path": "$.data.name", "target_field": {"name": "企业名称"}},
|
|
2084
|
+
{"alias": "credit_code", "path": "$.data.creditCode", "target_field": {"name": "统一社会信用代码"}},
|
|
2085
|
+
],
|
|
2086
|
+
},
|
|
2087
|
+
},
|
|
2088
|
+
],
|
|
2089
|
+
"update_fields": [],
|
|
2090
|
+
"remove_fields": [],
|
|
2091
|
+
},
|
|
1948
2092
|
},
|
|
1949
2093
|
"app_layout_plan": {
|
|
1950
2094
|
"allowed_keys": ["app_key", "mode", "sections", "preset"],
|
|
1951
2095
|
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
1952
|
-
"section_allowed_keys": ["section_id", "title", "rows"],
|
|
2096
|
+
"section_allowed_keys": ["type", "paragraph_id", "section_id", "title", "rows"],
|
|
1953
2097
|
"section_aliases": {
|
|
1954
2098
|
"name": "title",
|
|
2099
|
+
"paragraphId": "paragraph_id",
|
|
1955
2100
|
"sectionId": "section_id",
|
|
1956
2101
|
"fields": "rows",
|
|
1957
2102
|
"field_ids": "rows",
|
|
1958
2103
|
"columns": "rows_chunk_size",
|
|
1959
2104
|
},
|
|
1960
2105
|
"allowed_values": {"mode": [member.value for member in LayoutApplyMode], "preset": [member.value for member in LayoutPreset]},
|
|
1961
|
-
"minimal_section_example": {"title": "基础信息", "rows": [["字段A", "字段B"]]},
|
|
2106
|
+
"minimal_section_example": {"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]},
|
|
1962
2107
|
"minimal_example": {
|
|
1963
2108
|
"profile": "default",
|
|
1964
2109
|
"app_key": "APP_KEY",
|
|
1965
2110
|
"mode": "merge",
|
|
1966
|
-
"sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
2111
|
+
"sections": [{"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]}],
|
|
1967
2112
|
},
|
|
1968
2113
|
"preset_example": {"profile": "default", "app_key": "APP_KEY", "mode": "merge", "preset": "balanced", "sections": []},
|
|
1969
2114
|
},
|
|
1970
2115
|
"app_layout_apply": {
|
|
1971
2116
|
"allowed_keys": ["app_key", "mode", "publish", "sections"],
|
|
1972
2117
|
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
1973
|
-
"section_allowed_keys": ["section_id", "title", "rows"],
|
|
2118
|
+
"section_allowed_keys": ["type", "paragraph_id", "section_id", "title", "rows"],
|
|
1974
2119
|
"section_aliases": {
|
|
1975
2120
|
"name": "title",
|
|
2121
|
+
"paragraphId": "paragraph_id",
|
|
1976
2122
|
"sectionId": "section_id",
|
|
1977
2123
|
"fields": "rows",
|
|
1978
2124
|
"field_ids": "rows",
|
|
@@ -1983,13 +2129,13 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
1983
2129
|
"layout verification is split into layout_verified and layout_summary_verified",
|
|
1984
2130
|
"LAYOUT_SUMMARY_UNVERIFIED means raw form readback is stronger than the compact summary",
|
|
1985
2131
|
],
|
|
1986
|
-
"minimal_section_example": {"title": "基础信息", "rows": [["字段A", "字段B"]]},
|
|
2132
|
+
"minimal_section_example": {"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]},
|
|
1987
2133
|
"minimal_example": {
|
|
1988
2134
|
"profile": "default",
|
|
1989
2135
|
"app_key": "APP_KEY",
|
|
1990
2136
|
"mode": "merge",
|
|
1991
2137
|
"publish": True,
|
|
1992
|
-
"sections": [{"title": "基础信息", "rows": [["项目名称", "项目负责人"]]}],
|
|
2138
|
+
"sections": [{"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["项目名称", "项目负责人", "项目阶段", "优先级"]]}],
|
|
1993
2139
|
},
|
|
1994
2140
|
},
|
|
1995
2141
|
"app_flow_plan": {
|
|
@@ -277,6 +277,7 @@ class ApprovalTools(ToolBase):
|
|
|
277
277
|
fields=fields,
|
|
278
278
|
public_action="task_transfer",
|
|
279
279
|
)
|
|
280
|
+
self._raise_if_self_transfer(profile=profile, payload=payload)
|
|
280
281
|
raw = self.record_transfer(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
|
|
281
282
|
return self._public_action_response(
|
|
282
283
|
raw,
|
|
@@ -326,7 +327,9 @@ class ApprovalTools(ToolBase):
|
|
|
326
327
|
audit_node_id=workflow_node_id,
|
|
327
328
|
keyword=keyword,
|
|
328
329
|
)
|
|
329
|
-
|
|
330
|
+
original_items = _approval_page_items(raw.get("page"))
|
|
331
|
+
items = self._filter_self_transfer_candidates(profile=profile, items=original_items)
|
|
332
|
+
filtered_count = max(len(original_items) - len(items), 0)
|
|
330
333
|
return self._public_page_response(
|
|
331
334
|
raw,
|
|
332
335
|
items=items,
|
|
@@ -335,7 +338,7 @@ class ApprovalTools(ToolBase):
|
|
|
335
338
|
"page_size": page_size,
|
|
336
339
|
"returned_items": len(items),
|
|
337
340
|
"page_amount": _approval_page_amount(raw.get("page")),
|
|
338
|
-
"reported_total": _approval_page_total(raw.get("page")),
|
|
341
|
+
"reported_total": max(_approval_page_total(raw.get("page")) - filtered_count, 0),
|
|
339
342
|
},
|
|
340
343
|
selection={"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id, "keyword": keyword},
|
|
341
344
|
)
|
|
@@ -849,6 +852,32 @@ class ApprovalTools(ToolBase):
|
|
|
849
852
|
if payload.get("handSignImageUrl"):
|
|
850
853
|
raise_tool_error(QingflowApiError.not_supported("NOT_SUPPORTED_IN_V1: handSignImageUrl is not supported"))
|
|
851
854
|
|
|
855
|
+
def _extract_transfer_target_uid(self, payload: dict[str, Any]) -> int | None:
|
|
856
|
+
for key in ("uid", "target_member_id", "targetMemberId"):
|
|
857
|
+
value = payload.get(key)
|
|
858
|
+
if isinstance(value, int) and value > 0:
|
|
859
|
+
return value
|
|
860
|
+
return None
|
|
861
|
+
|
|
862
|
+
def _raise_if_self_transfer(self, *, profile: str, payload: dict[str, Any]) -> None:
|
|
863
|
+
target_uid = self._extract_transfer_target_uid(payload)
|
|
864
|
+
if target_uid is None:
|
|
865
|
+
return
|
|
866
|
+
session_profile = self.sessions.get_profile(profile)
|
|
867
|
+
if session_profile is not None and target_uid == session_profile.uid:
|
|
868
|
+
raise_tool_error(
|
|
869
|
+
QingflowApiError.config_error(
|
|
870
|
+
"task transfer does not support transferring to the current user; choose another transfer member"
|
|
871
|
+
)
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
def _filter_self_transfer_candidates(self, *, profile: str, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
875
|
+
session_profile = self.sessions.get_profile(profile)
|
|
876
|
+
if session_profile is None:
|
|
877
|
+
return items
|
|
878
|
+
current_uid = session_profile.uid
|
|
879
|
+
return [item for item in items if item.get("uid") != current_uid]
|
|
880
|
+
|
|
852
881
|
def _delegate_task_action(
|
|
853
882
|
self,
|
|
854
883
|
*,
|