fix(socket): sync deploy button state across collaborators (#4206)

* fix(socket): sync deploy button state across collaborators

Broadcast workflow-deployed events via socket so all connected users
invalidate their deployment query cache when any user deploys, undeploys,
activates a version, or triggers a deploy through chat/form endpoints.

* fix(socket): check response status on deployment notification

Log a warning when the socket server returns a non-2xx status for
deployment notifications, matching the pattern in lifecycle.ts.

* improvement(config): consolidate socket server URL into getSocketServerUrl/getSocketUrl

Replace all inline `env.SOCKET_SERVER_URL || 'http://localhost:3002'` and
`getEnv('NEXT_PUBLIC_SOCKET_URL') || 'http://localhost:3002'` with centralized
utility functions in urls.ts, matching the getBaseUrl() pattern.

* improvement(config): consolidate Ollama URL and CSP socket/Ollama hardcodes

Add getOllamaUrl() to urls.ts and replace inline env.OLLAMA_URL fallbacks
in the provider and API route. Update CSP to use getSocketUrl(),
getOllamaUrl(), and a local toWebSocketUrl() helper instead of hardcoded
localhost strings.

* lint

* fix(tests): add missing mocks for new URL utility exports

Update lifecycle, async execute, and chat manage test mocks to include
getSocketServerUrl, getOllamaUrl, and notifySocketDeploymentChanged.

* fix(csp): remove urls.ts import to fix next.config.ts build

CSP is loaded by next.config.ts which transpiles outside the @/ alias
context. Use local constants instead of importing from urls.ts.

* fix(queries): invalidate chat and form status on deployment change

Add chatStatus and formStatus to invalidateDeploymentQueries so all
deployment-related queries refresh when any user deploys or undeploys.
This commit is contained in:
Waleed
2026-04-16 17:46:53 -07:00
committed by GitHub
parent c06361b142
commit 1d0e118eef
23 changed files with 277 additions and 119 deletions
@@ -89,6 +89,7 @@ vi.mock('@/lib/workflows/persistence/utils', () => ({
}))
vi.mock('@/lib/workflows/orchestration', () => ({
performChatUndeploy: mockPerformChatUndeploy,
notifySocketDeploymentChanged: vi.fn().mockResolvedValue(undefined),
}))
vi.mock('drizzle-orm', () => ({
and: vi.fn((...conditions: unknown[]) => ({ type: 'and', conditions })),
+2 -1
View File
@@ -9,7 +9,7 @@ import { getSession } from '@/lib/auth'
import { isDev } from '@/lib/core/config/feature-flags'
import { encryptSecret } from '@/lib/core/security/encryption'
import { getEmailDomain } from '@/lib/core/utils/urls'
import { performChatUndeploy } from '@/lib/workflows/orchestration'
import { notifySocketDeploymentChanged, performChatUndeploy } from '@/lib/workflows/orchestration'
import { deployWorkflow } from '@/lib/workflows/persistence/utils'
import { checkChatAccess } from '@/app/api/chat/utils'
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
@@ -155,6 +155,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
logger.info(
`Redeployed workflow ${existingChat[0].workflowId} for chat update (v${deployResult.version})`
)
await notifySocketDeploymentChanged(existingChat[0].workflowId)
}
let encryptedPassword
+3
View File
@@ -10,6 +10,7 @@ import { isDev } from '@/lib/core/config/feature-flags'
import { encryptSecret } from '@/lib/core/security/encryption'
import { getEmailDomain } from '@/lib/core/utils/urls'
import { generateId } from '@/lib/core/utils/uuid'
import { notifySocketDeploymentChanged } from '@/lib/workflows/orchestration'
import { deployWorkflow } from '@/lib/workflows/persistence/utils'
import {
checkWorkflowAccessForFormCreation,
@@ -152,6 +153,8 @@ export async function POST(request: NextRequest) {
`${workflowRecord.isDeployed ? 'Redeployed' : 'Auto-deployed'} workflow ${workflowId} for form (v${result.version})`
)
await notifySocketDeploymentChanged(workflowId)
let encryptedPassword = null
if (authType === 'password' && password) {
const { encrypted } = await encryptSecret(password)
@@ -1,11 +1,11 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { env } from '@/lib/core/config/env'
import { getOllamaUrl } from '@/lib/core/utils/urls'
import type { ModelsObject } from '@/providers/ollama/types'
import { filterBlacklistedModels, isProviderBlacklisted } from '@/providers/utils'
const logger = createLogger('OllamaModelsAPI')
const OLLAMA_HOST = env.OLLAMA_URL || 'http://localhost:11434'
const OLLAMA_HOST = getOllamaUrl()
/**
* Get available Ollama models
@@ -53,6 +53,7 @@ vi.mock('@/lib/core/utils/request', () => ({
vi.mock('@/lib/core/utils/urls', () => ({
getBaseUrl: vi.fn().mockReturnValue('http://localhost:3000'),
getOllamaUrl: vi.fn().mockReturnValue('http://localhost:11434'),
}))
vi.mock('@/lib/execution/call-chain', () => ({
@@ -7,6 +7,7 @@ import { z } from 'zod'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { env } from '@/lib/core/config/env'
import { generateRequestId } from '@/lib/core/utils/request'
import { getSocketServerUrl } from '@/lib/core/utils/urls'
import { extractAndPersistCustomTools } from '@/lib/workflows/persistence/custom-tools-persistence'
import {
loadWorkflowFromNormalizedTables,
@@ -305,8 +306,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
logger.info(`[${requestId}] Successfully saved workflow ${workflowId} state in ${elapsed}ms`)
try {
const socketUrl = env.SOCKET_SERVER_URL || 'http://localhost:3002'
const notifyResponse = await fetch(`${socketUrl}/api/workflow-updated`, {
const notifyResponse = await fetch(`${getSocketServerUrl()}/api/workflow-updated`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -13,7 +13,7 @@ import {
import { createLogger } from '@sim/logger'
import { useParams } from 'next/navigation'
import type { Socket } from 'socket.io-client'
import { getEnv } from '@/lib/core/config/env'
import { getSocketUrl } from '@/lib/core/utils/urls'
import { generateId } from '@/lib/core/utils/uuid'
import {
type SocketJoinCommand,
@@ -102,6 +102,7 @@ interface SocketContextType {
onWorkflowDeleted: (handler: (data: any) => void) => void
onWorkflowReverted: (handler: (data: any) => void) => void
onWorkflowUpdated: (handler: (data: any) => void) => void
onWorkflowDeployed: (handler: (data: any) => void) => void
onOperationConfirmed: (handler: (data: any) => void) => void
onOperationFailed: (handler: (data: any) => void) => void
}
@@ -132,6 +133,7 @@ const SocketContext = createContext<SocketContextType>({
onWorkflowDeleted: () => {},
onWorkflowReverted: () => {},
onWorkflowUpdated: () => {},
onWorkflowDeployed: () => {},
onOperationConfirmed: () => {},
onOperationFailed: () => {},
})
@@ -176,6 +178,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
workflowDeleted?: (data: any) => void
workflowReverted?: (data: any) => void
workflowUpdated?: (data: any) => void
workflowDeployed?: (data: any) => void
operationConfirmed?: (data: any) => void
operationFailed?: (data: any) => void
}>({})
@@ -337,7 +340,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
const initializeSocket = async () => {
try {
const { io } = await import('socket.io-client')
const socketUrl = getEnv('NEXT_PUBLIC_SOCKET_URL') || 'http://localhost:3002'
const socketUrl = getSocketUrl()
logger.info('Attempting to connect to Socket.IO server', {
url: socketUrl,
@@ -550,6 +553,11 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
eventHandlers.current.workflowUpdated?.(data)
})
socketInstance.on('workflow-deployed', (data) => {
logger.info(`Workflow ${data.workflowId} deployment state changed`)
eventHandlers.current.workflowDeployed?.(data)
})
const rehydrateWorkflowStores = async (workflowId: string, workflowState: any) => {
const [
{ useOperationQueueStore },
@@ -994,6 +1002,10 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
eventHandlers.current.workflowUpdated = handler
}, [])
const onWorkflowDeployed = useCallback((handler: (data: any) => void) => {
eventHandlers.current.workflowDeployed = handler
}, [])
const onOperationConfirmed = useCallback((handler: (data: any) => void) => {
eventHandlers.current.operationConfirmed = handler
}, [])
@@ -1029,6 +1041,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
onWorkflowDeleted,
onWorkflowReverted,
onWorkflowUpdated,
onWorkflowDeployed,
onOperationConfirmed,
onOperationFailed,
}),
@@ -1058,6 +1071,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
onWorkflowDeleted,
onWorkflowReverted,
onWorkflowUpdated,
onWorkflowDeployed,
onOperationConfirmed,
onOperationFailed,
]
+2
View File
@@ -42,6 +42,8 @@ export function invalidateDeploymentQueries(queryClient: QueryClient, workflowId
queryClient.invalidateQueries({ queryKey: deploymentKeys.info(workflowId) }),
queryClient.invalidateQueries({ queryKey: deploymentKeys.deployedState(workflowId) }),
queryClient.invalidateQueries({ queryKey: deploymentKeys.versions(workflowId) }),
queryClient.invalidateQueries({ queryKey: deploymentKeys.chatStatus(workflowId) }),
queryClient.invalidateQueries({ queryKey: deploymentKeys.formStatus(workflowId) }),
])
}
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef } from 'react'
import { createLogger } from '@sim/logger'
import { useQueryClient } from '@tanstack/react-query'
import type { Edge } from 'reactflow'
import { useShallow } from 'zustand/react/shallow'
import { useSession } from '@/lib/auth/auth-client'
@@ -7,6 +8,7 @@ import { generateId } from '@/lib/core/utils/uuid'
import { useSocket } from '@/app/workspace/providers/socket-provider'
import { getBlock } from '@/blocks'
import { normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants'
import { invalidateDeploymentQueries } from '@/hooks/queries/deployments'
import { useUndoRedo } from '@/hooks/use-undo-redo'
import {
BLOCK_OPERATIONS,
@@ -34,6 +36,7 @@ import { findAllDescendantNodes, isBlockProtected } from '@/stores/workflows/wor
const logger = createLogger('CollaborativeWorkflow')
export function useCollaborativeWorkflow() {
const queryClient = useQueryClient()
const undoRedo = useUndoRedo()
const isUndoRedoInProgress = useRef(false)
const lastDiffOperationId = useRef<string | null>(null)
@@ -125,6 +128,7 @@ export function useCollaborativeWorkflow() {
onWorkflowDeleted,
onWorkflowReverted,
onWorkflowUpdated,
onWorkflowDeployed,
onOperationConfirmed,
onOperationFailed,
} = useSocket()
@@ -645,6 +649,15 @@ export function useCollaborativeWorkflow() {
}
}
const handleWorkflowDeployed = (data: any) => {
const { workflowId } = data
logger.info(`Workflow ${workflowId} deployment state changed`)
if (workflowId !== activeWorkflowId) return
invalidateDeploymentQueries(queryClient, workflowId)
}
const handleOperationConfirmed = (data: any) => {
const { operationId } = data
logger.debug('Operation confirmed', { operationId })
@@ -664,6 +677,7 @@ export function useCollaborativeWorkflow() {
onWorkflowDeleted(handleWorkflowDeleted)
onWorkflowReverted(handleWorkflowReverted)
onWorkflowUpdated(handleWorkflowUpdated)
onWorkflowDeployed(handleWorkflowDeployed)
onOperationConfirmed(handleOperationConfirmed)
onOperationFailed(handleOperationFailed)
}, [
@@ -673,9 +687,11 @@ export function useCollaborativeWorkflow() {
onWorkflowDeleted,
onWorkflowReverted,
onWorkflowUpdated,
onWorkflowDeployed,
onOperationConfirmed,
onOperationFailed,
activeWorkflowId,
queryClient,
confirmOperation,
failOperation,
emitWorkflowOperation,
@@ -10,7 +10,7 @@ export interface ToolRuntimeSchemaEntry {
}
export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
['agent']: {
agent: {
parameters: {
properties: {
request: {
@@ -23,7 +23,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['auth']: {
auth: {
parameters: {
properties: {
request: {
@@ -36,7 +36,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['check_deployment_status']: {
check_deployment_status: {
parameters: {
type: 'object',
properties: {
@@ -48,7 +48,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['complete_job']: {
complete_job: {
parameters: {
type: 'object',
properties: {
@@ -61,7 +61,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['context_write']: {
context_write: {
parameters: {
type: 'object',
properties: {
@@ -78,7 +78,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['crawl_website']: {
crawl_website: {
parameters: {
type: 'object',
properties: {
@@ -113,7 +113,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['create_file']: {
create_file: {
parameters: {
type: 'object',
properties: {
@@ -149,7 +149,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
required: ['success', 'message'],
},
},
['create_folder']: {
create_folder: {
parameters: {
type: 'object',
properties: {
@@ -170,7 +170,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['create_job']: {
create_job: {
parameters: {
type: 'object',
properties: {
@@ -220,7 +220,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['create_workflow']: {
create_workflow: {
parameters: {
type: 'object',
properties: {
@@ -245,7 +245,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['create_workspace_mcp_server']: {
create_workspace_mcp_server: {
parameters: {
type: 'object',
properties: {
@@ -266,7 +266,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['debug']: {
debug: {
parameters: {
properties: {
context: {
@@ -285,7 +285,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['delete_file']: {
delete_file: {
parameters: {
type: 'object',
properties: {
@@ -314,7 +314,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
required: ['success', 'message'],
},
},
['delete_folder']: {
delete_folder: {
parameters: {
type: 'object',
properties: {
@@ -330,7 +330,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['delete_workflow']: {
delete_workflow: {
parameters: {
type: 'object',
properties: {
@@ -346,7 +346,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['delete_workspace_mcp_server']: {
delete_workspace_mcp_server: {
parameters: {
type: 'object',
properties: {
@@ -359,7 +359,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['deploy']: {
deploy: {
parameters: {
properties: {
request: {
@@ -373,7 +373,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['deploy_api']: {
deploy_api: {
parameters: {
type: 'object',
properties: {
@@ -447,7 +447,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
],
},
},
['deploy_chat']: {
deploy_chat: {
parameters: {
type: 'object',
properties: {
@@ -595,7 +595,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
],
},
},
['deploy_mcp']: {
deploy_mcp: {
parameters: {
type: 'object',
properties: {
@@ -711,7 +711,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
required: ['deploymentType', 'deploymentStatus'],
},
},
['download_to_workspace_file']: {
download_to_workspace_file: {
parameters: {
type: 'object',
properties: {
@@ -730,7 +730,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['edit_content']: {
edit_content: {
parameters: {
type: 'object',
properties: {
@@ -762,7 +762,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
required: ['success', 'message'],
},
},
['edit_workflow']: {
edit_workflow: {
parameters: {
type: 'object',
properties: {
@@ -801,13 +801,13 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['file']: {
file: {
parameters: {
type: 'object',
},
resultSchema: undefined,
},
['function_execute']: {
function_execute: {
parameters: {
type: 'object',
properties: {
@@ -868,7 +868,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['generate_api_key']: {
generate_api_key: {
parameters: {
type: 'object',
properties: {
@@ -886,7 +886,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['generate_image']: {
generate_image: {
parameters: {
type: 'object',
properties: {
@@ -923,7 +923,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['generate_visualization']: {
generate_visualization: {
parameters: {
type: 'object',
properties: {
@@ -963,7 +963,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['get_block_outputs']: {
get_block_outputs: {
parameters: {
type: 'object',
properties: {
@@ -984,7 +984,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['get_block_upstream_references']: {
get_block_upstream_references: {
parameters: {
type: 'object',
properties: {
@@ -1006,7 +1006,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['get_deployed_workflow_state']: {
get_deployed_workflow_state: {
parameters: {
type: 'object',
properties: {
@@ -1019,7 +1019,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['get_deployment_version']: {
get_deployment_version: {
parameters: {
type: 'object',
properties: {
@@ -1036,7 +1036,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['get_execution_summary']: {
get_execution_summary: {
parameters: {
type: 'object',
properties: {
@@ -1063,7 +1063,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['get_job_logs']: {
get_job_logs: {
parameters: {
type: 'object',
properties: {
@@ -1088,7 +1088,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['get_page_contents']: {
get_page_contents: {
parameters: {
type: 'object',
properties: {
@@ -1116,14 +1116,14 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['get_platform_actions']: {
get_platform_actions: {
parameters: {
type: 'object',
properties: {},
},
resultSchema: undefined,
},
['get_workflow_data']: {
get_workflow_data: {
parameters: {
type: 'object',
properties: {
@@ -1142,7 +1142,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['get_workflow_logs']: {
get_workflow_logs: {
parameters: {
type: 'object',
properties: {
@@ -1168,7 +1168,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['glob']: {
glob: {
parameters: {
type: 'object',
properties: {
@@ -1187,7 +1187,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['grep']: {
grep: {
parameters: {
type: 'object',
properties: {
@@ -1234,7 +1234,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['job']: {
job: {
parameters: {
properties: {
request: {
@@ -1247,7 +1247,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['knowledge']: {
knowledge: {
parameters: {
properties: {
request: {
@@ -1260,7 +1260,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['knowledge_base']: {
knowledge_base: {
parameters: {
type: 'object',
properties: {
@@ -1452,7 +1452,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
required: ['success', 'message'],
},
},
['list_folders']: {
list_folders: {
parameters: {
type: 'object',
properties: {
@@ -1464,14 +1464,14 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['list_user_workspaces']: {
list_user_workspaces: {
parameters: {
type: 'object',
properties: {},
},
resultSchema: undefined,
},
['list_workspace_mcp_servers']: {
list_workspace_mcp_servers: {
parameters: {
type: 'object',
properties: {
@@ -1483,7 +1483,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['manage_credential']: {
manage_credential: {
parameters: {
type: 'object',
properties: {
@@ -1512,7 +1512,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['manage_custom_tool']: {
manage_custom_tool: {
parameters: {
type: 'object',
properties: {
@@ -1591,7 +1591,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['manage_job']: {
manage_job: {
parameters: {
type: 'object',
properties: {
@@ -1661,7 +1661,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['manage_mcp_tool']: {
manage_mcp_tool: {
parameters: {
type: 'object',
properties: {
@@ -1712,7 +1712,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['manage_skill']: {
manage_skill: {
parameters: {
type: 'object',
properties: {
@@ -1744,7 +1744,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['materialize_file']: {
materialize_file: {
parameters: {
type: 'object',
properties: {
@@ -1778,7 +1778,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['move_folder']: {
move_folder: {
parameters: {
type: 'object',
properties: {
@@ -1796,7 +1796,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['move_workflow']: {
move_workflow: {
parameters: {
type: 'object',
properties: {
@@ -1816,7 +1816,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['oauth_get_auth_link']: {
oauth_get_auth_link: {
parameters: {
type: 'object',
properties: {
@@ -1830,7 +1830,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['oauth_request_access']: {
oauth_request_access: {
parameters: {
type: 'object',
properties: {
@@ -1844,7 +1844,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['open_resource']: {
open_resource: {
parameters: {
type: 'object',
properties: {
@@ -1872,7 +1872,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['read']: {
read: {
parameters: {
type: 'object',
properties: {
@@ -1899,7 +1899,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['redeploy']: {
redeploy: {
parameters: {
type: 'object',
properties: {
@@ -1967,7 +1967,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
],
},
},
['rename_file']: {
rename_file: {
parameters: {
type: 'object',
properties: {
@@ -2002,7 +2002,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
required: ['success', 'message'],
},
},
['rename_workflow']: {
rename_workflow: {
parameters: {
type: 'object',
properties: {
@@ -2019,7 +2019,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['research']: {
research: {
parameters: {
properties: {
topic: {
@@ -2032,7 +2032,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['respond']: {
respond: {
parameters: {
additionalProperties: true,
properties: {
@@ -2055,7 +2055,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['restore_resource']: {
restore_resource: {
parameters: {
type: 'object',
properties: {
@@ -2073,7 +2073,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['revert_to_version']: {
revert_to_version: {
parameters: {
type: 'object',
properties: {
@@ -2090,7 +2090,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['run']: {
run: {
parameters: {
properties: {
context: {
@@ -2107,7 +2107,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['run_block']: {
run_block: {
parameters: {
type: 'object',
properties: {
@@ -2139,7 +2139,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['run_from_block']: {
run_from_block: {
parameters: {
type: 'object',
properties: {
@@ -2171,7 +2171,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['run_workflow']: {
run_workflow: {
parameters: {
type: 'object',
properties: {
@@ -2199,7 +2199,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['run_workflow_until_block']: {
run_workflow_until_block: {
parameters: {
type: 'object',
properties: {
@@ -2231,7 +2231,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['scrape_page']: {
scrape_page: {
parameters: {
type: 'object',
properties: {
@@ -2252,7 +2252,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['search_documentation']: {
search_documentation: {
parameters: {
type: 'object',
properties: {
@@ -2269,7 +2269,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['search_library_docs']: {
search_library_docs: {
parameters: {
type: 'object',
properties: {
@@ -2290,7 +2290,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['search_online']: {
search_online: {
parameters: {
type: 'object',
properties: {
@@ -2331,7 +2331,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['search_patterns']: {
search_patterns: {
parameters: {
type: 'object',
properties: {
@@ -2353,7 +2353,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['set_block_enabled']: {
set_block_enabled: {
parameters: {
type: 'object',
properties: {
@@ -2375,7 +2375,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['set_environment_variables']: {
set_environment_variables: {
parameters: {
type: 'object',
properties: {
@@ -2409,7 +2409,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['set_global_workflow_variables']: {
set_global_workflow_variables: {
parameters: {
type: 'object',
properties: {
@@ -2447,7 +2447,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['superagent']: {
superagent: {
parameters: {
properties: {
task: {
@@ -2461,7 +2461,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['table']: {
table: {
parameters: {
properties: {
request: {
@@ -2474,7 +2474,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['tool_search_tool_regex']: {
tool_search_tool_regex: {
parameters: {
properties: {
case_insensitive: {
@@ -2495,7 +2495,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['update_job_history']: {
update_job_history: {
parameters: {
type: 'object',
properties: {
@@ -2513,7 +2513,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['update_workspace_mcp_server']: {
update_workspace_mcp_server: {
parameters: {
type: 'object',
properties: {
@@ -2538,7 +2538,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['user_memory']: {
user_memory: {
parameters: {
type: 'object',
properties: {
@@ -2586,7 +2586,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
resultSchema: undefined,
},
['user_table']: {
user_table: {
parameters: {
type: 'object',
properties: {
@@ -2777,13 +2777,13 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
required: ['success', 'message'],
},
},
['workflow']: {
workflow: {
parameters: {
type: 'object',
},
resultSchema: undefined,
},
['workspace_file']: {
workspace_file: {
parameters: {
type: 'object',
properties: {
@@ -6,6 +6,7 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import type { ExecutionContext, ToolCallResult } from '@/lib/copilot/request/types'
import { env } from '@/lib/core/config/env'
import { generateRequestId } from '@/lib/core/utils/request'
import { getSocketServerUrl } from '@/lib/core/utils/urls'
import { generateId } from '@/lib/core/utils/uuid'
import { executeWorkflow } from '@/lib/workflows/executor/execute-workflow'
import {
@@ -147,8 +148,7 @@ function findDescendants(containerId: string, blocksById: Record<string, BlockSt
}
function notifyWorkflowUpdated(workflowId: string): void {
const socketUrl = env.SOCKET_SERVER_URL || 'http://localhost:3002'
fetch(`${socketUrl}/api/workflow-updated`, {
fetch(`${getSocketServerUrl()}/api/workflow-updated`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -9,6 +9,7 @@ import {
type ServerToolContext,
} from '@/lib/copilot/tools/server/base-tool'
import { env } from '@/lib/core/config/env'
import { getSocketServerUrl } from '@/lib/core/utils/urls'
import {
applyTargetedLayout,
getTargetedLayoutImpact,
@@ -284,8 +285,7 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, unknown>
logger.info('Workflow state persisted to database', { workflowId })
const socketUrl = env.SOCKET_SERVER_URL || 'http://localhost:3002'
fetch(`${socketUrl}/api/workflow-updated`, {
fetch(`${getSocketServerUrl()}/api/workflow-updated`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
+17 -13
View File
@@ -3,8 +3,19 @@ import { isDev, isHosted, isReactGrabEnabled } from '../config/feature-flags'
/**
* Content Security Policy (CSP) configuration builder
*
* NOTE: This file is loaded by next.config.ts at build time, before @/ path
* aliases are resolved. Do NOT import from ../utils/urls (which uses @/ imports).
* Keep all URL constants local to this file.
*/
const DEFAULT_SOCKET_URL = 'http://localhost:3002'
const DEFAULT_OLLAMA_URL = 'http://localhost:11434'
function toWebSocketUrl(httpUrl: string): string {
return httpUrl.replace('http://', 'ws://').replace('https://', 'wss://')
}
function getHostnameFromUrl(url: string | undefined): string[] {
if (!url) return []
try {
@@ -156,14 +167,11 @@ export const buildTimeCSPDirectives: CSPDirectives = {
'connect-src': [
...STATIC_CONNECT_SRC,
env.NEXT_PUBLIC_APP_URL || '',
...(env.OLLAMA_URL ? [env.OLLAMA_URL] : isDev ? ['http://localhost:11434'] : []),
...(env.OLLAMA_URL ? [env.OLLAMA_URL] : isDev ? [DEFAULT_OLLAMA_URL] : []),
...(env.NEXT_PUBLIC_SOCKET_URL
? [
env.NEXT_PUBLIC_SOCKET_URL,
env.NEXT_PUBLIC_SOCKET_URL.replace('http://', 'ws://').replace('https://', 'wss://'),
]
? [env.NEXT_PUBLIC_SOCKET_URL, toWebSocketUrl(env.NEXT_PUBLIC_SOCKET_URL)]
: isDev
? ['http://localhost:3002', 'ws://localhost:3002']
? [DEFAULT_SOCKET_URL, toWebSocketUrl(DEFAULT_SOCKET_URL)]
: []),
...getHostnameFromUrl(env.NEXT_PUBLIC_BRAND_LOGO_URL),
...getHostnameFromUrl(env.NEXT_PUBLIC_PRIVACY_URL),
@@ -201,13 +209,9 @@ export function buildCSPString(directives: CSPDirectives): string {
export function generateRuntimeCSP(): string {
const appUrl = getEnv('NEXT_PUBLIC_APP_URL') || ''
const socketUrl = getEnv('NEXT_PUBLIC_SOCKET_URL') || (isDev ? 'http://localhost:3002' : '')
const socketWsUrl = socketUrl
? socketUrl.replace('http://', 'ws://').replace('https://', 'wss://')
: isDev
? 'ws://localhost:3002'
: ''
const ollamaUrl = getEnv('OLLAMA_URL') || (isDev ? 'http://localhost:11434' : '')
const socketUrl = getEnv('NEXT_PUBLIC_SOCKET_URL') || (isDev ? DEFAULT_SOCKET_URL : '')
const socketWsUrl = socketUrl ? toWebSocketUrl(socketUrl) : ''
const ollamaUrl = getEnv('OLLAMA_URL') || (isDev ? DEFAULT_OLLAMA_URL : '')
const brandLogoDomains = getHostnameFromUrl(getEnv('NEXT_PUBLIC_BRAND_LOGO_URL'))
const brandFaviconDomains = getHostnameFromUrl(getEnv('NEXT_PUBLIC_BRAND_FAVICON_URL'))
+28 -1
View File
@@ -1,4 +1,4 @@
import { getEnv } from '@/lib/core/config/env'
import { env, getEnv } from '@/lib/core/config/env'
import { isProd } from '@/lib/core/config/feature-flags'
/** Canonical base URL for the public-facing marketing site. No trailing slash. */
@@ -100,3 +100,30 @@ export function getEmailDomain(): string {
return isProd ? 'sim.ai' : 'localhost:3000'
}
}
const DEFAULT_SOCKET_URL = 'http://localhost:3002'
const DEFAULT_OLLAMA_URL = 'http://localhost:11434'
/**
* Returns the socket server URL for server-side internal API calls.
* Reads from SOCKET_SERVER_URL with a localhost fallback for development.
*/
export function getSocketServerUrl(): string {
return env.SOCKET_SERVER_URL || DEFAULT_SOCKET_URL
}
/**
* Returns the socket server URL for client-side Socket.IO connections.
* Reads from NEXT_PUBLIC_SOCKET_URL with a localhost fallback for development.
*/
export function getSocketUrl(): string {
return getEnv('NEXT_PUBLIC_SOCKET_URL') || DEFAULT_SOCKET_URL
}
/**
* Returns the Ollama server URL.
* Reads from OLLAMA_URL with a localhost fallback for development.
*/
export function getOllamaUrl(): string {
return env.OLLAMA_URL || DEFAULT_OLLAMA_URL
}
+5
View File
@@ -56,6 +56,11 @@ vi.mock('@/lib/core/config/env', () => ({
SOCKET_SERVER_URL: 'http://socket.test',
INTERNAL_API_SECRET: 'secret',
},
getEnv: vi.fn(),
}))
vi.mock('@/lib/core/utils/urls', () => ({
getSocketServerUrl: vi.fn().mockReturnValue('http://socket.test'),
}))
vi.mock('@/lib/core/telemetry', () => ({
+2 -2
View File
@@ -18,6 +18,7 @@ import { env } from '@/lib/core/config/env'
import { getRedisClient } from '@/lib/core/config/redis'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
import { getSocketServerUrl } from '@/lib/core/utils/urls'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { getWorkflowById } from '@/lib/workflows/utils'
@@ -31,8 +32,7 @@ interface ArchiveWorkflowOptions {
async function notifyWorkflowArchived(workflowId: string, requestId: string): Promise<void> {
try {
const socketUrl = env.SOCKET_SERVER_URL || 'http://localhost:3002'
const socketResponse = await fetch(`${socketUrl}/api/workflow-deleted`, {
const socketResponse = await fetch(`${getSocketServerUrl()}/api/workflow-deleted`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
+32 -3
View File
@@ -5,7 +5,7 @@ import { NextRequest } from 'next/server'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { env } from '@/lib/core/config/env'
import { generateRequestId } from '@/lib/core/utils/request'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { getBaseUrl, getSocketServerUrl } from '@/lib/core/utils/urls'
import { removeMcpToolsForWorkflow, syncMcpToolsForWorkflow } from '@/lib/mcp/workflow-mcp-sync'
import { captureServerEvent } from '@/lib/posthog/server'
import {
@@ -31,6 +31,30 @@ import type { WorkflowState } from '@/stores/workflows/workflow/types'
const logger = createLogger('DeployOrchestration')
/**
* Notifies the socket server that a workflow's deployment state has changed,
* so all connected clients can refresh their deployment queries.
*/
export async function notifySocketDeploymentChanged(workflowId: string): Promise<void> {
try {
const response = await fetch(`${getSocketServerUrl()}/api/workflow-deployed`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': env.INTERNAL_API_SECRET,
},
body: JSON.stringify({ workflowId }),
})
if (!response.ok) {
logger.warn(
`Socket deployment notification failed (${response.status}) for workflow ${workflowId}`
)
}
} catch (error) {
logger.error('Error sending workflow deployed event to socket server', error)
}
}
export interface PerformFullDeployParams {
workflowId: string
userId: string
@@ -222,6 +246,8 @@ export async function performFullDeploy(
request,
})
await notifySocketDeploymentChanged(workflowId)
return {
success: true,
deployedAt,
@@ -296,6 +322,8 @@ export async function performFullUndeploy(
description: `Undeployed workflow "${(workflowData.name as string) || workflowId}"`,
})
await notifySocketDeploymentChanged(workflowId)
return { success: true }
}
@@ -509,6 +537,8 @@ export async function performActivateVersion(
},
})
await notifySocketDeploymentChanged(workflowId)
return {
success: true,
deployedAt: result.deployedAt,
@@ -596,8 +626,7 @@ export async function performRevertToVersion(
.where(eq(workflowTable.id, workflowId))
try {
const socketServerUrl = env.SOCKET_SERVER_URL || 'http://localhost:3002'
await fetch(`${socketServerUrl}/api/workflow-reverted`, {
await fetch(`${getSocketServerUrl()}/api/workflow-reverted`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -7,6 +7,7 @@ export {
performChatUndeploy,
} from './chat-deploy'
export {
notifySocketDeploymentChanged,
type PerformActivateVersionParams,
type PerformActivateVersionResult,
type PerformFullDeployParams,
+2 -2
View File
@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger'
import OpenAI from 'openai'
import type { ChatCompletionCreateParamsStreaming } from 'openai/resources/chat/completions'
import { env } from '@/lib/core/config/env'
import { getOllamaUrl } from '@/lib/core/utils/urls'
import type { StreamingExecution } from '@/executor/types'
import { MAX_TOOL_ITERATIONS } from '@/providers'
import type { ModelsObject } from '@/providers/ollama/types'
@@ -18,7 +18,7 @@ import { useProvidersStore } from '@/stores/providers'
import { executeTool } from '@/tools'
const logger = createLogger('OllamaProvider')
const OLLAMA_HOST = env.OLLAMA_URL || 'http://localhost:11434'
const OLLAMA_HOST = getOllamaUrl()
export const ollamaProvider: ProviderConfig = {
id: 'ollama',
+17
View File
@@ -238,4 +238,21 @@ export class MemoryRoomManager implements IRoomManager {
logger.info(`Notified ${room.users.size} users about workflow update: ${workflowId}`)
}
async handleWorkflowDeployed(workflowId: string): Promise<void> {
logger.info(`Handling workflow deployed notification for ${workflowId}`)
const room = this.workflowRooms.get(workflowId)
if (!room) {
logger.debug(`No active room found for deployed workflow ${workflowId}`)
return
}
this._io.to(workflowId).emit('workflow-deployed', {
workflowId,
timestamp: Date.now(),
})
logger.info(`Notified ${room.users.size} users about workflow deployment change: ${workflowId}`)
}
}
+18
View File
@@ -439,4 +439,22 @@ export class RedisRoomManager implements IRoomManager {
const userCount = await this.getUniqueUserCount(workflowId)
logger.info(`Notified ${userCount} users about workflow update: ${workflowId}`)
}
async handleWorkflowDeployed(workflowId: string): Promise<void> {
logger.info(`Handling workflow deployed notification for ${workflowId}`)
const hasRoom = await this.hasWorkflowRoom(workflowId)
if (!hasRoom) {
logger.debug(`No active room found for deployed workflow ${workflowId}`)
return
}
this._io.to(workflowId).emit('workflow-deployed', {
workflowId,
timestamp: Date.now(),
})
const userCount = await this.getUniqueUserCount(workflowId)
logger.info(`Notified ${userCount} users about workflow deployment change: ${workflowId}`)
}
}
+5
View File
@@ -138,4 +138,9 @@ export interface IRoomManager {
* Handle workflow update - notify users
*/
handleWorkflowUpdate(workflowId: string): Promise<void>
/**
* Handle workflow deployment change - notify users to refresh deployment state
*/
handleWorkflowDeployed(workflowId: string): Promise<void>
}
+14
View File
@@ -122,6 +122,20 @@ export function createHttpHandler(roomManager: IRoomManager, logger: Logger) {
return
}
// Handle workflow deployment change notifications from the main API
if (req.method === 'POST' && req.url === '/api/workflow-deployed') {
try {
const body = await readRequestBody(req)
const { workflowId } = JSON.parse(body)
await roomManager.handleWorkflowDeployed(workflowId)
sendSuccess(res)
} catch (error) {
logger.error('Error handling workflow deployed notification:', error)
sendError(res, 'Failed to process deployment notification')
}
return
}
// Handle workflow revert notifications from the main API
if (req.method === 'POST' && req.url === '/api/workflow-reverted') {
try {