Skip to content

Feature: Recursive Subagent Workflows & Nested Run Log Viewer - #19

Open
Jacobcdsmith wants to merge 1 commit into
mainfrom
jules-13311550323730717229-711e2972
Open

Feature: Recursive Subagent Workflows & Nested Run Log Viewer#19
Jacobcdsmith wants to merge 1 commit into
mainfrom
jules-13311550323730717229-711e2972

Conversation

@Jacobcdsmith

@Jacobcdsmith Jacobcdsmith commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Replaces the schematic subagent execution placeholders with fully recursive, live in-browser execution, adds workflow target select dropdown configuration in the node inspector, and renders beautifully indented, collapsible nested sub-workflow step logs in the run drawer.


PR created automatically by Jules for task 13311550323730717229 started by @Jacobcdsmith

Summary by CodeRabbit

  • New Features

    • Added support for configuring subagents with reusable workflows.
    • Workflow selectors distinguish templates from custom workflows and hide the currently active workflow.
    • Run logs now display nested sub-workflow activity, outputs, errors, and expandable state snapshots.
    • Generated Python and JavaScript code includes referenced workflow metadata.
  • Bug Fixes

    • Subagent workflows now execute recursively with inherited runtime settings and preserve nested logs and results.
  • Tests

    • Added coverage for nested workflow execution and generated-code metadata.

@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-flow-canvas Ready Ready Preview Aug 6, 2026 3:12pm

@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Subagent nodes can select custom or template workflows, execute them recursively, and return nested logs and outputs. Generated Python and JavaScript include workflow metadata. The UI renders nested logs and state snapshots.

Changes

Nested subagent workflows

Layer / File(s) Summary
Workflow selection and context wiring
frontend/src/flow/Inspector.tsx, frontend/src/pages/Index.tsx
Inspector receives workflow context, lists templates and custom workflows, excludes the active workflow, and exposes a subagent workflow selector.
Recursive subagent execution
frontend/src/flow/runFlow.ts, frontend/src/test/subagents.test.ts
Subagents resolve and execute nested workflows with inherited runtime options. Tests verify nested output, logs, and trigger steps.
Nested workflow code metadata
frontend/src/flow/codegen.ts, frontend/src/test/subagents.test.ts
Python and JavaScript generation adds nested workflow name, node count, and edge count comments.
Nested run-log rendering
frontend/src/pages/Index.tsx
Run logs render nested sublogs recursively with expandable outputs, errors, and state snapshots.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ParentWorkflow
  participant runFlow
  participant WorkflowLoader
  participant NestedWorkflow
  ParentWorkflow->>runFlow: execute subagent with interpolated input
  runFlow->>WorkflowLoader: resolve configured workflow
  WorkflowLoader-->>runFlow: return workflow definition
  runFlow->>NestedWorkflow: execute recursively
  NestedWorkflow-->>runFlow: return output and sublogs
  runFlow-->>ParentWorkflow: return nested workflow result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes recursive subagent workflows and nested run-log viewing, which are the main changes in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jules-13311550323730717229-711e2972

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
frontend/src/pages/Index.tsx (1)

83-85: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace the any cast with a narrow type.

log.output is unknown in the RunLog contract. A local type keeps the check type-safe.

♻️ Proposed refactor
-  const outputObj = log.output as any;
-  const hasSubLogs = outputObj && Array.isArray(outputObj.subLogs);
-  const subLogs = hasSubLogs ? (outputObj.subLogs as RunLog[]) : [];
+  const outputObj = log.output as { subLogs?: RunLog[] } | null | undefined;
+  const subLogs = Array.isArray(outputObj?.subLogs) ? outputObj.subLogs : [];
+  const hasSubLogs = subLogs.length > 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Index.tsx` around lines 83 - 85, Replace the any cast in
the sub-log extraction around outputObj with a narrow local type describing an
optional subLogs array of RunLog. Keep the existing Array.isArray guard and
fallback to an empty array, while accessing log.output without bypassing type
safety.
frontend/src/flow/runFlow.ts (1)

314-346: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pass the workflow library through RunOptions instead of reading localStorage.

loadWorkflows() runs on every subagent node execution. It parses localStorage each time, and it ignores unsaved canvas edits, so a nested run can use a stale copy of a workflow. An optional workflows field in RunOptions removes the storage dependency from the executor and makes the resolution testable.

-      const allWfs = [...TEMPLATES, ...loadWorkflows()];
+      const allWfs = [...TEMPLATES, ...(opts.workflows ?? loadWorkflows())];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/flow/runFlow.ts` around lines 314 - 346, Add an optional
workflows collection to RunOptions and pass it through the runFlow execution
path. In the nested workflow resolution near allWfs, use the supplied
RunOptions.workflows when available, falling back to loadWorkflows() only when
it is absent; preserve the TEMPLATES entries and avoid reading localStorage on
each subagent execution when workflows are provided.
frontend/src/test/subagents.test.ts (1)

43-114: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a test for nested-workflow cycle protection.

The suite covers one nesting level only. It does not cover a workflow that references another workflow which references the first one. That case currently recurses without limit, as noted in frontend/src/flow/runFlow.ts. Add a test after the guard exists. Do you want me to generate that test?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/test/subagents.test.ts` around lines 43 - 114, Add a test
alongside the existing nested subagent test that constructs two workflows
referencing each other and executes one through runFlow. Verify recursive
execution terminates via the cycle-protection guard in runFlow, producing
bounded logs or the established cycle error rather than recursing indefinitely.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/flow/codegen.ts`:
- Around line 361-380: Add a shared comment-text sanitizer in
frontend/src/flow/codegen.ts that neutralizes \n, \r\n, \u2028, and \u2029
before metadata is embedded in generated comments; apply it to persisted
workflow names in both generatePython (lines 361-380) and generateJavaScript
(lines 580-592), preserving valid single-line # and // comments. Add coverage
for line-terminator-containing workflow names in
frontend/src/test/subagents.test.ts.

In `@frontend/src/flow/Inspector.tsx`:
- Around line 68-89: Update the subagent graph <select> in Inspector so an
existing non-empty node.data.config graph value that is absent from the filtered
allWorkflows options remains visible by adding a fallback option for that value.
Preserve the existing workflow options and avoid adding a fallback for empty
values.

In `@frontend/src/flow/runFlow.ts`:
- Around line 344-378: Add recursion protection to runFlow and nested
sub-workflow execution: extend RunOptions with a depth counter and visited
workflow-ID set, stop with a bounded fallback when the depth limit is reached or
the current workflow ID is already visited, and propagate updated depth/visited
values through the recursive runFlow call in the nested workflow branch. Ensure
direct and indirect cycles terminate without changing normal acyclic execution.

In `@frontend/src/pages/Index.tsx`:
- Around line 79-80: Update handleLogsExpandAll to generate snapshot keys using
the same step-nodeId-depth format as LogItemRow’s uniqueKey, and traverse nested
logs so every sub-workflow row is included when expanding snapshots. Preserve
the existing expand-all behavior while ensuring the resulting keys match
expandedSnapshots lookups.

---

Nitpick comments:
In `@frontend/src/flow/runFlow.ts`:
- Around line 314-346: Add an optional workflows collection to RunOptions and
pass it through the runFlow execution path. In the nested workflow resolution
near allWfs, use the supplied RunOptions.workflows when available, falling back
to loadWorkflows() only when it is absent; preserve the TEMPLATES entries and
avoid reading localStorage on each subagent execution when workflows are
provided.

In `@frontend/src/pages/Index.tsx`:
- Around line 83-85: Replace the any cast in the sub-log extraction around
outputObj with a narrow local type describing an optional subLogs array of
RunLog. Keep the existing Array.isArray guard and fallback to an empty array,
while accessing log.output without bypassing type safety.

In `@frontend/src/test/subagents.test.ts`:
- Around line 43-114: Add a test alongside the existing nested subagent test
that constructs two workflows referencing each other and executes one through
runFlow. Verify recursive execution terminates via the cycle-protection guard in
runFlow, producing bounded logs or the established cycle error rather than
recursing indefinitely.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ae2b55c-97d6-402f-982e-56174cc7e2b7

📥 Commits

Reviewing files that changed from the base of the PR and between 0b7a689 and d8325c7.

⛔ Files ignored due to path filters (1)
  • dev_server.log is excluded by !**/*.log
📒 Files selected for processing (5)
  • frontend/src/flow/Inspector.tsx
  • frontend/src/flow/codegen.ts
  • frontend/src/flow/runFlow.ts
  • frontend/src/pages/Index.tsx
  • frontend/src/test/subagents.test.ts

Comment on lines +361 to +380
case "subagent": {
let subWfComment = "";
try {
const allWfs = [...TEMPLATES, ...loadWorkflows()];
const subWf = allWfs.find((w) => w.id === c.graph);
if (subWf) {
subWfComment = `# Nested Subagent details:\n# Name: ${subWf.name}\n# Node Count: ${subWf.nodes.length}\n# Edge Count: ${subWf.edges.length}\n`;
}
} catch {
// ignore
}
return [
subWfComment +
`input_val = interpolate(${pyStr(c.input || "input")}, state)`,
`payload = state.get(input_val, state.last) if input_val in state.data else input_val`,
`result = await run_subgraph(${pyStr(c.graph || "sub")}, payload, state)`,
`state.last = result`,
`return "on_success"`,
].join("\n");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

if rg -n '\$\{subWf\.name\}' frontend/src/flow/codegen.ts; then
  echo "Unsanitized workflow-name interpolation remains" >&2
  exit 1
fi

rg -n 'sanitizeCommentText' frontend/src/flow/codegen.ts

Repository: Jacobcdsmith/agent-flow-canvas

Length of output: 552


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== codegen subagent sections =="
sed -n '330,405p' frontend/src/flow/codegen.ts
echo "---"
sed -n '550,605p' frontend/src/flow/codegen.ts

echo "== imports/types around codegen =="
sed -n '1,80p' frontend/src/flow/codegen.ts

echo "== tests mentioning workflow names/subagent generator =="
rg -n 'workflow|subagent|Nested Subagent|run_subgraph|runSubgraph|loadWorkflows|JSON.stringify\(c\.input' frontend/src -S || true

echo "== deterministic terminator behavior probe =="
python3 - <<'PY'
templates = [
    f"""def subgraph(input, state):
# Nested Subagent details:
#   Name: invalid
print("untrusted")
input_val = interpolate(input, state)
""",
    f"""def subgraph(input, state):
# Nested Subagent details:
#   Name: safe
input_val = interpolate(input, state)
""",
]
for i, t in enumerate(templates, 1):
    print(f"TEMPLATE_{i}_safe={t == '# Nested Subagent details:\\n#   Name: safe\\ninput_val = interpolate(input, state)\\n'}")
PY

node - <<'JS'
const templates = [
  `function subgraph(input, state) {
// Nested Subagent details:
//   Name: invalid
console.log("untrusted")
const inputVal = interpolate(input, state)
}`,
  `function subgraph(input, state) {
// Nested Subagent details:
//   Name: safe
const inputVal = interpolate(input, state)
}`,
]
for (const [i, t] of templates.entries()) {
  console.log(`TEMPLATE_${i + 1}_safe=${t === ['function subgraph(input, state) {','// Nested Subagent details:','//   Name: safe','const inputVal = interpolate(input, state)','}'].join('\n')}`)
}
JS

echo "== current files change stat =="
git diff --stat -- frontend/src/flow/codegen.ts frontend/src/test/subagents.test.ts 2>/dev/null || true

Repository: Jacobcdsmith/agent-flow-canvas

Length of output: 22609


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== deterministic terminator behavior probe =="
python3 - <<'PY'
line_separator = "invalid\nprint(\"untrusted\")\n"
template = f"""def subgraph(input, state):
# Nested Subagent details:
#   Name: {line_separator}input_val = interpolate(input, state)
"""
expected_safe = """def subgraph(input, state):
# Nested Subagent details:
#   Name: safe
input_val = interpolate(input, state)
"""
expected_unsafe = """def subgraph(input, state):
# Nested Subagent details:
#   Name: invalid
print("untrusted")
input_val = interpolate(input, state)
"""
print("line_break_exits_python_comment", line_separator in template and expected_unsafe == template)
print("line_break_would_execute_untrusted_code", template.index('print("untrusted")') > template.index('#   Name: invalid\n'), end="\n\n")

safe_template = template.replace(line_separator, "safe")
print("replace_line_break_contains_untrusted_code", 'print("untrusted")' not in safe_template)
PY

node - <<'JS'
const unsafeName = 'invalid\nconsole.log("untrusted")\n';
const template =
  `// Nested Subagent details:\n//   Name: ` + unsafeName +
  `const inputVal = interpolate(input, state);\n`;
const expectedUnsafe =
  `// Nested Subagent details:\n//   Name: invalid\nconsole.log("untrusted")\n` +
  `const inputVal = interpolate(input, state);\n`;
const expectedSafe =
  `// Nested Subagent details:\n//   Name: unsafe\nconst inputVal = interpolate(input, state);\n`;

console.log('line_break_exits_js_comment', template.includes('console.log("untrusted")\n'));
console.log('js_unsafe_equals_template', template === expectedUnsafe);
console.log('js_safe_name_preserved', template.includes('//   Name: unsafe\nconst inputVal = interpolate(input, state);') === false);
JS

echo "== related tests =="
sed -n '1,130p' frontend/src/test/subagents.test.ts 2>/dev/null || true

echo "== line-terminator tests for subagent metadata in tests =="
rg -n 'line_break|line_break|\\\\n|\\u2028|\\u2029|subagent details|Nested Subagent|subWf|workflowsAndDebugger|subagents' frontend/src/test -S || true

Repository: Jacobcdsmith/agent-flow-canvas

Length of output: 4571


Sanitize persisted workflow metadata before embedding it in generated source.

Persisted workflow names are inserted into generated Python and JavaScript comments without line-terminator handling. A name containing \n, \r\n, \u2028, or \u2029 exits the # or // comment, so the remaining text becomes executable source in the generated subagent function.

Add a shared comment-text sanitizer and apply it in both generatePython and generateJavaScript. Add tests for workflow names containing line terminators in frontend/src/test/subagents.test.ts.

📍 Affects 1 file
  • frontend/src/flow/codegen.ts#L361-L380 (this comment)
  • frontend/src/flow/codegen.ts#L580-L592
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/flow/codegen.ts` around lines 361 - 380, Add a shared
comment-text sanitizer in frontend/src/flow/codegen.ts that neutralizes \n,
\r\n, \u2028, and \u2029 before metadata is embedded in generated comments;
apply it to persisted workflow names in both generatePython (lines 361-380) and
generateJavaScript (lines 580-592), preserving valid single-line # and //
comments. Add coverage for line-terminator-containing workflow names in
frontend/src/test/subagents.test.ts.

Comment on lines +68 to +89
{node.data.kind === "subagent" && f.key === "graph" ? (
<select
value={node.data.config?.[f.key] ?? ""}
onChange={(e) =>
onChange(node.id, {
config: { ...node.data.config, [f.key]: e.target.value },
})
}
className="mt-1 w-full bg-[hsl(var(--paper))] border border-dashed border-[hsl(var(--ink-faint))] focus:border-[hsl(var(--ink))] outline-none py-1.5 px-2 text-[hsl(var(--ink))]"
>
<option value="" disabled>
-- select a subagent graph --
</option>
{allWorkflows
.filter((w) => w.id !== activeWorkflowId)
.map((w) => (
<option key={w.id} value={w.id}>
{w.name} {w.isTemplate ? "(Template)" : "(Custom)"}
</option>
))}
</select>
) : f.type === "textarea" ? (

Copy link
Copy Markdown
Contributor

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

Show existing graph values that do not match a workflow id.

The select only lists workflow ids. Existing nodes can store a free-text graph value. For example, TEMPLATES in frontend/src/flow/workflows.ts defines the node react-fallback with config: { graph: "deep_research", ... }. For such nodes the value matches no <option>, so the control renders empty and the stored value becomes invisible while it stays in the node data. Add a fallback option for an unmatched non-empty value.

🐛 Proposed fix
               <option value="" disabled>
                 -- select a subagent graph --
               </option>
+              {node.data.config?.[f.key] &&
+                !allWorkflows.some((w) => w.id === node.data.config?.[f.key]) && (
+                  <option value={node.data.config[f.key]}>
+                    {node.data.config[f.key]} (unresolved)
+                  </option>
+                )}
               {allWorkflows
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/flow/Inspector.tsx` around lines 68 - 89, Update the subagent
graph <select> in Inspector so an existing non-empty node.data.config graph
value that is absent from the filtered allWorkflows options remains visible by
adding a fallback option for that value. Preserve the existing workflow options
and avoid adding a fallback for empty values.

Comment on lines +344 to 378
// Lookup target nested workflow from Workflows Library (Templates + Custom Workflows)
const allWfs = [...TEMPLATES, ...loadWorkflows()];
const subWf = allWfs.find((w) => w.id === cfg.graph);

if (!subWf) {
return {
simulated: true,
subagent: cfg.graph || "unknown",
input: interpolatedInput,
note: `Sub-workflow ${cfg.graph} not found — simulated fallback`,
};
}

// Execute sub-workflow recursively in-browser
const subLogs = await runFlow({
nodes: subWf.nodes,
edges: subWf.edges,
gateways,
initialState: subInitialState,
maxSteps: opts.maxSteps,
stepDelay: opts.stepDelay,
onHumanApproval: opts.onHumanApproval,
globals: globalsList,
secrets: secretsList,
});

const finalLog = subLogs[subLogs.length - 1];
const finalOutput = finalLog ? (finalLog.output ?? finalLog.stateSnapshot?.last_output ?? null) : null;

return {
simulated: true,
subagent: cfg.graph || "unknown",
input: interpolate(cfg.input || "", state, globalsList, secretsList),
note: "subagent execution is schematic",
subagent: subWf.name,
input: subInitialState,
output: finalOutput,
subLogs,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Add a recursion guard for nested workflow execution.

runNode calls runFlow for the resolved sub-workflow, and that flow can contain another subagent node. maxSteps limits steps inside one flow only. It does not limit nesting depth. If workflow A references B and B references A, the recursion never terminates and the browser tab hangs. The Inspector filter only excludes the currently active workflow, so an indirect cycle is reachable from the UI, and imported JSON can also contain a direct self-reference.

Add a depth counter to RunOptions and stop when the limit is reached. A visited-id set also blocks cycles.

🛡️ Proposed fix
 export interface RunOptions {
   nodes: Node<AgentNodeData>[];
   edges: Edge[];
   gateways: Gateway[];
   initialState?: Record<string, unknown>;
   maxSteps?: number;
+  depth?: number;
+  maxDepth?: number;
+  workflowStack?: string[];
   onLog?: (log: RunLog) => void;
       const allWfs = [...TEMPLATES, ...loadWorkflows()];
       const subWf = allWfs.find((w) => w.id === cfg.graph);
 
       if (!subWf) {
         return {
           simulated: true,
           subagent: cfg.graph || "unknown",
           input: interpolatedInput,
           note: `Sub-workflow ${cfg.graph} not found — simulated fallback`,
         };
       }
 
+      const depth = opts.depth ?? 0;
+      const maxDepth = opts.maxDepth ?? 5;
+      const stack = opts.workflowStack ?? [];
+      if (depth >= maxDepth || stack.includes(subWf.id)) {
+        return {
+          simulated: true,
+          subagent: subWf.name,
+          input: subInitialState,
+          note:
+            stack.includes(subWf.id)
+              ? `Cycle detected — ${subWf.id} is already running`
+              : `Max nesting depth ${maxDepth} reached`,
+        };
+      }
+
       // Execute sub-workflow recursively in-browser
       const subLogs = await runFlow({
         nodes: subWf.nodes,
         edges: subWf.edges,
         gateways,
         initialState: subInitialState,
         maxSteps: opts.maxSteps,
+        depth: depth + 1,
+        maxDepth,
+        workflowStack: [...stack, subWf.id],
         stepDelay: opts.stepDelay,

runFlow must forward the new fields when it calls runNode; it already passes opts through, so no further change is needed there.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/flow/runFlow.ts` around lines 344 - 378, Add recursion
protection to runFlow and nested sub-workflow execution: extend RunOptions with
a depth counter and visited workflow-ID set, stop with a bounded fallback when
the depth limit is reached or the current workflow ID is already visited, and
propagate updated depth/visited values through the recursive runFlow call in the
nested workflow branch. Ensure direct and indirect cycles terminate without
changing normal acyclic execution.

Comment on lines +79 to +80
const uniqueKey = `${log.step}-${log.nodeId}-${depth}`;
const isExpanded = !!expandedSnapshots[uniqueKey];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The snapshot key format no longer matches handleLogsExpandAll.

LogItemRow builds uniqueKey as ${log.step}-${log.nodeId}-${depth}. handleLogsExpandAll (lines 1282-1291) still writes keys as ${l.step}-${l.nodeId}. The "Expand Snapshots" button therefore sets keys that no row reads, and no snapshot expands. Update the handler to the new format, and include nested logs so the action also covers sub-workflow rows.

🐛 Proposed fix (outside the selected range, lines 1282-1291)
   const handleLogsExpandAll = () => {
     if (!runLogs) return;
     const patch: Record<string, boolean> = {};
-    runLogs.forEach((l) => {
-      if (l.stateSnapshot) {
-        patch[`${l.step}-${l.nodeId}`] = true;
-      }
-    });
+    const walk = (items: RunLog[], depth: number) => {
+      items.forEach((l) => {
+        if (l.stateSnapshot) patch[`${l.step}-${l.nodeId}-${depth}`] = true;
+        const nested = (l.output as { subLogs?: RunLog[] } | undefined)?.subLogs;
+        if (Array.isArray(nested)) walk(nested, depth + 1);
+      });
+    };
+    walk(runLogs, 0);
     setExpandedLogSnapshots(patch);
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Index.tsx` around lines 79 - 80, Update
handleLogsExpandAll to generate snapshot keys using the same step-nodeId-depth
format as LogItemRow’s uniqueKey, and traverse nested logs so every sub-workflow
row is included when expanding snapshots. Preserve the existing expand-all
behavior while ensuring the resulting keys match expandedSnapshots lookups.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant