@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.
Files changed (43) hide show
  1. package/README.md +2 -2
  2. package/docs/local-agent-install.md +70 -11
  3. package/package.json +1 -1
  4. package/pyproject.toml +1 -1
  5. package/src/qingflow_mcp/__init__.py +1 -1
  6. package/src/qingflow_mcp/builder_facade/service.py +47 -21
  7. package/src/qingflow_mcp/cli/commands/auth.py +14 -43
  8. package/src/qingflow_mcp/cli/commands/task.py +4 -1
  9. package/src/qingflow_mcp/cli/commands/workspace.py +0 -8
  10. package/src/qingflow_mcp/cli/formatters.py +0 -21
  11. package/src/qingflow_mcp/config.py +39 -0
  12. package/src/qingflow_mcp/errors.py +2 -2
  13. package/src/qingflow_mcp/public_surface.py +2 -6
  14. package/src/qingflow_mcp/response_trim.py +1 -8
  15. package/src/qingflow_mcp/server.py +1 -1
  16. package/src/qingflow_mcp/server_app_builder.py +4 -28
  17. package/src/qingflow_mcp/server_app_user.py +4 -28
  18. package/src/qingflow_mcp/session_store.py +31 -5
  19. package/src/qingflow_mcp/tools/ai_builder_tools.py +117 -1
  20. package/src/qingflow_mcp/tools/app_tools.py +51 -1
  21. package/src/qingflow_mcp/tools/approval_tools.py +82 -1
  22. package/src/qingflow_mcp/tools/auth_tools.py +258 -288
  23. package/src/qingflow_mcp/tools/base.py +204 -4
  24. package/src/qingflow_mcp/tools/code_block_tools.py +21 -0
  25. package/src/qingflow_mcp/tools/custom_button_tools.py +24 -1
  26. package/src/qingflow_mcp/tools/directory_tools.py +28 -1
  27. package/src/qingflow_mcp/tools/feedback_tools.py +8 -0
  28. package/src/qingflow_mcp/tools/file_tools.py +25 -1
  29. package/src/qingflow_mcp/tools/import_tools.py +40 -1
  30. package/src/qingflow_mcp/tools/navigation_tools.py +34 -1
  31. package/src/qingflow_mcp/tools/package_tools.py +37 -1
  32. package/src/qingflow_mcp/tools/portal_tools.py +28 -1
  33. package/src/qingflow_mcp/tools/qingbi_report_tools.py +38 -1
  34. package/src/qingflow_mcp/tools/record_tools.py +255 -2
  35. package/src/qingflow_mcp/tools/repository_dev_tools.py +21 -2
  36. package/src/qingflow_mcp/tools/resource_read_tools.py +23 -1
  37. package/src/qingflow_mcp/tools/role_tools.py +19 -1
  38. package/src/qingflow_mcp/tools/solution_tools.py +56 -1
  39. package/src/qingflow_mcp/tools/task_context_tools.py +205 -6
  40. package/src/qingflow_mcp/tools/task_tools.py +49 -3
  41. package/src/qingflow_mcp/tools/view_tools.py +56 -1
  42. package/src/qingflow_mcp/tools/workflow_tools.py +65 -1
  43. 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
- @mcp.tool()
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": len(items),
186
- "page_amount": _task_page_amount(task_page),
187
- "reported_total": _task_page_total(task_page),
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)