@josephyan/qingflow-cli 1.1.4 → 1.1.6
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 +282 -102
- package/src/qingflow_mcp/builder_facade/service.py +4192 -935
- package/src/qingflow_mcp/cli/commands/builder.py +316 -298
- 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 +1780 -406
- 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
|
@@ -3,13 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
from copy import deepcopy
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
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
|
|
8
8
|
from ..list_type_labels import SYSTEM_VIEW_DEFINITIONS
|
|
9
9
|
from ..solution.compiler.icon_utils import workspace_icon_config
|
|
10
10
|
from .app_tools import _analysis_supported_for_view_type
|
|
11
11
|
from .base import ToolBase, tool_cn_name
|
|
12
|
-
from .qingbi_report_tools import QingbiReportTools
|
|
12
|
+
from .qingbi_report_tools import QingbiReportTools, _coerce_tool_error
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class ResourceReadTools(ToolBase):
|
|
@@ -141,21 +141,60 @@ class ResourceReadTools(ToolBase):
|
|
|
141
141
|
warnings: list[JSONObject] = []
|
|
142
142
|
verification = {
|
|
143
143
|
"view_exists": True,
|
|
144
|
+
"config_verified": True,
|
|
145
|
+
"base_info_verified": True,
|
|
144
146
|
"questions_verified": True,
|
|
145
147
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
try:
|
|
149
|
+
config = self.backend.request("GET", context, f"/view/{view_key}/viewConfig")
|
|
150
|
+
if not isinstance(config, dict):
|
|
151
|
+
config = {}
|
|
152
|
+
except QingflowApiError as error:
|
|
153
|
+
if not _is_optional_view_config_error(error):
|
|
154
|
+
raise
|
|
155
|
+
config = {}
|
|
156
|
+
verification["config_verified"] = False
|
|
157
|
+
warnings.append(
|
|
158
|
+
{
|
|
159
|
+
"code": "VIEW_CONFIG_UNAVAILABLE",
|
|
160
|
+
"message": "view_get used baseInfo because viewConfig is unavailable for this user.",
|
|
161
|
+
"backend_code": error.backend_code,
|
|
162
|
+
"http_status": error.http_status,
|
|
163
|
+
"request_id": error.request_id,
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
try:
|
|
167
|
+
base_info = self.backend.request("GET", context, f"/view/{view_key}/viewConfig/baseInfo")
|
|
168
|
+
except QingflowApiError as error:
|
|
169
|
+
if not _is_optional_view_base_error(error):
|
|
170
|
+
raise
|
|
171
|
+
base_info = {}
|
|
172
|
+
verification["base_info_verified"] = False
|
|
173
|
+
warnings.append(
|
|
174
|
+
{
|
|
175
|
+
"code": "VIEW_BASE_INFO_UNAVAILABLE",
|
|
176
|
+
"message": "view_get used viewConfig because view baseInfo is unavailable for this user.",
|
|
177
|
+
"backend_code": error.backend_code,
|
|
178
|
+
"http_status": error.http_status,
|
|
179
|
+
"request_id": error.request_id,
|
|
180
|
+
}
|
|
181
|
+
)
|
|
148
182
|
questions: list[dict[str, Any]] = []
|
|
149
183
|
try:
|
|
150
184
|
questions_payload = self.backend.request("GET", context, f"/view/{view_key}/question")
|
|
151
185
|
if isinstance(questions_payload, list):
|
|
152
186
|
questions = [deepcopy(item) for item in questions_payload if isinstance(item, dict)]
|
|
153
|
-
except QingflowApiError:
|
|
187
|
+
except QingflowApiError as error:
|
|
188
|
+
if not _is_optional_view_question_error(error):
|
|
189
|
+
raise
|
|
154
190
|
verification["questions_verified"] = False
|
|
155
191
|
warnings.append(
|
|
156
192
|
{
|
|
157
193
|
"code": "VIEW_QUESTIONS_UNAVAILABLE",
|
|
158
194
|
"message": "view_get could not load visible columns because question readback is unavailable.",
|
|
195
|
+
"backend_code": error.backend_code,
|
|
196
|
+
"http_status": error.http_status,
|
|
197
|
+
"request_id": error.request_id,
|
|
159
198
|
}
|
|
160
199
|
)
|
|
161
200
|
|
|
@@ -223,13 +262,32 @@ class ResourceReadTools(ToolBase):
|
|
|
223
262
|
self._require_chart_id(chart_id)
|
|
224
263
|
|
|
225
264
|
def runner(session_profile, _context):
|
|
226
|
-
base = self.charts.qingbi_report_get_base(profile=profile, chart_id=chart_id).get("result") or {}
|
|
227
265
|
warnings: list[JSONObject] = []
|
|
228
266
|
verification = {
|
|
229
267
|
"chart_exists": True,
|
|
268
|
+
"chart_base_loaded": False,
|
|
230
269
|
"chart_data_loaded": False,
|
|
231
270
|
"chart_config_loaded": False,
|
|
232
271
|
}
|
|
272
|
+
transport_errors: list[JSONObject] = []
|
|
273
|
+
base: dict[str, Any] = {}
|
|
274
|
+
try:
|
|
275
|
+
base_result = self.charts.qingbi_report_get_base(profile=profile, chart_id=chart_id).get("result") or {}
|
|
276
|
+
if isinstance(base_result, dict):
|
|
277
|
+
base = deepcopy(base_result)
|
|
278
|
+
verification["chart_base_loaded"] = True
|
|
279
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
280
|
+
api_error = _coerce_tool_error(error)
|
|
281
|
+
if api_error is None or not _is_optional_chart_base_error(api_error):
|
|
282
|
+
raise
|
|
283
|
+
transport_errors.append(_transport_error_payload(stage="base_info", error=api_error))
|
|
284
|
+
warnings.append(
|
|
285
|
+
{
|
|
286
|
+
"code": "CHART_BASE_INFO_UNAVAILABLE",
|
|
287
|
+
"message": "chart_get could not load chart base info; continuing with chart data when available.",
|
|
288
|
+
**_transport_warning_fields(api_error),
|
|
289
|
+
}
|
|
290
|
+
)
|
|
233
291
|
data: Any = None
|
|
234
292
|
data_config: dict[str, Any] = {}
|
|
235
293
|
try:
|
|
@@ -239,23 +297,54 @@ class ResourceReadTools(ToolBase):
|
|
|
239
297
|
data_config = deepcopy(data.get("config"))
|
|
240
298
|
verification["chart_config_loaded"] = True
|
|
241
299
|
except (QingflowApiError, RuntimeError) as error:
|
|
242
|
-
api_error =
|
|
300
|
+
api_error = _coerce_tool_error(error)
|
|
301
|
+
if api_error is None or not _is_optional_chart_data_error(api_error):
|
|
302
|
+
raise
|
|
303
|
+
transport_errors.append(_transport_error_payload(stage="data", error=api_error))
|
|
243
304
|
warnings.append(
|
|
244
305
|
{
|
|
245
306
|
"code": "CHART_DATA_UNAVAILABLE",
|
|
246
307
|
"message": "chart_get could not load chart data; returning chart metadata and config when available.",
|
|
247
|
-
|
|
248
|
-
"http_status": api_error.http_status if api_error is not None else None,
|
|
308
|
+
**_transport_warning_fields(api_error),
|
|
249
309
|
}
|
|
250
310
|
)
|
|
251
311
|
if not data_config:
|
|
252
312
|
try:
|
|
253
313
|
config_result = self.charts.qingbi_report_get_config(profile=profile, chart_id=chart_id).get("result") or {}
|
|
254
|
-
except (QingflowApiError, RuntimeError):
|
|
314
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
315
|
+
api_error = _coerce_tool_error(error)
|
|
316
|
+
if api_error is None or not _is_optional_chart_config_error(api_error):
|
|
317
|
+
raise
|
|
318
|
+
transport_errors.append(_transport_error_payload(stage="config", error=api_error))
|
|
319
|
+
warnings.append(
|
|
320
|
+
{
|
|
321
|
+
"code": "CHART_CONFIG_UNAVAILABLE",
|
|
322
|
+
"message": "chart_get could not load chart config; returning chart base info or data when available.",
|
|
323
|
+
**_transport_warning_fields(api_error),
|
|
324
|
+
}
|
|
325
|
+
)
|
|
255
326
|
config_result = {}
|
|
256
327
|
if isinstance(config_result, dict) and config_result:
|
|
257
328
|
data_config = deepcopy(config_result)
|
|
258
329
|
verification["chart_config_loaded"] = True
|
|
330
|
+
if not any(
|
|
331
|
+
bool(verification[key])
|
|
332
|
+
for key in ("chart_base_loaded", "chart_data_loaded", "chart_config_loaded")
|
|
333
|
+
):
|
|
334
|
+
return {
|
|
335
|
+
"profile": profile,
|
|
336
|
+
"ws_id": session_profile.selected_ws_id,
|
|
337
|
+
"ok": False,
|
|
338
|
+
"status": "failed",
|
|
339
|
+
"error_code": "CHART_READ_UNAVAILABLE",
|
|
340
|
+
"message": "chart_get could not load chart base info, data, or config in this permission context.",
|
|
341
|
+
"warnings": warnings,
|
|
342
|
+
"verification": verification,
|
|
343
|
+
"details": {
|
|
344
|
+
"chart_id": chart_id,
|
|
345
|
+
"transport_errors": transport_errors,
|
|
346
|
+
},
|
|
347
|
+
}
|
|
259
348
|
data_payload: dict[str, Any] = {
|
|
260
349
|
"chart_id": chart_id,
|
|
261
350
|
"chart_name": str(base.get("chartName") or base.get("name") or chart_id).strip() or chart_id,
|
|
@@ -296,7 +385,9 @@ class ResourceReadTools(ToolBase):
|
|
|
296
385
|
def _resolve_app_key_from_view_form(self, *, context: Any, view_key: str) -> str | None:
|
|
297
386
|
try:
|
|
298
387
|
form_payload = self.backend.request("GET", context, f"/view/{view_key}/form")
|
|
299
|
-
except QingflowApiError:
|
|
388
|
+
except QingflowApiError as exc:
|
|
389
|
+
if not _is_optional_view_metadata_resolution_error(exc):
|
|
390
|
+
raise
|
|
300
391
|
return None
|
|
301
392
|
if not isinstance(form_payload, dict):
|
|
302
393
|
return None
|
|
@@ -309,38 +400,13 @@ class ResourceReadTools(ToolBase):
|
|
|
309
400
|
return None
|
|
310
401
|
try:
|
|
311
402
|
payload = self.backend.request("GET", context, "/tag/apps")
|
|
312
|
-
except QingflowApiError:
|
|
403
|
+
except QingflowApiError as exc:
|
|
404
|
+
if not _is_optional_view_metadata_resolution_error(exc):
|
|
405
|
+
raise
|
|
313
406
|
payload = None
|
|
314
407
|
app_key = _find_visible_app_key_by_form_id(payload, form_id=form_id)
|
|
315
408
|
if app_key:
|
|
316
409
|
return app_key
|
|
317
|
-
page_num = 1
|
|
318
|
-
page_size = 200
|
|
319
|
-
while page_num <= 20:
|
|
320
|
-
try:
|
|
321
|
-
page = self.backend.request(
|
|
322
|
-
"GET",
|
|
323
|
-
context,
|
|
324
|
-
"/app/item",
|
|
325
|
-
params={"pageNum": page_num, "pageSize": page_size},
|
|
326
|
-
)
|
|
327
|
-
except QingflowApiError:
|
|
328
|
-
return None
|
|
329
|
-
items = page.get("list") if isinstance(page, dict) else []
|
|
330
|
-
if not isinstance(items, list) or not items:
|
|
331
|
-
return None
|
|
332
|
-
for item in items:
|
|
333
|
-
if not isinstance(item, dict):
|
|
334
|
-
continue
|
|
335
|
-
if _coerce_positive_int(item.get("formId") or item.get("form_id")) != form_id:
|
|
336
|
-
continue
|
|
337
|
-
app_key = str(item.get("appKey") or item.get("app_key") or "").strip()
|
|
338
|
-
if app_key:
|
|
339
|
-
return app_key
|
|
340
|
-
total = _coerce_positive_int(page.get("total")) if isinstance(page, dict) else None
|
|
341
|
-
if total is not None and page_num * page_size >= total:
|
|
342
|
-
break
|
|
343
|
-
page_num += 1
|
|
344
410
|
return None
|
|
345
411
|
|
|
346
412
|
|
|
@@ -423,6 +489,89 @@ def _normalize_user_portal_components(components: Any) -> list[dict[str, Any]]:
|
|
|
423
489
|
return items
|
|
424
490
|
|
|
425
491
|
|
|
492
|
+
def _is_optional_chart_base_error(error: QingflowApiError) -> bool:
|
|
493
|
+
if is_auth_like_error(error):
|
|
494
|
+
return False
|
|
495
|
+
backend_code = _coerce_backend_code(error)
|
|
496
|
+
return backend_code in {40002, 40027, 404, 81007} or error.http_status == 404
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _is_optional_chart_data_error(error: QingflowApiError) -> bool:
|
|
500
|
+
if is_auth_like_error(error):
|
|
501
|
+
return False
|
|
502
|
+
backend_code = _coerce_backend_code(error)
|
|
503
|
+
return backend_code in {40002, 40027, 404, 44011, 81007, 81011} or error.http_status == 404
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _is_optional_chart_config_error(error: QingflowApiError) -> bool:
|
|
507
|
+
if is_auth_like_error(error):
|
|
508
|
+
return False
|
|
509
|
+
backend_code = _coerce_backend_code(error)
|
|
510
|
+
return backend_code in {40002, 40027, 404, 44011, 81007, 81011} or error.http_status == 404
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _is_optional_view_base_error(error: QingflowApiError) -> bool:
|
|
514
|
+
if is_auth_like_error(error):
|
|
515
|
+
return False
|
|
516
|
+
backend_code = _coerce_backend_code(error)
|
|
517
|
+
return backend_code in {40002, 40027, 404} or error.http_status == 404
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _is_optional_view_config_error(error: QingflowApiError) -> bool:
|
|
521
|
+
if is_auth_like_error(error):
|
|
522
|
+
return False
|
|
523
|
+
backend_code = _coerce_backend_code(error)
|
|
524
|
+
return backend_code in {40002, 40027, 404} or error.http_status == 404
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _is_optional_view_question_error(error: QingflowApiError) -> bool:
|
|
528
|
+
if is_auth_like_error(error):
|
|
529
|
+
return False
|
|
530
|
+
backend_code = _coerce_backend_code(error)
|
|
531
|
+
return backend_code in {40002, 40027, 404} or error.http_status == 404
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _is_optional_view_metadata_resolution_error(error: QingflowApiError) -> bool:
|
|
535
|
+
if is_auth_like_error(error):
|
|
536
|
+
return False
|
|
537
|
+
backend_code = _coerce_backend_code(error)
|
|
538
|
+
return backend_code in {40002, 40027, 404} or error.http_status == 404
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _coerce_backend_code(error: QingflowApiError) -> int | None:
|
|
542
|
+
return backend_code_int(error)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def _transport_error_payload(*, stage: str, error: QingflowApiError) -> JSONObject:
|
|
546
|
+
payload: JSONObject = {
|
|
547
|
+
"stage": stage,
|
|
548
|
+
"category": error.category,
|
|
549
|
+
"message": error.message,
|
|
550
|
+
}
|
|
551
|
+
if error.backend_code is not None:
|
|
552
|
+
payload["backend_code"] = error.backend_code
|
|
553
|
+
if error.http_status is not None:
|
|
554
|
+
payload["http_status"] = error.http_status
|
|
555
|
+
if error.request_id:
|
|
556
|
+
payload["request_id"] = error.request_id
|
|
557
|
+
if error.details:
|
|
558
|
+
payload["details"] = deepcopy(error.details)
|
|
559
|
+
return payload
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _transport_warning_fields(error: QingflowApiError) -> JSONObject:
|
|
563
|
+
payload: JSONObject = {}
|
|
564
|
+
if error.backend_code is not None:
|
|
565
|
+
payload["backend_code"] = error.backend_code
|
|
566
|
+
if error.http_status is not None:
|
|
567
|
+
payload["http_status"] = error.http_status
|
|
568
|
+
if error.request_id:
|
|
569
|
+
payload["request_id"] = error.request_id
|
|
570
|
+
if error.details:
|
|
571
|
+
payload["details"] = deepcopy(error.details)
|
|
572
|
+
return payload
|
|
573
|
+
|
|
574
|
+
|
|
426
575
|
def _normalize_portal_component_source_type(value: Any) -> str:
|
|
427
576
|
raw = str(value or "").strip()
|
|
428
577
|
mapping = {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
3
5
|
from mcp.server.fastmcp import FastMCP
|
|
4
6
|
|
|
5
7
|
from ..config import DEFAULT_PROFILE
|
|
6
|
-
from ..errors import QingflowApiError, raise_tool_error
|
|
8
|
+
from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
|
|
7
9
|
from ..json_types import JSONObject
|
|
8
10
|
from .base import ToolBase, tool_cn_name
|
|
9
11
|
|
|
@@ -20,7 +22,7 @@ class RoleTools(ToolBase):
|
|
|
20
22
|
|
|
21
23
|
def register(self, mcp: FastMCP) -> None:
|
|
22
24
|
"""注册当前工具到 MCP 服务。"""
|
|
23
|
-
@mcp.tool()
|
|
25
|
+
@mcp.tool(description="Search workspace roles for builder permission and workflow configuration. Requires contact/role management permission and a non-empty keyword; do not use for record member/department field candidates.")
|
|
24
26
|
def role_search(
|
|
25
27
|
profile: str = DEFAULT_PROFILE,
|
|
26
28
|
keyword: str = "",
|
|
@@ -50,18 +52,36 @@ class RoleTools(ToolBase):
|
|
|
50
52
|
"""执行角色相关逻辑。"""
|
|
51
53
|
if page_num <= 0 or page_size <= 0:
|
|
52
54
|
raise_tool_error(QingflowApiError.config_error("page_num and page_size must be positive"))
|
|
55
|
+
normalized_keyword = keyword.strip()
|
|
56
|
+
if not normalized_keyword:
|
|
57
|
+
raise_tool_error(
|
|
58
|
+
QingflowApiError.config_error(
|
|
59
|
+
"keyword is required for role_search; role lookup is a contact-management path, not a record candidate fallback"
|
|
60
|
+
)
|
|
61
|
+
)
|
|
53
62
|
|
|
54
63
|
def runner(session_profile, context):
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
try:
|
|
65
|
+
result = self.backend.request(
|
|
66
|
+
"GET",
|
|
67
|
+
context,
|
|
68
|
+
"/contact/roleByPage",
|
|
69
|
+
params={"keyword": normalized_keyword, "pageNum": page_num, "pageSize": page_size},
|
|
70
|
+
)
|
|
71
|
+
except QingflowApiError as exc:
|
|
72
|
+
if _is_role_permission_denied(exc):
|
|
73
|
+
return _contact_role_permission_denied_payload(
|
|
74
|
+
profile=profile,
|
|
75
|
+
ws_id=session_profile.selected_ws_id,
|
|
76
|
+
error=exc,
|
|
77
|
+
operation="role_search",
|
|
78
|
+
selection={"keyword": normalized_keyword, "page_num": page_num, "page_size": page_size},
|
|
79
|
+
)
|
|
80
|
+
raise
|
|
61
81
|
return {
|
|
62
82
|
"profile": profile,
|
|
63
83
|
"ws_id": session_profile.selected_ws_id,
|
|
64
|
-
"keyword":
|
|
84
|
+
"keyword": normalized_keyword,
|
|
65
85
|
"page": result,
|
|
66
86
|
}
|
|
67
87
|
|
|
@@ -110,3 +130,54 @@ class RoleTools(ToolBase):
|
|
|
110
130
|
)
|
|
111
131
|
|
|
112
132
|
return self._run(profile, runner)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _is_role_permission_denied(error: QingflowApiError) -> bool:
|
|
136
|
+
if is_auth_like_error(error):
|
|
137
|
+
return False
|
|
138
|
+
return backend_code_int(error) in {40002, 40027}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _contact_role_permission_denied_payload(
|
|
142
|
+
*,
|
|
143
|
+
profile: str,
|
|
144
|
+
ws_id: Any,
|
|
145
|
+
error: QingflowApiError,
|
|
146
|
+
operation: str,
|
|
147
|
+
selection: JSONObject,
|
|
148
|
+
) -> JSONObject:
|
|
149
|
+
return {
|
|
150
|
+
"profile": profile,
|
|
151
|
+
"ws_id": ws_id,
|
|
152
|
+
"ok": False,
|
|
153
|
+
"status": "failed",
|
|
154
|
+
"error_code": "CONTACT_ROLE_PERMISSION_DENIED",
|
|
155
|
+
"message": (
|
|
156
|
+
"Contact role-management data is not readable for the current user. "
|
|
157
|
+
"This is a role/contact-management permission boundary, not proof that record "
|
|
158
|
+
"member or department field candidates are unavailable."
|
|
159
|
+
),
|
|
160
|
+
"backend_code": error.backend_code,
|
|
161
|
+
"request_id": error.request_id,
|
|
162
|
+
"http_status": error.http_status,
|
|
163
|
+
"keyword": selection.get("keyword"),
|
|
164
|
+
"page": {"list": [], "total": 0, "pageAmount": 0},
|
|
165
|
+
"data": {
|
|
166
|
+
"items": [],
|
|
167
|
+
"pagination": {
|
|
168
|
+
"page": selection.get("page_num"),
|
|
169
|
+
"page_size": selection.get("page_size"),
|
|
170
|
+
"returned_items": 0,
|
|
171
|
+
"reported_total": 0,
|
|
172
|
+
"page_amount": 0,
|
|
173
|
+
},
|
|
174
|
+
"selection": selection,
|
|
175
|
+
},
|
|
176
|
+
"warnings": [
|
|
177
|
+
{
|
|
178
|
+
"code": "CONTACT_ROLE_PERMISSION_DENIED",
|
|
179
|
+
"message": "This is a role/contact-management permission boundary, not a record field candidate failure.",
|
|
180
|
+
"operation": operation,
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
}
|
|
@@ -9,7 +9,7 @@ from mcp.server.fastmcp import FastMCP
|
|
|
9
9
|
from pydantic import BaseModel, ValidationError
|
|
10
10
|
|
|
11
11
|
from ..config import DEFAULT_PROFILE
|
|
12
|
-
from ..errors import QingflowApiError
|
|
12
|
+
from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
|
|
13
13
|
from ..list_type_labels import get_record_list_type_label
|
|
14
14
|
from ..solution.build_assembly_store import BuildAssemblyStore, default_manifest
|
|
15
15
|
from ..solution.compiler import CompiledSolution, ExecutionPlan, ExecutionStep, build_execution_plan, compile_solution
|
|
@@ -38,6 +38,7 @@ from .qingbi_report_tools import QingbiReportTools
|
|
|
38
38
|
from .record_tools import RecordTools
|
|
39
39
|
from .role_tools import RoleTools
|
|
40
40
|
from .view_tools import ViewTools
|
|
41
|
+
from .workflow_tools import WorkflowTools
|
|
41
42
|
from .workspace_tools import WorkspaceTools
|
|
42
43
|
|
|
43
44
|
STAGED_BUILD_MODES = {"preflight", "plan", "apply", "repair"}
|
|
@@ -736,36 +737,6 @@ class SolutionTools(ToolBase):
|
|
|
736
737
|
) -> dict[str, Any]:
|
|
737
738
|
"""执行方案相关逻辑。"""
|
|
738
739
|
mode = _normalize_staged_build_mode(mode)
|
|
739
|
-
app_key = str(flow_spec.get("app_key") or "").strip()
|
|
740
|
-
spec_payload = flow_spec.get("spec")
|
|
741
|
-
if app_key and isinstance(spec_payload, dict) and spec_payload:
|
|
742
|
-
from .ai_builder_tools import AiBuilderTools
|
|
743
|
-
|
|
744
|
-
builder = AiBuilderTools(self.sessions, self.backend)
|
|
745
|
-
if mode in {"preflight", "plan"}:
|
|
746
|
-
return {
|
|
747
|
-
"build_id": build_id or _generate_build_id(run_label=run_label, stage_name="flow"),
|
|
748
|
-
"mode": mode,
|
|
749
|
-
"stage": "flow",
|
|
750
|
-
"status": "planned" if mode == "plan" else "preflighted",
|
|
751
|
-
"normalized_args": {"app_key": app_key, "spec": spec_payload},
|
|
752
|
-
"tool_name": "solution_build_flow",
|
|
753
|
-
}
|
|
754
|
-
result = builder.app_flow_apply(
|
|
755
|
-
profile=profile,
|
|
756
|
-
app_key=app_key,
|
|
757
|
-
spec=spec_payload,
|
|
758
|
-
publish=publish,
|
|
759
|
-
schema_version=flow_spec.get("schema_version") or flow_spec.get("schemaVersion"),
|
|
760
|
-
)
|
|
761
|
-
return {
|
|
762
|
-
"build_id": build_id,
|
|
763
|
-
"mode": mode,
|
|
764
|
-
"stage": "flow",
|
|
765
|
-
"status": result.get("status"),
|
|
766
|
-
"result": result,
|
|
767
|
-
"tool_name": "solution_build_flow",
|
|
768
|
-
}
|
|
769
740
|
return self._stage_build(
|
|
770
741
|
profile=profile,
|
|
771
742
|
mode=mode,
|
|
@@ -1438,6 +1409,7 @@ class SolutionTools(ToolBase):
|
|
|
1438
1409
|
role_tools=RoleTools(self.sessions, self.backend),
|
|
1439
1410
|
app_tools=AppTools(self.sessions, self.backend),
|
|
1440
1411
|
record_tools=RecordTools(self.sessions, self.backend),
|
|
1412
|
+
workflow_tools=WorkflowTools(self.sessions, self.backend),
|
|
1441
1413
|
view_tools=ViewTools(self.sessions, self.backend),
|
|
1442
1414
|
chart_tools=QingbiReportTools(self.sessions, self.backend),
|
|
1443
1415
|
portal_tools=PortalTools(self.sessions, self.backend),
|
|
@@ -1643,14 +1615,15 @@ class SolutionTools(ToolBase):
|
|
|
1643
1615
|
)
|
|
1644
1616
|
except Exception as exc: # noqa: BLE001
|
|
1645
1617
|
verification["status"] = "partial"
|
|
1646
|
-
|
|
1618
|
+
error_payload = _verification_error_payload(exc)
|
|
1619
|
+
error_payload.update(
|
|
1647
1620
|
{
|
|
1648
1621
|
"category": "verification",
|
|
1649
|
-
"detail": str(exc),
|
|
1650
1622
|
"entity_id": entity_id,
|
|
1651
1623
|
"app_key": app_key,
|
|
1652
1624
|
}
|
|
1653
1625
|
)
|
|
1626
|
+
verification["errors"].append(error_payload)
|
|
1654
1627
|
if verification["views_created"]:
|
|
1655
1628
|
verification["views_strategy"] = "created"
|
|
1656
1629
|
else:
|
|
@@ -1936,7 +1909,9 @@ class SolutionTools(ToolBase):
|
|
|
1936
1909
|
result = packages.package_get(profile=profile, tag_id=package_tag_id, include_raw=False)
|
|
1937
1910
|
except (QingflowApiError, RuntimeError) as exc:
|
|
1938
1911
|
error = _coerce_solution_api_error(exc)
|
|
1939
|
-
if error
|
|
1912
|
+
if is_auth_like_error(error):
|
|
1913
|
+
raise_tool_error(error)
|
|
1914
|
+
if backend_code_int(error) in {40002, 40027}:
|
|
1940
1915
|
return {
|
|
1941
1916
|
"status": "resolved",
|
|
1942
1917
|
"matched_via": "tag_id",
|
|
@@ -1962,13 +1937,33 @@ class SolutionTools(ToolBase):
|
|
|
1962
1937
|
retried=False,
|
|
1963
1938
|
)
|
|
1964
1939
|
summary = result.get("result") if isinstance(result.get("result"), dict) else {}
|
|
1965
|
-
|
|
1940
|
+
resolution: dict[str, Any] = {
|
|
1966
1941
|
"status": "resolved",
|
|
1967
1942
|
"matched_via": "tag_id",
|
|
1968
1943
|
"tag_id": package_tag_id,
|
|
1969
1944
|
"tag_name": summary.get("tagName"),
|
|
1970
1945
|
"candidates": [],
|
|
1971
1946
|
}
|
|
1947
|
+
warnings = result.get("warnings")
|
|
1948
|
+
if isinstance(warnings, list):
|
|
1949
|
+
for warning in warnings:
|
|
1950
|
+
if not isinstance(warning, dict):
|
|
1951
|
+
continue
|
|
1952
|
+
if warning.get("code") not in {"PACKAGE_BASE_INFO_UNAVAILABLE", "PACKAGE_DETAIL_READ_DEGRADED"}:
|
|
1953
|
+
continue
|
|
1954
|
+
resolution["metadata_unverified"] = True
|
|
1955
|
+
resolution["lookup_permission_blocked"] = {
|
|
1956
|
+
"scope": "package",
|
|
1957
|
+
"target": {"tag_id": package_tag_id},
|
|
1958
|
+
"transport_error": {
|
|
1959
|
+
"http_status": warning.get("http_status"),
|
|
1960
|
+
"backend_code": warning.get("backend_code"),
|
|
1961
|
+
"category": "backend",
|
|
1962
|
+
"request_id": warning.get("request_id"),
|
|
1963
|
+
},
|
|
1964
|
+
}
|
|
1965
|
+
break
|
|
1966
|
+
return resolution
|
|
1972
1967
|
if not normalized_name:
|
|
1973
1968
|
return {
|
|
1974
1969
|
"status": "new_package",
|
|
@@ -3118,24 +3113,30 @@ def _builder_package_resolution_failed(
|
|
|
3118
3113
|
error: QingflowApiError,
|
|
3119
3114
|
retried: bool,
|
|
3120
3115
|
) -> dict[str, Any]:
|
|
3116
|
+
permission_restricted_listing = (
|
|
3117
|
+
package_tag_id <= 0
|
|
3118
|
+
and not is_auth_like_error(error)
|
|
3119
|
+
and backend_code_int(error) in {40002, 40027}
|
|
3120
|
+
)
|
|
3121
3121
|
if package_tag_id > 0:
|
|
3122
3122
|
detail = f"failed to resolve package_tag_id '{package_tag_id}': {error.message}"
|
|
3123
|
-
elif
|
|
3123
|
+
elif permission_restricted_listing:
|
|
3124
3124
|
detail = (
|
|
3125
3125
|
f"failed to resolve package '{package_name}' because package listing is permission-restricted; "
|
|
3126
3126
|
"provide package_tag_id explicitly or use an account that can list packages"
|
|
3127
3127
|
)
|
|
3128
3128
|
else:
|
|
3129
3129
|
detail = f"failed to resolve package '{package_name}': {error.message}"
|
|
3130
|
-
|
|
3131
|
-
error_fields
|
|
3130
|
+
category = "config" if permission_restricted_listing else str(error.category or "backend")
|
|
3131
|
+
error_fields = _solution_error_fields(category=category, detail=detail, suggested_next_call=None, stage="app")
|
|
3132
|
+
error_fields["error_code"] = "AUTH_REQUIRED" if is_auth_like_error(error) else "PACKAGE_RESOLVE_FAILED"
|
|
3132
3133
|
return {
|
|
3133
3134
|
"status": "failed",
|
|
3134
3135
|
"response": {
|
|
3135
3136
|
"status": "failed",
|
|
3136
3137
|
"mode": "plan",
|
|
3137
3138
|
"stage": "app",
|
|
3138
|
-
"errors": [{"category":
|
|
3139
|
+
"errors": [{"category": category, "detail": detail}],
|
|
3139
3140
|
"package_resolution": {
|
|
3140
3141
|
"status": "failed",
|
|
3141
3142
|
"requested_name": package_name or None,
|
|
@@ -3437,15 +3438,28 @@ def _stage_skip_reason(stage_name: str) -> str:
|
|
|
3437
3438
|
return reasons.get(stage_name, "Stage skipped because no applicable payload was provided.")
|
|
3438
3439
|
|
|
3439
3440
|
|
|
3441
|
+
def _verification_error_payload(exc: Exception) -> dict[str, Any]:
|
|
3442
|
+
payload: dict[str, Any] = {"detail": str(exc)}
|
|
3443
|
+
if isinstance(exc, QingflowApiError):
|
|
3444
|
+
payload["transport_error"] = {
|
|
3445
|
+
"http_status": exc.http_status,
|
|
3446
|
+
"backend_code": exc.backend_code,
|
|
3447
|
+
"category": exc.category,
|
|
3448
|
+
"request_id": exc.request_id,
|
|
3449
|
+
}
|
|
3450
|
+
payload["request_id"] = exc.request_id
|
|
3451
|
+
payload["backend_code"] = exc.backend_code
|
|
3452
|
+
payload["http_status"] = exc.http_status
|
|
3453
|
+
return payload
|
|
3454
|
+
|
|
3455
|
+
|
|
3440
3456
|
def _append_verification_error(verification: dict[str, Any], scope: str, exc: Exception) -> None:
|
|
3441
3457
|
errors = verification.setdefault("errors", [])
|
|
3442
3458
|
if isinstance(errors, list):
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
}
|
|
3448
|
-
)
|
|
3459
|
+
error_payload = _verification_error_payload(exc)
|
|
3460
|
+
error_payload["scope"] = scope
|
|
3461
|
+
error_payload["message"] = str(exc)
|
|
3462
|
+
errors.append(error_payload)
|
|
3449
3463
|
|
|
3450
3464
|
|
|
3451
3465
|
PREFERRED_RECORD_TITLE_LABELS = (
|