@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.
- package/README.md +5 -6
- package/docs/local-agent-install.md +6 -57
- package/entry_point.py +1 -1
- package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
- package/npm/lib/runtime.mjs +101 -21
- package/npm/scripts/postinstall.mjs +10 -1
- package/package.json +2 -3
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +42 -104
- package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
- package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
- package/skills/qingflow-app-builder/references/create-app.md +11 -30
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/gotchas.md +9 -31
- package/skills/qingflow-app-builder/references/match-rules.md +0 -15
- package/skills/qingflow-app-builder/references/solution-playbooks.md +0 -10
- package/skills/qingflow-app-builder/references/tool-selection.md +23 -38
- package/skills/qingflow-app-builder/references/update-flow.md +38 -22
- package/skills/qingflow-app-builder/references/update-schema.md +0 -14
- package/skills/qingflow-app-builder/references/update-views.md +14 -83
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +3 -5
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +1 -1
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +1 -1
- package/skills/qingflow-mcp-setup/SKILL.md +111 -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 +61 -0
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
- package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
- package/skills/qingflow-workflow-builder/SKILL.md +96 -0
- package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
- package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
- package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
- package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
- package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
- package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
- package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
- package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
- package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/__main__.py +2 -6
- package/src/qingflow_mcp/builder_facade/models.py +108 -485
- package/src/qingflow_mcp/builder_facade/service.py +1686 -6674
- package/src/qingflow_mcp/cli/commands/builder.py +364 -322
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/common.py +3 -12
- 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 +27 -101
- package/src/qingflow_mcp/cli/commands/task.py +47 -28
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +16 -784
- package/src/qingflow_mcp/cli/main.py +33 -117
- package/src/qingflow_mcp/errors.py +2 -43
- package/src/qingflow_mcp/public_surface.py +17 -26
- package/src/qingflow_mcp/response_trim.py +17 -81
- package/src/qingflow_mcp/server.py +12 -14
- package/src/qingflow_mcp/server_app_builder.py +36 -79
- package/src/qingflow_mcp/server_app_user.py +16 -22
- package/src/qingflow_mcp/session_store.py +7 -11
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/executor.py +18 -245
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1396 -3103
- package/src/qingflow_mcp/tools/app_tools.py +43 -184
- package/src/qingflow_mcp/tools/approval_tools.py +35 -197
- package/src/qingflow_mcp/tools/auth_tools.py +16 -92
- package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
- package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
- package/src/qingflow_mcp/tools/directory_tools.py +72 -236
- package/src/qingflow_mcp/tools/export_tools.py +34 -244
- package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
- package/src/qingflow_mcp/tools/file_tools.py +3 -9
- package/src/qingflow_mcp/tools/import_tools.py +49 -336
- package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
- package/src/qingflow_mcp/tools/package_tools.py +6 -118
- package/src/qingflow_mcp/tools/portal_tools.py +3 -39
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
- package/src/qingflow_mcp/tools/record_tools.py +360 -1145
- package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
- package/src/qingflow_mcp/tools/role_tools.py +9 -80
- package/src/qingflow_mcp/tools/solution_tools.py +45 -59
- package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
- package/src/qingflow_mcp/tools/task_tools.py +29 -113
- package/src/qingflow_mcp/tools/view_tools.py +3 -106
- package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
- package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
- package/npm/bin/qingflow-skills.mjs +0 -5
- package/skills/qingflow-app-builder/references/complete-system-development-guide.md +0 -172
- package/skills/qingflow-app-builder/references/single-app-development-guide.md +0 -87
- package/skills/qingflow-app-builder/scripts/validate_system_build_summary.py +0 -124
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -5,7 +5,7 @@ from copy import deepcopy
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
|
-
from ..errors import QingflowApiError
|
|
8
|
+
from ..errors import QingflowApiError
|
|
9
9
|
from ..tools.app_tools import AppTools
|
|
10
10
|
from ..tools.navigation_tools import NavigationTools
|
|
11
11
|
from ..tools.package_tools import PackageTools
|
|
@@ -14,7 +14,6 @@ from ..tools.qingbi_report_tools import QingbiReportTools
|
|
|
14
14
|
from ..tools.record_tools import RecordTools
|
|
15
15
|
from ..tools.role_tools import RoleTools
|
|
16
16
|
from ..tools.view_tools import ViewTools
|
|
17
|
-
from ..tools.workflow_tools import WorkflowTools
|
|
18
17
|
from ..tools.workspace_tools import WorkspaceTools
|
|
19
18
|
from .compiler import CompiledEntity, CompiledRole, CompiledSolution
|
|
20
19
|
from .compiler.form_compiler import QUESTION_TYPE_MAP
|
|
@@ -36,7 +35,6 @@ class SolutionExecutor:
|
|
|
36
35
|
role_tools: RoleTools,
|
|
37
36
|
app_tools: AppTools,
|
|
38
37
|
record_tools: RecordTools,
|
|
39
|
-
workflow_tools: WorkflowTools,
|
|
40
38
|
view_tools: ViewTools,
|
|
41
39
|
chart_tools: QingbiReportTools,
|
|
42
40
|
portal_tools: PortalTools,
|
|
@@ -47,7 +45,6 @@ class SolutionExecutor:
|
|
|
47
45
|
self.role_tools = role_tools
|
|
48
46
|
self.app_tools = app_tools
|
|
49
47
|
self.record_tools = record_tools
|
|
50
|
-
self.workflow_tools = workflow_tools
|
|
51
48
|
self.view_tools = view_tools
|
|
52
49
|
self.chart_tools = chart_tools
|
|
53
50
|
self.portal_tools = portal_tools
|
|
@@ -76,8 +73,7 @@ class SolutionExecutor:
|
|
|
76
73
|
except Exception as exc: # noqa: BLE001
|
|
77
74
|
store.record_step_failed(step.step_name, str(exc), debug_context=debug_context)
|
|
78
75
|
return store.summary()
|
|
79
|
-
|
|
80
|
-
store.mark_finished(status=final_status)
|
|
76
|
+
store.mark_finished(status="success")
|
|
81
77
|
return store.summary()
|
|
82
78
|
|
|
83
79
|
def _repair_start_index(self, compiled: CompiledSolution, store: RunArtifactStore) -> int:
|
|
@@ -204,11 +200,6 @@ class SolutionExecutor:
|
|
|
204
200
|
dash_key = store.get_artifact("portal", "dash_key")
|
|
205
201
|
if dash_key:
|
|
206
202
|
self.portal_tools.portal_publish(profile=profile, dash_key=dash_key)
|
|
207
|
-
store.set_artifact(
|
|
208
|
-
"portal",
|
|
209
|
-
"publish",
|
|
210
|
-
{"published": True, "write_executed": True, "safe_to_retry": False},
|
|
211
|
-
)
|
|
212
203
|
self._refresh_portal_artifact(profile=profile, store=store, being_draft=False, artifact_key="published_result")
|
|
213
204
|
return
|
|
214
205
|
if step_name == "publish.navigation" and publish and compiled.normalized_spec.publish_policy.navigation:
|
|
@@ -353,32 +344,7 @@ class SolutionExecutor:
|
|
|
353
344
|
updated_items.insert(insert_at, item)
|
|
354
345
|
self.package_tools.package_sort_items(profile=profile, tag_id=tag_id, tag_items=updated_items)
|
|
355
346
|
|
|
356
|
-
|
|
357
|
-
verified_detail = _verify_package_attachment(self.package_tools, profile=profile, tag_id=tag_id, app_key=app_key)
|
|
358
|
-
except Exception as exc: # noqa: BLE001
|
|
359
|
-
api_error = _coerce_qingflow_error(exc)
|
|
360
|
-
if api_error is None or not _is_permission_restricted_error(api_error):
|
|
361
|
-
raise
|
|
362
|
-
store.set_artifact(
|
|
363
|
-
"package",
|
|
364
|
-
"attachment_readback",
|
|
365
|
-
_post_write_readback_artifact(
|
|
366
|
-
resource="package_attach",
|
|
367
|
-
target={"tag_id": tag_id, "app_key": app_key},
|
|
368
|
-
error=api_error,
|
|
369
|
-
),
|
|
370
|
-
)
|
|
371
|
-
self._record_package_attachment(
|
|
372
|
-
store,
|
|
373
|
-
entity.entity_id,
|
|
374
|
-
app_artifact,
|
|
375
|
-
tag_id=tag_id,
|
|
376
|
-
attached=True,
|
|
377
|
-
reused=False,
|
|
378
|
-
readback_status="unavailable",
|
|
379
|
-
readback_verified=False,
|
|
380
|
-
)
|
|
381
|
-
return
|
|
347
|
+
verified_detail = _verify_package_attachment(self.package_tools, profile=profile, tag_id=tag_id, app_key=app_key)
|
|
382
348
|
verified_result = verified_detail.get("result") if isinstance(verified_detail.get("result"), dict) else {}
|
|
383
349
|
verified_items = [deepcopy(existing) for existing in verified_result.get("tagItems", []) if isinstance(existing, dict)]
|
|
384
350
|
if not any(_package_item_app_key(existing) == app_key for existing in verified_items):
|
|
@@ -400,18 +366,12 @@ class SolutionExecutor:
|
|
|
400
366
|
tag_id: int,
|
|
401
367
|
attached: bool,
|
|
402
368
|
reused: bool,
|
|
403
|
-
readback_status: str = "verified",
|
|
404
|
-
readback_verified: bool = True,
|
|
405
369
|
) -> None:
|
|
406
370
|
next_artifact = deepcopy(app_artifact)
|
|
407
371
|
next_artifact["package_attachment"] = {
|
|
408
372
|
"tag_id": tag_id,
|
|
409
373
|
"attached": attached,
|
|
410
374
|
"reused": reused,
|
|
411
|
-
"readback_status": readback_status,
|
|
412
|
-
"readback_verified": readback_verified,
|
|
413
|
-
"write_executed": not reused,
|
|
414
|
-
"safe_to_retry": reused or not attached,
|
|
415
375
|
}
|
|
416
376
|
store.set_artifact("apps", entity_id, next_artifact)
|
|
417
377
|
|
|
@@ -456,123 +416,10 @@ class SolutionExecutor:
|
|
|
456
416
|
def _build_workflow(self, profile: str, entity: CompiledEntity, store: RunArtifactStore) -> None:
|
|
457
417
|
if entity.workflow_plan is None:
|
|
458
418
|
return
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
entity.entity_id,
|
|
463
|
-
store,
|
|
464
|
-
app_key=app_key,
|
|
465
|
-
force_new=True,
|
|
466
|
-
)
|
|
467
|
-
node_artifacts = store.get_artifact("apps", entity.entity_id, {}).get("workflow_nodes", {})
|
|
468
|
-
existing_nodes = self.workflow_tools.workflow_list_nodes(profile=profile, app_key=app_key).get("result") or {}
|
|
469
|
-
current_nodes = _coerce_workflow_nodes(existing_nodes)
|
|
470
|
-
existing_nodes_by_name = {
|
|
471
|
-
node.get("auditNodeName"): int(node_id)
|
|
472
|
-
for node_id, node in current_nodes.items()
|
|
473
|
-
if isinstance(node, dict) and node.get("auditNodeName")
|
|
474
|
-
}
|
|
475
|
-
applicant_node_id = next(
|
|
476
|
-
(
|
|
477
|
-
int(node_id)
|
|
478
|
-
for node_id, node in current_nodes.items()
|
|
479
|
-
if isinstance(node, dict) and node.get("type") == 0 and node.get("dealType") == 3
|
|
480
|
-
),
|
|
481
|
-
None,
|
|
419
|
+
raise RuntimeError(
|
|
420
|
+
"Legacy auditNode workflow execution was removed. "
|
|
421
|
+
"Pass {app_key, spec} to solution_build_flow or use qingflow builder flow apply."
|
|
482
422
|
)
|
|
483
|
-
if applicant_node_id is not None:
|
|
484
|
-
node_artifacts.setdefault("__applicant__", applicant_node_id)
|
|
485
|
-
|
|
486
|
-
desired_global_settings = deepcopy(entity.workflow_plan["global_settings"])
|
|
487
|
-
explicit_global_settings = _has_explicit_workflow_global_settings(desired_global_settings)
|
|
488
|
-
current_global_settings: dict[str, Any] = {}
|
|
489
|
-
if explicit_global_settings:
|
|
490
|
-
current_global_settings = self.workflow_tools.workflow_get_global_settings(profile=profile, app_key=app_key).get("result") or {}
|
|
491
|
-
else:
|
|
492
|
-
try:
|
|
493
|
-
current_global_settings = self.workflow_tools.workflow_get_global_settings(profile=profile, app_key=app_key).get("result") or {}
|
|
494
|
-
except (QingflowApiError, RuntimeError) as error:
|
|
495
|
-
api_error = QingflowApiError(**_coerce_nested_error_payload(error))
|
|
496
|
-
if api_error.http_status != 404:
|
|
497
|
-
raise
|
|
498
|
-
current_global_settings = {}
|
|
499
|
-
if explicit_global_settings:
|
|
500
|
-
global_settings = deepcopy(current_global_settings if isinstance(current_global_settings, dict) else {})
|
|
501
|
-
global_settings.update(desired_global_settings)
|
|
502
|
-
global_settings["editVersionNo"] = workflow_edit_version_no or global_settings.get("editVersionNo") or 1
|
|
503
|
-
self.workflow_tools.workflow_update_global_settings(profile=profile, app_key=app_key, payload=global_settings)
|
|
504
|
-
for action in entity.workflow_plan["actions"]:
|
|
505
|
-
if action["action"] == "create_sub_branch" and node_artifacts.get(action["node_id"]) is not None:
|
|
506
|
-
continue
|
|
507
|
-
if action["action"] == "add_node":
|
|
508
|
-
if action.get("node_type") == "branch":
|
|
509
|
-
existing_branch_id = node_artifacts.get(action["node_id"])
|
|
510
|
-
if existing_branch_id is not None and not _workflow_node_is_branch(current_nodes, existing_branch_id):
|
|
511
|
-
existing_branch_id = None
|
|
512
|
-
if existing_branch_id is not None:
|
|
513
|
-
for branch_index, lane_id in enumerate(_find_branch_lane_ids(current_nodes, existing_branch_id), start=1):
|
|
514
|
-
node_artifacts[_branch_lane_ref(action["node_id"], branch_index)] = lane_id
|
|
515
|
-
apps_artifact = store.get_artifact("apps", entity.entity_id, {})
|
|
516
|
-
apps_artifact["workflow_nodes"] = node_artifacts
|
|
517
|
-
store.set_artifact("apps", entity.entity_id, apps_artifact)
|
|
518
|
-
continue
|
|
519
|
-
existing_node_id = node_artifacts.get(action["node_id"]) or existing_nodes_by_name.get(action.get("node_name"))
|
|
520
|
-
if existing_node_id is not None:
|
|
521
|
-
node_artifacts[action["node_id"]] = existing_node_id
|
|
522
|
-
apps_artifact = store.get_artifact("apps", entity.entity_id, {})
|
|
523
|
-
apps_artifact["workflow_nodes"] = node_artifacts
|
|
524
|
-
store.set_artifact("apps", entity.entity_id, apps_artifact)
|
|
525
|
-
continue
|
|
526
|
-
before_node_ids = set(current_nodes)
|
|
527
|
-
payload = self._resolve_workflow_payload(action["payload"], node_artifacts)
|
|
528
|
-
if workflow_edit_version_no is not None:
|
|
529
|
-
payload["editVersionNo"] = int(workflow_edit_version_no)
|
|
530
|
-
if action["action"] == "create_sub_branch":
|
|
531
|
-
result = self.workflow_tools.workflow_create_sub_branch(profile=profile, app_key=app_key, payload=payload)
|
|
532
|
-
elif action["action"] == "update_node":
|
|
533
|
-
target_node_id = node_artifacts.get(action["node_id"])
|
|
534
|
-
if target_node_id is None:
|
|
535
|
-
raise RuntimeError(f"workflow lane '{action['node_id']}' could not be resolved before update")
|
|
536
|
-
result = self.workflow_tools.workflow_update_node(
|
|
537
|
-
profile=profile,
|
|
538
|
-
app_key=app_key,
|
|
539
|
-
audit_node_id=target_node_id,
|
|
540
|
-
payload=payload,
|
|
541
|
-
)
|
|
542
|
-
else:
|
|
543
|
-
result = self.workflow_tools.workflow_add_node(profile=profile, app_key=app_key, payload=payload)
|
|
544
|
-
expected_type = 1 if action.get("node_type") == "branch" else None
|
|
545
|
-
audit_node_id = _extract_workflow_node_id(result.get("result"), expected_type=expected_type)
|
|
546
|
-
if action.get("node_type") == "branch" or action["action"] == "create_sub_branch":
|
|
547
|
-
current_nodes = _coerce_workflow_nodes(
|
|
548
|
-
self.workflow_tools.workflow_list_nodes(profile=profile, app_key=app_key).get("result") or {}
|
|
549
|
-
)
|
|
550
|
-
if audit_node_id is not None:
|
|
551
|
-
node_artifacts[action["node_id"]] = audit_node_id
|
|
552
|
-
if action.get("node_type") == "branch":
|
|
553
|
-
branch_node_id = node_artifacts.get(action["node_id"]) or _find_created_branch_node_id(
|
|
554
|
-
current_nodes,
|
|
555
|
-
before_node_ids=before_node_ids,
|
|
556
|
-
prev_id=payload.get("prevId"),
|
|
557
|
-
)
|
|
558
|
-
if branch_node_id is not None:
|
|
559
|
-
node_artifacts[action["node_id"]] = branch_node_id
|
|
560
|
-
for branch_index, lane_id in enumerate(_find_branch_lane_ids(current_nodes, branch_node_id), start=1):
|
|
561
|
-
node_artifacts[_branch_lane_ref(action["node_id"], branch_index)] = lane_id
|
|
562
|
-
if action["action"] == "create_sub_branch" and node_artifacts.get(action["node_id"]) is None:
|
|
563
|
-
created_lane_id = audit_node_id or _find_created_sub_branch_lane_id(
|
|
564
|
-
current_nodes,
|
|
565
|
-
before_node_ids=before_node_ids,
|
|
566
|
-
branch_node_id=payload.get("auditNodeId"),
|
|
567
|
-
)
|
|
568
|
-
if created_lane_id is not None:
|
|
569
|
-
node_artifacts[action["node_id"]] = created_lane_id
|
|
570
|
-
apps_artifact = store.get_artifact("apps", entity.entity_id, {})
|
|
571
|
-
apps_artifact["workflow_nodes"] = node_artifacts
|
|
572
|
-
store.set_artifact("apps", entity.entity_id, apps_artifact)
|
|
573
|
-
apps_artifact = store.get_artifact("apps", entity.entity_id, {})
|
|
574
|
-
apps_artifact["workflow_nodes"] = node_artifacts
|
|
575
|
-
store.set_artifact("apps", entity.entity_id, apps_artifact)
|
|
576
423
|
|
|
577
424
|
def _build_views(self, profile: str, entity: CompiledEntity, store: RunArtifactStore) -> None:
|
|
578
425
|
app_key = self._get_app_key(store, entity.entity_id)
|
|
@@ -735,23 +582,12 @@ class SolutionExecutor:
|
|
|
735
582
|
api_error = _coerce_qingflow_error(exc)
|
|
736
583
|
if api_error is None or not _is_permission_restricted_error(api_error):
|
|
737
584
|
raise
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
) from exc
|
|
745
|
-
base_payload = {}
|
|
746
|
-
store.set_artifact(
|
|
747
|
-
"portal",
|
|
748
|
-
"draft_readback_before_update",
|
|
749
|
-
_post_write_readback_artifact(
|
|
750
|
-
resource="portal",
|
|
751
|
-
target={"dash_key": dash_key, "phase": "created_portal_draft_readback"},
|
|
752
|
-
error=api_error,
|
|
753
|
-
),
|
|
754
|
-
)
|
|
585
|
+
raise _required_state_read_blocked_error(
|
|
586
|
+
resource="portal",
|
|
587
|
+
message=f"portal update requires readable draft state for dash '{dash_key}'",
|
|
588
|
+
error=api_error,
|
|
589
|
+
details={"dash_key": dash_key},
|
|
590
|
+
) from exc
|
|
755
591
|
update_payload = self._resolve_portal_payload(compiled.portal_plan["update_payload"], store, base_payload=base_payload)
|
|
756
592
|
self.portal_tools.portal_update(profile=profile, dash_key=dash_key, payload=update_payload)
|
|
757
593
|
self._refresh_portal_artifact(profile=profile, store=store, being_draft=True, artifact_key="draft_result")
|
|
@@ -792,19 +628,7 @@ class SolutionExecutor:
|
|
|
792
628
|
return
|
|
793
629
|
try:
|
|
794
630
|
result = self.portal_tools.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft)
|
|
795
|
-
except Exception
|
|
796
|
-
api_error = _coerce_qingflow_error(exc)
|
|
797
|
-
if api_error is None:
|
|
798
|
-
raise
|
|
799
|
-
store.set_artifact(
|
|
800
|
-
"portal",
|
|
801
|
-
f"{artifact_key}_readback",
|
|
802
|
-
_post_write_readback_artifact(
|
|
803
|
-
resource="portal",
|
|
804
|
-
target={"dash_key": dash_key, "being_draft": being_draft, "artifact_key": artifact_key},
|
|
805
|
-
error=api_error,
|
|
806
|
-
),
|
|
807
|
-
)
|
|
631
|
+
except Exception: # noqa: BLE001
|
|
808
632
|
return
|
|
809
633
|
store.set_artifact("portal", artifact_key, result)
|
|
810
634
|
store.set_artifact("portal", "result", result)
|
|
@@ -2157,24 +1981,11 @@ def _find_created_sub_branch_lane_id(
|
|
|
2157
1981
|
return candidates[0] if candidates else None
|
|
2158
1982
|
|
|
2159
1983
|
|
|
2160
|
-
def _has_explicit_workflow_global_settings(global_settings: dict[str, Any] | None) -> bool:
|
|
2161
|
-
if not isinstance(global_settings, dict):
|
|
2162
|
-
return False
|
|
2163
|
-
for key, value in global_settings.items():
|
|
2164
|
-
if key == "editVersionNo":
|
|
2165
|
-
continue
|
|
2166
|
-
if value is None:
|
|
2167
|
-
continue
|
|
2168
|
-
if isinstance(value, (list, dict)) and not value:
|
|
2169
|
-
continue
|
|
2170
|
-
return True
|
|
2171
|
-
return False
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
1984
|
def _is_navigation_plugin_unavailable(error: QingflowApiError) -> bool:
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
1985
|
+
try:
|
|
1986
|
+
backend_code = int(error.backend_code)
|
|
1987
|
+
except (TypeError, ValueError):
|
|
1988
|
+
backend_code = None
|
|
2178
1989
|
if backend_code != 50004:
|
|
2179
1990
|
return False
|
|
2180
1991
|
message = error.message or ""
|
|
@@ -2211,9 +2022,7 @@ def _coerce_qingflow_error(error: Exception) -> QingflowApiError | None:
|
|
|
2211
2022
|
|
|
2212
2023
|
|
|
2213
2024
|
def _is_permission_restricted_error(error: QingflowApiError) -> bool:
|
|
2214
|
-
|
|
2215
|
-
return False
|
|
2216
|
-
return backend_code_int(error) in {40002, 40027}
|
|
2025
|
+
return error.backend_code in {40002, 40027}
|
|
2217
2026
|
|
|
2218
2027
|
|
|
2219
2028
|
def _required_state_read_blocked_error(
|
|
@@ -2243,42 +2052,6 @@ def _required_state_read_blocked_error(
|
|
|
2243
2052
|
)
|
|
2244
2053
|
|
|
2245
2054
|
|
|
2246
|
-
def _post_write_readback_artifact(
|
|
2247
|
-
*,
|
|
2248
|
-
resource: str,
|
|
2249
|
-
target: dict[str, Any],
|
|
2250
|
-
error: QingflowApiError,
|
|
2251
|
-
) -> dict[str, Any]:
|
|
2252
|
-
return {
|
|
2253
|
-
"resource": resource,
|
|
2254
|
-
"target": deepcopy(target),
|
|
2255
|
-
"readback_status": "unavailable",
|
|
2256
|
-
"readback_verified": False,
|
|
2257
|
-
"write_executed": True,
|
|
2258
|
-
"safe_to_retry": False,
|
|
2259
|
-
"transport_error": {
|
|
2260
|
-
"http_status": error.http_status,
|
|
2261
|
-
"backend_code": error.backend_code,
|
|
2262
|
-
"category": error.category,
|
|
2263
|
-
"request_id": error.request_id,
|
|
2264
|
-
},
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
def _artifacts_have_post_write_readback_pending(value: Any) -> bool:
|
|
2269
|
-
if isinstance(value, dict):
|
|
2270
|
-
if (
|
|
2271
|
-
value.get("write_executed") is True
|
|
2272
|
-
and value.get("safe_to_retry") is False
|
|
2273
|
-
and value.get("readback_status") == "unavailable"
|
|
2274
|
-
):
|
|
2275
|
-
return True
|
|
2276
|
-
return any(_artifacts_have_post_write_readback_pending(item) for item in value.values())
|
|
2277
|
-
if isinstance(value, list):
|
|
2278
|
-
return any(_artifacts_have_post_write_readback_pending(item) for item in value)
|
|
2279
|
-
return False
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
2055
|
def _portal_component_position(
|
|
2283
2056
|
source_type: Any,
|
|
2284
2057
|
*,
|