feat(workflow): implement workflow system with steps terminology (#102)

This commit is contained in:
João Salavisa
2026-01-27 01:21:47 +00:00
committed by GitHub
parent 7c82ccdf24
commit 0b573bbdda
99 changed files with 7269 additions and 4624 deletions
+20 -13
View File
@@ -7,12 +7,14 @@ export function registerTaskSessionHandlers(store: StoreApi<AppState>): WsHandle
return {
'session.state_changed': (message) => {
const payload = message.payload;
if (!payload?.task_id || !payload?.new_state) {
if (!payload?.task_id) {
return;
}
const taskId = payload.task_id;
const newState = payload.new_state as TaskSessionState;
const newState = payload.new_state as TaskSessionState | undefined;
const sessionId = payload.session_id;
const reviewStatus = payload.review_status as string | undefined;
const workflowStepId = payload.workflow_step_id as string | undefined;
// Also update or create the session object if we have the session ID
if (sessionId) {
@@ -20,16 +22,21 @@ export function registerTaskSessionHandlers(store: StoreApi<AppState>): WsHandle
const agentProfileId = payload.agent_profile_id;
const agentProfileSnapshot = payload.agent_profile_snapshot as Record<string, unknown> | undefined;
// Build the update object with all possible fields
const sessionUpdate: Record<string, unknown> = {};
if (newState) sessionUpdate.state = newState;
if (reviewStatus !== undefined) sessionUpdate.review_status = reviewStatus;
if (workflowStepId !== undefined) sessionUpdate.workflow_step_id = workflowStepId;
if (agentProfileSnapshot) sessionUpdate.agent_profile_snapshot = agentProfileSnapshot;
if (existingSession) {
// Update existing session - preserve all existing fields, but update snapshot if provided
// Update existing session - preserve all existing fields
store.getState().setTaskSession({
...existingSession,
state: newState,
...(agentProfileSnapshot ? { agent_profile_snapshot: agentProfileSnapshot } : {}),
...sessionUpdate,
});
} else {
// Create partial session object
// Include agent_profile_id and agent_profile_snapshot from the payload if provided
} else if (newState) {
// Create partial session object only if we have a state
store.getState().setTaskSession({
id: sessionId,
task_id: taskId,
@@ -37,14 +44,14 @@ export function registerTaskSessionHandlers(store: StoreApi<AppState>): WsHandle
started_at: '',
updated_at: '',
...(agentProfileId ? { agent_profile_id: agentProfileId } : {}),
...(agentProfileSnapshot ? { agent_profile_snapshot: agentProfileSnapshot } : {}),
...sessionUpdate,
});
}
const sessionsByTask = store.getState().taskSessionsByTask.itemsByTaskId[taskId];
if (sessionsByTask) {
const hasSession = sessionsByTask.some((session) => session.id === sessionId);
if (!hasSession) {
if (!hasSession && newState) {
store.getState().setTaskSessionsForTask(taskId, [...sessionsByTask, {
id: sessionId,
task_id: taskId,
@@ -52,12 +59,12 @@ export function registerTaskSessionHandlers(store: StoreApi<AppState>): WsHandle
started_at: '',
updated_at: '',
...(agentProfileId ? { agent_profile_id: agentProfileId } : {}),
...(agentProfileSnapshot ? { agent_profile_snapshot: agentProfileSnapshot } : {}),
...sessionUpdate,
}]);
} else {
} else if (hasSession) {
const nextSessions = sessionsByTask.map((session) =>
session.id === sessionId
? { ...session, state: newState, ...(agentProfileSnapshot ? { agent_profile_snapshot: agentProfileSnapshot } : {}) }
? { ...session, ...sessionUpdate }
: session
);
store.getState().setTaskSessionsForTask(taskId, nextSessions);
+1 -1
View File
@@ -53,7 +53,7 @@ export function registerBoardsHandlers(store: StoreApi<AppState>): WsHandlers {
},
kanban:
state.kanban.boardId === message.payload.id
? { boardId: nextActiveId, columns: [], tasks: [] }
? { boardId: nextActiveId, steps: [], tasks: [] }
: state.kanban,
};
});
-82
View File
@@ -1,82 +0,0 @@
import type { StoreApi } from 'zustand';
import type { AppState } from '@/lib/state/store';
import type { WsHandlers } from '@/lib/ws/handlers/types';
export function registerColumnsHandlers(store: StoreApi<AppState>): WsHandlers {
return {
'column.created': (message) => {
store.setState((state) => {
if (state.kanban.boardId !== message.payload.board_id) {
return state;
}
const nextColumns = [
...state.kanban.columns,
{
id: message.payload.id,
title: message.payload.name,
color: message.payload.color,
position: message.payload.position,
},
].sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
return {
...state,
kanban: {
...state.kanban,
columns: nextColumns,
},
};
});
},
'column.updated': (message) => {
store.setState((state) => {
if (state.kanban.boardId !== message.payload.board_id) {
return state;
}
const exists = state.kanban.columns.some((column) => column.id === message.payload.id);
const updatedColumns = exists
? state.kanban.columns.map((column) =>
column.id === message.payload.id
? {
...column,
title: message.payload.name,
color: message.payload.color,
position: message.payload.position,
}
: column
)
: [
...state.kanban.columns,
{
id: message.payload.id,
title: message.payload.name,
color: message.payload.color,
position: message.payload.position,
},
];
const nextColumns = updatedColumns.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
return {
...state,
kanban: {
...state.kanban,
columns: nextColumns,
},
};
});
},
'column.deleted': (message) => {
store.setState((state) => {
if (state.kanban.boardId !== message.payload.board_id) {
return state;
}
const nextColumns = state.kanban.columns.filter((column) => column.id !== message.payload.id);
return {
...state,
kanban: {
...state.kanban,
columns: nextColumns,
},
};
});
},
};
}
+7 -6
View File
@@ -9,15 +9,16 @@ export function registerKanbanHandlers(store: StoreApi<AppState>): WsHandlers {
...state,
kanban: {
boardId: message.payload.boardId,
columns: message.payload.columns.map((column, index) => ({
id: column.id,
title: column.title,
color: column.color ?? 'bg-neutral-400',
position: column.position ?? index,
steps: message.payload.steps.map((step, index) => ({
id: step.id,
title: step.title,
color: step.color ?? 'bg-neutral-400',
position: step.position ?? index,
autoStartAgent: step.autoStartAgent ?? false,
})),
tasks: message.payload.tasks.map((task) => ({
id: task.id,
columnId: task.columnId,
workflowStepId: task.workflowStepId,
title: task.title,
description: task.description,
position: task.position ?? 0,
+3 -3
View File
@@ -13,7 +13,7 @@ export function registerTasksHandlers(store: StoreApi<AppState>): WsHandlers {
const existingTask = state.kanban.tasks.find((task) => task.id === message.payload.task_id);
const nextTask = {
id: message.payload.task_id,
columnId: message.payload.column_id,
workflowStepId: message.payload.workflow_step_id,
title: message.payload.title,
description: message.payload.description,
position: message.payload.position ?? 0,
@@ -39,7 +39,7 @@ export function registerTasksHandlers(store: StoreApi<AppState>): WsHandlers {
const existingTask = state.kanban.tasks.find((task) => task.id === message.payload.task_id);
const nextTask = {
id: message.payload.task_id,
columnId: message.payload.column_id,
workflowStepId: message.payload.workflow_step_id,
title: message.payload.title,
description: message.payload.description,
position: message.payload.position ?? 0,
@@ -74,7 +74,7 @@ export function registerTasksHandlers(store: StoreApi<AppState>): WsHandlers {
const existingTask = state.kanban.tasks.find((t) => t.id === message.payload.task_id);
const nextTask = {
id: message.payload.task_id,
columnId: message.payload.column_id,
workflowStepId: message.payload.workflow_step_id,
title: message.payload.title,
description: message.payload.description,
position: message.payload.position ?? 0,
+1 -1
View File
@@ -68,7 +68,7 @@ export function registerWorkspacesHandlers(store: StoreApi<AppState>): WsHandler
activeId,
},
boards: clearBoards ? { items: [], activeId: null } : state.boards,
kanban: clearBoards ? { boardId: null, columns: [], tasks: [] } : state.kanban,
kanban: clearBoards ? { boardId: null, steps: [], tasks: [] } : state.kanban,
};
});
},
+2 -2
View File
@@ -3,7 +3,7 @@ import type { AppState } from '@/lib/state/store';
import { registerAgentsHandlers } from '@/lib/ws/handlers/agents';
import { registerTaskSessionHandlers } from '@/lib/ws/handlers/agent-session';
import { registerBoardsHandlers } from '@/lib/ws/handlers/boards';
import { registerColumnsHandlers } from '@/lib/ws/handlers/columns';
import { registerMessagesHandlers } from '@/lib/ws/handlers/messages';
import { registerNotificationsHandlers } from '@/lib/ws/handlers/notifications';
import { registerDiffsHandlers } from '@/lib/ws/handlers/diffs';
@@ -23,7 +23,7 @@ export function registerWsHandlers(store: StoreApi<AppState>) {
...registerKanbanHandlers(store),
...registerTasksHandlers(store),
...registerBoardsHandlers(store),
...registerColumnsHandlers(store),
...registerWorkspacesHandlers(store),
...registerExecutorsHandlers(store),
...registerEnvironmentsHandlers(store),