Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions packages/e2e/tests/features/space-agent-centric-workflow.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ test.describe('Agent-Centric Workflow', () => {
const editor = page.getByTestId('visual-workflow-editor');
await editor.getByTestId('workflow-name-input').fill('Agent Channel Test');

// Wait for the canvas to fully render before clicking add-step.
await expect(editor.getByTestId('add-step-button')).toBeVisible({ timeout: 5000 });

// Add a node and configure two agents so agentRoles is populated,
// which gives the ChannelEditor select dropdowns instead of text inputs.
await editor.getByTestId('add-step-button').click();
Expand Down Expand Up @@ -169,6 +172,9 @@ test.describe('Agent-Centric Workflow', () => {
const editor = page.getByTestId('visual-workflow-editor');
await editor.getByTestId('workflow-name-input').fill('Gate Config Test');

// Wait for the canvas to fully render before clicking add-step.
await expect(editor.getByTestId('add-step-button')).toBeVisible({ timeout: 5000 });

// Add a node with two agents so agentRoles is populated
await editor.getByTestId('add-step-button').click();
const nodes = editor.locator('[data-testid^="workflow-node-"]').filter({
Expand Down Expand Up @@ -220,6 +226,9 @@ test.describe('Agent-Centric Workflow', () => {
const editor = page.getByTestId('visual-workflow-editor');
await editor.getByTestId('workflow-name-input').fill(WORKFLOW_NAME);

// Wait for the canvas to fully render before clicking add-step.
await expect(editor.getByTestId('add-step-button')).toBeVisible({ timeout: 5000 });

// Add a step with two agents
await editor.getByTestId('add-step-button').click();
const nodes = editor.locator('[data-testid^="workflow-node-"]').filter({
Expand All @@ -239,8 +248,8 @@ test.describe('Agent-Centric Workflow', () => {
const node = nodes.first();
const agentBadges = node.getByTestId('agent-badges');
await expect(agentBadges).toBeVisible({ timeout: 3000 });
await expect(agentBadges.locator(`text=${AGENT_A_NAME}`)).toBeVisible({ timeout: 2000 });
await expect(agentBadges.locator(`text=${AGENT_B_NAME}`)).toBeVisible({ timeout: 2000 });
await expect(agentBadges.locator(`text=${ROLE_A}`)).toBeVisible({ timeout: 2000 });
await expect(agentBadges.locator(`text=${ROLE_B}`)).toBeVisible({ timeout: 2000 });

// Without an active workflow run, no completion state icons should be visible
// (no agent-status-spinner, agent-status-check, or agent-status-fail)
Expand Down Expand Up @@ -268,8 +277,8 @@ test.describe('Agent-Centric Workflow', () => {
const reopenedNode = reopenedNodes.first();
const reopenedBadges = reopenedNode.getByTestId('agent-badges');
await expect(reopenedBadges).toBeVisible({ timeout: 3000 });
await expect(reopenedBadges.locator(`text=${AGENT_A_NAME}`)).toBeVisible({ timeout: 2000 });
await expect(reopenedBadges.locator(`text=${AGENT_B_NAME}`)).toBeVisible({ timeout: 2000 });
await expect(reopenedBadges.locator(`text=${ROLE_A}`)).toBeVisible({ timeout: 2000 });
await expect(reopenedBadges.locator(`text=${ROLE_B}`)).toBeVisible({ timeout: 2000 });

// Still no completion state icons (no active run)
await expect(reopenedNode.getByTestId('agent-status-spinner')).toHaveCount(0);
Expand All @@ -288,6 +297,9 @@ test.describe('Agent-Centric Workflow', () => {
const editor = page.getByTestId('visual-workflow-editor');
await editor.getByTestId('workflow-name-input').fill(WORKFLOW_NAME);

// Wait for the canvas to fully render before clicking add-step.
await expect(editor.getByTestId('add-step-button')).toBeVisible({ timeout: 5000 });

// Add step with two agents
await editor.getByTestId('add-step-button').click();
const nodes = editor.locator('[data-testid^="workflow-node-"]').filter({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,29 +362,22 @@ export function VisualWorkflowEditor({ workflow, onSave, onCancel }: VisualWorkf
// Node operations
// ------------------------------------------------------------------

function addStep() {
const addStep = useCallback(() => {
const newLocalId = generateUUID();
const newStep: NodeDraft = { localId: newLocalId, name: '', agentId: '', instructions: '' };

// Capture emptiness before the setNodes call so we can call setStartStepId
// outside the updater. State setter calls inside updater functions are side
// effects and violate the purity requirement (React StrictMode double-invokes
// updaters to catch exactly this pattern).
// Exclude the Task Agent virtual node — it is always present but not a real workflow step.
const isFirstNode =
nodes.filter((n) => n.step.id !== TASK_AGENT_NODE_ID && n.step.localId !== TASK_AGENT_NODE_ID)
.length === 0;
setNodes((prev) => {
// Stagger new nodes vertically so they don't overlap (nodes are ~160×80px).
// Count only regular nodes so the Task Agent's fixed slot doesn't offset the stagger.
const regularCount = prev.filter(
(n) => n.step.id !== TASK_AGENT_NODE_ID && n.step.localId !== TASK_AGENT_NODE_ID
).length;
const position: Point = { x: 120, y: 80 + regularCount * 100 };
const isFirstNode = regularCount === 0;
if (isFirstNode) setStartStepId(newLocalId);
return [...prev, { step: newStep, position }];
});
if (isFirstNode) setStartStepId(newLocalId);
}
}, []);

const handleNodePositionChange = useCallback((localId: string, newPosition: Point) => {
// Task Agent is pinned — its position must never change.
Expand Down
Loading