Skip to content

[REF-1261][REF-1262] Enhance targeted node editing functionality in Copilot#2132

Merged
mrcfps merged 10 commits intomainfrom
feat/copilot-patch
Mar 3, 2026
Merged

[REF-1261][REF-1262] Enhance targeted node editing functionality in Copilot#2132
mrcfps merged 10 commits intomainfrom
feat/copilot-patch

Conversation

@anthhub
Copy link
Copy Markdown
Contributor

@anthhub anthhub commented Jan 23, 2026

  • Introduced NodeEditContext to manage state for targeted node editing, capturing relevant information for user-selected nodes.
  • Implemented useNodeEditContext hook to synchronize selected node context with the Copilot store.
  • Added NodeEditBanner component to display editing context and options for modifying or extending nodes.
  • Updated various components to utilize the new node editing context, ensuring consistent use of optional chaining and nullish coalescing.
  • Enhanced workflow patch operations to support targeted editing, allowing for incremental updates to workflow plans.

This update improves user experience by providing clear context and options for editing nodes directly within the Copilot interface.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added workflow patching capabilities enabling incremental updates (title changes, task creation/modification/deletion, variable management).
    • Introduced canvas snapshot tool for copilot to read and summarize current canvas state.
    • Enabled node-targeted editing context for precise workflow modifications.
    • Added workflow plan version tracking with patch operation history.
    • Expanded workflow plan retrieval to support canvas-based lookups.

- Introduced NodeEditContext to manage state for targeted node editing, capturing relevant information for user-selected nodes.
- Implemented useNodeEditContext hook to synchronize selected node context with the Copilot store.
- Added NodeEditBanner component to display editing context and options for modifying or extending nodes.
- Updated various components to utilize the new node editing context, ensuring consistent use of optional chaining and nullish coalescing.
- Enhanced workflow patch operations to support targeted editing, allowing for incremental updates to workflow plans.

This update improves user experience by providing clear context and options for editing nodes directly within the Copilot interface.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces targeted node editing context and incremental workflow plan patching capabilities. It adds infrastructure to track selected nodes, pass node context through the skill invocation pipeline, and apply patch operations incrementally to canvas data rather than regenerating plans entirely. A new GetCanvasSnapshot tool provides canvas state summaries.

Changes

Cohort / File(s) Summary
Node Edit Context Store & Types
packages/stores/src/stores/copilot.ts, packages/stores/src/index.ts, packages/openapi-schema/src/types.gen.ts, packages/openapi-schema/src/schemas.gen.ts, packages/openapi-schema/schema.yml
Added NodeEditContext type, state field, and actions (setNodeEditContext, setNodeEditMode) to the copilot store; extended OpenAPI schema with NodeEditContext structure for tracking node metadata, edit mode, and graph context.
Node Edit Context Hook & Canvas Integration
packages/ai-workspace-common/src/hooks/canvas/use-node-edit-context.ts, packages/ai-workspace-common/src/hooks/canvas/index.ts, packages/ai-workspace-common/src/components/canvas/index.tsx
Created useNodeEditContext hook to synchronize selected skillResponse nodes with copilot store, computing graph context (upstream/downstream nodes); integrated hook into Flow component and exported via canvas hooks index.
Node Edit Context Propagation
packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts, packages/ai-workspace-common/src/components/canvas/copilot/chat-box.tsx, packages/skill-template/src/base.ts, apps/api/src/modules/skill/skill-invoker.service.ts
Propagated nodeEditContext through action invocation payloads from frontend to backend; extended SkillInvokerService to unconditionally include nodeEditContext (and copilotSessionId) in config.configurable.
Workflow Plan Patching Infrastructure
packages/ai-workspace-common/src/requests/types.gen.ts, packages/openapi-schema/src/types.gen.ts, packages/openapi-schema/schema.yml, packages/canvas-common/src/workflow-plan.ts, packages/canvas-common/src/types.ts
Introduced WorkflowPatchOp, WorkflowPatchData, WorkflowPatchOperation types and applyIncrementalChangesToCanvas function for incremental updates; extended WorkflowPlanRecord with patchOperations field; added taskId to SkillNodeMeta for mapping edits to nodes.
Workflow Plan Service Methods
apps/api/src/modules/workflow/workflow-plan.service.ts, apps/api/src/modules/skill/skill-engine.service.ts
Added getLatestWorkflowPlanByCanvas to retrieve latest plan by canvas ID; updated DTO conversion to expose patchOperations from patch data; implemented getCanvasData to fetch raw canvas state (nodes, edges, variables, title).
Frontend Patch Handling
packages/ai-workspace-common/src/components/canvas/copilot/copilot-message.tsx
Added incremental patch rendering path using applyIncrementalChangesToCanvas when patchOperations exist; imports CanvasEdge and retrieves edges via getEdges for patch application; logs copilot_patch events for affected nodes.
Copilot Tool & Agent Updates
packages/agent-tools/src/builtin/interface.ts, packages/agent-tools/src/copilot/index.ts, packages/skill-template/src/prompts/copilot-agent.ts, packages/skill-template/src/skills/agent.ts
Extended ReflyService interface with getLatestWorkflowPlanByCanvas and getCanvasData methods; introduced GetCanvasSnapshot tool class for retrieving and summarizing canvas state; updated copilot agent prompts to include nodeEditContext section with editing rules and examples.
GetCanvasSnapshot Tool Implementation
apps/api/src/modules/tool/tool.service.ts, packages/ai-workspace-common/src/components/markdown/plugins/tool-call/internal-tool-renderers/copilot-snapshot-renderer.tsx, packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
Instantiated GetCanvasSnapshot tool in copilot toolsets; created CopilotSnapshotRenderer UI component to display snapshot execution status; added rendering branch in tool-call render pipeline for get_canvas_snapshot tool name.
Prompts & Templates
packages/skill-template/src/prompts/templates/copilot-agent-system.md, packages/skill-template/src/prompts/copilot-agent.ts
Added nodeEditContextSection placeholder to system prompt template; extended buildWorkflowCopilotPrompt to inject structured node edit context (JSON, editing rules, mode instructions, examples) when nodeEditContext is provided.
Translations
packages/i18n/src/en-US/ui.ts, packages/i18n/src/zh-Hans/ui.ts
Added copilotSnapshot translation keys with values "Reading canvas snapshot" (English) and "阅读画布快照" (Simplified Chinese).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Canvas as Flow Component
    participant Hook as useNodeEditContext
    participant Store as Copilot Store
    participant Backend as Copilot Agent

    User->>Canvas: Select skillResponse node
    Canvas->>Hook: Node selection change detected
    Hook->>Hook: Compute graph context<br/>(upstream/downstream nodes)
    Hook->>Store: setNodeEditContext(canvasId, context)
    Store->>Store: Update nodeEditContext state
    Canvas->>Canvas: Render with context
    User->>Canvas: Trigger Copilot action
    Canvas->>Canvas: Retrieve nodeEditContext from store
    Canvas->>Backend: invokeAction with nodeEditContext
    Backend->>Backend: buildInvokeConfig includes nodeEditContext
    Backend->>Backend: buildWorkflowCopilotPrompt with context
    Backend->>Backend: Agent processes patch operations
    Backend->>Canvas: Return patch operations
    Canvas->>Canvas: applyIncrementalChangesToCanvas
    Canvas->>Canvas: Update nodes/edges/variables
Loading
sequenceDiagram
    participant Agent as Copilot Agent
    participant Tool as GetCanvasSnapshot Tool
    participant Service as SkillEngineService
    participant Canvas as Canvas Service
    participant Frontend as Canvas UI

    Agent->>Tool: Invoke GetCanvasSnapshot
    Tool->>Service: getCanvasData(canvasId)
    Service->>Canvas: getCanvasRawData(canvasId)
    Canvas-->>Service: {nodes, edges, variables, title}
    Service-->>Tool: Canvas state
    Tool->>Tool: Generate snapshot<br/>(node/edge counts, previews)
    Tool->>Tool: Truncate long content
    Tool-->>Agent: Snapshot summary
    Agent->>Agent: Include in response
    Agent-->>Frontend: Patch operations + snapshot
    Frontend->>Frontend: Render CopilotSnapshotRenderer
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • mrcfps

Poem

🐰 A patch here, a node edit there,
Canvas snapshots float through the air!
Incremental flows so swift and clean,
The finest workflow changes I've seen.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title directly and accurately summarizes the main feature being introduced: targeted node editing functionality in Copilot, supported by extensive changes across the codebase for node context management, incremental workflow patching, and related UI components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/copilot-patch

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/agent-tools/src/copilot/index.ts (1)

168-182: Use validated parsed data instead of unchecked input with type assertions.

After validating with parseWorkflowPlanPatch at line 125, use the parsed result (parsed.data?.operations) instead of the original input.operations. This ensures you're always working with validated data and removes the unnecessary type assertion. The nullish coalescing provides defensive safety.

🛠️ Suggested change
      const result = await reflyService.patchWorkflowPlan(user, {
        planId: planId!,
-       operations: input.operations as WorkflowPatchOperation[],
+       operations: parsed.data?.operations ?? [],
        resultId,
        resultVersion,
      });

      return {
        status: 'success',
        data: {
          planId: result.planId,
          version: result.version,
        },
-       summary: `Successfully patched workflow plan with ID: ${result.planId} and created new version: ${result.version}. Applied ${input.operations.length} operation(s).`,
+       summary: `Successfully patched workflow plan with ID: ${result.planId} and created new version: ${result.version}. Applied ${(parsed.data?.operations ?? []).length} operation(s).`,
      };

Per coding guidelines, prefer validated parsed data and nullish coalescing over direct access with type assertions.

packages/ai-workspace-common/src/components/canvas/copilot/chat-box.tsx (1)

43-54: Use optional chaining and nullish coalescing for safe property access on map lookups.

Line 53 should use optional chaining when accessing the nodeEditContext map and normalize to null for consistency with how the value is used throughout the component.

🛠️ Suggested change
-    nodeEditContext: state.nodeEditContext[canvasId],
+    nodeEditContext: state.nodeEditContext?.[canvasId] ?? null,

This follows the coding guidelines for optional chaining and ensures stable values when working with map-like store properties.

🤖 Fix all issues with AI agents
In
`@packages/ai-workspace-common/src/components/canvas/copilot/copilot-message.tsx`:
- Around line 174-195: The code extracts variableOperations from
applyIncrementalChangesToCanvas but then ignores them and blindly calls
setVariables(finalPlan.variables ?? []), which can clear variables; instead,
when variableOperations?.length exists you must apply those operations to the
current variables and pass the resulting list to setVariables. Locate where
variable state is stored (e.g., currentVariables from state or a variablesRef),
implement or call a utility to apply create/update/delete operations (e.g.,
applyVariableOperations(currentVariables, variableOperations)), compute
newVariables, then call setVariables(newVariables, { archiveOldFiles: false })
rather than using finalPlan.variables; keep existing behavior when no
variableOperations are present.

In
`@packages/ai-workspace-common/src/components/canvas/copilot/node-edit-banner.tsx`:
- Around line 18-46: Replace the fallback using logical OR with nullish
coalescing for the node title: update the displayTitle assignment to use
currentState.title ?? t('copilot.nodeEdit.untitled') (referencing displayTitle
and currentState.title) so empty strings are preserved; do not add any extra
guards around canvasId from useCanvasContext() because that hook already
guarantees a string.

In `@packages/canvas-common/src/workflow-plan.ts`:
- Around line 433-458: planVariableToWorkflowVariable currently casts incoming
strings to union types allowing invalid values through; update it to
clamp/normalize incoming enums by explicitly mapping/checking
planVariable.variableType to one of 'string'|'option'|'resource' (default
'string' if not valid), map each value.type to 'text'|'resource' (default
'text'), and map resource.fileType to 'document'|'image'|'video'|'audio'
(default or omit if invalid). Apply the same normalization logic in the
corresponding updateVariable conversion (lines ~911-931) so both creation and
updates only produce allowed enum values in the WorkflowVariable payloads.

In `@packages/openapi-schema/schema.yml`:
- Around line 12873-12917: WorkflowPatchData currently lacks support for
option-type variables so patch requests cannot change option variables; update
the WorkflowPatchData schema (the object under WorkflowPatchData in the diff) to
include "option" in the variableType enum (matching
WorkflowVariable.variableType), and add properties to carry option configuration
such as an options array (items referencing the existing or new
VariableOption/VariableValue schema) and a selectionMode field (enum like
"single"/"multiple" or referencing VariableSelectionMode) so patch operations
can update option lists and selection behavior; ensure property names match the
runtime model (e.g., variableType, options, selectionMode) and reference
existing component schemas where appropriate.

In `@packages/openapi-schema/src/types.gen.ts`:
- Around line 8078-8134: The WorkflowPatchData type's variableType union is
missing the 'option' member, causing patches to reject valid updates; update the
OpenAPI schema definition that describes WorkflowPatchData (and/or the
referenced WorkflowVariable.variableType enum) to include 'option' in the
allowed values, then regenerate the TypeScript types so
WorkflowPatchData.variableType becomes 'string' | 'resource' | 'option' and
consumers can PATCH option-typed variables without casting.

In `@packages/request/src/requests/types.gen.ts`:
- Around line 8089-8110: The WorkflowPatchData type's variableType union is
missing 'option' causing inconsistency with WorkflowVariable.variableType;
update the schema so WorkflowPatchData.variableType includes 'option' (i.e.,
change the union for variableType in WorkflowPatchData to 'string' | 'option' |
'resource') and then regenerate the OpenAPI-generated types so the updated union
appears in packages/request/src/requests/types.gen.ts; target the
WorkflowPatchData and ensure it matches WorkflowVariable.variableType.

In `@packages/stores/src/stores/copilot.ts`:
- Around line 139-160: The setNodeEditMode updater should use optional chaining
when reading state.nodeEditContext[canvasId] to match the pattern used
elsewhere; in the setNodeEditMode function replace the direct lookup into
state.nodeEditContext with an optional-chained access (e.g., const
currentContext = state.nodeEditContext?.[canvasId]) and keep the early return if
no context, while leaving setNodeEditContext's spread logic as-is since
state.nodeEditContext is always an object.
🧹 Nitpick comments (14)
packages/stores/src/stores/copilot.ts (2)

1-1: Use type-only import for OpenAPI types.

Line 1 only brings in types, so switch to import type (and keep type imports in their own group) to avoid runtime dependencies.

♻️ Suggested change
-import { CopilotSession, CanvasNodeType } from '@refly/openapi-schema';
+import type { CopilotSession, CanvasNodeType } from '@refly/openapi-schema';

As per coding guidelines, use type-only imports and separate type import groups.


6-33: Avoid duplicating NodeEditContext; reuse the shared OpenAPI type.

The OpenAPI schema already defines NodeEditContext (with optional currentState/graphContext). Duplicating it here risks drift and forces stricter requirements than the API contract. Consider aliasing or extending the shared type instead.

♻️ Suggested change
-import type { CopilotSession, CanvasNodeType } from '@refly/openapi-schema';
+import type {
+  CopilotSession,
+  NodeEditContext as OpenApiNodeEditContext,
+} from '@refly/openapi-schema';

-export interface NodeEditContext {
-  /** The internal node ID from ReactFlow */
-  nodeId: string;
-  ...
-  /** The edit mode: modify the current node or extend from it */
-  editMode: 'modify' | 'extend';
-}
+export type NodeEditContext = OpenApiNodeEditContext;

As per coding guidelines, prefer extending existing types over creating new ones.

packages/agent-tools/src/copilot/index.ts (1)

11-11: Use type-only import for OpenAPI types.

Line 11 imports types only; switch to import type and keep type imports grouped separately.

♻️ Suggested change
-import { User, WorkflowPlanRecord, WorkflowPatchOperation } from '@refly/openapi-schema';
+import type { User, WorkflowPlanRecord, WorkflowPatchOperation } from '@refly/openapi-schema';

As per coding guidelines, use type-only imports and separate type import groups.

packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (1)

5-20: Prefer import type for schema-only types.

This avoids runtime imports and aligns with TS type-only import guidance. If any of these are runtime values, keep them as value imports.

♻️ Suggested change
-import {
+import type {
   ActionMessage,
   ActionResult,
   ActionStatus,
   ActionStep,
   ActionStepMeta,
   AgentMode,
   Artifact,
   CodeArtifactType,
   Entity,
   GenericToolset,
   InvokeSkillRequest,
   ModelInfo,
   NodeEditContext,
   SkillEvent,
 } from '@refly/openapi-schema';
packages/skill-template/src/prompts/copilot-agent.ts (1)

1-1: Guard nodeEditContext field access and avoid magic numbers in the prompt.

Direct dereferences can throw if the payload is partial at runtime. Also, hoist the preview length to a named constant for clarity and reuse.

♻️ Suggested change
   // Build node edit context section if provided
   let nodeEditContextSection = '';
   if (params.nodeEditContext) {
+    const QUERY_PREVIEW_LENGTH = 100;
+    const nodeEditContext = params.nodeEditContext;
+    const taskId = nodeEditContext?.taskId ?? '';
+    const currentTitle = nodeEditContext?.currentState?.title ?? '';
+    const currentQuery = nodeEditContext?.currentState?.query ?? '';
+    const currentToolsets = nodeEditContext?.currentState?.toolsets ?? [];
+    const upstreamTasks = nodeEditContext?.graphContext?.upstreamTaskIds ?? [];
+
     nodeEditContextSection = `
 ## Targeted Node Editing Mode
@@
-| **modify** | \`patch_workflow\` | \`updateTask\` | Update the selected task (taskId: "${params.nodeEditContext.taskId}"); only change what user requests; preserve other fields |
-| **extend** | \`patch_workflow\` | \`createTask\` | Create new task with \`dependentTasks: ["${params.nodeEditContext.taskId}"]\` to extend from selected node |
+| **modify** | \`patch_workflow\` | \`updateTask\` | Update the selected task (taskId: "${taskId}"); only change what user requests; preserve other fields |
+| **extend** | \`patch_workflow\` | \`createTask\` | Create new task with \`dependentTasks: ["${taskId}"]\` to extend from selected node |
@@
-- Current state: ${params.nodeEditContext.currentState?.title ? `"${params.nodeEditContext.currentState.title}"` : 'Untitled'}
-- Current query/prompt: ${params.nodeEditContext.currentState?.query ? `"${params.nodeEditContext.currentState.query.substring(0, 100)}..."` : 'None'}
-- Current toolsets: ${params.nodeEditContext.currentState?.toolsets?.length ? params.nodeEditContext.currentState.toolsets.join(', ') : 'None'}
+- Current state: ${currentTitle ? `"${currentTitle}"` : 'Untitled'}
+- Current query/prompt: ${currentQuery ? `"${currentQuery.substring(0, QUERY_PREVIEW_LENGTH)}..."` : 'None'}
+- Current toolsets: ${currentToolsets.length ? currentToolsets.join(', ') : 'None'}
@@
-- Upstream context available: ${params.nodeEditContext.graphContext?.upstreamTaskIds?.join(', ') || 'None (this is a root task)'}
+- Upstream context available: ${upstreamTasks.length ? upstreamTasks.join(', ') : 'None (this is a root task)'}

Also applies to: 15-18, 25-66

packages/ai-workspace-common/src/hooks/canvas/use-node-edit-context.ts (2)

1-16: Use import type for type-only imports and add an explicit hook return type.

This aligns with TS guidelines and avoids runtime imports for types.

♻️ Suggested change
-import { CanvasNode, SkillNodeMeta } from '@refly/canvas-common';
-import { useCopilotStoreShallow, NodeEditContext } from '@refly/stores';
+import type { CanvasNode, SkillNodeMeta } from '@refly/canvas-common';
+import { useCopilotStoreShallow } from '@refly/stores';
+import type { NodeEditContext } from '@refly/stores';
@@
-export const useNodeEditContext = () => {
+export const useNodeEditContext = (): void => {

22-25: Guard the nodes array before calling .filter.

This prevents a potential crash if ReactFlow’s store ever returns undefined and satisfies the array-existence guideline.

♻️ Suggested change
-  const selectedNodes = useStore(
-    useShallow((state) => state.nodes.filter((node) => node.selected)),
-  );
+  const selectedNodes = useStore(
+    useShallow((state) => state.nodes?.filter?.((node) => node.selected) ?? []),
+  );
apps/api/src/modules/workflow/workflow-plan.service.ts (1)

4-10: Prefer import type for schema-only workflow plan types.

This keeps runtime imports lean and adheres to TS type-only import guidance. If any of these are runtime values, keep them as value imports.

♻️ Suggested change
-import {
+import type {
   GetWorkflowPlanDetailData,
   WorkflowPlan,
   WorkflowPlanRecord,
   WorkflowPatchOperation,
   User,
 } from '@refly/openapi-schema';
packages/openapi-schema/src/schemas.gen.ts (1)

11319-11352: Consider op-specific schema variants to enforce required fields.

Right now only op is required, so invalid combinations (e.g., updateTask without taskId/data) still validate. Consider a oneOf with op const + required fields per operation to tighten the API contract (update in schema.yml and regenerate).

packages/ai-workspace-common/src/components/canvas/copilot/node-edit-banner.tsx (2)

77-104: Avoid inline onClick lambdas for mode buttons

Inline lambdas create new handlers each render. Memoize the two button handlers to keep referential stability and align with the performance guidelines. As per coding guidelines, avoid inline function creation in render and use useCallback for function props.

♻️ Memoized handlers
+  const handleModifyClick = useCallback(() => {
+    handleModeChange('modify');
+  }, [handleModeChange]);
+
+  const handleExtendClick = useCallback(() => {
+    handleModeChange('extend');
+  }, [handleModeChange]);
...
-            onClick={() => handleModeChange('modify')}
+            onClick={handleModifyClick}
...
-            onClick={() => handleModeChange('extend')}
+            onClick={handleExtendClick}

17-17: Add JSDoc + explicit return type for the exported component

The component is exported and should carry a JSDoc comment and an explicit return type (JSX.Element | null) to align with the TypeScript/JSX guidelines. As per coding guidelines, please add JSDoc for functions and explicit return types.

packages/ai-workspace-common/src/components/canvas/copilot/copilot-message.tsx (1)

21-21: Use type-only import for CanvasNode / CanvasEdge

These are used as types only; switch to import type to keep runtime bundles leaner and satisfy TS import guidelines. As per coding guidelines, always import types explicitly using import type.

♻️ Suggested change
-import { CanvasNode, CanvasEdge } from '@refly/openapi-schema';
+import type { CanvasNode, CanvasEdge } from '@refly/openapi-schema';
packages/canvas-common/src/workflow-plan.ts (2)

1-12: Use import type for OpenAPI-only symbols

These are type-only imports, so import type keeps the emitted JS clean and follows the project’s TS conventions.

♻️ Suggested update
-import {
+import type {
   GenericToolset,
   RawCanvasData,
   CanvasNode,
   WorkflowVariable,
   WorkflowTask,
   WorkflowPlan,
   ModelInfo,
   VariableValue,
   WorkflowPatchOperation as OpenApiWorkflowPatchOperation,
 } from '@refly/openapi-schema';

As per coding guidelines.


688-695: Remove unnecessary any casts; properties are already typed

Lines 688–695 and 773–777 use any to access properties that are already explicitly typed in CanvasNodeData:

  • node.data is typed as CanvasNodeData<T>, which defines entityId: string (required) and metadata?: T (optional)
  • Direct access without any casts preserves type safety

Similarly, the nodes and edges parameters already satisfy AddNodeParam's expected types (Node[] and Edge[]) since CanvasNode extends Node, making the as any[] casts unnecessary.

Suggested refactor
- const metadata = (node.data as any)?.metadata;
+ const metadata = node.data.metadata;
  const taskId = metadata?.taskId;
- taskIdToEntityId.set(taskId, (node.data as any)?.entityId);
+ taskIdToEntityId.set(taskId, node.data.entityId);
- nodes: nodes as any[],
- edges: edges as any[],
+ nodes,
+ edges,

Comment on lines +174 to +195
// For patch operations with patchOperations available, use incremental changes
if (isPatchOperation && finalPlan.patchOperations?.length) {
const currentEdges = getEdges() as CanvasEdge[];

const { nodes, edges, affectedNodeIds, variableOperations } = applyIncrementalChangesToCanvas(
finalPlan.patchOperations,
currentNodes,
currentEdges,
tools?.data ?? [],
{ defaultModel: defaultAgentModel },
);

setNodes(nodes);
setEdges(edges);

// Handle variable operations
if (variableOperations?.length) {
// For now, we'll handle this by setting all variables including updates
// The setVariables function will merge with existing variables
const existingVariables = finalPlan.variables ?? [];
setVariables(existingVariables, { archiveOldFiles: false });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "applyIncrementalChangesToCanvas" -g '*.ts' -g '*.tsx' -A 3 -B 1

Repository: refly-ai/refly

Length of output: 172


🏁 Script executed:

rg -n -C5 "variableOperations" -g '*.ts' -g '*.tsx'

Repository: refly-ai/refly

Length of output: 7454


🏁 Script executed:

rg -n "setVariables" -g '*.ts' -g '*.tsx' -A 2 -B 1

Repository: refly-ai/refly

Length of output: 8753


Apply variableOperations instead of ignoring them

The extracted variableOperations are never applied. When variableOperations exist, passing only finalPlan.variables ?? [] bypasses the operations and can lose data. If patch_workflow returns operations with no full variable list, calling setVariables([], {archiveOldFiles: false}) silently clears variables. Apply the operations (create, update, delete) to the current variables or restructure to fetch the complete plan before calling setVariables.

🤖 Prompt for AI Agents
In
`@packages/ai-workspace-common/src/components/canvas/copilot/copilot-message.tsx`
around lines 174 - 195, The code extracts variableOperations from
applyIncrementalChangesToCanvas but then ignores them and blindly calls
setVariables(finalPlan.variables ?? []), which can clear variables; instead,
when variableOperations?.length exists you must apply those operations to the
current variables and pass the resulting list to setVariables. Locate where
variable state is stored (e.g., currentVariables from state or a variablesRef),
implement or call a utility to apply create/update/delete operations (e.g.,
applyVariableOperations(currentVariables, variableOperations)), compute
newVariables, then call setVariables(newVariables, { archiveOldFiles: false })
rather than using finalPlan.variables; keep existing behavior when no
variableOperations are present.

Comment on lines +18 to +46
const { t } = useTranslation();
const { canvasId } = useCanvasContext();

const { nodeEditContext, setNodeEditContext, setNodeEditMode } = useCopilotStoreShallow(
(state) => ({
nodeEditContext: state.nodeEditContext[canvasId],
setNodeEditContext: state.setNodeEditContext,
setNodeEditMode: state.setNodeEditMode,
}),
);

const handleClear = useCallback(() => {
setNodeEditContext(canvasId, null);
}, [canvasId, setNodeEditContext]);

const handleModeChange = useCallback(
(mode: 'modify' | 'extend') => {
setNodeEditMode(canvasId, mode);
},
[canvasId, setNodeEditMode],
);

if (!nodeEditContext) {
return null;
}

const { currentState, editMode } = nodeEditContext;
const displayTitle = currentState.title || t('copilot.nodeEdit.untitled');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the complete file to understand the context
cat -n packages/ai-workspace-common/src/components/canvas/copilot/node-edit-banner.tsx

Repository: refly-ai/refly

Length of output: 4430


🏁 Script executed:

# Search for the useCanvasContext hook definition
fd -t f "useCanvasContext" packages/ai-workspace-common/src --exec grep -l "export.*useCanvasContext"

Repository: refly-ai/refly

Length of output: 40


🏁 Script executed:

# Search for the useCopilotStoreShallow hook definition
fd -t f "copilot" packages/ai-workspace-common/src --type f -name "*store*" | head -10

Repository: refly-ai/refly

Length of output: 288


🏁 Script executed:

# Search for useCanvasContext definition
rg -t ts -t tsx "export.*useCanvasContext" --max-count 5

Repository: refly-ai/refly

Length of output: 85


🏁 Script executed:

# Search for copilot store files
fd copilot packages/ai-workspace-common/src -type f | head -20

Repository: refly-ai/refly

Length of output: 228


🏁 Script executed:

# Also search in the stores package
fd -t f "store" packages | grep -i copilot | head -10

Repository: refly-ai/refly

Length of output: 40


🏁 Script executed:

# Look for the stores package
fd -t f "packages/stores" --max-depth 1

Repository: refly-ai/refly

Length of output: 423


🏁 Script executed:

# Search for useCanvasContext definition with proper syntax
rg "export.*useCanvasContext" --max-count 5

Repository: refly-ai/refly

Length of output: 165


🏁 Script executed:

# Find copilot store files
find packages -name "*copilot*store*" -o -name "*store*copilot*" | head -20

Repository: refly-ai/refly

Length of output: 40


🏁 Script executed:

# Look for stores directory and files
find packages -type d -name "stores" | head -5

Repository: refly-ai/refly

Length of output: 100


🏁 Script executed:

# Search for nodeEditContext in the codebase
rg "nodeEditContext" --max-count 10 -A 2

Repository: refly-ai/refly

Length of output: 8727


🏁 Script executed:

# Check useCanvasContext implementation
cat packages/ai-workspace-common/src/context/canvas.tsx

Repository: refly-ai/refly

Length of output: 29557


🏁 Script executed:

# Check copilot store implementation
cat packages/stores/src/stores/copilot.ts

Repository: refly-ai/refly

Length of output: 6267


🏁 Script executed:

# Search for NodeEditContext type definition
rg "type NodeEditContext|interface NodeEditContext" -A 10

Repository: refly-ai/refly

Length of output: 2202


🏁 Script executed:

# Verify if currentState is guaranteed to exist after the guard
grep -A 5 "if (!nodeEditContext)" packages/ai-workspace-common/src/components/canvas/copilot/node-edit-banner.tsx

Repository: refly-ai/refly

Length of output: 236


🏁 Script executed:

# Check other files using the same pattern to confirm best practices
rg "nodeEditContext\[canvasId\]" --context 5 | head -40

Repository: refly-ai/refly

Length of output: 3360


Use nullish coalescing for optional field access

currentState.title is an optional field, so use ?? instead of || to align with coding guidelines. However, canvasId from useCanvasContext() is guaranteed to be a string—the hook throws an error if no context is available, so guards for canvasId are unnecessary.

- const displayTitle = currentState.title || t('copilot.nodeEdit.untitled');
+ const displayTitle = currentState.title ?? t('copilot.nodeEdit.untitled');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { t } = useTranslation();
const { canvasId } = useCanvasContext();
const { nodeEditContext, setNodeEditContext, setNodeEditMode } = useCopilotStoreShallow(
(state) => ({
nodeEditContext: state.nodeEditContext[canvasId],
setNodeEditContext: state.setNodeEditContext,
setNodeEditMode: state.setNodeEditMode,
}),
);
const handleClear = useCallback(() => {
setNodeEditContext(canvasId, null);
}, [canvasId, setNodeEditContext]);
const handleModeChange = useCallback(
(mode: 'modify' | 'extend') => {
setNodeEditMode(canvasId, mode);
},
[canvasId, setNodeEditMode],
);
if (!nodeEditContext) {
return null;
}
const { currentState, editMode } = nodeEditContext;
const displayTitle = currentState.title || t('copilot.nodeEdit.untitled');
const { t } = useTranslation();
const { canvasId } = useCanvasContext();
const { nodeEditContext, setNodeEditContext, setNodeEditMode } = useCopilotStoreShallow(
(state) => ({
nodeEditContext: state.nodeEditContext[canvasId],
setNodeEditContext: state.setNodeEditContext,
setNodeEditMode: state.setNodeEditMode,
}),
);
const handleClear = useCallback(() => {
setNodeEditContext(canvasId, null);
}, [canvasId, setNodeEditContext]);
const handleModeChange = useCallback(
(mode: 'modify' | 'extend') => {
setNodeEditMode(canvasId, mode);
},
[canvasId, setNodeEditMode],
);
if (!nodeEditContext) {
return null;
}
const { currentState, editMode } = nodeEditContext;
const displayTitle = currentState.title ?? t('copilot.nodeEdit.untitled');
🤖 Prompt for AI Agents
In
`@packages/ai-workspace-common/src/components/canvas/copilot/node-edit-banner.tsx`
around lines 18 - 46, Replace the fallback using logical OR with nullish
coalescing for the node title: update the displayTitle assignment to use
currentState.title ?? t('copilot.nodeEdit.untitled') (referencing displayTitle
and currentState.title) so empty strings are preserved; do not add any extra
guards around canvasId from useCanvasContext() because that hook already
guarantees a string.

Comment on lines 433 to +458
export const planVariableToWorkflowVariable = (
planVariable: WorkflowVariable,
): WorkflowVariable => {
planVariable: PlanVariableInput,
): WorkflowVariable | null => {
// Ensure required fields are present
if (!planVariable.variableId || !planVariable.name) {
return null;
}

return {
variableId: planVariable.variableId,
variableType: planVariable.variableType as 'string' | 'option' | 'resource',
variableType: (planVariable.variableType as 'string' | 'option' | 'resource') ?? 'string',
name: planVariable.name,
value: planVariable.value?.map((value) => ({
type: value?.type as 'text' | 'resource',
text: value?.text,
// Only include resource if it has the required fields
...(value?.resource?.name && value?.resource?.fileType
? {
resource: {
name: value.resource.name,
fileType: value.resource.fileType,
},
}
: {}),
})),
description: planVariable.description,
value:
planVariable.value?.map((value) => ({
type: (value?.type as 'text' | 'resource') ?? 'text',
text: value?.text,
// Only include resource if it has the required fields
...(value?.resource?.name && value?.resource?.fileType
? {
resource: {
name: value.resource.name,
fileType: value.resource.fileType as 'document' | 'image' | 'video' | 'audio',
},
}
: {}),
})) ?? [],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clamp loose variable/value types to allowed enums

The current casts let arbitrary strings through (e.g., variableType = 'foo'), which can leak invalid values into WorkflowVariable and patch updates. Normalize to allowed enums before defaulting.

✅ Suggested normalization (apply similarly in updateVariable conversion)
-  return {
-    variableId: planVariable.variableId,
-    variableType: (planVariable.variableType as 'string' | 'option' | 'resource') ?? 'string',
+  const normalizedVariableType =
+    planVariable.variableType === 'string' ||
+    planVariable.variableType === 'option' ||
+    planVariable.variableType === 'resource'
+      ? planVariable.variableType
+      : 'string';
+  return {
+    variableId: planVariable.variableId,
+    variableType: normalizedVariableType,
     name: planVariable.name,
     value:
       planVariable.value?.map((value) => ({
-        type: (value?.type as 'text' | 'resource') ?? 'text',
+        type: value?.type === 'resource' ? 'resource' : 'text',
         text: value?.text,

Also applies to: 911-931

🤖 Prompt for AI Agents
In `@packages/canvas-common/src/workflow-plan.ts` around lines 433 - 458,
planVariableToWorkflowVariable currently casts incoming strings to union types
allowing invalid values through; update it to clamp/normalize incoming enums by
explicitly mapping/checking planVariable.variableType to one of
'string'|'option'|'resource' (default 'string' if not valid), map each
value.type to 'text'|'resource' (default 'text'), and map resource.fileType to
'document'|'image'|'video'|'audio' (default or omit if invalid). Apply the same
normalization logic in the corresponding updateVariable conversion (lines
~911-931) so both creation and updates only produce allowed enum values in the
WorkflowVariable payloads.

Comment on lines +12873 to +12917
type: object
description: Data for updating a task or variable
properties:
title:
type: string
description: New display title for the task
prompt:
type: string
description: New prompt or instruction for this task
dependentTasks:
type: array
items:
type: string
description: New list of task IDs that must execute before this task
toolsets:
type: array
items:
type: string
description: New list of toolset IDs for this task
variableType:
type: string
enum:
- string
- resource
description: New variable type
name:
type: string
description: New variable name
description:
type: string
description: New variable description
required:
type: boolean
description: Whether this variable is required
resourceTypes:
type: array
items:
$ref: '#/components/schemas/VariableResourceType'
description: New accepted resource types
value:
type: array
items:
$ref: '#/components/schemas/VariableValue'
description: New variable values
WorkflowPatchOperation:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the schema file to see WorkflowVariable definition and confirm current WorkflowPatchData state
fd -t f "schema.yml" packages/openapi-schema/ --exec cat -n {} \; | head -100

Repository: refly-ai/refly

Length of output: 4058


🏁 Script executed:

# Get file size and structure
wc -l packages/openapi-schema/schema.yml

Repository: refly-ai/refly

Length of output: 98


🏁 Script executed:

# Search for WorkflowVariable and WorkflowPatchData definitions
rg -n "WorkflowVariable:|WorkflowPatchData:" packages/openapi-schema/schema.yml -A 20

Repository: refly-ai/refly

Length of output: 1515


🏁 Script executed:

# Search for variableType enum definitions in the schema
rg -n "variableType:" packages/openapi-schema/schema.yml -B 2 -A 8

Repository: refly-ai/refly

Length of output: 1348


🏁 Script executed:

# Search TypeScript implementation for patch operations and option handling
rg -n "WorkflowPatchData|WorkflowPatchOperation" --type=ts -B 3 -A 5

Repository: refly-ai/refly

Length of output: 24932


🏁 Script executed:

# Search for option variable handling in codebase
rg -n "variableType.*option|option.*variable" --type=ts -B 2 -A 2

Repository: refly-ai/refly

Length of output: 50370


🏁 Script executed:

# Search for isSingle property references
rg -n "isSingle" --type=ts packages

Repository: refly-ai/refly

Length of output: 4746


Add support for option-type variables in WorkflowPatchData.

WorkflowVariable.variableType supports option, but WorkflowPatchData.variableType does not. This prevents patch operations from updating option variables and their configuration (option lists and selection mode).

🛠️ Suggested schema extension
         variableType:
           type: string
           enum:
             - string
+            - option
             - resource
           description: New variable type
+        options:
+          type: array
+          items:
+            type: string
+          description: Option values for option-type variables
+        isSingle:
+          type: boolean
+          description: Whether the option variable is single-select or multi-select
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type: object
description: Data for updating a task or variable
properties:
title:
type: string
description: New display title for the task
prompt:
type: string
description: New prompt or instruction for this task
dependentTasks:
type: array
items:
type: string
description: New list of task IDs that must execute before this task
toolsets:
type: array
items:
type: string
description: New list of toolset IDs for this task
variableType:
type: string
enum:
- string
- resource
description: New variable type
name:
type: string
description: New variable name
description:
type: string
description: New variable description
required:
type: boolean
description: Whether this variable is required
resourceTypes:
type: array
items:
$ref: '#/components/schemas/VariableResourceType'
description: New accepted resource types
value:
type: array
items:
$ref: '#/components/schemas/VariableValue'
description: New variable values
WorkflowPatchOperation:
type: object
description: Data for updating a task or variable
properties:
title:
type: string
description: New display title for the task
prompt:
type: string
description: New prompt or instruction for this task
dependentTasks:
type: array
items:
type: string
description: New list of task IDs that must execute before this task
toolsets:
type: array
items:
type: string
description: New list of toolset IDs for this task
variableType:
type: string
enum:
- string
- option
- resource
description: New variable type
options:
type: array
items:
type: string
description: Option values for option-type variables
isSingle:
type: boolean
description: Whether the option variable is single-select or multi-select
name:
type: string
description: New variable name
description:
type: string
description: New variable description
required:
type: boolean
description: Whether this variable is required
resourceTypes:
type: array
items:
$ref: '#/components/schemas/VariableResourceType'
description: New accepted resource types
value:
type: array
items:
$ref: '#/components/schemas/VariableValue'
description: New variable values
WorkflowPatchOperation:
🤖 Prompt for AI Agents
In `@packages/openapi-schema/schema.yml` around lines 12873 - 12917,
WorkflowPatchData currently lacks support for option-type variables so patch
requests cannot change option variables; update the WorkflowPatchData schema
(the object under WorkflowPatchData in the diff) to include "option" in the
variableType enum (matching WorkflowVariable.variableType), and add properties
to carry option configuration such as an options array (items referencing the
existing or new VariableOption/VariableValue schema) and a selectionMode field
(enum like "single"/"multiple" or referencing VariableSelectionMode) so patch
operations can update option lists and selection behavior; ensure property names
match the runtime model (e.g., variableType, options, selectionMode) and
reference existing component schemas where appropriate.

Comment on lines +8078 to +8134
/**
* Type of patch operation to apply to a workflow plan
*/
export type WorkflowPatchOp =
| 'updateTitle'
| 'createTask'
| 'updateTask'
| 'deleteTask'
| 'createVariable'
| 'updateVariable'
| 'deleteVariable';

/**
* Data for updating a task or variable
*/
export type WorkflowPatchData = {
/**
* New display title for the task
*/
title?: string;
/**
* New prompt or instruction for this task
*/
prompt?: string;
/**
* New list of task IDs that must execute before this task
*/
dependentTasks?: Array<string>;
/**
* New list of toolset IDs for this task
*/
toolsets?: Array<string>;
/**
* New variable type
*/
variableType?: 'string' | 'resource';
/**
* New variable name
*/
name?: string;
/**
* New variable description
*/
description?: string;
/**
* Whether this variable is required
*/
required?: boolean;
/**
* New accepted resource types
*/
resourceTypes?: Array<VariableResourceType>;
/**
* New variable values
*/
value?: Array<VariableValue>;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

WorkflowPatchData.variableType blocks 'option' updates

WorkflowVariable.variableType now supports 'option', but patch updates still only accept 'string' | 'resource'. That prevents valid updates and forces downstream casts. Please update the OpenAPI schema and regenerate types so patch updates can set 'option'.

🐛 Suggested schema outcome (regenerate after updating schema)
-  variableType?: 'string' | 'resource';
+  variableType?: 'string' | 'resource' | 'option';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Type of patch operation to apply to a workflow plan
*/
export type WorkflowPatchOp =
| 'updateTitle'
| 'createTask'
| 'updateTask'
| 'deleteTask'
| 'createVariable'
| 'updateVariable'
| 'deleteVariable';
/**
* Data for updating a task or variable
*/
export type WorkflowPatchData = {
/**
* New display title for the task
*/
title?: string;
/**
* New prompt or instruction for this task
*/
prompt?: string;
/**
* New list of task IDs that must execute before this task
*/
dependentTasks?: Array<string>;
/**
* New list of toolset IDs for this task
*/
toolsets?: Array<string>;
/**
* New variable type
*/
variableType?: 'string' | 'resource';
/**
* New variable name
*/
name?: string;
/**
* New variable description
*/
description?: string;
/**
* Whether this variable is required
*/
required?: boolean;
/**
* New accepted resource types
*/
resourceTypes?: Array<VariableResourceType>;
/**
* New variable values
*/
value?: Array<VariableValue>;
};
/**
* Type of patch operation to apply to a workflow plan
*/
export type WorkflowPatchOp =
| 'updateTitle'
| 'createTask'
| 'updateTask'
| 'deleteTask'
| 'createVariable'
| 'updateVariable'
| 'deleteVariable';
/**
* Data for updating a task or variable
*/
export type WorkflowPatchData = {
/**
* New display title for the task
*/
title?: string;
/**
* New prompt or instruction for this task
*/
prompt?: string;
/**
* New list of task IDs that must execute before this task
*/
dependentTasks?: Array<string>;
/**
* New list of toolset IDs for this task
*/
toolsets?: Array<string>;
/**
* New variable type
*/
variableType?: 'string' | 'resource' | 'option';
/**
* New variable name
*/
name?: string;
/**
* New variable description
*/
description?: string;
/**
* Whether this variable is required
*/
required?: boolean;
/**
* New accepted resource types
*/
resourceTypes?: Array<VariableResourceType>;
/**
* New variable values
*/
value?: Array<VariableValue>;
};
🤖 Prompt for AI Agents
In `@packages/openapi-schema/src/types.gen.ts` around lines 8078 - 8134, The
WorkflowPatchData type's variableType union is missing the 'option' member,
causing patches to reject valid updates; update the OpenAPI schema definition
that describes WorkflowPatchData (and/or the referenced
WorkflowVariable.variableType enum) to include 'option' in the allowed values,
then regenerate the TypeScript types so WorkflowPatchData.variableType becomes
'string' | 'resource' | 'option' and consumers can PATCH option-typed variables
without casting.

Comment on lines +8089 to +8110
export type WorkflowPatchData = {
/**
* New display title for the task
*/
title?: string;
/**
* New prompt or instruction for this task
*/
prompt?: string;
/**
* New list of task IDs that must execute before this task
*/
dependentTasks?: Array<string>;
/**
* New list of toolset IDs for this task
*/
toolsets?: Array<string>;
/**
* New variable type
*/
variableType?: 'string' | 'resource';
/**
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the structure of WorkflowPatchData around the given lines
sed -n '8089,8130p' packages/request/src/requests/types.gen.ts

Repository: refly-ai/refly

Length of output: 853


🏁 Script executed:

# Search for WorkflowVariable type definition in the same file
grep -n "type WorkflowVariable" packages/request/src/requests/types.gen.ts | head -20

Repository: refly-ai/refly

Length of output: 95


🏁 Script executed:

# Search for all variableType definitions/references in the file
grep -n "variableType" packages/request/src/requests/types.gen.ts | head -30

Repository: refly-ai/refly

Length of output: 280


🏁 Script executed:

# Check if there's an OpenAPI schema file
find packages -name "*.openapi.*" -o -name "*openapi*" -o -name "*.yaml" -o -name "*.yml" | grep -i workflow

Repository: refly-ai/refly

Length of output: 40


🏁 Script executed:

# Read WorkflowVariable definition starting at line 8421
sed -n '8421,8475p' packages/request/src/requests/types.gen.ts

Repository: refly-ai/refly

Length of output: 1167


🏁 Script executed:

# Get context around line 8449 to see what type it belongs to
sed -n '8435,8460p' packages/request/src/requests/types.gen.ts

Repository: refly-ai/refly

Length of output: 574


Update WorkflowPatchData.variableType to include 'option'

WorkflowPatchData.variableType supports only 'string' | 'resource' (line 8109), but WorkflowVariable.variableType includes 'string' | 'option' | 'resource' (line 8449). WorkflowVariable explicitly documents options field as "only valid when variable type is option". If patch operations cannot set or update option-type variables, this breaks consistency. Update the OpenAPI schema to include 'option' in the patch operation's variableType field and regenerate this file.

🤖 Prompt for AI Agents
In `@packages/request/src/requests/types.gen.ts` around lines 8089 - 8110, The
WorkflowPatchData type's variableType union is missing 'option' causing
inconsistency with WorkflowVariable.variableType; update the schema so
WorkflowPatchData.variableType includes 'option' (i.e., change the union for
variableType in WorkflowPatchData to 'string' | 'option' | 'resource') and then
regenerate the OpenAPI-generated types so the updated union appears in
packages/request/src/requests/types.gen.ts; target the WorkflowPatchData and
ensure it matches WorkflowVariable.variableType.

Comment on lines +139 to +160
setNodeEditContext: (canvasId: string, context: NodeEditContext | null) =>
set((state) => ({
nodeEditContext: {
...state.nodeEditContext,
[canvasId]: context,
},
})),

setNodeEditMode: (canvasId: string, editMode: 'modify' | 'extend') =>
set((state) => {
const currentContext = state.nodeEditContext[canvasId];
if (!currentContext) return state;
return {
nodeEditContext: {
...state.nodeEditContext,
[canvasId]: {
...currentContext,
editMode,
},
},
};
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n packages/stores/src/stores/copilot.ts | sed -n '130,170p'

Repository: refly-ai/refly

Length of output: 172


🏁 Script executed:

head -n 200 packages/stores/src/stores/copilot.ts

Repository: refly-ai/refly

Length of output: 6267


Use optional chaining for consistency when accessing nodeEditContext values.

Line 149 should use optional chaining to align with the pattern used elsewhere in the file (e.g., removeHistoryTemplateSession). However, the spread in setNodeEditContext does not need nullish coalescing since state.nodeEditContext is always initialized as an object.

🛠️ Suggested change
         setNodeEditMode: (canvasId: string, editMode: 'modify' | 'extend') =>
           set((state) => {
-            const currentContext = state.nodeEditContext[canvasId];
+            const currentContext = state.nodeEditContext?.[canvasId] ?? null;
             if (!currentContext) return state;
🤖 Prompt for AI Agents
In `@packages/stores/src/stores/copilot.ts` around lines 139 - 160, The
setNodeEditMode updater should use optional chaining when reading
state.nodeEditContext[canvasId] to match the pattern used elsewhere; in the
setNodeEditMode function replace the direct lookup into state.nodeEditContext
with an optional-chained access (e.g., const currentContext =
state.nodeEditContext?.[canvasId]) and keep the early return if no context,
while leaving setNodeEditContext's spread logic as-is since
state.nodeEditContext is always an object.

- Introduced comprehensive documentation for the Node Edit Context feature, detailing its architecture, functionality, and core capabilities.
- Included flowcharts and sequence diagrams to illustrate the user interaction and backend processing for targeted node editing.
- Documented the core implementation details, including file changes, type definitions, and key design decisions.
- Enhanced clarity on the incremental update mechanism and the distinction between modify and extend modes for user actions.

This documentation serves to improve understanding and facilitate future development related to the Node Edit Context feature.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
packages/openapi-schema/schema.yml (1)

11770-11794: ⚠️ Potential issue | 🟠 Major

WorkflowPatchData cannot patch option variables yet.

WorkflowVariable.variableType supports option, but WorkflowPatchData.variableType (Line 11770) does not. Also, patch payload lacks option config fields (options, isSingle), so updateVariable cannot fully update option-type variables.

🛠️ Suggested schema alignment
     WorkflowPatchData:
       type: object
       description: Data for updating a task or variable
       properties:
         variableType:
           type: string
           enum:
             - string
+            - option
             - resource
           description: New variable type
+        options:
+          type: array
+          items:
+            type: string
+          description: Option values for option-type variables
+        isSingle:
+          type: boolean
+          description: Whether option variable is single-select
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/openapi-schema/schema.yml` around lines 11770 - 11794, The
WorkflowPatchData schema is missing support for option-type variables: update
the variableType enum in WorkflowPatchData to include "option" (matching
WorkflowVariable.variableType) and add the option-specific patch fields (e.g.,
options: array of the appropriate option item schema, and isSingle: boolean) so
updateVariable can fully patch option-type variables; refer to the existing
WorkflowVariable definition and the VariableOption/VariableValue schemas to
reuse the exact field names and item $ref types when adding these fields.
packages/openapi-schema/src/types.gen.ts (1)

7870-7897: ⚠️ Potential issue | 🟠 Major

WorkflowPatchData.variableType still blocks 'option' and creates type-surface drift.

Line 7870 and Line 7896 only allow 'string' | 'resource', while WorkflowVariable.variableType (Line 8294) allows 'option'. This still prevents valid option-variable patch updates and contributes to the variableType2 divergence at Line 8316.

🐛 Suggested generated outcome
 export type WorkflowPatchData = {
   ...
-  variableType?: 'string' | 'resource';
+  variableType?: 'string' | 'option' | 'resource';
   ...
 };

-export type variableType = 'string' | 'resource';
+export type variableType = 'string' | 'option' | 'resource';

Also applies to: 8316-8316

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/openapi-schema/src/types.gen.ts` around lines 7870 - 7897, The union
for variable types in WorkflowPatchData.variableType and the exported type alias
variableType currently only permits 'string' | 'resource' and must be expanded
to include 'option' to match WorkflowVariable.variableType and avoid the
variableType2 divergence; update the optional property
WorkflowPatchData.variableType and the exported type alias variableType (and any
related alias variableType2) to accept 'string' | 'resource' | 'option' so the
patch type surface aligns with WorkflowVariable.
🧹 Nitpick comments (4)
apps/api/src/modules/skill/skill-invoker.service.ts (1)

403-405: Remove redundant copilotSessionId reassignment.

copilotSessionId is already set in config.configurable at Line 345, so this block is now duplicate and can drift.

♻️ Proposed cleanup
-    if (data.copilotSessionId) {
-      config.configurable.copilotSessionId = data.copilotSessionId;
-    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/modules/skill/skill-invoker.service.ts` around lines 403 - 405,
Remove the duplicate assignment of copilotSessionId: locate the block that
checks data.copilotSessionId and assigns it to
config.configurable.copilotSessionId (the code around the copilotSessionId check
in skill-invoker.service.ts) and delete it, since copilotSessionId is already
set earlier on config.configurable (see the prior assignment near line 345).
Ensure no other logic depends on this second assignment before removing it.
packages/openapi-schema/src/types.gen.ts (1)

8005-8011: Reuse the exported editMode alias in NodeEditContext.

NodeEditContext.editMode currently duplicates the string union already exported as editMode. Referencing the alias avoids drift.

♻️ Suggested tweak
-  editMode: 'modify' | 'extend';
+  editMode: editMode;
As per coding guidelines, "Avoid magic numbers and strings - use named constants in TypeScript/JavaScript".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/openapi-schema/src/types.gen.ts` around lines 8005 - 8011, The
NodeEditContext type currently repeats the string union "'modify' | 'extend'";
change its editMode property to reference the already exported alias editMode
instead (i.e., use editMode as the type for NodeEditContext.editMode) so the
declaration reuses the existing type alias and avoids duplication; ensure the
symbol name editMode is in scope or exported/imported appropriately if needed.
packages/canvas-common/src/workflow-plan.ts (2)

719-727: Consider avoiding any casts for node data access.

Lines 720, 725 use any to access metadata and entityId from node.data. Per coding guidelines, prefer unknown with type guards or define a proper interface for the node data structure.

♻️ Suggested approach
+interface SkillResponseNodeData {
+  entityId?: string;
+  metadata?: {
+    taskId?: string;
+    [key: string]: unknown;
+  };
+}
+
 for (const node of currentNodes) {
-  const metadata = (node.data as any)?.metadata;
-  const taskId = metadata?.taskId;
+  const nodeData = node.data as SkillResponseNodeData | undefined;
+  const taskId = nodeData?.metadata?.taskId;
   if (taskId) {
     taskIdToNode.set(taskId, node);
     taskIdToNodeId.set(taskId, node.id);
-    taskIdToEntityId.set(taskId, (node.data as any)?.entityId);
+    taskIdToEntityId.set(taskId, nodeData?.entityId ?? '');
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/canvas-common/src/workflow-plan.ts` around lines 719 - 727, The loop
over currentNodes is using unsafe (node.data as any) casts to read metadata and
entityId; define a proper interface (e.g. NodeData with optional
metadata{taskId?: string} and entityId?: string) or use nodeData: unknown plus a
type-guard function isNodeData(obj): obj is NodeData, then replace (node.data as
any)?.metadata and (node.data as any)?.entityId with typed-safe accesses and
checks before setting taskIdToNode, taskIdToNodeId, and taskIdToEntityId so the
compiler enforces the shape instead of relying on any.

737-985: Add default case to switch for defensive handling.

The switch statement covers all WorkflowPatchOp values but lacks a default case. While TypeScript's exhaustiveness checking helps, a defensive default prevents silent failures if new operations are added or if runtime data contains unexpected values.

🛡️ Suggested fix
       case 'deleteVariable': {
         if (variableId) {
           variableOperations.push({
             type: 'delete',
             variableId,
           });
         }
         break;
       }
+
+      default: {
+        // Defensive: log or handle unknown operation types
+        const _exhaustiveCheck: never = op;
+        console.warn(`Unknown patch operation type: ${_exhaustiveCheck as string}`);
+        break;
+      }
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/canvas-common/src/workflow-plan.ts` around lines 737 - 985, The
switch on op in the workflow patch handling (the switch starting with cases
'updateTitle', 'createTask', 'updateTask', 'deleteTask', 'createVariable',
'updateVariable', 'deleteVariable') lacks a default branch; add a default case
that defensively handles unexpected ops (e.g., log a warning via the same
logger/context used in this module or throw a clear Error) so unknown
WorkflowPatchOp values don't silently pass — implement the default at the end of
the switch to either throw or processLogger.warn with the unexpected op value
and return/break.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ai-workspace-common/src/components/canvas/copilot/chat-box.tsx`:
- Line 92: The code directly indexes state.nodeEditContext[canvasId], which can
be undefined for missing keys; change the access to use optional chaining
(state.nodeEditContext?.[canvasId]) wherever nodeEditContext is indexed (e.g.,
in the object passed on line with nodeEditContext) to safely return undefined
instead of throwing—update usages in chat-box.tsx that reference
state.nodeEditContext[canvasId] to use ?.[] and keep existing variable names
(state, nodeEditContext, canvasId) unchanged.

In `@packages/openapi-schema/schema.yml`:
- Around line 11818-11821: The YAML for WorkflowPatchOperation.data currently
has a duplicate $ref entry causing a parse error; edit the
WorkflowPatchOperation.data property to remove the second/extra "$ref:
\"#/components/schemas/WorkflowVariable\"" so that it matches the pattern used
by the adjacent variable property — i.e., a single $ref pointing to
"#/components/schemas/WorkflowPatchData" and a description field referencing
WorkflowVariable as needed.

---

Duplicate comments:
In `@packages/openapi-schema/schema.yml`:
- Around line 11770-11794: The WorkflowPatchData schema is missing support for
option-type variables: update the variableType enum in WorkflowPatchData to
include "option" (matching WorkflowVariable.variableType) and add the
option-specific patch fields (e.g., options: array of the appropriate option
item schema, and isSingle: boolean) so updateVariable can fully patch
option-type variables; refer to the existing WorkflowVariable definition and the
VariableOption/VariableValue schemas to reuse the exact field names and item
$ref types when adding these fields.

In `@packages/openapi-schema/src/types.gen.ts`:
- Around line 7870-7897: The union for variable types in
WorkflowPatchData.variableType and the exported type alias variableType
currently only permits 'string' | 'resource' and must be expanded to include
'option' to match WorkflowVariable.variableType and avoid the variableType2
divergence; update the optional property WorkflowPatchData.variableType and the
exported type alias variableType (and any related alias variableType2) to accept
'string' | 'resource' | 'option' so the patch type surface aligns with
WorkflowVariable.

---

Nitpick comments:
In `@apps/api/src/modules/skill/skill-invoker.service.ts`:
- Around line 403-405: Remove the duplicate assignment of copilotSessionId:
locate the block that checks data.copilotSessionId and assigns it to
config.configurable.copilotSessionId (the code around the copilotSessionId check
in skill-invoker.service.ts) and delete it, since copilotSessionId is already
set earlier on config.configurable (see the prior assignment near line 345).
Ensure no other logic depends on this second assignment before removing it.

In `@packages/canvas-common/src/workflow-plan.ts`:
- Around line 719-727: The loop over currentNodes is using unsafe (node.data as
any) casts to read metadata and entityId; define a proper interface (e.g.
NodeData with optional metadata{taskId?: string} and entityId?: string) or use
nodeData: unknown plus a type-guard function isNodeData(obj): obj is NodeData,
then replace (node.data as any)?.metadata and (node.data as any)?.entityId with
typed-safe accesses and checks before setting taskIdToNode, taskIdToNodeId, and
taskIdToEntityId so the compiler enforces the shape instead of relying on any.
- Around line 737-985: The switch on op in the workflow patch handling (the
switch starting with cases 'updateTitle', 'createTask', 'updateTask',
'deleteTask', 'createVariable', 'updateVariable', 'deleteVariable') lacks a
default branch; add a default case that defensively handles unexpected ops
(e.g., log a warning via the same logger/context used in this module or throw a
clear Error) so unknown WorkflowPatchOp values don't silently pass — implement
the default at the end of the switch to either throw or processLogger.warn with
the unexpected op value and return/break.

In `@packages/openapi-schema/src/types.gen.ts`:
- Around line 8005-8011: The NodeEditContext type currently repeats the string
union "'modify' | 'extend'"; change its editMode property to reference the
already exported alias editMode instead (i.e., use editMode as the type for
NodeEditContext.editMode) so the declaration reuses the existing type alias and
avoids duplication; ensure the symbol name editMode is in scope or
exported/imported appropriately if needed.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 520c4ed and aba895f.

📒 Files selected for processing (11)
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/ai-workspace-common/src/components/canvas/copilot/chat-box.tsx
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/canvas-common/src/workflow-plan.ts
  • packages/i18n/src/en-US/ui.ts
  • packages/i18n/src/zh-Hans/ui.ts
  • packages/openapi-schema/schema.yml
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/openapi-schema/src/types.gen.ts
  • packages/skill-template/src/prompts/templates/copilot-agent-system.md
  • packages/stores/src/stores/copilot.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/skill-template/src/prompts/templates/copilot-agent-system.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/stores/src/stores/copilot.ts
  • packages/openapi-schema/src/schemas.gen.ts

pendingFiles: state.pendingFiles[canvasId],
setPendingPrompt: state.setPendingPrompt,
setPendingFiles: state.setPendingFiles,
nodeEditContext: state.nodeEditContext[canvasId],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the store file for copilot
store_file=$(find . -type f -name '*copilot*.ts' -o -name '*store*.ts' | grep -E 'copilot.*store|store.*copilot' | head -n1)

if [ -z "$store_file" ]; then
  # Try broader search
  store_file=$(fd 'copilot' --type f | grep -E '\.ts$' | grep -i store | head -n1)
fi

echo "=== Store file search ==="
if [ -n "$store_file" ]; then
  echo "Found: $store_file"
  echo ""
  echo "=== nodeEditContext initialization ==="
  rg -n -C5 'nodeEditContext' "$store_file" | head -50
else
  echo "Store file not found via exact match, searching for nodeEditContext references..."
  rg -l 'nodeEditContext' --type ts --type tsx | head -5
fi

echo ""
echo "=== Checking chat-box.tsx context around line 92 ==="
chat_file="packages/ai-workspace-common/src/components/canvas/copilot/chat-box.tsx"
if [ -f "$chat_file" ]; then
  rg -n 'nodeEditContext' "$chat_file" | head -20
  echo ""
  sed -n '85,100p' "$chat_file" | cat -n
fi

Repository: refly-ai/refly

Length of output: 3138


Guard store map access with optional chaining.

Line 92 directly indexes state.nodeEditContext[canvasId] without safe access. While the object is always initialized, the property access can return undefined if the key doesn't exist.

🛡️ Proposed fix
-      nodeEditContext: state.nodeEditContext[canvasId],
+      nodeEditContext: state.nodeEditContext?.[canvasId],

Use optional chaining to comply with the coding guideline: "Always use optional chaining (?.) when accessing object properties in TypeScript/JavaScript."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
nodeEditContext: state.nodeEditContext[canvasId],
nodeEditContext: state.nodeEditContext?.[canvasId],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ai-workspace-common/src/components/canvas/copilot/chat-box.tsx` at
line 92, The code directly indexes state.nodeEditContext[canvasId], which can be
undefined for missing keys; change the access to use optional chaining
(state.nodeEditContext?.[canvasId]) wherever nodeEditContext is indexed (e.g.,
in the object passed on line with nodeEditContext) to safely return undefined
instead of throwing—update usages in chat-box.tsx that reference
state.nodeEditContext[canvasId] to use ?.[] and keep existing variable names
(state, nodeEditContext, canvasId) unchanged.

Comment on lines +11818 to 11821
data:
$ref: '#/components/schemas/WorkflowPatchData'
description: Update data (for updateTask, updateVariable operations)
$ref: "#/components/schemas/WorkflowVariable"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, read the specific lines mentioned in the review
echo "=== Reading lines 11818-11821 from schema.yml ==="
sed -n '11818,11821p' packages/openapi-schema/schema.yml | cat -n

echo ""
echo "=== Reading broader context (lines 11810-11830) ==="
sed -n '11810,11830p' packages/openapi-schema/schema.yml | cat -n

Repository: refly-ai/refly

Length of output: 1405


🏁 Script executed:

#!/bin/bash
python - <<'PY'
import sys, subprocess, pkgutil
from pathlib import Path

try:
    from ruamel.yaml import YAML
except Exception:
    subprocess.check_call([sys.executable, "-m", "pip", "install", "ruamel.yaml", "-q"])
    from ruamel.yaml import YAML

yaml = YAML(typ='rt')
yaml.allow_duplicate_keys = False

path = Path("packages/openapi-schema/schema.yml")
with path.open("r", encoding="utf-8") as f:
    try:
        result = yaml.load(f)
        print("OK: YAML parsed successfully")
        # Let's check what value was assigned to the 'data' key
        print("\nChecking WorkflowPatchOperation.data structure:")
        if 'components' in result and 'schemas' in result['components']:
            if 'WorkflowPatchOperation' in result['components']['schemas']:
                data_value = result['components']['schemas']['WorkflowPatchOperation']['properties'].get('data')
                print(f"data value: {data_value}")
    except Exception as e:
        print("FAIL: YAML parsing error:", str(e)[:200])
PY

Repository: refly-ai/refly

Length of output: 184


Remove the duplicate $ref under WorkflowPatchOperation.data.

Line 11821 contains a second $ref that causes YAML parsing to fail with "mapping values are not allowed here". The property should follow the same pattern as the adjacent variable property: a single $ref with a description. The second $ref must be removed.

🛠️ Minimal fix
         data:
           $ref: '#/components/schemas/WorkflowPatchData'
           description: Update data (for updateTask, updateVariable operations)
-            $ref: "#/components/schemas/WorkflowVariable"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data:
$ref: '#/components/schemas/WorkflowPatchData'
description: Update data (for updateTask, updateVariable operations)
$ref: "#/components/schemas/WorkflowVariable"
data:
$ref: '#/components/schemas/WorkflowPatchData'
description: Update data (for updateTask, updateVariable operations)
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 11821-11821: syntax error: mapping values are not allowed here

(syntax)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/openapi-schema/schema.yml` around lines 11818 - 11821, The YAML for
WorkflowPatchOperation.data currently has a duplicate $ref entry causing a parse
error; edit the WorkflowPatchOperation.data property to remove the second/extra
"$ref: \"#/components/schemas/WorkflowVariable\"" so that it matches the pattern
used by the adjacent variable property — i.e., a single $ref pointing to
"#/components/schemas/WorkflowPatchData" and a description field referencing
WorkflowVariable as needed.

- Implemented getCanvasData method in SkillEngineService to retrieve canvas details with optional chaining and nullish coalescing for safety.
- Added GetCanvasSnapshot tool to retrieve current canvas state, including nodes and edges, with proper error handling.
- Updated tool service to include the new GetCanvasSnapshot tool and integrated it into the existing workflow.
- Removed NodeEditBanner component from Copilot as it is no longer needed.
- Added CopilotSnapshotRenderer for rendering canvas snapshot tool calls in markdown.
- Updated internationalization files to include translations for canvas snapshot messages.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/agent-tools/src/copilot/index.ts (1)

134-191: ⚠️ Potential issue | 🟠 Major

Use the validated patch payload consistently after parsing.

Line 135 validates with parseWorkflowPlanPatch, but Lines 144, 180, and 191 still read from raw input. That can bypass parser normalization/sanitization.

Suggested fix
-      let planId = input.planId;
+      const validatedInput = parsed.data;
+      let planId = validatedInput.planId;
@@
       const result = await reflyService.patchWorkflowPlan(user, {
         planId: planId!,
-        operations: input.operations as WorkflowPatchOperation[],
+        operations: validatedInput.operations as WorkflowPatchOperation[],
         resultId,
         resultVersion,
       });
@@
-        summary: `Successfully patched workflow plan with ID: ${result.planId} and created new version: ${result.version}. Applied ${input.operations.length} operation(s).`,
+        summary: `Successfully patched workflow plan with ID: ${result.planId} and created new version: ${result.version}. Applied ${validatedInput.operations.length} operation(s).`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-tools/src/copilot/index.ts` around lines 134 - 191, The patch
validator parseWorkflowPlanPatch returns normalized/sanitized data in
parsed.value but the code continues to use the raw input; update all places that
currently read from input to use parsed.value instead — specifically: initialize
planId from parsed.value.planId (not input.planId), pass parsed.value.operations
to reflyService.patchWorkflowPlan (instead of input.operations) and use
parsed.value.operations.length in the success summary; keep existing checks
(copilotSessionId/resultId/resultVersion) and types but consistently consume
parsed.value for the validated payload when calling patchWorkflowPlan and when
composing the response summary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/modules/skill/skill-engine.service.ts`:
- Around line 145-150: The returned object from the call to getCanvasRawData
uses nullish coalescing for title/nodes/edges but leaves variables possibly
undefined; update the return to default variables as well (e.g., variables:
rawData.variables ?? {}) so callers always receive a defined value; locate the
return in SkillEngineService where rawData is assigned from
this.canvasService.getCanvasRawData and add the nullish-coalescing default for
rawData.variables in that returned object.

In `@packages/agent-tools/src/copilot/index.ts`:
- Around line 383-391: The snapshot currently returns raw canvasData.variables
which can leak secrets/PII; instead, replace that field with a minimized
projection (e.g., map canvasData.variables to only non-sensitive metadata such
as {name, type, readOnly, description} or a masked placeholder for value) and
ensure no raw value/secret fields are included. Locate the return object that
contains nodes: nodesToReturn and edges: edgesToReturn (the block referencing
canvasData.variables) and change variables: canvasData.variables to variables:
canvasData.variables.map(v => ({ name: v.name, type: v.type, description:
v.description, value: '[REDACTED]' })) or similar projection that omits any raw
value/secret properties.

---

Outside diff comments:
In `@packages/agent-tools/src/copilot/index.ts`:
- Around line 134-191: The patch validator parseWorkflowPlanPatch returns
normalized/sanitized data in parsed.value but the code continues to use the raw
input; update all places that currently read from input to use parsed.value
instead — specifically: initialize planId from parsed.value.planId (not
input.planId), pass parsed.value.operations to reflyService.patchWorkflowPlan
(instead of input.operations) and use parsed.value.operations.length in the
success summary; keep existing checks (copilotSessionId/resultId/resultVersion)
and types but consistently consume parsed.value for the validated payload when
calling patchWorkflowPlan and when composing the response summary.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aba895f and 381fa1a.

📒 Files selected for processing (9)
  • apps/api/src/modules/skill/skill-engine.service.ts
  • apps/api/src/modules/tool/tool.service.ts
  • packages/agent-tools/src/builtin/interface.ts
  • packages/agent-tools/src/copilot/index.ts
  • packages/ai-workspace-common/src/components/canvas/copilot/node-edit-banner.tsx
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/internal-tool-renderers/copilot-snapshot-renderer.tsx
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/i18n/src/en-US/ui.ts
  • packages/i18n/src/zh-Hans/ui.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/ai-workspace-common/src/components/canvas/copilot/node-edit-banner.tsx
  • packages/agent-tools/src/builtin/interface.ts
  • packages/i18n/src/en-US/ui.ts

Comment on lines +145 to +150
const rawData = await this.canvasService.getCanvasRawData(user, params.canvasId);
return {
title: rawData.title ?? '',
nodes: rawData.nodes ?? [],
edges: rawData.edges ?? [],
variables: rawData.variables,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd 'canvas.service.ts' -t f

Repository: refly-ai/refly

Length of output: 103


🏁 Script executed:

rg -n 'getCanvasRawData' --type ts -C 5

Repository: refly-ai/refly

Length of output: 23668


🏁 Script executed:

rg -n 'CanvasRawData|type.*Canvas.*Data' --type ts

Repository: refly-ai/refly

Length of output: 10687


🏁 Script executed:

cat -n apps/api/src/modules/skill/skill-engine.service.ts | sed -n '140,155p'

Repository: refly-ai/refly

Length of output: 849


🏁 Script executed:

rg -n 'type RawCanvasData' --type ts -A 15

Repository: refly-ai/refly

Length of output: 2194


🏁 Script executed:

cat -n packages/openapi-schema/src/types.gen.ts | sed -n '3246,3270p'

Repository: refly-ai/refly

Length of output: 630


🏁 Script executed:

cat -n packages/openapi-schema/src/types.gen.ts | sed -n '3202,3245p'

Repository: refly-ai/refly

Length of output: 1100


🏁 Script executed:

rg -n 'export type CanvasData' --type ts -A 20 packages/openapi-schema/src/types.gen.ts | head -40

Repository: refly-ai/refly

Length of output: 497


🏁 Script executed:

cat -n apps/api/src/modules/canvas/canvas.service.ts | sed -n '259,305p'

Repository: refly-ai/refly

Length of output: 1587


🏁 Script executed:

rg -n 'variables:' apps/api/src/modules/skill/skill-engine.service.ts -B 3 -A 3

Repository: refly-ai/refly

Length of output: 309


🏁 Script executed:

# Check how getCanvasData result is used in skill-engine
ast-grep --pattern 'getCanvasData.*{
  $$$
}'

Repository: refly-ai/refly

Length of output: 40


Normalize variables default for consistency with other properties.

The title, nodes, and edges properties all apply nullish coalescing defaults, but variables does not. Since variables is an optional property in RawCanvasData, it should also be defaulted to maintain consistent handling of potentially undefined values.

Proposed fix
       getCanvasData: async (user, params) => {
         const rawData = await this.canvasService.getCanvasRawData(user, params.canvasId);
         return {
           title: rawData.title ?? '',
           nodes: rawData.nodes ?? [],
           edges: rawData.edges ?? [],
-          variables: rawData.variables,
+          variables: rawData.variables ?? [],
         };
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/modules/skill/skill-engine.service.ts` around lines 145 - 150,
The returned object from the call to getCanvasRawData uses nullish coalescing
for title/nodes/edges but leaves variables possibly undefined; update the return
to default variables as well (e.g., variables: rawData.variables ?? {}) so
callers always receive a defined value; locate the return in SkillEngineService
where rawData is assigned from this.canvasService.getCanvasRawData and add the
nullish-coalescing default for rawData.variables in that returned object.

Comment on lines +383 to +391
data: {
title: canvasData.title,
nodeCount: allNodes.length,
edgeCount: allEdges.length,
nodeTypeCounts,
nodes: nodesToReturn,
edges: edgesToReturn,
variables: canvasData.variables,
truncated,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Redact variable payloads in canvas snapshots.

Line 390 returns raw canvasData.variables, which can leak secrets/PII through tool output. Return a minimized projection instead.

Suggested fix
-          variables: canvasData.variables,
+          variables: (canvasData.variables ?? []).map((variable) => ({
+            variableId: variable.variableId,
+            name: variable.name,
+            variableType: variable.variableType,
+            required: variable.required,
+          })),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data: {
title: canvasData.title,
nodeCount: allNodes.length,
edgeCount: allEdges.length,
nodeTypeCounts,
nodes: nodesToReturn,
edges: edgesToReturn,
variables: canvasData.variables,
truncated,
data: {
title: canvasData.title,
nodeCount: allNodes.length,
edgeCount: allEdges.length,
nodeTypeCounts,
nodes: nodesToReturn,
edges: edgesToReturn,
variables: (canvasData.variables ?? []).map((variable) => ({
variableId: variable.variableId,
name: variable.name,
variableType: variable.variableType,
required: variable.required,
})),
truncated,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-tools/src/copilot/index.ts` around lines 383 - 391, The
snapshot currently returns raw canvasData.variables which can leak secrets/PII;
instead, replace that field with a minimized projection (e.g., map
canvasData.variables to only non-sensitive metadata such as {name, type,
readOnly, description} or a masked placeholder for value) and ensure no raw
value/secret fields are included. Locate the return object that contains nodes:
nodesToReturn and edges: edgesToReturn (the block referencing
canvasData.variables) and change variables: canvasData.variables to variables:
canvasData.variables.map(v => ({ name: v.name, type: v.type, description:
v.description, value: '[REDACTED]' })) or similar projection that omits any raw
value/secret properties.

- Updated the getCanvasData method to use specific types for nodes, edges, and variables, enhancing type safety.
- Removed the unused NodeEditBanner component to streamline the codebase.
- Simplified variable handling in CopilotMessage by directly using finalPlan.variables.
- Improved metadata access in applyIncrementalChangesToCanvas with optional chaining.
- Cleaned up internationalization files by removing obsolete node edit translations.
- Implemented getLatestWorkflowPlanByCanvas in WorkflowPlanService to retrieve the latest workflow plan for a specific canvas based on the most recent copilot session.
- Updated SkillEngineService to include the new method in the service interface.
- Modified ReflyService interface to declare the new method for consistency.
- Enhanced error handling for missing canvas ID in the new method.
- Updated Copilot logic to utilize the new method for retrieving workflow plans when the plan ID is not provided.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
packages/canvas-common/src/workflow-plan.ts (1)

962-964: ⚠️ Potential issue | 🟡 Minor

Apply same type normalization for variableType.

This cast has the same issue—normalize before assignment.

           if (data.variableType !== undefined) {
-            updateData.variableType = data.variableType as 'string' | 'option' | 'resource';
+            updateData.variableType =
+              data.variableType === 'string' ||
+              data.variableType === 'option' ||
+              data.variableType === 'resource'
+                ? data.variableType
+                : 'string';
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/canvas-common/src/workflow-plan.ts` around lines 962 - 964, The
assignment to updateData.variableType currently just casts data.variableType;
instead normalize the incoming value before assignment (same approach used
elsewhere in this file for other fields). Replace the direct cast of
data.variableType with a normalization step that maps/validates
data.variableType into the allowed union ('string' | 'option' | 'resource') and
only then set updateData.variableType; use the same normalization helper or
logic pattern used for other fields in workflow-plan.ts to ensure invalid values
are handled consistently for data.variableType and updateData.variableType.
packages/agent-tools/src/copilot/index.ts (1)

420-420: ⚠️ Potential issue | 🟠 Major

Redact variables in canvas snapshot output (still leaking raw payloads).

Line 420 returns canvasData.variables directly, which can expose secret/PII fields through tool output. Return a minimized projection instead.

🔐 Proposed fix
-          variables: canvasData.variables,
+          variables: (canvasData.variables ?? []).map((variable) => ({
+            variableId: variable.variableId,
+            name: variable.name,
+            variableType: variable.variableType,
+            required: variable.required,
+          })),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-tools/src/copilot/index.ts` at line 420, Don't return
canvasData.variables directly in the canvas snapshot output; instead replace the
raw payload with a sanitized projection that strips or masks sensitive values.
Locate where canvasData.variables is included in the snapshot, iterate its
entries and produce a minimized shape (e.g., { key: variableName, type: typeof
value, value: "[REDACTED]" } or include metadata like length/type but never the
raw value), and return that projection in place of canvasData.variables so no
secret/PII is leaked.
🧹 Nitpick comments (3)
packages/canvas-common/src/workflow-plan.ts (1)

303-319: Consider avoiding double cast with proper type alignment.

The as unknown as pattern is typically a code smell. Since data comes from a validated schema, consider aligning the OpenAPI type with the Zod schema to avoid this intermediate cast, or extract a shared type definition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/canvas-common/src/workflow-plan.ts` around lines 303 - 319, The
double-cast "data as unknown as Partial<z.infer<typeof
workflowPatchDataSchema>>" is a smell; instead align the actual parameter/type
with the Zod schema by creating a shared type alias (e.g., type
WorkflowPatchData = z.infer<typeof workflowPatchDataSchema>) and use
Partial<WorkflowPatchData> for the incoming data or the function parameter so
you can remove the intermediate cast and directly use that typed variable where
typedData is referenced (affecting workflowPatchDataSchema, the typedData usage,
and construction of updatedVar); alternatively ensure the OpenAPI-generated type
matches WorkflowPatchData so no runtime cast is necessary.
packages/agent-tools/src/copilot/index.ts (2)

11-16: Use import type for type-only OpenAPI imports.

These symbols (User, WorkflowPlanRecord, WorkflowPatchOperation, CanvasNode) are used only in type annotations throughout this file. Convert to import type to make this explicit and prevent unnecessary runtime import emission.

Proposed fix
-import {
+import type {
   User,
   WorkflowPlanRecord,
   WorkflowPatchOperation,
   CanvasNode,
 } from '@refly/openapi-schema';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-tools/src/copilot/index.ts` around lines 11 - 16, The import
currently brings runtime values for OpenAPI types; change the line importing
User, WorkflowPlanRecord, WorkflowPatchOperation, and CanvasNode so it uses a
type-only import (e.g., `import type { User, WorkflowPlanRecord,
WorkflowPatchOperation, CanvasNode } from '@refly/openapi-schema'`) so these
symbols are treated as types only and do not emit runtime imports; update the
single import statement in packages/agent-tools/src/copilot/index.ts where those
symbols are referenced.

374-393: Replace any type annotations with proper typed guards.

Lines 374–392 use any type which weakens type safety and makes metadata handling brittle. The code would benefit from explicit typing with proper type guards and optional chaining for safer property access.

Suggested refactoring
+        type SnapshotToolset = { id?: string; toolset?: { key?: string } };
+        type SnapshotMetadata = {
+          query?: string;
+          selectedToolsets?: SnapshotToolset[];
+          taskId?: string;
+        };
-        const metadata = (node.data as any)?.metadata;
-        const base: Record<string, any> = {
+        const metadata = (node.data as { metadata?: SnapshotMetadata } | undefined)?.metadata;
+        const base: Record<string, unknown> = {
           id: node.id,
           type: node.type,
           title: node.data?.title,
           entityId: node.data?.entityId,
           contentPreview: node.data?.contentPreview
             ? truncateContent(node.data.contentPreview, MAX_PREVIEW_LENGTH)
             : undefined,
         };

         // For skillResponse nodes, include query and toolsets so Agent can understand the workflow
         if (node.type === 'skillResponse' && metadata) {
-          if (metadata.query) {
+          if (metadata?.query) {
             base.query = truncateContent(metadata.query, 200);
           }
-          if (metadata.selectedToolsets?.length) {
+          if (metadata?.selectedToolsets?.length) {
             base.toolsets = metadata.selectedToolsets
-              .map((t: any) => t.id ?? t.toolset?.key)
+              .map((t) => t.id ?? t.toolset?.key)
-              .filter(Boolean);
+              .filter((key): key is string => Boolean(key));
           }
-          if (metadata.taskId) {
+          if (metadata?.taskId) {
             base.taskId = metadata.taskId;
           }
         }

Aligns with guideline: **/*.{ts,tsx}: Avoid using any type – use unknown type instead with proper type guards.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-tools/src/copilot/index.ts` around lines 374 - 393, The code
is using loose any types for node.data and metadata; replace any with unknown
and add explicit type guards (e.g., isNodeData, isSkillResponseMetadata) that
validate shapes before access, then narrow types for node.data and metadata
prior to building base; specifically update references around node, (node.data
as any)?.metadata, and the skillResponse branch to use the guards and optional
chaining so truncateContent, MAX_PREVIEW_LENGTH, metadata.query, and
metadata.selectedToolsets are only accessed when their types are confirmed (use
typeof checks for strings and Array.isArray for selectedToolsets, and map using
validated toolset objects with id or toolset.key).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/modules/workflow/workflow-plan.service.ts`:
- Around line 235-268: Update the method return types to match the DTO
conversion: change getLatestWorkflowPlanByCanvas and getLatestWorkflowPlan
signatures to return Promise<WorkflowPlanRecord | null> (instead of
Promise<WorkflowPlan | null>) because workflowPlanPO2DTO returns a
WorkflowPlanRecord; ensure any related type imports/exports align with
WorkflowPlanRecord so the compiler sees the correct return type.

In `@packages/agent-tools/src/copilot/index.ts`:
- Around line 186-189: The call to reflyService.patchWorkflowPlan is passing
unsafely-cast input.operations; instead use the validated parser output from
parseWorkflowPlanPatch (which runs against workflowPlanPatchSchema). Replace the
type assertion input.operations as WorkflowPatchOperation[] with
parsed.data.operations (the result of parseWorkflowPlanPatch) when constructing
the patch payload so the service receives the validated/transformed operations
array.

In `@packages/canvas-common/src/workflow-plan.ts`:
- Around line 941-975: In the updateVariable case, normalize and validate
enum-like fields instead of blind casting: when building convertedValue from
data.value, explicitly map v.type to either 'text' or 'resource' (defaulting to
'text' for unknown values) and validate v.resource.fileType against the allowed
set ('document'|'image'|'video'|'audio') before assigning to
result.resource.fileType; likewise normalize data.variableType into the allowed
WorkflowVariable types ('string'|'option'|'resource') before setting
updateData.variableType. Update the logic around convertedValue, updateData, and
the object pushed to variableOperations so only validated, normalized enum
values (not unchecked casts) are stored.

---

Duplicate comments:
In `@packages/agent-tools/src/copilot/index.ts`:
- Line 420: Don't return canvasData.variables directly in the canvas snapshot
output; instead replace the raw payload with a sanitized projection that strips
or masks sensitive values. Locate where canvasData.variables is included in the
snapshot, iterate its entries and produce a minimized shape (e.g., { key:
variableName, type: typeof value, value: "[REDACTED]" } or include metadata like
length/type but never the raw value), and return that projection in place of
canvasData.variables so no secret/PII is leaked.

In `@packages/canvas-common/src/workflow-plan.ts`:
- Around line 962-964: The assignment to updateData.variableType currently just
casts data.variableType; instead normalize the incoming value before assignment
(same approach used elsewhere in this file for other fields). Replace the direct
cast of data.variableType with a normalization step that maps/validates
data.variableType into the allowed union ('string' | 'option' | 'resource') and
only then set updateData.variableType; use the same normalization helper or
logic pattern used for other fields in workflow-plan.ts to ensure invalid values
are handled consistently for data.variableType and updateData.variableType.

---

Nitpick comments:
In `@packages/agent-tools/src/copilot/index.ts`:
- Around line 11-16: The import currently brings runtime values for OpenAPI
types; change the line importing User, WorkflowPlanRecord,
WorkflowPatchOperation, and CanvasNode so it uses a type-only import (e.g.,
`import type { User, WorkflowPlanRecord, WorkflowPatchOperation, CanvasNode }
from '@refly/openapi-schema'`) so these symbols are treated as types only and do
not emit runtime imports; update the single import statement in
packages/agent-tools/src/copilot/index.ts where those symbols are referenced.
- Around line 374-393: The code is using loose any types for node.data and
metadata; replace any with unknown and add explicit type guards (e.g.,
isNodeData, isSkillResponseMetadata) that validate shapes before access, then
narrow types for node.data and metadata prior to building base; specifically
update references around node, (node.data as any)?.metadata, and the
skillResponse branch to use the guards and optional chaining so truncateContent,
MAX_PREVIEW_LENGTH, metadata.query, and metadata.selectedToolsets are only
accessed when their types are confirmed (use typeof checks for strings and
Array.isArray for selectedToolsets, and map using validated toolset objects with
id or toolset.key).

In `@packages/canvas-common/src/workflow-plan.ts`:
- Around line 303-319: The double-cast "data as unknown as
Partial<z.infer<typeof workflowPatchDataSchema>>" is a smell; instead align the
actual parameter/type with the Zod schema by creating a shared type alias (e.g.,
type WorkflowPatchData = z.infer<typeof workflowPatchDataSchema>) and use
Partial<WorkflowPatchData> for the incoming data or the function parameter so
you can remove the intermediate cast and directly use that typed variable where
typedData is referenced (affecting workflowPatchDataSchema, the typedData usage,
and construction of updatedVar); alternatively ensure the OpenAPI-generated type
matches WorkflowPatchData so no runtime cast is necessary.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 381fa1a and 3815c97.

📒 Files selected for processing (9)
  • apps/api/src/modules/skill/skill-engine.service.ts
  • apps/api/src/modules/workflow/workflow-plan.service.ts
  • packages/agent-tools/src/builtin/interface.ts
  • packages/agent-tools/src/copilot/index.ts
  • packages/ai-workspace-common/src/components/canvas/copilot/copilot-message.tsx
  • packages/canvas-common/src/workflow-plan.ts
  • packages/i18n/src/en-US/ui.ts
  • packages/i18n/src/zh-Hans/ui.ts
  • packages/stores/src/stores/copilot.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/i18n/src/zh-Hans/ui.ts
  • packages/stores/src/stores/copilot.ts
  • apps/api/src/modules/skill/skill-engine.service.ts
  • packages/agent-tools/src/builtin/interface.ts

Comment on lines +235 to +268
/**
* Get the latest workflow plan for a canvas by looking up the most recent copilot session
*/
async getLatestWorkflowPlanByCanvas(
user: User,
params: { canvasId: string },
): Promise<WorkflowPlan | null> {
const { canvasId } = params;
if (!canvasId) {
throw new ParamsError('Canvas ID is required');
}

// Step 1: Find the most recent copilot session for this canvas
const session = await this.prisma.copilotSession.findFirst({
where: { canvasId, uid: user.uid },
orderBy: { createdAt: 'desc' },
});

if (!session) {
return null;
}

// Step 2: Find the latest plan version in that session
const latestPlan = await this.prisma.workflowPlan.findFirst({
where: { copilotSessionId: session.sessionId, uid: user.uid },
orderBy: { version: 'desc' },
});

if (!latestPlan) {
return null;
}

return this.workflowPlanPO2DTO(latestPlan as WorkflowPlanPO);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Return type mismatch in method signature.

The JSDoc states the method returns WorkflowPlan | null but workflowPlanPO2DTO returns WorkflowPlanRecord. The same inconsistency exists in getLatestWorkflowPlan on line 217. Both methods should declare Promise<WorkflowPlanRecord | null> for consistency with the DTO conversion.

🔧 Proposed fix
   /**
    * Get the latest workflow plan for a canvas by looking up the most recent copilot session
    */
   async getLatestWorkflowPlanByCanvas(
     user: User,
     params: { canvasId: string },
-  ): Promise<WorkflowPlan | null> {
+  ): Promise<WorkflowPlanRecord | null> {
     const { canvasId } = params;

Similarly for getLatestWorkflowPlan:

   async getLatestWorkflowPlan(
     user: User,
     params: { copilotSessionId: string },
-  ): Promise<WorkflowPlan | null> {
+  ): Promise<WorkflowPlanRecord | null> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/modules/workflow/workflow-plan.service.ts` around lines 235 -
268, Update the method return types to match the DTO conversion: change
getLatestWorkflowPlanByCanvas and getLatestWorkflowPlan signatures to return
Promise<WorkflowPlanRecord | null> (instead of Promise<WorkflowPlan | null>)
because workflowPlanPO2DTO returns a WorkflowPlanRecord; ensure any related type
imports/exports align with WorkflowPlanRecord so the compiler sees the correct
return type.

Comment on lines 186 to 189
const result = await reflyService.patchWorkflowPlan(user, {
planId: planId!,
operations: input.operations,
operations: input.operations as WorkflowPatchOperation[],
resultId,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file structure and the specific lines in question
fd "index.ts" packages/agent-tools/src/copilot --type f

Repository: refly-ai/refly

Length of output: 99


🏁 Script executed:

# Get the actual code context around lines 186-189
cat -n packages/agent-tools/src/copilot/index.ts | sed -n '175,200p'

Repository: refly-ai/refly

Length of output: 1106


🏁 Script executed:

# Find where parseWorkflowPlanPatch is defined and what it returns
rg -n "parseWorkflowPlanPatch" packages/agent-tools/src/copilot/ --type=ts -A 5

Repository: refly-ai/refly

Length of output: 975


🏁 Script executed:

# Look for the schema definition
rg -n "workflowPlanPatchSchema" packages/agent-tools/src/copilot/ --type=ts -B 2 -A 5

Repository: refly-ai/refly

Length of output: 1177


🏁 Script executed:

# Find where parseWorkflowPlanPatch is defined - likely in canvas-common
fd "canvas-common" --type d

Repository: refly-ai/refly

Length of output: 81


🏁 Script executed:

# Look for schema or parser definitions related to workflowPlanPatch
rg -n "parseWorkflowPlanPatch\s*=" --type=ts -A 10

Repository: refly-ai/refly

Length of output: 933


🏁 Script executed:

# Also check imports - maybe it's defined in a separate file
rg -n "export.*parseWorkflowPlanPatch" --type=ts -A 5

Repository: refly-ai/refly

Length of output: 556


🏁 Script executed:

# Find WorkflowPlanPatch type definition
rg -n "type WorkflowPlanPatch|interface WorkflowPlanPatch" --type=ts -A 15

Repository: refly-ai/refly

Length of output: 1248


🏁 Script executed:

# Also look for workflowPlanPatchSchema definition
rg -n "workflowPlanPatchSchema\s*=" --type=ts -A 20

Repository: refly-ai/refly

Length of output: 1956


🏁 Script executed:

# Show the complete flow from line 135 to 191
cat -n packages/agent-tools/src/copilot/index.ts | sed -n '135,191p'

Repository: refly-ai/refly

Length of output: 2283


Use validated schema output instead of type-casting raw input.

parseWorkflowPlanPatch validates and transforms the input according to workflowPlanPatchSchema, but line 188 still uses input.operations as WorkflowPatchOperation[] with an unsafe type assertion. Replace with parsed.data.operations to use the validated, properly-typed output from the parser.

Proposed fix
       const result = await reflyService.patchWorkflowPlan(user, {
         planId: planId!,
-        operations: input.operations as WorkflowPatchOperation[],
+        operations: parsed.data.operations,
         resultId,
         resultVersion,
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-tools/src/copilot/index.ts` around lines 186 - 189, The call
to reflyService.patchWorkflowPlan is passing unsafely-cast input.operations;
instead use the validated parser output from parseWorkflowPlanPatch (which runs
against workflowPlanPatchSchema). Replace the type assertion input.operations as
WorkflowPatchOperation[] with parsed.data.operations (the result of
parseWorkflowPlanPatch) when constructing the patch payload so the service
receives the validated/transformed operations array.

Comment on lines +941 to +975
case 'updateVariable': {
if (variableId && data) {
// Convert value array to ensure proper typing
const convertedValue: VariableValue[] | undefined = data.value?.map((v) => {
const result: VariableValue = {
type: (v?.type as 'text' | 'resource') ?? 'text',
text: v?.text,
};
// Only include resource if it has the required fields
if (v?.resource?.name && v?.resource?.fileType) {
result.resource = {
name: v.resource.name,
fileType: v.resource.fileType as 'document' | 'image' | 'video' | 'audio',
};
}
return result;
});

const updateData: Partial<WorkflowVariable> = {};
if (data.name !== undefined) updateData.name = data.name;
if (data.description !== undefined) updateData.description = data.description;
if (data.variableType !== undefined) {
updateData.variableType = data.variableType as 'string' | 'option' | 'resource';
}
if (data.required !== undefined) updateData.required = data.required;
if (data.resourceTypes !== undefined) updateData.resourceTypes = data.resourceTypes;
if (convertedValue !== undefined) updateData.value = convertedValue;

variableOperations.push({
type: 'update',
variableId,
data: updateData,
});
}
break;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Apply consistent type normalization for variable updates.

The updateVariable case has the same loose type casting issue as planVariableToWorkflowVariable. Line 946 casts v?.type and line 953 casts fileType without validation, which can propagate invalid enum values.

🔧 Proposed fix for consistent normalization
           const convertedValue: VariableValue[] | undefined = data.value?.map((v) => {
             const result: VariableValue = {
-              type: (v?.type as 'text' | 'resource') ?? 'text',
+              type: v?.type === 'resource' ? 'resource' : 'text',
               text: v?.text,
             };
             // Only include resource if it has the required fields
             if (v?.resource?.name && v?.resource?.fileType) {
+              const normalizedFileType =
+                v.resource.fileType === 'document' ||
+                v.resource.fileType === 'image' ||
+                v.resource.fileType === 'video' ||
+                v.resource.fileType === 'audio'
+                  ? v.resource.fileType
+                  : 'document';
               result.resource = {
                 name: v.resource.name,
-                fileType: v.resource.fileType as 'document' | 'image' | 'video' | 'audio',
+                fileType: normalizedFileType,
               };
             }
             return result;
           });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/canvas-common/src/workflow-plan.ts` around lines 941 - 975, In the
updateVariable case, normalize and validate enum-like fields instead of blind
casting: when building convertedValue from data.value, explicitly map v.type to
either 'text' or 'resource' (defaulting to 'text' for unknown values) and
validate v.resource.fileType against the allowed set
('document'|'image'|'video'|'audio') before assigning to
result.resource.fileType; likewise normalize data.variableType into the allowed
WorkflowVariable types ('string'|'option'|'resource') before setting
updateData.variableType. Update the logic around convertedValue, updateData, and
the object pushed to variableOperations so only validated, normalized enum
values (not unchecked casts) are stored.

…apshot usage

- Added new entries to the Tool Selection Guide for `get_canvas_snapshot` to clarify its use cases.
- Updated the Default Preference section to include guidance on when to use `get_canvas_snapshot`.
- Introduced a new section on `get_canvas_snapshot` usage, detailing scenarios and expected data returned.
- Included important fallback instructions for handling cases when no workflow plan exists.
@anthhub anthhub changed the title Enhance targeted node editing functionality in Copilot [REF-1261][REF-1262] Enhance targeted node editing functionality in Copilot Feb 27, 2026
@linear
Copy link
Copy Markdown

linear Bot commented Feb 27, 2026

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Mar 3, 2026

Deploying refly-branch-test with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8f38d81
Status: ✅  Deploy successful!
Preview URL: https://9b1276b4.refly-branch-test.pages.dev
Branch Preview URL: https://feat-copilot-patch.refly-branch-test.pages.dev

View logs

@mrcfps mrcfps merged commit 876905c into main Mar 3, 2026
4 checks passed
@mrcfps mrcfps deleted the feat/copilot-patch branch March 3, 2026 07:28
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.

2 participants