-
Notifications
You must be signed in to change notification settings - Fork 1
Scope workflow execution to the active trigger graph #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b816b4a
e96082f
7a00d2b
a919ffa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |||||||||||||||||||||||||||||||||||||||||
| from backend.schemas import workflow as workflow_schemas | ||||||||||||||||||||||||||||||||||||||||||
| from backend.schemas.common import ApiResponse | ||||||||||||||||||||||||||||||||||||||||||
| from backend.workflow.compiler import compile_workflow_project | ||||||||||||||||||||||||||||||||||||||||||
| from backend.workflow.trigger_scope import scoped_project, select_active_union | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| router = APIRouter() | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -57,6 +58,60 @@ def _isolated_source_errors( | |||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| def _parked_diagnostics( | ||||||||||||||||||||||||||||||||||||||||||
| project: workflow_schemas.WorkflowProject, | ||||||||||||||||||||||||||||||||||||||||||
| parked_ids: list[str], | ||||||||||||||||||||||||||||||||||||||||||
| ) -> list[workflow_schemas.WorkflowCompileError]: | ||||||||||||||||||||||||||||||||||||||||||
| """Emit node-anchored diagnostics for every parked canvas node. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Membership comes first (one ``parked_node`` row per parked id, in authored | ||||||||||||||||||||||||||||||||||||||||||
| order). Any original configuration diagnostic for the same node follows in | ||||||||||||||||||||||||||||||||||||||||||
| its existing order so the UI can render each failure cause individually. | ||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| parked_set = set(parked_ids) | ||||||||||||||||||||||||||||||||||||||||||
| diagnostics: list[workflow_schemas.WorkflowCompileError] = [] | ||||||||||||||||||||||||||||||||||||||||||
| for node_id in parked_ids: | ||||||||||||||||||||||||||||||||||||||||||
| diagnostics.append( | ||||||||||||||||||||||||||||||||||||||||||
| workflow_schemas.WorkflowCompileError( | ||||||||||||||||||||||||||||||||||||||||||
| code="parked_node", | ||||||||||||||||||||||||||||||||||||||||||
| message=f'Workflow node "{node_id}" is not connected to a supported trigger.', | ||||||||||||||||||||||||||||||||||||||||||
| node_id=node_id, | ||||||||||||||||||||||||||||||||||||||||||
| path=["nodes", node_id], | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Compile parked nodes in isolation to surface configuration diagnostics | ||||||||||||||||||||||||||||||||||||||||||
| # (unknown bindings, missing params, etc.) as warnings without edges that | ||||||||||||||||||||||||||||||||||||||||||
| # would produce irrelevant port-mismatch noise. | ||||||||||||||||||||||||||||||||||||||||||
| if not parked_set: | ||||||||||||||||||||||||||||||||||||||||||
| return diagnostics | ||||||||||||||||||||||||||||||||||||||||||
| parked_nodes = [n for n in project.nodes if n.id in parked_set] | ||||||||||||||||||||||||||||||||||||||||||
| parked_project = workflow_schemas.WorkflowProject( | ||||||||||||||||||||||||||||||||||||||||||
| id=project.id, | ||||||||||||||||||||||||||||||||||||||||||
| name=project.name, | ||||||||||||||||||||||||||||||||||||||||||
| profile=project.profile, | ||||||||||||||||||||||||||||||||||||||||||
| version=project.version, | ||||||||||||||||||||||||||||||||||||||||||
| nodes=parked_nodes, | ||||||||||||||||||||||||||||||||||||||||||
| edges=[], | ||||||||||||||||||||||||||||||||||||||||||
| settings=project.settings, | ||||||||||||||||||||||||||||||||||||||||||
| adapters=list(project.adapters), | ||||||||||||||||||||||||||||||||||||||||||
| agentPermissions=project.agentPermissions, | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| parked_result = compile_workflow_project(parked_project) | ||||||||||||||||||||||||||||||||||||||||||
| for error in parked_result.errors: | ||||||||||||||||||||||||||||||||||||||||||
| if error.node_id and error.node_id in parked_set: | ||||||||||||||||||||||||||||||||||||||||||
| diagnostics.append( | ||||||||||||||||||||||||||||||||||||||||||
| workflow_schemas.WorkflowCompileError( | ||||||||||||||||||||||||||||||||||||||||||
| code=error.code, | ||||||||||||||||||||||||||||||||||||||||||
| message=error.message, | ||||||||||||||||||||||||||||||||||||||||||
| node_id=error.node_id, | ||||||||||||||||||||||||||||||||||||||||||
| path=error.path, | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| return diagnostics | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| def _image_generation_nodes( | ||||||||||||||||||||||||||||||||||||||||||
| nodes: object, | ||||||||||||||||||||||||||||||||||||||||||
| *, | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -185,6 +240,9 @@ async def validate_draft( | |||||||||||||||||||||||||||||||||||||||||
| project_id=project_id, | ||||||||||||||||||||||||||||||||||||||||||
| workflow_id=workflow_id, | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| warnings: list[workflow_schemas.WorkflowCompileError] = [] | ||||||||||||||||||||||||||||||||||||||||||
| valid = False | ||||||||||||||||||||||||||||||||||||||||||
| stored_graph: dict[str, Any] | None = None | ||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||
| project = workflow_schemas.WorkflowProject.model_validate(resolved_graph) | ||||||||||||||||||||||||||||||||||||||||||
| except ValidationError as exc: | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -196,25 +254,47 @@ async def validate_draft( | |||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| for error in exc.errors() | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| valid = False | ||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||
| errors.extend(_isolated_source_errors(project)) | ||||||||||||||||||||||||||||||||||||||||||
| if errors: | ||||||||||||||||||||||||||||||||||||||||||
| valid = False | ||||||||||||||||||||||||||||||||||||||||||
| active_union = select_active_union(project) | ||||||||||||||||||||||||||||||||||||||||||
| if not active_union.has_supported_trigger: | ||||||||||||||||||||||||||||||||||||||||||
| # Legacy / media-canvas / non-trigger workflows preserve the | ||||||||||||||||||||||||||||||||||||||||||
| # existing full-graph validation path unchanged. | ||||||||||||||||||||||||||||||||||||||||||
| errors.extend(_isolated_source_errors(project)) | ||||||||||||||||||||||||||||||||||||||||||
| if not errors: | ||||||||||||||||||||||||||||||||||||||||||
| result = compile_workflow_project(project) | ||||||||||||||||||||||||||||||||||||||||||
| errors = list(result.errors) | ||||||||||||||||||||||||||||||||||||||||||
| if result.valid and result.plan is not None: | ||||||||||||||||||||||||||||||||||||||||||
| valid = True | ||||||||||||||||||||||||||||||||||||||||||
| stored_graph = resolved_graph | ||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||
| result = compile_workflow_project(project) | ||||||||||||||||||||||||||||||||||||||||||
| errors = result.errors | ||||||||||||||||||||||||||||||||||||||||||
| valid = result.valid | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| scoped = scoped_project( | ||||||||||||||||||||||||||||||||||||||||||
| project=project, | ||||||||||||||||||||||||||||||||||||||||||
| active_ids=active_union.active_node_ids, | ||||||||||||||||||||||||||||||||||||||||||
| external_ids={ | ||||||||||||||||||||||||||||||||||||||||||
| node.id | ||||||||||||||||||||||||||||||||||||||||||
| for node in project.nodes | ||||||||||||||||||||||||||||||||||||||||||
| if isinstance(node.params.get("externalWorkflow"), dict) | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+270
to
+278
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Reuse the selector's external-workflow predicate instead of duplicating it.
Rename the helper to ♻️ Proposed refactor-from backend.workflow.trigger_scope import scoped_project, select_active_union
+from backend.workflow.trigger_scope import (
+ external_workflow_ids,
+ scoped_project,
+ select_active_union,
+) scoped = scoped_project(
project=project,
active_ids=active_union.active_node_ids,
- external_ids={
- node.id
- for node in project.nodes
- if isinstance(node.params.get("externalWorkflow"), dict)
- },
+ external_ids=external_workflow_ids(project.nodes),
)🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| errors.extend(_isolated_source_errors(scoped)) | ||||||||||||||||||||||||||||||||||||||||||
| if not errors: | ||||||||||||||||||||||||||||||||||||||||||
| scoped_result = compile_workflow_project(scoped) | ||||||||||||||||||||||||||||||||||||||||||
| errors = list(scoped_result.errors) | ||||||||||||||||||||||||||||||||||||||||||
| if scoped_result.valid and scoped_result.plan is not None: | ||||||||||||||||||||||||||||||||||||||||||
| valid = True | ||||||||||||||||||||||||||||||||||||||||||
| stored_graph = scoped.model_dump(mode="json") | ||||||||||||||||||||||||||||||||||||||||||
| warnings.extend( | ||||||||||||||||||||||||||||||||||||||||||
| _parked_diagnostics(project, active_union.parked_node_ids) | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+279
to
+288
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Emit parked-node warnings even when the active graph is invalid.
Compute the parked diagnostics once in the scoped branch, before the validity check. 🐛 Proposed fix errors.extend(_isolated_source_errors(scoped))
+ warnings.extend(
+ _parked_diagnostics(project, active_union.parked_node_ids)
+ )
if not errors:
scoped_result = compile_workflow_project(scoped)
errors = list(scoped_result.errors)
if scoped_result.valid and scoped_result.plan is not None:
valid = True
stored_graph = scoped.model_dump(mode="json")
- warnings.extend(
- _parked_diagnostics(project, active_union.parked_node_ids)
- )📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| row = StudioWorkflowValidationRun( | ||||||||||||||||||||||||||||||||||||||||||
| workflow_id=workflow_id, | ||||||||||||||||||||||||||||||||||||||||||
| draft_revision=draft.revision, | ||||||||||||||||||||||||||||||||||||||||||
| status="completed" if valid else "failed", | ||||||||||||||||||||||||||||||||||||||||||
| valid=valid, | ||||||||||||||||||||||||||||||||||||||||||
| errors=[error.model_dump(mode="json") for error in errors], | ||||||||||||||||||||||||||||||||||||||||||
| warnings=[], | ||||||||||||||||||||||||||||||||||||||||||
| warnings=[warning.model_dump(mode="json") for warning in warnings], | ||||||||||||||||||||||||||||||||||||||||||
| compile_version=workflow_schemas.WORKFLOW_COMPILE_VERSION, | ||||||||||||||||||||||||||||||||||||||||||
| resolved_graph=resolved_graph if valid else None, | ||||||||||||||||||||||||||||||||||||||||||
| resolved_graph=stored_graph, | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| db.add(row) | ||||||||||||||||||||||||||||||||||||||||||
| await db.flush() | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -305,14 +305,63 @@ async def start_workflow_run( | |
| trace_id = body.traceId or str(uuid.uuid4()) | ||
| started_at = _utcnow() | ||
| prior_events = list(existing_events or []) | ||
| # Source-level trigger scope selection runs before authoritative compilation | ||
| # so a disconnected, incomplete canvas node cannot block a valid | ||
| # trigger-reachable component. The compiled-runtime selector remains as a | ||
| # defensive assertion against post-compile drift (e.g. template expansion | ||
| # producing a second matching trigger entry). | ||
| from backend.workflow.trigger_scope import has_supported_triggers, select_trigger_scope | ||
|
|
||
| has_triggers = has_supported_triggers(body.project) | ||
| if has_triggers: | ||
| scope_result = select_trigger_scope( | ||
| body.project, | ||
| trigger_kind=body.trigger.kind, | ||
| trigger_node_id=body.trigger.triggerNodeId, | ||
| ) | ||
| if scope_result.selection_error is not None: | ||
| scope_project = body.project | ||
| runtime_nodes: list[CompiledWorkflowNode] = [] | ||
| errors = [scope_result.selection_error] | ||
| events = _compile_failure_events( | ||
| workflow_id=body.project.id, | ||
| run_id=run_id, | ||
| trace_id=trace_id, | ||
| errors=errors, | ||
| ) | ||
| stored_events = [*prior_events, *events] | ||
| projection = _build_projection( | ||
| workflow_id=body.project.id, | ||
| run_id=run_id, | ||
| trace_id=trace_id, | ||
| package_node_id=body.packageNodeId, | ||
| started_at=started_at, | ||
| valid=False, | ||
| errors=errors, | ||
| runtime_nodes=[], | ||
| events=stored_events, | ||
| ) | ||
| await _store_workflow_run( | ||
| run_id, | ||
| request=body, | ||
| projection=projection, | ||
| events=stored_events, | ||
| session=session, | ||
| workflow_version_id=workflow_version_id, | ||
| studio_workflow_version_id=studio_workflow_version_id, | ||
| ) | ||
| return projection | ||
| scope_project = scope_result.project | ||
| else: | ||
| scope_project = body.project | ||
| compile_result = ( | ||
| await compile_managed_dify_workflow_project( | ||
| body.project, | ||
| scope_project, | ||
| graphon_client=graphon_client, | ||
| session=session, | ||
| ) | ||
| if graphon_client is not None | ||
| else compile_workflow_project(body.project) | ||
| else compile_workflow_project(scope_project) | ||
| ) | ||
|
|
||
| if not compile_result.valid or compile_result.plan is None: | ||
|
|
@@ -428,7 +477,7 @@ async def start_workflow_run( | |
| ) and (body.packageNodeId is not None or _select_package_id(runtime_nodes, None) is not None) | ||
| trace = ( | ||
| build_opencli_hda_trace( | ||
| body.project, | ||
| scope_project, | ||
| package_node_id=body.packageNodeId, | ||
| run_id=run_id, | ||
| trace_id=trace_id, | ||
|
|
@@ -1565,8 +1614,8 @@ async def start_workflow_run( | |
| trace_id=trace_id, | ||
| package_node_id=(trace.packageNodeId if trace else None) or body.packageNodeId, | ||
| started_at=started_at, | ||
| valid=trace.valid if trace else True, | ||
| errors=trace.errors if trace else [], | ||
| valid=compile_result.valid, | ||
| errors=list(compile_result.errors) if trace is None else compile_result.errors, | ||
|
Comment on lines
+1617
to
+1618
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find assertions tied to trace-level failure codes on run projections.
rg -n -C4 'missing_opencli_hda_package|missing_opencli_hda_sources' --glob '!**/node_modules/**'
rg -n -C3 'trace\.valid|trace\.errors' backend/workflow/opencli_hda_tracer.pyRepository: 2233admin/opencli-Razormind Length of output: 165 🏁 Script executed: #!/bin/bash
set -u
printf 'Files matching tracer/compiler:\n'
fd -a 'opencli_hda_tracer.py|compiler.py' . || true
printf '\nRelevant lines in opencli_hda_tracer.py:\n'
if [ -f backend/workflow/opencli_hda_tracer.py ]; then
wc -l backend/workflow/opencli_hda_tracer.py
sed -n '340,420p' backend/workflow/opencli_hda_tracer.py | nl -ba -v340
sed -n '1490,1535p' backend/workflow/opencli_hda_tracer.py | nl -ba -v1490
sed -n '1595,1650p' backend/workflow/opencli_hda_tracer.py | nl -ba -v1595
fi
printf '\nSearch failure codes in repository:\n'
rg -n 'missing_opencli_hda_package|missing_opencli_hda_sources|compile_result\.valid|compile_result\.errors|trace\.valid|trace\.errors' . --glob '!**/node_modules/**' || true
printf '\nRelevant lines in compiler.py:\n'
if [ -f backend/workflow/compiler.py ]; then
wc -l backend/workflow/compiler.py
sed -n '200,255p' backend/workflow/compiler.py | nl -ba -v200
fiRepository: 2233admin/opencli-Razormind Length of output: 2919 🏁 Script executed: #!/bin/bash
set -u
printf 'Files matching tracer/compiler:\n'
fd -a 'opencli_hda_tracer.py|compiler.py' . || true
printf '\nRelevant lines in opencli_hda_tracer.py:\n'
if [ -f backend/workflow/opencli_hda_tracer.py ]; then
wc -l backend/workflow/opencli_hda_tracer.py
sed -n '340,420p' backend/workflow/opencli_hda_tracer.py | nl -ba -v340
sed -n '1490,1535p' backend/workflow/opencli_hda_tracer.py | nl -ba -v1490
sed -n '1595,1650p' backend/workflow/opencli_hda_tracer.py | nl -ba -v1595
fi
printf '\nSearch failure codes in repository:\n'
rg -n 'missing_opencli_hda_package|missing_opencli_hda_sources|compile_result\.valid|compile_result\.errors|trace\.valid|trace\.errors' . --glob '!**/node_modules/**' || true
printf '\nRelevant lines in compiler.py:\n'
if [ -f backend/workflow/compiler.py ]; t
wc -l backend/workflow/compiler.py
sed -n '200,255p' backend/workflow/compiler.py | nl -ba -v200
fiRepository: 2233admin/opencli-Razormind Length of output: 2919 🏁 Script executed: #!/bin/bash
set -u
printf 'backend/workflow/opencli_hda_tracer.py relevant sections:\n'
sed -n '340,410p' backend/workflow/opencli_hda_tracer.py
sed -n '190,280p' backend/workflow/opencli_hda_tracer.py
sed -n '1485,1532p' backend/workflow/opencli_hda_tracer.py
sed -n '1605,1652p' backend/workflow/opencli_hda_tracer.py
printf '\nbackend/workflow/compiler.py compile success path:\n'
sed -n '200,255p' backend/workflow/compiler.py
printf '\nintegration test context around missing_opencli_hda_sources:\n'
sed -n '1150,1225p' tests/integration/test_workflow_opencli_hda_trace_api.pyRepository: 2233admin/opencli-Razormind Length of output: 14159 🏁 Script executed: #!/bin/bash
set -u
printf 'Trace result construction / failure return context:\n'
python3 - <<'PY'
from pathlib import Path
p = Path("backend/workflow/opencli_hda_tracer.py")
text = p.read_text()
for name in (
"def build_opencli_hda_trace(",
"selected_package_id =",
"if not dispatches:",
"return WorkflowOpenCLIHDATraceResponse",
):
try:
idx = text.index(name)
except ValueError:
print(f"MISSING {name}", idx if "idx" in locals() else "")
continue
line = text[:idx].count("\n") + 1
lines = text[idx:]
print(f"\n-- {name} at line {line} --")
end = lines.find("\n")
while end != -1 and (end == 0 or not lines[end-1:end+5].startswith("\n\n")):
end = lines.find("\n", end + 1)
print(lines[:250])
PY
printf '\nCall paths containing build_opencli_hda_trace:\n'
rg -n -C3 'build_opencli_hda_trace\(' . --glob '!**/node_modules/**' || trueRepository: 2233admin/opencli-Razormind Length of output: 3026 🏁 Script executed: #!/bin/bash
set -u
printf 'Workflow execution path for trace parameter handling:\n'
sed -n '460,510p' backend/workflow/opencli_hda_tracer.py
printf '\n'
rg -n -C2 'opencliHdaTrace|opencli_hda_trace|traceId|trace_id|WorkflowOpenCLIHDATraceResponse' backend/api backend/tests backend/workflow tests/integration --glob '!**/node_modules/**' || trueRepository: 2233admin/opencli-Razormind Length of output: 50384 Fold the OpenCLI HDA trace result into the run projection.
🤖 Prompt for AI Agents |
||
| runtime_nodes=runtime_nodes, | ||
| events=events, | ||
| ) | ||
|
|
@@ -1589,8 +1638,8 @@ async def start_workflow_run( | |
| package_node_id=(trace.packageNodeId if trace else None) | ||
| or body.packageNodeId, | ||
| started_at=started_at, | ||
| valid=trace.valid if trace else True, | ||
| errors=trace.errors if trace else [], | ||
| valid=compile_result.valid, | ||
| errors=list(compile_result.errors) if trace is None else compile_result.errors, | ||
| runtime_nodes=runtime_nodes, | ||
| events=stored.events, | ||
| ) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: 2233admin/opencli-Razormind
Length of output: 1532
🏁 Script executed:
Repository: 2233admin/opencli-Razormind
Length of output: 19991
🏁 Script executed:
Repository: 2233admin/opencli-Razormind
Length of output: 50384
🏁 Script executed:
Repository: 2233admin/opencli-Razormind
Length of output: 389
🏁 Script executed:
Repository: 2233admin/opencli-Razormind
Length of output: 4866
Preserve node configuration diagnostics before structural edge errors.
compile_workflow_projectreturns as soon as_validate_projectreports any error. Whenedges=[], structural diagnostics such as orphan-merge/port-mismatch messages can block later node-anchored diagnostics likeunknown_node_library_binding. Check forparked_nodemembership first, collect matching structural errors separately, then append matching node-anchored configuration diagnostics in their original order so parkedsourcenodes and parked nodes with required upstream dependencies surface all relevant failures.🤖 Prompt for AI Agents