@josephyan/qingflow-cli 0.2.0-beta.984 → 0.2.0-beta.986
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 +70 -11
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +47 -21
- package/src/qingflow_mcp/cli/commands/auth.py +14 -43
- package/src/qingflow_mcp/cli/commands/task.py +4 -1
- package/src/qingflow_mcp/cli/commands/workspace.py +0 -8
- package/src/qingflow_mcp/cli/formatters.py +0 -21
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/errors.py +2 -2
- package/src/qingflow_mcp/public_surface.py +2 -6
- package/src/qingflow_mcp/response_trim.py +1 -8
- package/src/qingflow_mcp/server.py +1 -1
- package/src/qingflow_mcp/server_app_builder.py +4 -28
- package/src/qingflow_mcp/server_app_user.py +4 -28
- package/src/qingflow_mcp/session_store.py +31 -5
- package/src/qingflow_mcp/tools/ai_builder_tools.py +117 -1
- package/src/qingflow_mcp/tools/app_tools.py +51 -1
- package/src/qingflow_mcp/tools/approval_tools.py +82 -1
- package/src/qingflow_mcp/tools/auth_tools.py +258 -288
- package/src/qingflow_mcp/tools/base.py +204 -4
- package/src/qingflow_mcp/tools/code_block_tools.py +21 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +24 -1
- package/src/qingflow_mcp/tools/directory_tools.py +28 -1
- package/src/qingflow_mcp/tools/feedback_tools.py +8 -0
- package/src/qingflow_mcp/tools/file_tools.py +25 -1
- package/src/qingflow_mcp/tools/import_tools.py +40 -1
- package/src/qingflow_mcp/tools/navigation_tools.py +34 -1
- package/src/qingflow_mcp/tools/package_tools.py +37 -1
- package/src/qingflow_mcp/tools/portal_tools.py +28 -1
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +38 -1
- package/src/qingflow_mcp/tools/record_tools.py +255 -2
- package/src/qingflow_mcp/tools/repository_dev_tools.py +21 -2
- package/src/qingflow_mcp/tools/resource_read_tools.py +23 -1
- package/src/qingflow_mcp/tools/role_tools.py +19 -1
- package/src/qingflow_mcp/tools/solution_tools.py +56 -1
- package/src/qingflow_mcp/tools/task_context_tools.py +205 -6
- package/src/qingflow_mcp/tools/task_tools.py +49 -3
- package/src/qingflow_mcp/tools/view_tools.py +56 -1
- package/src/qingflow_mcp/tools/workflow_tools.py +65 -1
- package/src/qingflow_mcp/tools/workspace_tools.py +14 -225
|
@@ -12,7 +12,7 @@ from ..config import DEFAULT_PROFILE
|
|
|
12
12
|
from ..errors import QingflowApiError, raise_tool_error
|
|
13
13
|
from ..json_types import JSONObject
|
|
14
14
|
from .approval_tools import ApprovalTools, _approval_page_amount, _approval_page_items, _approval_page_total
|
|
15
|
-
from .base import ToolBase
|
|
15
|
+
from .base import ToolBase, tool_cn_name
|
|
16
16
|
from .qingbi_report_tools import _qingbi_base_url
|
|
17
17
|
from .record_tools import (
|
|
18
18
|
FieldIndex,
|
|
@@ -38,14 +38,30 @@ from .task_tools import TaskTools, _task_page_amount, _task_page_items, _task_pa
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class TaskContextTools(ToolBase):
|
|
41
|
+
"""任务上下文工具(中文名:任务上下文与审批执行)。
|
|
42
|
+
|
|
43
|
+
类型:任务深度上下文工具。
|
|
44
|
+
主要职责:
|
|
45
|
+
1. 聚合任务详情、候选人、关联报表与流程日志;
|
|
46
|
+
2. 执行审批动作(通过、驳回、转交等);
|
|
47
|
+
3. 为任务处理过程提供可执行上下文而非仅列表数据。
|
|
48
|
+
"""
|
|
49
|
+
|
|
41
50
|
def __init__(self, sessions, backend) -> None: # type: ignore[no-untyped-def]
|
|
51
|
+
"""执行内部辅助逻辑。"""
|
|
42
52
|
super().__init__(sessions, backend)
|
|
43
53
|
self._task_tools = TaskTools(sessions, backend)
|
|
44
54
|
self._approval_tools = ApprovalTools(sessions, backend)
|
|
45
55
|
self._record_tools = RecordTools(sessions, backend)
|
|
46
56
|
|
|
47
57
|
def register(self, mcp: FastMCP) -> None:
|
|
48
|
-
|
|
58
|
+
"""注册当前工具到 MCP 服务。"""
|
|
59
|
+
@mcp.tool(
|
|
60
|
+
description=(
|
|
61
|
+
"List workflow tasks. `query` first uses backend task search; if the backend returns zero rows, "
|
|
62
|
+
"public task_list falls back to local matching on app_name, workflow_node_name, app_key, and record_id."
|
|
63
|
+
)
|
|
64
|
+
)
|
|
49
65
|
def task_list(
|
|
50
66
|
profile: str = DEFAULT_PROFILE,
|
|
51
67
|
task_box: str = "todo",
|
|
@@ -139,6 +155,7 @@ class TaskContextTools(ToolBase):
|
|
|
139
155
|
workflow_node_id=workflow_node_id,
|
|
140
156
|
)
|
|
141
157
|
|
|
158
|
+
@tool_cn_name("任务上下文列表")
|
|
142
159
|
def task_list(
|
|
143
160
|
self,
|
|
144
161
|
*,
|
|
@@ -151,6 +168,7 @@ class TaskContextTools(ToolBase):
|
|
|
151
168
|
page: int,
|
|
152
169
|
page_size: int,
|
|
153
170
|
) -> dict[str, Any]:
|
|
171
|
+
"""执行任务相关逻辑。"""
|
|
154
172
|
normalized_type = self._task_tools._task_box_to_type(task_box)
|
|
155
173
|
normalized_status = self._task_tools._flow_status_to_process_status(flow_status)
|
|
156
174
|
raw = self._task_tools.task_list(
|
|
@@ -165,26 +183,55 @@ class TaskContextTools(ToolBase):
|
|
|
165
183
|
create_time_asc=None,
|
|
166
184
|
)
|
|
167
185
|
task_page = raw.get("page", {})
|
|
186
|
+
warnings: list[dict[str, Any]] = []
|
|
168
187
|
items = [
|
|
169
188
|
self._normalize_task_item(item, task_box=task_box, flow_status=flow_status)
|
|
170
189
|
for item in _task_page_items(task_page)
|
|
171
190
|
if isinstance(item, dict)
|
|
172
191
|
]
|
|
192
|
+
returned_items = len(items)
|
|
193
|
+
page_amount = _task_page_amount(task_page)
|
|
194
|
+
reported_total = _task_page_total(task_page)
|
|
195
|
+
if query and not items:
|
|
196
|
+
fallback = self._task_list_local_query_fallback(
|
|
197
|
+
profile=profile,
|
|
198
|
+
task_box=task_box,
|
|
199
|
+
flow_status=flow_status,
|
|
200
|
+
app_key=app_key,
|
|
201
|
+
workflow_node_id=workflow_node_id,
|
|
202
|
+
query=query,
|
|
203
|
+
page=page,
|
|
204
|
+
page_size=page_size,
|
|
205
|
+
)
|
|
206
|
+
if fallback is not None:
|
|
207
|
+
items = fallback["items"]
|
|
208
|
+
returned_items = len(items)
|
|
209
|
+
page_amount = fallback["page_amount"]
|
|
210
|
+
reported_total = fallback["reported_total"]
|
|
211
|
+
warnings.append(
|
|
212
|
+
{
|
|
213
|
+
"code": "TASK_LIST_QUERY_FALLBACK_APPLIED",
|
|
214
|
+
"message": (
|
|
215
|
+
"backend searchKey returned zero tasks; task_list fell back to local matching on "
|
|
216
|
+
"app_name, workflow_node_name, app_key, and record_id."
|
|
217
|
+
),
|
|
218
|
+
}
|
|
219
|
+
)
|
|
173
220
|
return {
|
|
174
221
|
"profile": profile,
|
|
175
222
|
"ws_id": raw.get("ws_id"),
|
|
176
223
|
"ok": True,
|
|
177
224
|
"request_route": raw.get("request_route"),
|
|
178
|
-
"warnings":
|
|
225
|
+
"warnings": warnings,
|
|
179
226
|
"output_profile": "normal",
|
|
180
227
|
"data": {
|
|
181
228
|
"items": items,
|
|
182
229
|
"pagination": {
|
|
183
230
|
"page": page,
|
|
184
231
|
"page_size": page_size,
|
|
185
|
-
"returned_items":
|
|
186
|
-
"page_amount":
|
|
187
|
-
"reported_total":
|
|
232
|
+
"returned_items": returned_items,
|
|
233
|
+
"page_amount": page_amount,
|
|
234
|
+
"reported_total": reported_total,
|
|
188
235
|
},
|
|
189
236
|
"selection": {
|
|
190
237
|
"task_box": task_box,
|
|
@@ -196,6 +243,7 @@ class TaskContextTools(ToolBase):
|
|
|
196
243
|
},
|
|
197
244
|
}
|
|
198
245
|
|
|
246
|
+
@tool_cn_name("任务上下文详情")
|
|
199
247
|
def task_get(
|
|
200
248
|
self,
|
|
201
249
|
*,
|
|
@@ -206,6 +254,7 @@ class TaskContextTools(ToolBase):
|
|
|
206
254
|
include_candidates: bool,
|
|
207
255
|
include_associated_reports: bool,
|
|
208
256
|
) -> dict[str, Any]:
|
|
257
|
+
"""执行任务相关逻辑。"""
|
|
209
258
|
self._require_app_record_and_node(app_key, record_id, workflow_node_id)
|
|
210
259
|
|
|
211
260
|
def runner(session_profile, context):
|
|
@@ -232,6 +281,7 @@ class TaskContextTools(ToolBase):
|
|
|
232
281
|
|
|
233
282
|
return self._run(profile, runner)
|
|
234
283
|
|
|
284
|
+
@tool_cn_name("任务仅保存")
|
|
235
285
|
def task_save_only(
|
|
236
286
|
self,
|
|
237
287
|
*,
|
|
@@ -241,6 +291,7 @@ class TaskContextTools(ToolBase):
|
|
|
241
291
|
workflow_node_id: int,
|
|
242
292
|
fields: dict[str, Any] | None = None,
|
|
243
293
|
) -> dict[str, Any]:
|
|
294
|
+
"""执行任务相关逻辑。"""
|
|
244
295
|
field_updates = dict(fields or {})
|
|
245
296
|
if not field_updates:
|
|
246
297
|
raise_tool_error(QingflowApiError.config_error("fields is required and must be non-empty for task_save_only"))
|
|
@@ -254,6 +305,7 @@ class TaskContextTools(ToolBase):
|
|
|
254
305
|
fields=field_updates,
|
|
255
306
|
)
|
|
256
307
|
|
|
308
|
+
@tool_cn_name("执行任务动作")
|
|
257
309
|
def task_action_execute(
|
|
258
310
|
self,
|
|
259
311
|
*,
|
|
@@ -265,6 +317,7 @@ class TaskContextTools(ToolBase):
|
|
|
265
317
|
payload: dict[str, Any],
|
|
266
318
|
fields: dict[str, Any] | None = None,
|
|
267
319
|
) -> dict[str, Any]:
|
|
320
|
+
"""执行任务相关逻辑。"""
|
|
268
321
|
self._require_app_record_and_node(app_key, record_id, workflow_node_id)
|
|
269
322
|
normalized_action = (action or "").strip().lower()
|
|
270
323
|
if normalized_action not in {"approve", "reject", "rollback", "transfer", "urge", "save_only"}:
|
|
@@ -460,6 +513,7 @@ class TaskContextTools(ToolBase):
|
|
|
460
513
|
payload: dict[str, Any],
|
|
461
514
|
prepared_fields: dict[str, Any] | None,
|
|
462
515
|
) -> dict[str, Any]:
|
|
516
|
+
"""执行内部辅助逻辑。"""
|
|
463
517
|
merged_answers = None
|
|
464
518
|
normalized_answers = None
|
|
465
519
|
if isinstance(prepared_fields, dict):
|
|
@@ -555,6 +609,7 @@ class TaskContextTools(ToolBase):
|
|
|
555
609
|
before_apply_status: Any,
|
|
556
610
|
runtime_baseline: dict[str, Any] | None = None,
|
|
557
611
|
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
612
|
+
"""执行内部辅助逻辑。"""
|
|
558
613
|
verification: dict[str, Any] = {
|
|
559
614
|
"action_executed": True,
|
|
560
615
|
"runtime_continuation_verified": action == "urge",
|
|
@@ -656,6 +711,25 @@ class TaskContextTools(ToolBase):
|
|
|
656
711
|
or verification.get("downstream_todo_changed")
|
|
657
712
|
or verification.get("workflow_log_advanced")
|
|
658
713
|
)
|
|
714
|
+
record_state_error = verification.get("record_state_error")
|
|
715
|
+
runtime_consumed_after_action = bool(
|
|
716
|
+
runtime_verified
|
|
717
|
+
and isinstance(record_state_error, dict)
|
|
718
|
+
and record_state_error.get("backend_code") == 46001
|
|
719
|
+
)
|
|
720
|
+
if runtime_consumed_after_action:
|
|
721
|
+
verification["record_state_scope"] = "current_node_runtime"
|
|
722
|
+
verification["record_state_unavailable_reason"] = "runtime_consumed_after_action"
|
|
723
|
+
verification["record_state_unavailability_expected"] = True
|
|
724
|
+
warnings.append(
|
|
725
|
+
{
|
|
726
|
+
"code": "TASK_RUNTIME_CONSUMED_AFTER_ACTION",
|
|
727
|
+
"message": (
|
|
728
|
+
"the current workflow node runtime is no longer readable after the action (backend 46001), "
|
|
729
|
+
"which usually means the node has been consumed and the workflow has already continued."
|
|
730
|
+
),
|
|
731
|
+
}
|
|
732
|
+
)
|
|
659
733
|
verification["runtime_continuation_verified"] = runtime_verified
|
|
660
734
|
if not runtime_verified:
|
|
661
735
|
warnings.append(
|
|
@@ -677,6 +751,7 @@ class TaskContextTools(ToolBase):
|
|
|
677
751
|
expected_answers: list[dict[str, Any]],
|
|
678
752
|
task_context: dict[str, Any],
|
|
679
753
|
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
754
|
+
"""执行内部辅助逻辑。"""
|
|
680
755
|
verification: dict[str, Any] = {
|
|
681
756
|
"action_executed": True,
|
|
682
757
|
"scope": "task_field_save",
|
|
@@ -790,6 +865,7 @@ class TaskContextTools(ToolBase):
|
|
|
790
865
|
record_id: int,
|
|
791
866
|
workflow_node_id: int,
|
|
792
867
|
) -> dict[str, Any]:
|
|
868
|
+
"""执行内部辅助逻辑。"""
|
|
793
869
|
baseline: dict[str, Any] = {
|
|
794
870
|
"workflow_log_visible": False,
|
|
795
871
|
"workflow_log_count": None,
|
|
@@ -841,6 +917,7 @@ class TaskContextTools(ToolBase):
|
|
|
841
917
|
source_error: QingflowApiError,
|
|
842
918
|
before_apply_status: Any,
|
|
843
919
|
) -> dict[str, Any]:
|
|
920
|
+
"""执行内部辅助逻辑。"""
|
|
844
921
|
verification, warnings = self._verify_task_action_runtime(
|
|
845
922
|
profile=profile,
|
|
846
923
|
context=context,
|
|
@@ -916,6 +993,7 @@ class TaskContextTools(ToolBase):
|
|
|
916
993
|
}
|
|
917
994
|
|
|
918
995
|
def _safe_task_list_items(self, *, profile: str, task_box: str, app_key: str) -> list[dict[str, Any]]:
|
|
996
|
+
"""执行内部辅助逻辑。"""
|
|
919
997
|
try:
|
|
920
998
|
response = self.task_list(
|
|
921
999
|
profile=profile,
|
|
@@ -935,6 +1013,82 @@ class TaskContextTools(ToolBase):
|
|
|
935
1013
|
return []
|
|
936
1014
|
return [item for item in items if isinstance(item, dict)]
|
|
937
1015
|
|
|
1016
|
+
def _task_list_local_query_fallback(
|
|
1017
|
+
self,
|
|
1018
|
+
*,
|
|
1019
|
+
profile: str,
|
|
1020
|
+
task_box: str,
|
|
1021
|
+
flow_status: str,
|
|
1022
|
+
app_key: str | None,
|
|
1023
|
+
workflow_node_id: int | None,
|
|
1024
|
+
query: str,
|
|
1025
|
+
page: int,
|
|
1026
|
+
page_size: int,
|
|
1027
|
+
) -> dict[str, Any] | None:
|
|
1028
|
+
normalized_type = self._task_tools._task_box_to_type(task_box)
|
|
1029
|
+
normalized_status = self._task_tools._flow_status_to_process_status(flow_status)
|
|
1030
|
+
scan_page_size = max(page_size, 100)
|
|
1031
|
+
scan_page = 1
|
|
1032
|
+
page_amount: int | None = None
|
|
1033
|
+
matched_items: list[dict[str, Any]] = []
|
|
1034
|
+
while True:
|
|
1035
|
+
raw = self._task_tools.task_list(
|
|
1036
|
+
profile=profile,
|
|
1037
|
+
type=normalized_type,
|
|
1038
|
+
process_status=normalized_status,
|
|
1039
|
+
app_key=app_key,
|
|
1040
|
+
node_id=workflow_node_id,
|
|
1041
|
+
search_key=None,
|
|
1042
|
+
page_num=scan_page,
|
|
1043
|
+
page_size=scan_page_size,
|
|
1044
|
+
create_time_asc=None,
|
|
1045
|
+
)
|
|
1046
|
+
task_page = raw.get("page", {})
|
|
1047
|
+
raw_items = _task_page_items(task_page)
|
|
1048
|
+
normalized_items = [
|
|
1049
|
+
self._normalize_task_item(item, task_box=task_box, flow_status=flow_status)
|
|
1050
|
+
for item in raw_items
|
|
1051
|
+
if isinstance(item, dict)
|
|
1052
|
+
]
|
|
1053
|
+
matched_items.extend(item for item in normalized_items if self._task_item_matches_query(item, query))
|
|
1054
|
+
if page_amount is None:
|
|
1055
|
+
coerced_page_amount = _coerce_count(_task_page_amount(task_page))
|
|
1056
|
+
if coerced_page_amount is not None and coerced_page_amount > 0:
|
|
1057
|
+
page_amount = coerced_page_amount
|
|
1058
|
+
if page_amount is not None and scan_page >= page_amount:
|
|
1059
|
+
break
|
|
1060
|
+
if not raw_items or len(raw_items) < scan_page_size:
|
|
1061
|
+
break
|
|
1062
|
+
scan_page += 1
|
|
1063
|
+
if not matched_items:
|
|
1064
|
+
return None
|
|
1065
|
+
start = max(page - 1, 0) * page_size
|
|
1066
|
+
end = start + page_size
|
|
1067
|
+
matched_total = len(matched_items)
|
|
1068
|
+
matched_page_amount = (matched_total + page_size - 1) // page_size if page_size > 0 else 0
|
|
1069
|
+
return {
|
|
1070
|
+
"items": matched_items[start:end],
|
|
1071
|
+
"page_amount": matched_page_amount,
|
|
1072
|
+
"reported_total": matched_total,
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
def _task_item_matches_query(self, item: dict[str, Any], query: str) -> bool:
|
|
1076
|
+
needle = str(query or "").strip().casefold()
|
|
1077
|
+
if not needle:
|
|
1078
|
+
return False
|
|
1079
|
+
for candidate in (
|
|
1080
|
+
item.get("app_name"),
|
|
1081
|
+
item.get("workflow_node_name"),
|
|
1082
|
+
item.get("app_key"),
|
|
1083
|
+
item.get("record_id"),
|
|
1084
|
+
):
|
|
1085
|
+
if candidate in (None, ""):
|
|
1086
|
+
continue
|
|
1087
|
+
if needle in str(candidate).casefold():
|
|
1088
|
+
return True
|
|
1089
|
+
return False
|
|
1090
|
+
|
|
1091
|
+
@tool_cn_name("任务关联报表详情")
|
|
938
1092
|
def task_associated_report_detail_get(
|
|
939
1093
|
self,
|
|
940
1094
|
*,
|
|
@@ -946,6 +1100,7 @@ class TaskContextTools(ToolBase):
|
|
|
946
1100
|
page: int,
|
|
947
1101
|
page_size: int,
|
|
948
1102
|
) -> dict[str, Any]:
|
|
1103
|
+
"""执行任务相关逻辑。"""
|
|
949
1104
|
self._require_app_record_and_node(app_key, record_id, workflow_node_id)
|
|
950
1105
|
if report_id <= 0:
|
|
951
1106
|
raise_tool_error(QingflowApiError.config_error("report_id must be positive"))
|
|
@@ -1125,6 +1280,7 @@ class TaskContextTools(ToolBase):
|
|
|
1125
1280
|
|
|
1126
1281
|
return self._run(profile, runner)
|
|
1127
1282
|
|
|
1283
|
+
@tool_cn_name("任务流程日志")
|
|
1128
1284
|
def task_workflow_log_get(
|
|
1129
1285
|
self,
|
|
1130
1286
|
*,
|
|
@@ -1133,6 +1289,7 @@ class TaskContextTools(ToolBase):
|
|
|
1133
1289
|
record_id: int,
|
|
1134
1290
|
workflow_node_id: int,
|
|
1135
1291
|
) -> dict[str, Any]:
|
|
1292
|
+
"""执行任务相关逻辑。"""
|
|
1136
1293
|
self._require_app_record_and_node(app_key, record_id, workflow_node_id)
|
|
1137
1294
|
|
|
1138
1295
|
def runner(session_profile, context):
|
|
@@ -1202,6 +1359,7 @@ class TaskContextTools(ToolBase):
|
|
|
1202
1359
|
include_associated_reports: bool,
|
|
1203
1360
|
current_uid: int | None = None,
|
|
1204
1361
|
) -> dict[str, Any]:
|
|
1362
|
+
"""执行内部辅助逻辑。"""
|
|
1205
1363
|
audit_infos = self.backend.request(
|
|
1206
1364
|
"GET",
|
|
1207
1365
|
context,
|
|
@@ -1416,6 +1574,12 @@ class TaskContextTools(ToolBase):
|
|
|
1416
1574
|
},
|
|
1417
1575
|
},
|
|
1418
1576
|
}
|
|
1577
|
+
action_metadata = self._compact_task_action_metadata(capabilities)
|
|
1578
|
+
if action_metadata:
|
|
1579
|
+
compact["action_metadata"] = action_metadata
|
|
1580
|
+
editable_metadata = self._compact_task_editable_metadata(update_schema)
|
|
1581
|
+
if editable_metadata:
|
|
1582
|
+
compact["editable_metadata"] = editable_metadata
|
|
1419
1583
|
return compact
|
|
1420
1584
|
|
|
1421
1585
|
def _compact_task_action_metadata(self, capabilities: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -1553,6 +1717,7 @@ class TaskContextTools(ToolBase):
|
|
|
1553
1717
|
}
|
|
1554
1718
|
|
|
1555
1719
|
def _normalize_task_item(self, raw: dict[str, Any], *, task_box: str, flow_status: str) -> dict[str, Any]:
|
|
1720
|
+
"""执行内部辅助逻辑。"""
|
|
1556
1721
|
app_key = raw.get("appKey") or raw.get("app_key")
|
|
1557
1722
|
record_id = raw.get("rowRecordId") or raw.get("recordId") or raw.get("applyId")
|
|
1558
1723
|
workflow_node_id = raw.get("nodeId") or raw.get("auditNodeId")
|
|
@@ -1570,6 +1735,7 @@ class TaskContextTools(ToolBase):
|
|
|
1570
1735
|
}
|
|
1571
1736
|
|
|
1572
1737
|
def _select_task_node(self, infos: Any, workflow_node_id: int, *, app_key: str, record_id: int) -> dict[str, Any]:
|
|
1738
|
+
"""执行内部辅助逻辑。"""
|
|
1573
1739
|
if not isinstance(infos, list) or not infos:
|
|
1574
1740
|
raise_tool_error(
|
|
1575
1741
|
QingflowApiError.config_error(
|
|
@@ -1598,6 +1764,7 @@ class TaskContextTools(ToolBase):
|
|
|
1598
1764
|
warnings: list[JSONObject] | None = None,
|
|
1599
1765
|
save_only_source: str = "workflow_editable_que_ids",
|
|
1600
1766
|
) -> dict[str, Any]:
|
|
1767
|
+
"""执行内部辅助逻辑。"""
|
|
1601
1768
|
available_actions = ["approve"]
|
|
1602
1769
|
if self._coerce_bool(node_info.get("rejectBtnStatus")):
|
|
1603
1770
|
available_actions.append("reject")
|
|
@@ -1648,6 +1815,7 @@ class TaskContextTools(ToolBase):
|
|
|
1648
1815
|
node_info: dict[str, Any],
|
|
1649
1816
|
current_answers: Any,
|
|
1650
1817
|
) -> dict[str, Any]:
|
|
1818
|
+
"""执行内部辅助逻辑。"""
|
|
1651
1819
|
try:
|
|
1652
1820
|
app_schema = self._record_tools._get_form_schema(profile, context, app_key, force_refresh=False)
|
|
1653
1821
|
except QingflowApiError as error:
|
|
@@ -1768,6 +1936,7 @@ class TaskContextTools(ToolBase):
|
|
|
1768
1936
|
current_answers: Any,
|
|
1769
1937
|
editable_question_ids: set[int],
|
|
1770
1938
|
) -> tuple[FieldIndex, list[JSONObject]]:
|
|
1939
|
+
"""执行内部辅助逻辑。"""
|
|
1771
1940
|
if not editable_question_ids:
|
|
1772
1941
|
return index, []
|
|
1773
1942
|
missing_field_ids = {
|
|
@@ -1807,6 +1976,7 @@ class TaskContextTools(ToolBase):
|
|
|
1807
1976
|
workflow_node_id: int,
|
|
1808
1977
|
node_info: dict[str, Any],
|
|
1809
1978
|
) -> tuple[set[int], list[JSONObject], str]:
|
|
1979
|
+
"""执行内部辅助逻辑。"""
|
|
1810
1980
|
warnings: list[JSONObject] = []
|
|
1811
1981
|
try:
|
|
1812
1982
|
payload = self.backend.request(
|
|
@@ -1836,6 +2006,7 @@ class TaskContextTools(ToolBase):
|
|
|
1836
2006
|
app_key: str,
|
|
1837
2007
|
workflow_node_id: int,
|
|
1838
2008
|
) -> tuple[bool, list[JSONObject], str]:
|
|
2009
|
+
"""执行内部辅助逻辑。"""
|
|
1839
2010
|
try:
|
|
1840
2011
|
payload = self.backend.request(
|
|
1841
2012
|
"GET",
|
|
@@ -1855,6 +2026,7 @@ class TaskContextTools(ToolBase):
|
|
|
1855
2026
|
return bool(self._extract_question_ids(payload)), [], "workflow_editable_que_ids"
|
|
1856
2027
|
|
|
1857
2028
|
def _extract_question_ids(self, payload: Any) -> set[int]:
|
|
2029
|
+
"""执行内部辅助逻辑。"""
|
|
1858
2030
|
candidates: list[Any] = []
|
|
1859
2031
|
if isinstance(payload, list):
|
|
1860
2032
|
candidates = payload
|
|
@@ -1878,6 +2050,7 @@ class TaskContextTools(ToolBase):
|
|
|
1878
2050
|
return question_ids
|
|
1879
2051
|
|
|
1880
2052
|
def _editable_ids_from_que_auth_setting(self, payload: Any) -> set[int]:
|
|
2053
|
+
"""执行内部辅助逻辑。"""
|
|
1881
2054
|
if not isinstance(payload, list):
|
|
1882
2055
|
return set()
|
|
1883
2056
|
editable_ids: set[int] = set()
|
|
@@ -1912,6 +2085,7 @@ class TaskContextTools(ToolBase):
|
|
|
1912
2085
|
task_context: dict[str, Any],
|
|
1913
2086
|
fields: dict[str, Any],
|
|
1914
2087
|
) -> dict[str, Any]:
|
|
2088
|
+
"""执行内部辅助逻辑。"""
|
|
1915
2089
|
record = task_context.get("record") if isinstance(task_context.get("record"), dict) else {}
|
|
1916
2090
|
current_answers = record.get("answers") if isinstance(record.get("answers"), list) else []
|
|
1917
2091
|
node = task_context.get("node") if isinstance(task_context.get("node"), dict) else {}
|
|
@@ -1996,6 +2170,7 @@ class TaskContextTools(ToolBase):
|
|
|
1996
2170
|
index: Any,
|
|
1997
2171
|
effective_editable_ids: set[int],
|
|
1998
2172
|
) -> list[dict[str, Any]]:
|
|
2173
|
+
"""执行内部辅助逻辑。"""
|
|
1999
2174
|
if index is None:
|
|
2000
2175
|
return []
|
|
2001
2176
|
field_errors: list[dict[str, Any]] = []
|
|
@@ -2045,6 +2220,7 @@ class TaskContextTools(ToolBase):
|
|
|
2045
2220
|
workflow_node_id: int,
|
|
2046
2221
|
apply_answers: list[dict[str, Any]],
|
|
2047
2222
|
) -> dict[str, Any]:
|
|
2223
|
+
"""执行内部辅助逻辑。"""
|
|
2048
2224
|
def runner(session_profile, context):
|
|
2049
2225
|
result = self.backend.request(
|
|
2050
2226
|
"POST",
|
|
@@ -2064,6 +2240,7 @@ class TaskContextTools(ToolBase):
|
|
|
2064
2240
|
return self._run(profile, runner)
|
|
2065
2241
|
|
|
2066
2242
|
def _build_visibility(self, node_info: dict[str, Any], detail: dict[str, Any]) -> dict[str, bool]:
|
|
2243
|
+
"""执行内部辅助逻辑。"""
|
|
2067
2244
|
return {
|
|
2068
2245
|
"comment_visible": self._coerce_bool(node_info.get("commentStatus")),
|
|
2069
2246
|
"audit_record_visible": self._coerce_bool(node_info.get("auditRecordVisible")),
|
|
@@ -2073,12 +2250,14 @@ class TaskContextTools(ToolBase):
|
|
|
2073
2250
|
}
|
|
2074
2251
|
|
|
2075
2252
|
def _resolve_associated_report_visible(self, node_info: dict[str, Any], detail: dict[str, Any]) -> bool:
|
|
2253
|
+
"""执行内部辅助逻辑。"""
|
|
2076
2254
|
node_visible = node_info.get("asosChartVisible")
|
|
2077
2255
|
if node_visible is not None:
|
|
2078
2256
|
return self._coerce_bool(node_visible)
|
|
2079
2257
|
return self._coerce_bool(detail.get("viewAsosChartVisible"))
|
|
2080
2258
|
|
|
2081
2259
|
def _normalize_associated_report(self, raw: dict[str, Any]) -> dict[str, Any]:
|
|
2260
|
+
"""执行内部辅助逻辑。"""
|
|
2082
2261
|
graph_type = str(raw.get("graphType") or "").strip().lower()
|
|
2083
2262
|
source_type = str(raw.get("sourceType") or "").strip().lower()
|
|
2084
2263
|
return {
|
|
@@ -2094,6 +2273,7 @@ class TaskContextTools(ToolBase):
|
|
|
2094
2273
|
}
|
|
2095
2274
|
|
|
2096
2275
|
def _rollback_candidate_items(self, payload: Any) -> list[dict[str, Any]]:
|
|
2276
|
+
"""执行内部辅助逻辑。"""
|
|
2097
2277
|
if isinstance(payload, dict):
|
|
2098
2278
|
revert_nodes = payload.get("revertNodes")
|
|
2099
2279
|
if isinstance(revert_nodes, list):
|
|
@@ -2101,6 +2281,7 @@ class TaskContextTools(ToolBase):
|
|
|
2101
2281
|
return [item for item in _approval_page_items(payload) if isinstance(item, dict)]
|
|
2102
2282
|
|
|
2103
2283
|
def _filter_transfer_members(self, items: Any, *, current_uid: int | None) -> list[dict[str, Any]]:
|
|
2284
|
+
"""执行内部辅助逻辑。"""
|
|
2104
2285
|
if not isinstance(items, list):
|
|
2105
2286
|
return []
|
|
2106
2287
|
filtered: list[dict[str, Any]] = []
|
|
@@ -2187,6 +2368,7 @@ class TaskContextTools(ToolBase):
|
|
|
2187
2368
|
return json.dumps(item, ensure_ascii=False, sort_keys=True, default=str)
|
|
2188
2369
|
|
|
2189
2370
|
def _find_associated_report(self, task_context: dict[str, Any], report_id: int) -> dict[str, Any] | None:
|
|
2371
|
+
"""执行内部辅助逻辑。"""
|
|
2190
2372
|
associated_reports = ((task_context.get("associated_reports") or {}).get("items") or [])
|
|
2191
2373
|
for item in associated_reports:
|
|
2192
2374
|
if isinstance(item, dict) and item.get("report_id") == report_id:
|
|
@@ -2194,6 +2376,7 @@ class TaskContextTools(ToolBase):
|
|
|
2194
2376
|
return None
|
|
2195
2377
|
|
|
2196
2378
|
def _build_association_query(self, asos_chart: dict[str, Any], answers: list[dict[str, Any]]) -> dict[str, Any]:
|
|
2379
|
+
"""执行内部辅助逻辑。"""
|
|
2197
2380
|
key_que_ids = self._collect_match_rule_question_ids(asos_chart.get("matchRules") or [])
|
|
2198
2381
|
key_values: list[dict[str, Any]] = []
|
|
2199
2382
|
for answer in answers:
|
|
@@ -2228,6 +2411,7 @@ class TaskContextTools(ToolBase):
|
|
|
2228
2411
|
}
|
|
2229
2412
|
|
|
2230
2413
|
def _collect_match_rule_question_ids(self, match_rules: Any) -> set[int]:
|
|
2414
|
+
"""执行内部辅助逻辑。"""
|
|
2231
2415
|
question_ids: set[int] = set()
|
|
2232
2416
|
|
|
2233
2417
|
def visit(node: Any) -> None:
|
|
@@ -2249,6 +2433,7 @@ class TaskContextTools(ToolBase):
|
|
|
2249
2433
|
return question_ids
|
|
2250
2434
|
|
|
2251
2435
|
def _extract_answer_values(self, answer: dict[str, Any]) -> list[str]:
|
|
2436
|
+
"""执行内部辅助逻辑。"""
|
|
2252
2437
|
values = answer.get("values")
|
|
2253
2438
|
if not isinstance(values, list):
|
|
2254
2439
|
return []
|
|
@@ -2276,6 +2461,7 @@ class TaskContextTools(ToolBase):
|
|
|
2276
2461
|
return deduped
|
|
2277
2462
|
|
|
2278
2463
|
def _sanitize_associated_chart(self, asos_chart: dict[str, Any]) -> dict[str, Any]:
|
|
2464
|
+
"""执行内部辅助逻辑。"""
|
|
2279
2465
|
return {
|
|
2280
2466
|
"id": asos_chart.get("id"),
|
|
2281
2467
|
"appKey": asos_chart.get("appKey"),
|
|
@@ -2290,6 +2476,7 @@ class TaskContextTools(ToolBase):
|
|
|
2290
2476
|
}
|
|
2291
2477
|
|
|
2292
2478
|
def _qingflow_chart_uses_apply_filter(self, context: BackendRequestContext, chart_key: str) -> bool:
|
|
2479
|
+
"""执行内部辅助逻辑。"""
|
|
2293
2480
|
if not chart_key:
|
|
2294
2481
|
return False
|
|
2295
2482
|
try:
|
|
@@ -2305,6 +2492,7 @@ class TaskContextTools(ToolBase):
|
|
|
2305
2492
|
return self._coerce_bool(auth.get("detailedViewStatus")) and auth.get("lastViewType") == 1
|
|
2306
2493
|
|
|
2307
2494
|
def _normalize_chart_result(self, payload: Any) -> dict[str, Any]:
|
|
2495
|
+
"""执行内部辅助逻辑。"""
|
|
2308
2496
|
if isinstance(payload, dict):
|
|
2309
2497
|
rows = payload.get("rows")
|
|
2310
2498
|
if not isinstance(rows, list):
|
|
@@ -2327,6 +2515,7 @@ class TaskContextTools(ToolBase):
|
|
|
2327
2515
|
return {"summary": {}, "rows": [], "series": [], "metrics": []}
|
|
2328
2516
|
|
|
2329
2517
|
def _normalize_workflow_logs(self, payload: Any) -> list[dict[str, Any]]:
|
|
2518
|
+
"""执行内部辅助逻辑。"""
|
|
2330
2519
|
if isinstance(payload, dict):
|
|
2331
2520
|
page = payload.get("list") if isinstance(payload.get("list"), list) else payload.get("rows")
|
|
2332
2521
|
if not isinstance(page, list):
|
|
@@ -2375,6 +2564,7 @@ class TaskContextTools(ToolBase):
|
|
|
2375
2564
|
return items
|
|
2376
2565
|
|
|
2377
2566
|
def _workflow_log_digest(self, items: list[dict[str, Any]]) -> str | None:
|
|
2567
|
+
"""执行内部辅助逻辑。"""
|
|
2378
2568
|
if not items:
|
|
2379
2569
|
return None
|
|
2380
2570
|
try:
|
|
@@ -2383,6 +2573,7 @@ class TaskContextTools(ToolBase):
|
|
|
2383
2573
|
return str(items)
|
|
2384
2574
|
|
|
2385
2575
|
def _first_nested_operation_detail(self, operation: dict[str, Any]) -> Any:
|
|
2576
|
+
"""执行内部辅助逻辑。"""
|
|
2386
2577
|
for key in ("approval", "filling", "cc", "applicant", "qRobotAdd", "qRobotUpdate", "webhook", "qRobotSMS", "qRobotMail"):
|
|
2387
2578
|
value = operation.get(key)
|
|
2388
2579
|
if value is not None:
|
|
@@ -2390,6 +2581,7 @@ class TaskContextTools(ToolBase):
|
|
|
2390
2581
|
return None
|
|
2391
2582
|
|
|
2392
2583
|
def _extract_remark(self, detail: Any) -> Any:
|
|
2584
|
+
"""执行内部辅助逻辑。"""
|
|
2393
2585
|
if not isinstance(detail, dict):
|
|
2394
2586
|
return None
|
|
2395
2587
|
for key in ("remark", "feedback", "comment", "content"):
|
|
@@ -2399,6 +2591,7 @@ class TaskContextTools(ToolBase):
|
|
|
2399
2591
|
return None
|
|
2400
2592
|
|
|
2401
2593
|
def _extract_signature_url(self, detail: Any) -> Any:
|
|
2594
|
+
"""执行内部辅助逻辑。"""
|
|
2402
2595
|
if not isinstance(detail, dict):
|
|
2403
2596
|
return None
|
|
2404
2597
|
for key in ("signatureUrl", "handSignImageUrl"):
|
|
@@ -2408,6 +2601,7 @@ class TaskContextTools(ToolBase):
|
|
|
2408
2601
|
return None
|
|
2409
2602
|
|
|
2410
2603
|
def _extract_attachments(self, detail: Any) -> Any:
|
|
2604
|
+
"""执行内部辅助逻辑。"""
|
|
2411
2605
|
if not isinstance(detail, dict):
|
|
2412
2606
|
return []
|
|
2413
2607
|
for key in ("attachments", "files", "uploadFiles"):
|
|
@@ -2417,6 +2611,7 @@ class TaskContextTools(ToolBase):
|
|
|
2417
2611
|
return []
|
|
2418
2612
|
|
|
2419
2613
|
def _extract_audit_feedback(self, payload: dict[str, Any]) -> str | None:
|
|
2614
|
+
"""执行内部辅助逻辑。"""
|
|
2420
2615
|
for key in ("audit_feedback", "auditFeedback"):
|
|
2421
2616
|
value = payload.get(key)
|
|
2422
2617
|
if isinstance(value, str) and value.strip():
|
|
@@ -2424,6 +2619,7 @@ class TaskContextTools(ToolBase):
|
|
|
2424
2619
|
return None
|
|
2425
2620
|
|
|
2426
2621
|
def _extract_positive_int(self, payload: dict[str, Any], key: str, *, aliases: tuple[str, ...] = ()) -> int:
|
|
2622
|
+
"""执行内部辅助逻辑。"""
|
|
2427
2623
|
candidates = (key, *aliases)
|
|
2428
2624
|
value: Any = None
|
|
2429
2625
|
for candidate in candidates:
|
|
@@ -2436,6 +2632,7 @@ class TaskContextTools(ToolBase):
|
|
|
2436
2632
|
return value
|
|
2437
2633
|
|
|
2438
2634
|
def _require_app_record_and_node(self, app_key: str, record_id: int, workflow_node_id: int) -> None:
|
|
2635
|
+
"""执行内部辅助逻辑。"""
|
|
2439
2636
|
if not app_key:
|
|
2440
2637
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
2441
2638
|
if record_id <= 0:
|
|
@@ -2444,6 +2641,7 @@ class TaskContextTools(ToolBase):
|
|
|
2444
2641
|
raise_tool_error(QingflowApiError.config_error("workflow_node_id must be positive"))
|
|
2445
2642
|
|
|
2446
2643
|
def _coerce_bool(self, value: Any) -> bool:
|
|
2644
|
+
"""执行内部辅助逻辑。"""
|
|
2447
2645
|
if isinstance(value, bool):
|
|
2448
2646
|
return value
|
|
2449
2647
|
if isinstance(value, int):
|
|
@@ -2453,6 +2651,7 @@ class TaskContextTools(ToolBase):
|
|
|
2453
2651
|
return bool(value)
|
|
2454
2652
|
|
|
2455
2653
|
def _request_route_payload(self, context: BackendRequestContext) -> dict[str, Any]:
|
|
2654
|
+
"""执行内部辅助逻辑。"""
|
|
2456
2655
|
describe_route = getattr(self.backend, "describe_route", None)
|
|
2457
2656
|
if callable(describe_route):
|
|
2458
2657
|
payload = describe_route(context)
|