@josephyan/qingflow-cli 1.1.3 → 1.1.5
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 +7 -3
- package/docs/local-agent-install.md +57 -6
- package/entry_point.py +1 -1
- package/npm/bin/qingflow-skills.mjs +5 -0
- package/npm/bin/qingflow.mjs +1 -34
- package/npm/lib/runtime.mjs +21 -101
- package/npm/scripts/postinstall.mjs +1 -10
- package/package.json +3 -2
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +58 -44
- package/skills/qingflow-cli/manifest.yaml +1 -1
- package/skills/qingflow-cli/reference/00-INDEX.md +35 -0
- package/skills/qingflow-cli/reference/builder/10-build-single-app.md +38 -0
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +39 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md → builder/30-schema-fields.md} +52 -10
- package/skills/qingflow-cli/reference/builder/40-layout.md +52 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md → builder/50-views.md} +39 -15
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md → builder/60-charts.md} +36 -13
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md → builder/70-portal.md} +36 -13
- package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +41 -0
- package/skills/qingflow-cli/reference/builder/90-workflow.md +34 -0
- package/skills/qingflow-cli/reference/builder/99-publish-verify.md +46 -0
- package/skills/qingflow-cli/reference/builder/README.md +41 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/README.md +130 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/code-block.md +66 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/q-linker.md +77 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md → builder/reference/app-delivery-sop.md} +26 -16
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/README.md +293 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-complete-system.md +809 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-single-app.md +830 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/complete-system-development-guide.md +123 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/create-app.md +182 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/environments.md +63 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/flow-actors-and-permissions.md +142 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/gotchas.md +108 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/match-rules.md +114 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/public-surface-sync.md +75 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/single-app-development-guide.md +58 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/solution-playbooks.md +52 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/tool-selection.md +107 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-flow.md +7 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-layout.md +7 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-schema.md +7 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-views.md +7 -0
- package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +45 -0
- package/skills/qingflow-cli/reference/builder/workflow/02-update-mode.md +53 -0
- package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +57 -0
- package/skills/qingflow-cli/reference/builder/workflow/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-cli/reference/builder/workflow/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-cli/reference/builder/workflow/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-cli/reference/builder/workflow/09-stage6-summary.md +88 -0
- package/skills/qingflow-cli/reference/builder/workflow/10-node-config-reference.md +93 -0
- package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +15 -0
- package/skills/qingflow-cli/reference/builder/workflow/README.md +88 -0
- package/skills/qingflow-cli/reference/builder/workflow/workflow-schema.json +1754 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ADMIN_CHEATSHEET.md → core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md} +3 -3
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md → core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md} +6 -6
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_EXPLORATION_REPORT.md → core/QINGFLOW_CLI_EXPLORATION_REPORT.md} +2 -2
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_FIELD_DATA_TYPES.md → core/QINGFLOW_CLI_FIELD_DATA_TYPES.md} +11 -11
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_MEMBER_CHEATSHEET.md → core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md} +4 -4
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md → core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md} +4 -4
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md} +3 -3
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md +31 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md} +4 -4
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md} +7 -7
- package/skills/qingflow-cli/reference/record/analysis/README.md +130 -0
- package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +91 -0
- package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +112 -0
- package/skills/qingflow-cli/reference/record/analysis/business-context.md +74 -0
- package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +69 -0
- package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +106 -0
- package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +172 -0
- package/skills/qingflow-cli/reference/record/analysis/report-format.md +76 -0
- package/skills/qingflow-cli/reference/record/insert/README.md +75 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md → task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md} +5 -5
- package/skills/qingflow-cli/reference/task/ops/README.md +131 -0
- package/skills/qingflow-cli/reference/task/ops/environments.md +43 -0
- package/skills/qingflow-cli/reference/task/ops/workflow-usage.md +26 -0
- package/skills/qingflow-cli/scripts/validate_system_build_summary.py +124 -0
- package/skills/qingflow-cli/scripts/workflow/diff_flow_spec.py +275 -0
- package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +605 -0
- package/skills/qingflow-mcp-setup/SKILL.md +115 -0
- package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
- package/skills/qingflow-mcp-setup/references/environments.md +62 -0
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
- package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/__main__.py +6 -2
- package/src/qingflow_mcp/builder_facade/models.py +287 -25
- package/src/qingflow_mcp/builder_facade/service.py +4195 -856
- package/src/qingflow_mcp/cli/commands/builder.py +316 -247
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/common.py +12 -3
- package/src/qingflow_mcp/cli/commands/exports.py +2 -2
- package/src/qingflow_mcp/cli/commands/imports.py +3 -3
- package/src/qingflow_mcp/cli/commands/portal.py +2 -2
- package/src/qingflow_mcp/cli/commands/record.py +101 -27
- package/src/qingflow_mcp/cli/commands/task.py +28 -47
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/context.py +0 -3
- package/src/qingflow_mcp/cli/formatters.py +784 -16
- package/src/qingflow_mcp/cli/main.py +117 -33
- package/src/qingflow_mcp/errors.py +43 -2
- package/src/qingflow_mcp/public_surface.py +26 -17
- package/src/qingflow_mcp/response_trim.py +81 -17
- package/src/qingflow_mcp/server.py +14 -12
- package/src/qingflow_mcp/server_app_builder.py +65 -21
- package/src/qingflow_mcp/server_app_user.py +22 -16
- package/src/qingflow_mcp/session_store.py +11 -7
- package/src/qingflow_mcp/solution/compiler/__init__.py +3 -1
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
- package/src/qingflow_mcp/solution/executor.py +245 -18
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1782 -399
- package/src/qingflow_mcp/tools/app_tools.py +184 -43
- package/src/qingflow_mcp/tools/approval_tools.py +197 -35
- package/src/qingflow_mcp/tools/auth_tools.py +92 -16
- package/src/qingflow_mcp/tools/code_block_tools.py +298 -40
- package/src/qingflow_mcp/tools/custom_button_tools.py +64 -10
- package/src/qingflow_mcp/tools/directory_tools.py +236 -72
- package/src/qingflow_mcp/tools/export_tools.py +244 -34
- package/src/qingflow_mcp/tools/feedback_tools.py +9 -0
- package/src/qingflow_mcp/tools/file_tools.py +9 -3
- package/src/qingflow_mcp/tools/import_tools.py +336 -49
- package/src/qingflow_mcp/tools/navigation_tools.py +91 -12
- package/src/qingflow_mcp/tools/package_tools.py +118 -6
- package/src/qingflow_mcp/tools/portal_tools.py +39 -3
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +116 -7
- package/src/qingflow_mcp/tools/record_tools.py +1141 -356
- package/src/qingflow_mcp/tools/resource_read_tools.py +188 -39
- package/src/qingflow_mcp/tools/role_tools.py +80 -9
- package/src/qingflow_mcp/tools/solution_tools.py +59 -45
- package/src/qingflow_mcp/tools/task_context_tools.py +662 -158
- package/src/qingflow_mcp/tools/task_tools.py +113 -29
- package/src/qingflow_mcp/tools/view_tools.py +106 -3
- package/src/qingflow_mcp/tools/workflow_tools.py +48 -4
- package/src/qingflow_mcp/tools/workspace_tools.py +71 -3
- /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_MATCH_RULES.md → builder/reference/match-rules.md} +0 -0
- /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md → builder/reference/workspace-icons.md} +0 -0
- /package/skills/qingflow-cli/reference/{charts_remove.example.json → examples/charts/charts_remove.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_reorder.example.json → examples/charts/charts_reorder.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_upsert_bar.example.json → examples/charts/charts_upsert_bar.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_upsert_dashboard_starter.example.json → examples/charts/charts_upsert_dashboard_starter.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_upsert_minimal.example.json → examples/charts/charts_upsert_minimal.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{portal_sections_all_types.example.json → examples/portal/portal_sections_all_types.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{portal_sections_five_types.example.json → examples/portal/portal_sections_five_types.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{portal_sections_standard_workbench.example.json → examples/portal/portal_sections_standard_workbench.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{_batch_schema_complex.json → examples/schema/_batch_schema_complex.json} +0 -0
- /package/skills/qingflow-cli/reference/{_batch_schema_scalar.json → examples/schema/_batch_schema_scalar.json} +0 -0
- /package/skills/qingflow-cli/reference/{schema_add_fields_minimal.example.json → examples/schema/schema_add_fields_minimal.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{schema_apply_add_fields_all_types.json → examples/schema/schema_apply_add_fields_all_types.json} +0 -0
- /package/skills/qingflow-cli/reference/{views_upsert_table_minimal.example.json → examples/views/views_upsert_table_minimal.example.json} +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from mcp.server.fastmcp import FastMCP
|
|
6
7
|
|
|
7
8
|
from ..config import DEFAULT_PROFILE
|
|
8
|
-
from ..errors import QingflowApiError, raise_tool_error
|
|
9
|
+
from ..errors import QingflowApiError, backend_code_value_int, message_looks_like_invalid_token, raise_tool_error
|
|
9
10
|
from ..list_type_labels import get_record_list_type_label, get_task_type_label
|
|
10
11
|
from .base import ToolBase, tool_cn_name
|
|
11
12
|
|
|
@@ -150,7 +151,17 @@ class TaskTools(ToolBase):
|
|
|
150
151
|
app_key: str | None,
|
|
151
152
|
) -> dict[str, Any]:
|
|
152
153
|
"""执行任务相关逻辑。"""
|
|
153
|
-
|
|
154
|
+
try:
|
|
155
|
+
raw = self.task_statistics(profile=profile, app_key=app_key)
|
|
156
|
+
except RuntimeError as exc:
|
|
157
|
+
if not _is_optional_task_runtime_error(exc):
|
|
158
|
+
raise
|
|
159
|
+
return self._runtime_error_as_auxiliary_result(
|
|
160
|
+
exc,
|
|
161
|
+
error_code="TASK_SUMMARY_UNAVAILABLE",
|
|
162
|
+
selection={"app_key": app_key},
|
|
163
|
+
fallback_hint="Use task list/get directly; task summary is an auxiliary statistics entrypoint.",
|
|
164
|
+
)
|
|
154
165
|
statistics = raw.get("statistics", {})
|
|
155
166
|
summary = self._normalize_task_summary_payload(statistics)
|
|
156
167
|
return {
|
|
@@ -243,34 +254,51 @@ class TaskTools(ToolBase):
|
|
|
243
254
|
if limit <= 0:
|
|
244
255
|
raise_tool_error(QingflowApiError.config_error("limit must be positive"))
|
|
245
256
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
257
|
+
try:
|
|
258
|
+
if dimension == "worksheet":
|
|
259
|
+
raw = self.task_worksheet_statistics(
|
|
260
|
+
profile=profile,
|
|
261
|
+
type=normalized_type,
|
|
262
|
+
worksheet_name=query,
|
|
263
|
+
page_num=1,
|
|
264
|
+
page_size=max(limit, 20),
|
|
265
|
+
)
|
|
266
|
+
source = raw.get("page", {})
|
|
267
|
+
elif app_key:
|
|
268
|
+
raw = self.task_node_statistics(
|
|
269
|
+
profile=profile,
|
|
270
|
+
app_key=app_key,
|
|
271
|
+
type=normalized_type,
|
|
272
|
+
search_key=query,
|
|
273
|
+
)
|
|
274
|
+
source = raw.get("nodes", {})
|
|
275
|
+
else:
|
|
276
|
+
raw = self.task_workflow_nodes(
|
|
277
|
+
profile=profile,
|
|
278
|
+
type=normalized_type,
|
|
279
|
+
status=str(normalized_status),
|
|
280
|
+
app_key_list=None,
|
|
281
|
+
search_key=query,
|
|
282
|
+
page_num=1,
|
|
283
|
+
page_size=max(limit, 20),
|
|
284
|
+
)
|
|
285
|
+
source = raw.get("page", {})
|
|
286
|
+
except RuntimeError as exc:
|
|
287
|
+
if not _is_optional_task_runtime_error(exc):
|
|
288
|
+
raise
|
|
289
|
+
return self._runtime_error_as_auxiliary_result(
|
|
290
|
+
exc,
|
|
291
|
+
error_code="TASK_FACETS_UNAVAILABLE",
|
|
292
|
+
selection={
|
|
293
|
+
"task_box": task_box,
|
|
294
|
+
"flow_status": flow_status,
|
|
295
|
+
"dimension": dimension,
|
|
296
|
+
"app_key": app_key,
|
|
297
|
+
"query": query,
|
|
298
|
+
"limit": limit,
|
|
299
|
+
},
|
|
300
|
+
fallback_hint="Use task list/get directly; task facets are an auxiliary grouping entrypoint.",
|
|
272
301
|
)
|
|
273
|
-
source = raw.get("page", {})
|
|
274
302
|
|
|
275
303
|
groups = self._normalize_task_facets(source)
|
|
276
304
|
rows_truncated = len(groups) > limit
|
|
@@ -824,6 +852,46 @@ class TaskTools(ToolBase):
|
|
|
824
852
|
groups.append({"key": key if key is not None else label, "label": label, "count": count})
|
|
825
853
|
return groups
|
|
826
854
|
|
|
855
|
+
def _runtime_error_as_auxiliary_result(
|
|
856
|
+
self,
|
|
857
|
+
error: RuntimeError,
|
|
858
|
+
*,
|
|
859
|
+
error_code: str,
|
|
860
|
+
selection: dict[str, Any],
|
|
861
|
+
fallback_hint: str,
|
|
862
|
+
) -> dict[str, Any]:
|
|
863
|
+
"""Return a structured failure for optional task discovery helpers."""
|
|
864
|
+
try:
|
|
865
|
+
payload = json.loads(str(error))
|
|
866
|
+
except json.JSONDecodeError:
|
|
867
|
+
payload = {"message": str(error)}
|
|
868
|
+
details = payload.get("details") if isinstance(payload.get("details"), dict) else {}
|
|
869
|
+
warning: dict[str, Any] = {
|
|
870
|
+
"code": error_code,
|
|
871
|
+
"message": fallback_hint,
|
|
872
|
+
}
|
|
873
|
+
for key in ("category", "backend_code", "request_id", "http_status"):
|
|
874
|
+
if payload.get(key) is not None:
|
|
875
|
+
warning[key] = payload.get(key)
|
|
876
|
+
response: dict[str, Any] = {
|
|
877
|
+
"ok": False,
|
|
878
|
+
"status": "failed",
|
|
879
|
+
"error_code": details.get("error_code") or error_code,
|
|
880
|
+
"message": payload.get("message") or str(error),
|
|
881
|
+
"warnings": [warning],
|
|
882
|
+
"output_profile": "normal",
|
|
883
|
+
"data": {
|
|
884
|
+
"selection": selection,
|
|
885
|
+
"fallback_hint": fallback_hint,
|
|
886
|
+
},
|
|
887
|
+
}
|
|
888
|
+
for key in ("category", "backend_code", "request_id", "http_status"):
|
|
889
|
+
if payload.get(key) is not None:
|
|
890
|
+
response[key] = payload.get(key)
|
|
891
|
+
if details:
|
|
892
|
+
response["details"] = details
|
|
893
|
+
return response
|
|
894
|
+
|
|
827
895
|
def _public_task_action_response(
|
|
828
896
|
self,
|
|
829
897
|
raw: dict[str, Any],
|
|
@@ -859,6 +927,22 @@ class TaskTools(ToolBase):
|
|
|
859
927
|
}
|
|
860
928
|
|
|
861
929
|
|
|
930
|
+
def _is_optional_task_runtime_error(error: RuntimeError) -> bool:
|
|
931
|
+
try:
|
|
932
|
+
payload = json.loads(str(error))
|
|
933
|
+
except json.JSONDecodeError:
|
|
934
|
+
return False
|
|
935
|
+
if not isinstance(payload, dict):
|
|
936
|
+
return False
|
|
937
|
+
if str(payload.get("category") or "").strip().lower() == "auth":
|
|
938
|
+
return False
|
|
939
|
+
if message_looks_like_invalid_token(payload.get("message")):
|
|
940
|
+
return False
|
|
941
|
+
if backend_code_value_int(payload.get("http_status")) == 401:
|
|
942
|
+
return False
|
|
943
|
+
return backend_code_value_int(payload.get("backend_code")) in {40002, 40027, 404} or backend_code_value_int(payload.get("http_status")) == 404
|
|
944
|
+
|
|
945
|
+
|
|
862
946
|
def _task_page_items(payload: Any) -> list[Any]:
|
|
863
947
|
if isinstance(payload, list):
|
|
864
948
|
return payload
|
|
@@ -3,8 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
from mcp.server.fastmcp import FastMCP
|
|
4
4
|
|
|
5
5
|
from ..config import DEFAULT_PROFILE
|
|
6
|
-
from ..errors import QingflowApiError, raise_tool_error
|
|
6
|
+
from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
|
|
7
7
|
from ..json_types import JSONObject, JSONValue
|
|
8
|
+
from ..list_type_labels import SYSTEM_VIEW_DEFINITIONS
|
|
8
9
|
from .base import ToolBase, tool_cn_name
|
|
9
10
|
|
|
10
11
|
|
|
@@ -60,7 +61,31 @@ class ViewTools(ToolBase):
|
|
|
60
61
|
def view_list(self, *, profile: str, app_key: str) -> JSONObject:
|
|
61
62
|
"""执行视图相关逻辑。"""
|
|
62
63
|
self._require_app_key(app_key)
|
|
63
|
-
|
|
64
|
+
|
|
65
|
+
def runner(session_profile, context):
|
|
66
|
+
warnings: list[JSONObject] = []
|
|
67
|
+
verification: JSONObject = {"custom_views_loaded": True, "system_view_fallback_used": False}
|
|
68
|
+
try:
|
|
69
|
+
result = self.backend.request("GET", context, f"/app/{app_key}/view/viewList")
|
|
70
|
+
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
|
|
71
|
+
except QingflowApiError as exc:
|
|
72
|
+
if not _is_optional_view_list_error(exc):
|
|
73
|
+
raise
|
|
74
|
+
items, probe_warnings = self._resolve_accessible_system_views(context, app_key)
|
|
75
|
+
warnings.extend(probe_warnings)
|
|
76
|
+
warnings.append(_view_list_fallback_warning("VIEW_LIST_UNAVAILABLE", exc))
|
|
77
|
+
verification = {"custom_views_loaded": False, "system_view_fallback_used": True}
|
|
78
|
+
return {
|
|
79
|
+
"profile": profile,
|
|
80
|
+
"ws_id": session_profile.selected_ws_id,
|
|
81
|
+
"app_key": app_key,
|
|
82
|
+
"result": items,
|
|
83
|
+
"items": items,
|
|
84
|
+
"warnings": warnings,
|
|
85
|
+
"verification": verification,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return self._run(profile, runner)
|
|
64
89
|
|
|
65
90
|
@tool_cn_name("视图列表(扁平)")
|
|
66
91
|
def view_list_flat(self, *, profile: str, app_key: str) -> JSONObject:
|
|
@@ -107,7 +132,40 @@ class ViewTools(ToolBase):
|
|
|
107
132
|
"""执行视图相关逻辑。"""
|
|
108
133
|
self._require_viewgraph_key(viewgraph_key)
|
|
109
134
|
params = {"pass": passcode} if passcode else None
|
|
110
|
-
|
|
135
|
+
def runner(session_profile, context):
|
|
136
|
+
warnings: list[JSONObject] = []
|
|
137
|
+
verification: JSONObject = {"base_info_verified": True, "fallback_config_used": False}
|
|
138
|
+
try:
|
|
139
|
+
result = self.backend.request("GET", context, f"/view/{viewgraph_key}/viewConfig/baseInfo", params=params)
|
|
140
|
+
except QingflowApiError as error:
|
|
141
|
+
if not _is_optional_view_list_error(error):
|
|
142
|
+
raise
|
|
143
|
+
result = self.backend.request("GET", context, f"/view/{viewgraph_key}/viewConfig")
|
|
144
|
+
verification["base_info_verified"] = False
|
|
145
|
+
verification["fallback_config_used"] = True
|
|
146
|
+
warning: JSONObject = {
|
|
147
|
+
"code": "VIEW_BASE_INFO_UNAVAILABLE",
|
|
148
|
+
"message": "view_get_base_info used viewConfig because view baseInfo is unavailable in this permission context.",
|
|
149
|
+
}
|
|
150
|
+
if error.backend_code is not None:
|
|
151
|
+
warning["backend_code"] = error.backend_code
|
|
152
|
+
if error.http_status is not None:
|
|
153
|
+
warning["http_status"] = error.http_status
|
|
154
|
+
if error.request_id is not None:
|
|
155
|
+
warning["request_id"] = error.request_id
|
|
156
|
+
if error.details:
|
|
157
|
+
warning["details"] = error.details
|
|
158
|
+
warnings.append(warning)
|
|
159
|
+
return {
|
|
160
|
+
"profile": profile,
|
|
161
|
+
"ws_id": session_profile.selected_ws_id,
|
|
162
|
+
"viewgraph_key": viewgraph_key,
|
|
163
|
+
"result": result,
|
|
164
|
+
"warnings": warnings,
|
|
165
|
+
"verification": verification,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return self._run(profile, runner)
|
|
111
169
|
|
|
112
170
|
@tool_cn_name("更新视图配置")
|
|
113
171
|
def view_update(self, *, profile: str, viewgraph_key: str, payload: JSONObject) -> JSONObject:
|
|
@@ -303,6 +361,34 @@ class ViewTools(ToolBase):
|
|
|
303
361
|
if value <= 0:
|
|
304
362
|
raise_tool_error(QingflowApiError.config_error(f"{field_name} must be positive"))
|
|
305
363
|
|
|
364
|
+
def _resolve_accessible_system_views(self, context, app_key: str) -> tuple[list[JSONObject], list[JSONObject]]:
|
|
365
|
+
items: list[JSONObject] = []
|
|
366
|
+
warnings: list[JSONObject] = []
|
|
367
|
+
for view_id, list_type, name in SYSTEM_VIEW_DEFINITIONS:
|
|
368
|
+
try:
|
|
369
|
+
self.backend.request(
|
|
370
|
+
"POST",
|
|
371
|
+
context,
|
|
372
|
+
f"/app/{app_key}/apply/filter",
|
|
373
|
+
json_body={"type": list_type, "pageNum": 1, "pageSize": 1},
|
|
374
|
+
)
|
|
375
|
+
except QingflowApiError as exc:
|
|
376
|
+
if _is_optional_view_list_error(exc):
|
|
377
|
+
continue
|
|
378
|
+
raise
|
|
379
|
+
items.append(
|
|
380
|
+
{
|
|
381
|
+
"view_id": view_id,
|
|
382
|
+
"viewId": view_id,
|
|
383
|
+
"viewName": name,
|
|
384
|
+
"name": name,
|
|
385
|
+
"kind": "system",
|
|
386
|
+
"list_type": list_type,
|
|
387
|
+
"analysis_supported": True,
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
return items, warnings
|
|
391
|
+
|
|
306
392
|
def _normalize_reorder_payload(self, payload: list[JSONObject]) -> list[JSONObject]:
|
|
307
393
|
"""执行内部辅助逻辑。"""
|
|
308
394
|
first_item = payload[0]
|
|
@@ -333,3 +419,20 @@ class ViewTools(ToolBase):
|
|
|
333
419
|
if not view_keys:
|
|
334
420
|
raise_tool_error(QingflowApiError.config_error("payload must include viewgraphKey/viewKey or ordinalType/viewKeyList"))
|
|
335
421
|
return [{"ordinalType": "FIXED_VIEW_LIST", "viewKeyList": view_keys}]
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _is_optional_view_list_error(error: QingflowApiError) -> bool:
|
|
425
|
+
if is_auth_like_error(error):
|
|
426
|
+
return False
|
|
427
|
+
backend_code = backend_code_int(error)
|
|
428
|
+
return backend_code in {40002, 40027, 404} or error.http_status == 404
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _view_list_fallback_warning(code: str, error: QingflowApiError) -> JSONObject:
|
|
432
|
+
return {
|
|
433
|
+
"code": code,
|
|
434
|
+
"message": "custom view list is unavailable; returned accessible system views instead",
|
|
435
|
+
"backend_code": error.backend_code,
|
|
436
|
+
"http_status": error.http_status,
|
|
437
|
+
"request_id": error.request_id,
|
|
438
|
+
}
|
|
@@ -14,13 +14,25 @@ class WorkflowTools(ToolBase):
|
|
|
14
14
|
|
|
15
15
|
类型:流程配置工具。
|
|
16
16
|
主要职责:
|
|
17
|
-
1.
|
|
18
|
-
2.
|
|
19
|
-
|
|
17
|
+
1. 查询流程节点与流程图配置;
|
|
18
|
+
2. 读取与更新流程规则;
|
|
19
|
+
3. 支持流程发布与流程调试辅助能力。
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
def register(self, mcp: FastMCP) -> None:
|
|
23
23
|
"""注册当前工具到 MCP 服务。"""
|
|
24
|
+
@mcp.tool()
|
|
25
|
+
def workflow_list_nodes(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
26
|
+
return self.workflow_list_nodes(profile=profile, app_key=app_key)
|
|
27
|
+
|
|
28
|
+
@mcp.tool()
|
|
29
|
+
def workflow_get_node_detail(profile: str = DEFAULT_PROFILE, app_key: str = "", audit_node_id: int = 0) -> JSONObject:
|
|
30
|
+
return self.workflow_get_node_detail(profile=profile, app_key=app_key, audit_node_id=audit_node_id)
|
|
31
|
+
|
|
32
|
+
@mcp.tool()
|
|
33
|
+
def workflow_get_global_settings(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
34
|
+
return self.workflow_get_global_settings(profile=profile, app_key=app_key)
|
|
35
|
+
|
|
24
36
|
@mcp.tool()
|
|
25
37
|
def workflow_get_future_nodes(profile: str = DEFAULT_PROFILE, app_key: str = "", apply_id: int = 0) -> JSONObject:
|
|
26
38
|
return self.workflow_get_future_nodes(profile=profile, app_key=app_key, apply_id=apply_id)
|
|
@@ -41,6 +53,22 @@ class WorkflowTools(ToolBase):
|
|
|
41
53
|
audit_node_id=audit_node_id,
|
|
42
54
|
)
|
|
43
55
|
|
|
56
|
+
@mcp.tool()
|
|
57
|
+
def workflow_get_qsource_active(profile: str = DEFAULT_PROFILE, app_key: str = "", qsource_id: int = 0) -> JSONObject:
|
|
58
|
+
return self.workflow_get_qsource_active(profile=profile, app_key=app_key, qsource_id=qsource_id)
|
|
59
|
+
|
|
60
|
+
@mcp.tool()
|
|
61
|
+
def workflow_get_qsource_passive(profile: str = DEFAULT_PROFILE, app_key: str = "", qsource_id: int = 0) -> JSONObject:
|
|
62
|
+
return self.workflow_get_qsource_passive(profile=profile, app_key=app_key, qsource_id=qsource_id)
|
|
63
|
+
|
|
64
|
+
@mcp.tool()
|
|
65
|
+
def workflow_get_editable_question_ids(profile: str = DEFAULT_PROFILE, app_key: str = "", audit_node_id: int = 0) -> JSONObject:
|
|
66
|
+
return self.workflow_get_editable_question_ids(profile=profile, app_key=app_key, audit_node_id=audit_node_id)
|
|
67
|
+
|
|
68
|
+
@mcp.tool()
|
|
69
|
+
def workflow_get_print_nodes(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
70
|
+
return self.workflow_get_print_nodes(profile=profile, app_key=app_key)
|
|
71
|
+
|
|
44
72
|
@tool_cn_name("流程节点列表")
|
|
45
73
|
def workflow_list_nodes(self, *, profile: str, app_key: str) -> JSONObject:
|
|
46
74
|
"""执行流程相关逻辑。"""
|
|
@@ -323,7 +351,23 @@ class WorkflowTools(ToolBase):
|
|
|
323
351
|
for candidate_path in paths:
|
|
324
352
|
try:
|
|
325
353
|
result = self.backend.request("POST", call_context, candidate_path, json_body=json_body)
|
|
326
|
-
|
|
354
|
+
fallback_applied = candidate_path != path or call_context is not context
|
|
355
|
+
response: JSONObject = {
|
|
356
|
+
"profile": profile,
|
|
357
|
+
"ws_id": session_profile.selected_ws_id,
|
|
358
|
+
"result": result,
|
|
359
|
+
"request_path": candidate_path,
|
|
360
|
+
"requested_path": path,
|
|
361
|
+
"fallback_applied": fallback_applied,
|
|
362
|
+
"qf_version_source": (
|
|
363
|
+
call_context.qf_version_source
|
|
364
|
+
or ("context" if call_context.qf_version else "unset")
|
|
365
|
+
),
|
|
366
|
+
"verification": {
|
|
367
|
+
"primary_path_used": candidate_path == path,
|
|
368
|
+
"qf_version_used": call_context.qf_version is not None,
|
|
369
|
+
},
|
|
370
|
+
}
|
|
327
371
|
response.update(extra)
|
|
328
372
|
if risk_operation and risk_target:
|
|
329
373
|
return self._attach_human_review_notice(response, operation=risk_operation, target=risk_target)
|
|
@@ -6,10 +6,13 @@ from mcp.server.fastmcp import FastMCP
|
|
|
6
6
|
|
|
7
7
|
from ..backend_client import BackendRequestContext
|
|
8
8
|
from ..config import DEFAULT_PROFILE
|
|
9
|
-
from ..errors import QingflowApiError, raise_tool_error
|
|
9
|
+
from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
|
|
10
10
|
from .base import ToolBase, tool_cn_name
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
_WORKSPACE_DETAIL_WARNING_KEY = "_qingflow_workspace_detail_warning"
|
|
14
|
+
|
|
15
|
+
|
|
13
16
|
class WorkspaceTools(ToolBase):
|
|
14
17
|
"""工作区工具(中文名:工作区与插件管理)。
|
|
15
18
|
|
|
@@ -91,10 +94,14 @@ class WorkspaceTools(ToolBase):
|
|
|
91
94
|
normalized = result if isinstance(result, dict) else {"list": result if isinstance(result, list) else []}
|
|
92
95
|
workspace_list = normalized.get("list") if isinstance(normalized.get("list"), list) else []
|
|
93
96
|
selected_ws_id = _.selected_ws_id if _ is not None else None
|
|
97
|
+
warnings: list[dict[str, Any]] = []
|
|
94
98
|
if selected_ws_id and not any(isinstance(item, dict) and item.get("wsId") == selected_ws_id for item in workspace_list):
|
|
95
99
|
try:
|
|
96
100
|
selected_workspace = self.backend.request("GET", context, f"/user/workspace/{selected_ws_id}")
|
|
97
|
-
except QingflowApiError:
|
|
101
|
+
except QingflowApiError as exc:
|
|
102
|
+
if not _is_optional_workspace_detail_error(exc):
|
|
103
|
+
raise
|
|
104
|
+
warnings.append(_workspace_detail_fallback_warning(exc, ws_id=selected_ws_id))
|
|
98
105
|
selected_workspace = {
|
|
99
106
|
"wsId": selected_ws_id,
|
|
100
107
|
"workspaceName": _.selected_ws_name if _ is not None else None,
|
|
@@ -109,6 +116,14 @@ class WorkspaceTools(ToolBase):
|
|
|
109
116
|
"profile": profile,
|
|
110
117
|
"include_external": include_external,
|
|
111
118
|
"page": normalized,
|
|
119
|
+
"warnings": warnings,
|
|
120
|
+
"verification": {
|
|
121
|
+
"selected_workspace_detail_verified": not warnings,
|
|
122
|
+
"selected_workspace_included": bool(
|
|
123
|
+
selected_ws_id
|
|
124
|
+
and any(isinstance(item, dict) and item.get("wsId") == selected_ws_id for item in workspace_list)
|
|
125
|
+
),
|
|
126
|
+
},
|
|
112
127
|
}
|
|
113
128
|
|
|
114
129
|
return self._run(profile, runner, require_workspace=False)
|
|
@@ -127,6 +142,7 @@ class WorkspaceTools(ToolBase):
|
|
|
127
142
|
if target_ws_id is None or target_ws_id <= 0:
|
|
128
143
|
raise_tool_error(QingflowApiError.workspace_not_selected(profile))
|
|
129
144
|
workspace = self._fetch_workspace_with_fallback(context, ws_id=target_ws_id)
|
|
145
|
+
workspace, warnings = _strip_workspace_detail_warning(workspace)
|
|
130
146
|
system_version = self._workspace_system_version(workspace)
|
|
131
147
|
return {
|
|
132
148
|
"profile": profile,
|
|
@@ -134,6 +150,11 @@ class WorkspaceTools(ToolBase):
|
|
|
134
150
|
"qf_version": system_version,
|
|
135
151
|
"qf_version_source": "workspace_system_version" if system_version else "unverified",
|
|
136
152
|
"workspace": workspace,
|
|
153
|
+
"warnings": warnings,
|
|
154
|
+
"verification": {
|
|
155
|
+
"workspace_detail_verified": not warnings,
|
|
156
|
+
"workspace_list_fallback_used": bool(warnings),
|
|
157
|
+
},
|
|
137
158
|
}
|
|
138
159
|
|
|
139
160
|
return self._run(profile, runner, require_workspace=False)
|
|
@@ -151,6 +172,7 @@ class WorkspaceTools(ToolBase):
|
|
|
151
172
|
|
|
152
173
|
def runner(_, context):
|
|
153
174
|
workspace = self._fetch_workspace_with_fallback(context, ws_id=ws_id)
|
|
175
|
+
workspace, warnings = _strip_workspace_detail_warning(workspace)
|
|
154
176
|
workspace_name = str(workspace.get("workspaceName") or workspace.get("wsName") or workspace.get("remark") or "").strip() or None
|
|
155
177
|
selected = self.sessions.select_workspace(profile, ws_id=ws_id, ws_name=workspace_name)
|
|
156
178
|
system_version = self._workspace_system_version(workspace)
|
|
@@ -171,6 +193,11 @@ class WorkspaceTools(ToolBase):
|
|
|
171
193
|
"ws_id": selected.selected_ws_id,
|
|
172
194
|
"workspace_name": selected.selected_ws_name,
|
|
173
195
|
},
|
|
196
|
+
"warnings": warnings,
|
|
197
|
+
"verification": {
|
|
198
|
+
"workspace_detail_verified": not warnings,
|
|
199
|
+
"workspace_list_fallback_used": bool(warnings),
|
|
200
|
+
},
|
|
174
201
|
}
|
|
175
202
|
|
|
176
203
|
return self._run(profile, runner, require_workspace=False)
|
|
@@ -212,7 +239,15 @@ class WorkspaceTools(ToolBase):
|
|
|
212
239
|
*,
|
|
213
240
|
ws_id: int,
|
|
214
241
|
) -> dict[str, Any]:
|
|
215
|
-
|
|
242
|
+
try:
|
|
243
|
+
workspace = self.backend.request("GET", context, f"/user/workspace/{ws_id}")
|
|
244
|
+
except QingflowApiError as error:
|
|
245
|
+
if not _is_optional_workspace_detail_error(error):
|
|
246
|
+
raise
|
|
247
|
+
fallback = self._fetch_workspace_from_list(context, ws_id=ws_id)
|
|
248
|
+
if isinstance(fallback, dict):
|
|
249
|
+
return _with_workspace_detail_warning(fallback, error, ws_id=ws_id)
|
|
250
|
+
raise
|
|
216
251
|
if not isinstance(workspace, dict):
|
|
217
252
|
raise_tool_error(QingflowApiError(category="workspace", message=f"Workspace {ws_id} is not accessible"))
|
|
218
253
|
if self._workspace_needs_list_fallback(workspace):
|
|
@@ -264,3 +299,36 @@ class WorkspaceTools(ToolBase):
|
|
|
264
299
|
return None
|
|
265
300
|
normalized = str(value).strip()
|
|
266
301
|
return normalized or None
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _is_optional_workspace_detail_error(error: QingflowApiError) -> bool:
|
|
305
|
+
if is_auth_like_error(error):
|
|
306
|
+
return False
|
|
307
|
+
backend_code = backend_code_int(error)
|
|
308
|
+
return backend_code in {40002, 40027, 404, 59004} or error.http_status == 404
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _with_workspace_detail_warning(workspace: dict[str, Any], error: QingflowApiError, *, ws_id: int) -> dict[str, Any]:
|
|
312
|
+
enriched = dict(workspace)
|
|
313
|
+
enriched[_WORKSPACE_DETAIL_WARNING_KEY] = _workspace_detail_fallback_warning(error, ws_id=ws_id)
|
|
314
|
+
return enriched
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _strip_workspace_detail_warning(workspace: dict[str, Any]) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
318
|
+
public_workspace = dict(workspace)
|
|
319
|
+
warning = public_workspace.pop(_WORKSPACE_DETAIL_WARNING_KEY, None)
|
|
320
|
+
warnings = [warning] if isinstance(warning, dict) else []
|
|
321
|
+
return public_workspace, warnings
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _workspace_detail_fallback_warning(error: QingflowApiError, *, ws_id: int) -> dict[str, Any]:
|
|
325
|
+
return {
|
|
326
|
+
"code": "WORKSPACE_DETAIL_UNAVAILABLE",
|
|
327
|
+
"message": (
|
|
328
|
+
f"workspace detail route for ws_id {ws_id} was unavailable; "
|
|
329
|
+
"the tool used the visible workspace list as fallback."
|
|
330
|
+
),
|
|
331
|
+
"backend_code": error.backend_code,
|
|
332
|
+
"http_status": error.http_status,
|
|
333
|
+
"request_id": error.request_id,
|
|
334
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|