@josephyan/qingflow-app-builder-mcp 1.1.25 → 1.1.26

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 (98) hide show
  1. package/README.md +5 -6
  2. package/docs/local-agent-install.md +6 -57
  3. package/entry_point.py +1 -1
  4. package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
  5. package/npm/lib/runtime.mjs +101 -21
  6. package/npm/scripts/postinstall.mjs +10 -1
  7. package/package.json +2 -3
  8. package/pyproject.toml +1 -1
  9. package/skills/qingflow-app-builder/SKILL.md +42 -104
  10. package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
  11. package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
  12. package/skills/qingflow-app-builder/references/create-app.md +11 -30
  13. package/skills/qingflow-app-builder/references/environments.md +1 -1
  14. package/skills/qingflow-app-builder/references/gotchas.md +9 -31
  15. package/skills/qingflow-app-builder/references/match-rules.md +0 -15
  16. package/skills/qingflow-app-builder/references/solution-playbooks.md +0 -10
  17. package/skills/qingflow-app-builder/references/tool-selection.md +23 -38
  18. package/skills/qingflow-app-builder/references/update-flow.md +38 -22
  19. package/skills/qingflow-app-builder/references/update-schema.md +0 -14
  20. package/skills/qingflow-app-builder/references/update-views.md +14 -83
  21. package/skills/qingflow-app-builder-code-integrations/SKILL.md +3 -5
  22. package/skills/qingflow-app-builder-code-integrations/references/code-block.md +1 -1
  23. package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +1 -1
  24. package/skills/qingflow-mcp-setup/SKILL.md +111 -0
  25. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  26. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  27. package/skills/qingflow-mcp-setup/references/environments.md +61 -0
  28. package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
  29. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  30. package/skills/qingflow-workflow-builder/SKILL.md +96 -0
  31. package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
  32. package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
  33. package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
  34. package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
  35. package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
  36. package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
  37. package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
  38. package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
  39. package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
  40. package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
  41. package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
  42. package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
  43. package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
  44. package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
  45. package/src/qingflow_mcp/__init__.py +1 -1
  46. package/src/qingflow_mcp/__main__.py +2 -6
  47. package/src/qingflow_mcp/builder_facade/models.py +108 -485
  48. package/src/qingflow_mcp/builder_facade/service.py +1686 -6674
  49. package/src/qingflow_mcp/cli/commands/builder.py +364 -322
  50. package/src/qingflow_mcp/cli/commands/chart.py +1 -1
  51. package/src/qingflow_mcp/cli/commands/common.py +3 -12
  52. package/src/qingflow_mcp/cli/commands/exports.py +2 -2
  53. package/src/qingflow_mcp/cli/commands/imports.py +3 -3
  54. package/src/qingflow_mcp/cli/commands/portal.py +2 -2
  55. package/src/qingflow_mcp/cli/commands/record.py +27 -101
  56. package/src/qingflow_mcp/cli/commands/task.py +47 -28
  57. package/src/qingflow_mcp/cli/commands/view.py +1 -1
  58. package/src/qingflow_mcp/cli/context.py +3 -0
  59. package/src/qingflow_mcp/cli/formatters.py +16 -784
  60. package/src/qingflow_mcp/cli/main.py +33 -117
  61. package/src/qingflow_mcp/errors.py +2 -43
  62. package/src/qingflow_mcp/public_surface.py +17 -26
  63. package/src/qingflow_mcp/response_trim.py +17 -81
  64. package/src/qingflow_mcp/server.py +12 -14
  65. package/src/qingflow_mcp/server_app_builder.py +36 -79
  66. package/src/qingflow_mcp/server_app_user.py +16 -22
  67. package/src/qingflow_mcp/session_store.py +7 -11
  68. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  69. package/src/qingflow_mcp/solution/executor.py +18 -245
  70. package/src/qingflow_mcp/tools/ai_builder_tools.py +1396 -3103
  71. package/src/qingflow_mcp/tools/app_tools.py +43 -184
  72. package/src/qingflow_mcp/tools/approval_tools.py +35 -197
  73. package/src/qingflow_mcp/tools/auth_tools.py +16 -92
  74. package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
  75. package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
  76. package/src/qingflow_mcp/tools/directory_tools.py +72 -236
  77. package/src/qingflow_mcp/tools/export_tools.py +34 -244
  78. package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
  79. package/src/qingflow_mcp/tools/file_tools.py +3 -9
  80. package/src/qingflow_mcp/tools/import_tools.py +49 -336
  81. package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
  82. package/src/qingflow_mcp/tools/package_tools.py +6 -118
  83. package/src/qingflow_mcp/tools/portal_tools.py +3 -39
  84. package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
  85. package/src/qingflow_mcp/tools/record_tools.py +360 -1145
  86. package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
  87. package/src/qingflow_mcp/tools/role_tools.py +9 -80
  88. package/src/qingflow_mcp/tools/solution_tools.py +45 -59
  89. package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
  90. package/src/qingflow_mcp/tools/task_tools.py +29 -113
  91. package/src/qingflow_mcp/tools/view_tools.py +3 -106
  92. package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
  93. package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
  94. package/npm/bin/qingflow-skills.mjs +0 -5
  95. package/skills/qingflow-app-builder/references/complete-system-development-guide.md +0 -172
  96. package/skills/qingflow-app-builder/references/single-app-development-guide.md +0 -87
  97. package/skills/qingflow-app-builder/scripts/validate_system_build_summary.py +0 -124
  98. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
@@ -1,12 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  from typing import Any
5
4
 
6
5
  from mcp.server.fastmcp import FastMCP
7
6
 
8
7
  from ..config import DEFAULT_PROFILE
9
- from ..errors import QingflowApiError, backend_code_int, backend_code_value_int, is_auth_like_error, message_looks_like_invalid_token, raise_tool_error
8
+ from ..errors import QingflowApiError, raise_tool_error
10
9
  from ..json_types import JSONObject
11
10
  from ..list_type_labels import get_record_list_type_label
12
11
  from .base import ToolBase, tool_cn_name
@@ -173,26 +172,15 @@ class ApprovalTools(ToolBase):
173
172
  keyword: str | None = None,
174
173
  ) -> dict[str, Any]:
175
174
  """执行记录相关逻辑。"""
176
- selection = {"app_key": app_key, "record_id": record_id, "list_type": list_type, "keyword": keyword}
177
- try:
178
- raw = self.record_comment_mention_candidates(
179
- profile=profile,
180
- app_key=app_key,
181
- apply_id=record_id,
182
- page_size=page_size,
183
- page_num=page_num,
184
- list_type=list_type,
185
- keyword=keyword,
186
- )
187
- except RuntimeError as exc:
188
- if not _is_optional_approval_runtime_error(exc):
189
- raise
190
- return self._runtime_error_as_auxiliary_result(
191
- exc,
192
- error_code="RECORD_COMMENT_MENTIONS_UNAVAILABLE",
193
- selection=selection,
194
- fallback_hint="Mention candidates are unavailable in this permission context; write a plain comment or retry without mentions.",
195
- )
175
+ raw = self.record_comment_mention_candidates(
176
+ profile=profile,
177
+ app_key=app_key,
178
+ apply_id=record_id,
179
+ page_size=page_size,
180
+ page_num=page_num,
181
+ list_type=list_type,
182
+ keyword=keyword,
183
+ )
196
184
  items = _approval_page_items(raw.get("page"))
197
185
  return self._public_page_response(
198
186
  raw,
@@ -204,7 +192,7 @@ class ApprovalTools(ToolBase):
204
192
  "page_amount": _approval_page_amount(raw.get("page")),
205
193
  "reported_total": _approval_page_total(raw.get("page")),
206
194
  },
207
- selection=selection,
195
+ selection={"app_key": app_key, "record_id": record_id, "list_type": list_type, "keyword": keyword},
208
196
  )
209
197
 
210
198
  @tool_cn_name("任务通过")
@@ -254,24 +242,13 @@ class ApprovalTools(ToolBase):
254
242
  @tool_cn_name("任务退回候选")
255
243
  def task_rollback_candidates(self, *, profile: str, app_key: str, record_id: int, workflow_node_id: int) -> dict[str, Any]:
256
244
  """执行任务相关逻辑。"""
257
- selection = {"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id}
258
- try:
259
- raw = self.record_rollback_candidates(profile=profile, app_key=app_key, apply_id=record_id, audit_node_id=workflow_node_id)
260
- except RuntimeError as exc:
261
- if not _is_optional_approval_runtime_error(exc):
262
- raise
263
- return self._runtime_error_as_auxiliary_result(
264
- exc,
265
- error_code="TASK_ROLLBACK_CANDIDATES_UNAVAILABLE",
266
- selection=selection,
267
- fallback_hint="Rollback candidates are unavailable in this permission context; use task get for current context or retry with a valid actionable node.",
268
- )
245
+ raw = self.record_rollback_candidates(profile=profile, app_key=app_key, apply_id=record_id, audit_node_id=workflow_node_id)
269
246
  items = _approval_page_items(raw.get("result"))
270
247
  return self._public_page_response(
271
248
  raw,
272
249
  items=items,
273
250
  pagination={"returned_items": len(items)},
274
- selection=selection,
251
+ selection={"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id},
275
252
  )
276
253
 
277
254
  @tool_cn_name("任务退回")
@@ -354,26 +331,15 @@ class ApprovalTools(ToolBase):
354
331
  keyword: str | None = None,
355
332
  ) -> dict[str, Any]:
356
333
  """执行任务相关逻辑。"""
357
- selection = {"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id, "keyword": keyword}
358
- try:
359
- raw = self.record_transfer_candidates(
360
- profile=profile,
361
- app_key=app_key,
362
- apply_id=record_id,
363
- page_size=page_size,
364
- page_num=page_num,
365
- audit_node_id=workflow_node_id,
366
- keyword=keyword,
367
- )
368
- except RuntimeError as exc:
369
- if not _is_optional_approval_runtime_error(exc):
370
- raise
371
- return self._runtime_error_as_auxiliary_result(
372
- exc,
373
- error_code="TASK_TRANSFER_CANDIDATES_UNAVAILABLE",
374
- selection=selection,
375
- fallback_hint="Transfer candidates are unavailable in this permission context; use task get for current context or retry with a valid actionable node.",
376
- )
334
+ raw = self.record_transfer_candidates(
335
+ profile=profile,
336
+ app_key=app_key,
337
+ apply_id=record_id,
338
+ page_size=page_size,
339
+ page_num=page_num,
340
+ audit_node_id=workflow_node_id,
341
+ keyword=keyword,
342
+ )
377
343
  original_items = _approval_page_items(raw.get("page"))
378
344
  items = self._filter_self_transfer_candidates(profile=profile, items=original_items)
379
345
  filtered_count = max(len(original_items) - len(items), 0)
@@ -387,7 +353,7 @@ class ApprovalTools(ToolBase):
387
353
  "page_amount": _approval_page_amount(raw.get("page")),
388
354
  "reported_total": max(_approval_page_total(raw.get("page")) - filtered_count, 0),
389
355
  },
390
- selection=selection,
356
+ selection={"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id, "keyword": keyword},
391
357
  )
392
358
 
393
359
  @tool_cn_name("新增评论")
@@ -797,18 +763,9 @@ class ApprovalTools(ToolBase):
797
763
  node_id = self._extract_node_id(body)
798
764
  body["nodeId"] = self._resolve_actionable_node_id(context, app_key, apply_id, node_id)
799
765
  body["applyId"] = self._match_or_fill_int(body, field_name="applyId", expected_value=apply_id)
800
- current_detail: dict[str, Any] | None = None
801
- if body.get("answers") is None or body.get("formId") is None:
802
- current_detail = self._fetch_current_todo_detail(context, app_key, apply_id, body["nodeId"])
803
- body["formId"] = self._resolve_form_id(
804
- profile,
805
- context,
806
- app_key,
807
- explicit_form_id=body.get("formId"),
808
- current_detail=current_detail,
809
- )
766
+ body["formId"] = self._resolve_form_id(profile, context, app_key, explicit_form_id=body.get("formId"))
810
767
  if body.get("answers") is None:
811
- body["answers"] = self._extract_current_todo_answers(current_detail, apply_id=apply_id, node_id=body["nodeId"])
768
+ body["answers"] = self._fetch_current_todo_answers(context, app_key, apply_id, body["nodeId"])
812
769
 
813
770
  self._validate_approval_payload(body)
814
771
  return body
@@ -825,27 +782,12 @@ class ApprovalTools(ToolBase):
825
782
  raise_tool_error(QingflowApiError.config_error("payload.nodeId or payload.auditNodeId must be a positive integer"))
826
783
  return node_id
827
784
 
828
- def _resolve_form_id(
829
- self,
830
- profile: str,
831
- context,
832
- app_key: str,
833
- *,
834
- explicit_form_id: Any | None,
835
- current_detail: dict[str, Any] | None = None,
836
- ) -> int: # type: ignore[no-untyped-def]
785
+ def _resolve_form_id(self, profile: str, context, app_key: str, *, explicit_form_id: Any | None) -> int: # type: ignore[no-untyped-def]
837
786
  """执行内部辅助逻辑。"""
838
- detail_form_id = self._extract_form_id_from_current_detail(current_detail)
839
787
  if explicit_form_id is not None:
840
788
  if not isinstance(explicit_form_id, int) or explicit_form_id <= 0:
841
789
  raise_tool_error(QingflowApiError.config_error("payload.formId must be a positive integer"))
842
- try:
843
- form_id = detail_form_id or self._get_form_id(profile, context, app_key)
844
- except QingflowApiError as exc:
845
- if not _is_optional_approval_precheck_error(exc):
846
- raise
847
- self._form_id_cache[f"{profile}:{app_key}"] = explicit_form_id
848
- return explicit_form_id
790
+ form_id = self._get_form_id(profile, context, app_key)
849
791
  if form_id != explicit_form_id:
850
792
  raise_tool_error(
851
793
  QingflowApiError.config_error(
@@ -853,9 +795,6 @@ class ApprovalTools(ToolBase):
853
795
  )
854
796
  )
855
797
  return explicit_form_id
856
- if detail_form_id is not None:
857
- self._form_id_cache[f"{profile}:{app_key}"] = detail_form_id
858
- return detail_form_id
859
798
  return self._get_form_id(profile, context, app_key)
860
799
 
861
800
  def _get_form_id(self, profile: str, context, app_key: str) -> int: # type: ignore[no-untyped-def]
@@ -892,18 +831,12 @@ class ApprovalTools(ToolBase):
892
831
 
893
832
  def _resolve_actionable_node_id(self, context, app_key: str, apply_id: int, node_id: int) -> int: # type: ignore[no-untyped-def]
894
833
  """执行内部辅助逻辑。"""
895
- try:
896
- infos = self.backend.request(
897
- "GET",
898
- context,
899
- f"/app/{app_key}/apply/{apply_id}/auditInfo",
900
- params={"type": 1},
901
- )
902
- except QingflowApiError as exc:
903
- if not _is_optional_approval_precheck_error(exc):
904
- raise
905
- self._fetch_current_todo_detail(context, app_key, apply_id, node_id)
906
- return node_id
834
+ infos = self.backend.request(
835
+ "GET",
836
+ context,
837
+ f"/app/{app_key}/apply/{apply_id}/auditInfo",
838
+ params={"type": 1},
839
+ )
907
840
  if not isinstance(infos, list) or not infos:
908
841
  raise_tool_error(
909
842
  QingflowApiError.config_error(
@@ -925,7 +858,7 @@ class ApprovalTools(ToolBase):
925
858
  )
926
859
  return node_id
927
860
 
928
- def _fetch_current_todo_detail(self, context, app_key: str, apply_id: int, node_id: int) -> dict[str, Any]: # type: ignore[no-untyped-def]
861
+ def _fetch_current_todo_answers(self, context, app_key: str, apply_id: int, node_id: int) -> list[dict[str, Any]]: # type: ignore[no-untyped-def]
929
862
  """执行内部辅助逻辑。"""
930
863
  detail = self.backend.request(
931
864
  "GET",
@@ -933,21 +866,6 @@ class ApprovalTools(ToolBase):
933
866
  f"/app/{app_key}/apply/{apply_id}",
934
867
  params={"role": 3, "listType": 1, "auditNodeId": node_id},
935
868
  )
936
- if not isinstance(detail, dict):
937
- raise_tool_error(
938
- QingflowApiError.config_error(
939
- f"cannot resolve current todo detail for apply_id={apply_id} nodeId={node_id}"
940
- )
941
- )
942
- return detail
943
-
944
- def _fetch_current_todo_answers(self, context, app_key: str, apply_id: int, node_id: int) -> list[dict[str, Any]]: # type: ignore[no-untyped-def]
945
- """执行内部辅助逻辑。"""
946
- detail = self._fetch_current_todo_detail(context, app_key, apply_id, node_id)
947
- return self._extract_current_todo_answers(detail, apply_id=apply_id, node_id=node_id)
948
-
949
- def _extract_current_todo_answers(self, detail: dict[str, Any] | None, *, apply_id: int, node_id: int) -> list[dict[str, Any]]:
950
- """执行内部辅助逻辑。"""
951
869
  answers = detail.get("answers") if isinstance(detail, dict) else None
952
870
  if not isinstance(answers, list):
953
871
  raise_tool_error(
@@ -961,21 +879,6 @@ class ApprovalTools(ToolBase):
961
879
  normalized_answers.append(dict(item))
962
880
  return normalized_answers
963
881
 
964
- def _extract_form_id_from_current_detail(self, detail: dict[str, Any] | None) -> int | None:
965
- """执行内部辅助逻辑。"""
966
- if not isinstance(detail, dict):
967
- return None
968
- for key in ("formId", "form_id"):
969
- value = detail.get(key)
970
- if isinstance(value, int) and value > 0:
971
- return value
972
- form = detail.get("form")
973
- if isinstance(form, dict):
974
- value = form.get("formId") or form.get("form_id")
975
- if isinstance(value, int) and value > 0:
976
- return value
977
- return None
978
-
979
882
  def _validate_approval_payload(self, payload: dict[str, Any]) -> None:
980
883
  """执行内部辅助逻辑。"""
981
884
  self._reject_unsupported_fields(payload)
@@ -1055,7 +958,7 @@ class ApprovalTools(ToolBase):
1055
958
  if node_id is None:
1056
959
  node_payload = dict(payload or {})
1057
960
  node_id = self._extract_node_id(node_payload)
1058
- delegated = TaskContextTools(self.sessions, self.backend)._task_action_execute_with_locator(
961
+ delegated = TaskContextTools(self.sessions, self.backend).task_action_execute(
1059
962
  profile=profile,
1060
963
  app_key=app_key,
1061
964
  record_id=record_id,
@@ -1092,48 +995,6 @@ class ApprovalTools(ToolBase):
1092
995
  }
1093
996
  return response
1094
997
 
1095
- def _runtime_error_as_auxiliary_result(
1096
- self,
1097
- error: RuntimeError,
1098
- *,
1099
- error_code: str,
1100
- selection: dict[str, Any],
1101
- fallback_hint: str,
1102
- ) -> dict[str, Any]:
1103
- """Return a structured failure for optional approval/comment helpers."""
1104
- try:
1105
- payload = json.loads(str(error))
1106
- except json.JSONDecodeError:
1107
- payload = {"message": str(error)}
1108
- details = payload.get("details") if isinstance(payload.get("details"), dict) else {}
1109
- warning: dict[str, Any] = {
1110
- "code": error_code,
1111
- "message": fallback_hint,
1112
- }
1113
- for key in ("category", "backend_code", "request_id", "http_status"):
1114
- if payload.get(key) is not None:
1115
- warning[key] = payload.get(key)
1116
- response: dict[str, Any] = {
1117
- "ok": False,
1118
- "status": "failed",
1119
- "error_code": details.get("error_code") or error_code,
1120
- "message": payload.get("message") or str(error),
1121
- "warnings": [warning],
1122
- "output_profile": "normal",
1123
- "data": {
1124
- "items": [],
1125
- "pagination": {"returned_items": 0},
1126
- "selection": selection,
1127
- "fallback_hint": fallback_hint,
1128
- },
1129
- }
1130
- for key in ("category", "backend_code", "request_id", "http_status"):
1131
- if payload.get(key) is not None:
1132
- response[key] = payload.get(key)
1133
- if details:
1134
- response["details"] = details
1135
- return response
1136
-
1137
998
  def _public_action_response(
1138
999
  self,
1139
1000
  raw: dict[str, Any],
@@ -1199,26 +1060,3 @@ def _approval_page_total(payload: Any) -> Any:
1199
1060
  if isinstance(payload, dict):
1200
1061
  return payload.get("total", payload.get("count"))
1201
1062
  return None
1202
-
1203
-
1204
- def _is_optional_approval_precheck_error(error: QingflowApiError) -> bool:
1205
- if is_auth_like_error(error):
1206
- return False
1207
- backend_code = backend_code_int(error)
1208
- return backend_code in {40002, 40027, 404} or error.http_status == 404
1209
-
1210
-
1211
- def _is_optional_approval_runtime_error(error: RuntimeError) -> bool:
1212
- try:
1213
- payload = json.loads(str(error))
1214
- except json.JSONDecodeError:
1215
- return False
1216
- if not isinstance(payload, dict):
1217
- return False
1218
- if str(payload.get("category") or "").strip().lower() == "auth":
1219
- return False
1220
- if message_looks_like_invalid_token(payload.get("message")):
1221
- return False
1222
- if backend_code_value_int(payload.get("http_status")) == 401:
1223
- return False
1224
- return backend_code_value_int(payload.get("backend_code")) in {40002, 40027, 404} or backend_code_value_int(payload.get("http_status")) == 404
@@ -14,7 +14,7 @@ from ..config import (
14
14
  get_mcporter_config_path,
15
15
  normalize_base_url,
16
16
  )
17
- from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
17
+ from ..errors import QingflowApiError, raise_tool_error
18
18
  from ..session_store import SessionStore
19
19
  from .base import ToolBase, tool_cn_name
20
20
 
@@ -650,9 +650,7 @@ class AuthTools(ToolBase):
650
650
  qf_version=qf_version,
651
651
  qf_version_source=qf_version_source,
652
652
  )
653
- except QingflowApiError as exc:
654
- if is_auth_like_error(exc):
655
- raise
653
+ except QingflowApiError:
656
654
  return None, None
657
655
 
658
656
  def _fetch_first_workspace(
@@ -767,9 +765,7 @@ class AuthTools(ToolBase):
767
765
  qf_version=session_profile.qf_version,
768
766
  qf_version_source=session_profile.qf_version_source,
769
767
  )
770
- except QingflowApiError as exc:
771
- if is_auth_like_error(exc):
772
- raise
768
+ except QingflowApiError:
773
769
  return None
774
770
 
775
771
  ws_name = session_profile.selected_ws_name
@@ -827,18 +823,6 @@ class AuthTools(ToolBase):
827
823
  )
828
824
  payload = dict(default_payload)
829
825
  payload["permission_level"] = permission_level
830
- warnings: list[dict[str, Any]] = []
831
- if permission_level is None:
832
- warnings.append(
833
- {
834
- "code": "WORKSPACE_PERMISSION_LEVEL_UNAVAILABLE",
835
- "message": (
836
- "auth_whoami could not resolve the selected workspace permission level; "
837
- "do not infer the user is a basic member or an administrator from this null value."
838
- ),
839
- "ws_id": ws_id,
840
- }
841
- )
842
826
 
843
827
  context = BackendRequestContext(
844
828
  base_url=backend_session.base_url,
@@ -847,17 +831,14 @@ class AuthTools(ToolBase):
847
831
  qf_version=backend_session.qf_version,
848
832
  qf_version_source=backend_session.qf_version_source,
849
833
  )
850
- member_lookup_warnings: list[dict[str, Any]] = []
851
834
  member = self._lookup_current_member(
852
835
  context=context,
853
836
  uid=session_profile.uid,
854
837
  email=session_profile.email,
855
838
  nick_name=session_profile.nick_name,
856
- warnings=member_lookup_warnings,
857
839
  )
858
- warnings.extend(member_lookup_warnings)
859
840
  if member is None:
860
- warnings.append(
841
+ return payload, [
861
842
  {
862
843
  "code": "CURRENT_MEMBER_PROFILE_UNAVAILABLE",
863
844
  "message": (
@@ -865,12 +846,11 @@ class AuthTools(ToolBase):
865
846
  f"in workspace {ws_id}."
866
847
  ),
867
848
  }
868
- )
869
- return payload, warnings
849
+ ]
870
850
 
871
851
  payload["departments"] = self._compact_departments(member)
872
852
  payload["roles"] = self._compact_roles(member)
873
- return payload, warnings
853
+ return payload, []
874
854
 
875
855
  def _workspace_permission_level(
876
856
  self,
@@ -902,9 +882,7 @@ class AuthTools(ToolBase):
902
882
  """执行内部辅助逻辑。"""
903
883
  try:
904
884
  workspace = self.backend.request("GET", context, f"/user/workspace/{ws_id}")
905
- except QingflowApiError as exc:
906
- if not _is_optional_auth_lookup_error(exc):
907
- raise
885
+ except QingflowApiError:
908
886
  return None
909
887
  if not isinstance(workspace, dict):
910
888
  return None
@@ -919,9 +897,7 @@ class AuthTools(ToolBase):
919
897
  "/user/workspaceList/pageQuery",
920
898
  json_body={"pageNum": 1, "pageSize": 100, "authList": [0, 1, 2, 3]},
921
899
  )
922
- except QingflowApiError as exc:
923
- if not _is_optional_auth_lookup_error(exc):
924
- raise
900
+ except QingflowApiError:
925
901
  return None
926
902
  workspaces = payload.get("list") if isinstance(payload, dict) else []
927
903
  if not isinstance(workspaces, list):
@@ -939,21 +915,20 @@ class AuthTools(ToolBase):
939
915
  uid: int | None,
940
916
  email: str | None,
941
917
  nick_name: str | None,
942
- warnings: list[dict[str, Any]] | None = None,
943
918
  ) -> dict[str, Any] | None:
944
919
  """执行内部辅助逻辑。"""
945
920
  candidates: list[dict[str, Any]] = []
946
921
  for keyword in (email, nick_name):
947
- member = self._search_member_once(context, uid=uid, keyword=keyword, warnings=warnings)
922
+ member = self._search_member_once(context, uid=uid, keyword=keyword)
948
923
  if member is not None:
949
924
  return member
950
925
  if keyword:
951
- candidates.extend(self._search_member_items(context, keyword=keyword, warnings=warnings))
926
+ candidates.extend(self._search_member_items(context, keyword=keyword))
952
927
  if uid is not None and uid > 0:
953
928
  for item in candidates:
954
929
  if self._same_member(item, uid=uid):
955
930
  return item
956
- return self._search_member_once(context, uid=uid, keyword=None, warnings=warnings)
931
+ return self._search_member_once(context, uid=uid, keyword=None)
957
932
  return None
958
933
 
959
934
  def _search_member_once(
@@ -962,21 +937,14 @@ class AuthTools(ToolBase):
962
937
  *,
963
938
  uid: int | None,
964
939
  keyword: str | None,
965
- warnings: list[dict[str, Any]] | None = None,
966
940
  ) -> dict[str, Any] | None:
967
941
  """执行内部辅助逻辑。"""
968
- for item in self._search_member_items(context, keyword=keyword, warnings=warnings):
942
+ for item in self._search_member_items(context, keyword=keyword):
969
943
  if self._same_member(item, uid=uid):
970
944
  return item
971
945
  return None
972
946
 
973
- def _search_member_items(
974
- self,
975
- context: BackendRequestContext,
976
- *,
977
- keyword: str | None,
978
- warnings: list[dict[str, Any]] | None = None,
979
- ) -> list[dict[str, Any]]:
947
+ def _search_member_items(self, context: BackendRequestContext, *, keyword: str | None) -> list[dict[str, Any]]:
980
948
  """执行内部辅助逻辑。"""
981
949
  params: dict[str, Any] = {"pageNum": 1, "pageSize": 100, "containDisable": True}
982
950
  normalized_keyword = str(keyword or "").strip()
@@ -984,38 +952,11 @@ class AuthTools(ToolBase):
984
952
  params["keyword"] = normalized_keyword
985
953
  try:
986
954
  payload = self.backend.request("GET", context, "/contact", params=params)
987
- except QingflowApiError as exc:
988
- if not _is_optional_auth_lookup_error(exc):
989
- raise
990
- if warnings is not None and _is_contact_directory_permission_denied(exc):
991
- self._append_unique_warning(warnings, self._contact_directory_permission_warning(exc))
955
+ except QingflowApiError:
992
956
  return []
993
957
  items = self._extract_items(payload)
994
958
  return [item for item in items if isinstance(item, dict)]
995
959
 
996
- def _append_unique_warning(self, warnings: list[dict[str, Any]], warning: dict[str, Any]) -> None:
997
- """执行内部辅助逻辑。"""
998
- code = self._normalize_text(warning.get("code"))
999
- if code is not None and any(item.get("code") == code for item in warnings):
1000
- return
1001
- warnings.append(warning)
1002
-
1003
- def _contact_directory_permission_warning(self, error: QingflowApiError) -> dict[str, Any]:
1004
- """执行内部辅助逻辑。"""
1005
- warning: dict[str, Any] = {
1006
- "code": "CONTACT_DIRECTORY_PERMISSION_DENIED",
1007
- "message": (
1008
- "auth_whoami could not read current member departments and roles because "
1009
- "the contact directory is not readable in this permission context; "
1010
- "permission_level still comes from the workspace auth route."
1011
- ),
1012
- "category": error.category,
1013
- "backend_code": backend_code_int(error),
1014
- "http_status": error.http_status,
1015
- "request_id": error.request_id,
1016
- }
1017
- return {key: value for key, value in warning.items() if value is not None}
1018
-
1019
960
  def _same_member(self, item: dict[str, Any], *, uid: int | None) -> bool:
1020
961
  """执行内部辅助逻辑。"""
1021
962
  if uid is None or uid <= 0:
@@ -1164,9 +1105,7 @@ class AuthTools(ToolBase):
1164
1105
  qf_version=qf_version,
1165
1106
  qf_version_source=qf_version_source,
1166
1107
  )
1167
- except QingflowApiError as exc:
1168
- if is_auth_like_error(exc):
1169
- raise
1108
+ except QingflowApiError:
1170
1109
  workspace = None
1171
1110
  if isinstance(workspace, dict):
1172
1111
  workspace_name = str(workspace.get("workspaceName") or workspace.get("wsName") or "").strip()
@@ -1180,9 +1119,7 @@ class AuthTools(ToolBase):
1180
1119
  qf_version=qf_version,
1181
1120
  qf_version_source=qf_version_source,
1182
1121
  )
1183
- except QingflowApiError as exc:
1184
- if is_auth_like_error(exc):
1185
- raise
1122
+ except QingflowApiError:
1186
1123
  fallback = None
1187
1124
  return fallback or workspace
1188
1125
 
@@ -1220,16 +1157,3 @@ class AuthTools(ToolBase):
1220
1157
  None,
1221
1158
  )
1222
1159
  return found if isinstance(found, dict) else None
1223
-
1224
-
1225
- def _is_contact_directory_permission_denied(error: QingflowApiError) -> bool:
1226
- if is_auth_like_error(error):
1227
- return False
1228
- return backend_code_int(error) in {40002, 40027}
1229
-
1230
-
1231
- def _is_optional_auth_lookup_error(error: QingflowApiError) -> bool:
1232
- if is_auth_like_error(error):
1233
- return False
1234
- backend_code = backend_code_int(error)
1235
- return backend_code in {40002, 40027, 404} or error.http_status == 404