Compare commits

...

1 Commits

Author SHA1 Message Date
-LAN-
b2bb7cdd43 fix: preserve webapp workflow SSE reconnect 2026-03-17 08:32:51 +08:00
4 changed files with 40 additions and 4 deletions

View File

@@ -328,7 +328,7 @@ describe('createWorkflowStreamHandlers', () => {
vi.clearAllMocks()
})
const setupHandlers = (overrides: { isTimedOut?: () => boolean } = {}) => {
const setupHandlers = (overrides: { isPublicAPI?: boolean, isTimedOut?: () => boolean } = {}) => {
let completionRes = ''
let currentTaskId: string | null = null
let isStopping = false
@@ -359,6 +359,7 @@ describe('createWorkflowStreamHandlers', () => {
const handlers = createWorkflowStreamHandlers({
getCompletionRes: () => completionRes,
getWorkflowProcessData: () => workflowProcessData,
isPublicAPI: overrides.isPublicAPI ?? false,
isTimedOut: overrides.isTimedOut ?? (() => false),
markEnded,
notify,
@@ -391,7 +392,7 @@ describe('createWorkflowStreamHandlers', () => {
}
it('should process workflow success and paused events', () => {
const setup = setupHandlers()
const setup = setupHandlers({ isPublicAPI: true })
const handlers = setup.handlers as Required<Pick<IOtherOptions, 'onWorkflowStarted' | 'onTextChunk' | 'onHumanInputRequired' | 'onHumanInputFormFilled' | 'onHumanInputFormTimeout' | 'onWorkflowPaused' | 'onWorkflowFinished' | 'onNodeStarted' | 'onNodeFinished' | 'onIterationStart' | 'onIterationNext' | 'onIterationFinish' | 'onLoopStart' | 'onLoopNext' | 'onLoopFinish'>>
act(() => {
@@ -546,7 +547,11 @@ describe('createWorkflowStreamHandlers', () => {
resultText: 'Hello',
status: WorkflowRunningStatus.Succeeded,
}))
expect(sseGetMock).toHaveBeenCalledWith('/workflow/run-1/events', {}, expect.any(Object))
expect(sseGetMock).toHaveBeenCalledWith(
'/workflow/run-1/events',
{},
expect.objectContaining({ isPublicAPI: true }),
)
expect(setup.messageId()).toBe('run-1')
expect(setup.onCompleted).toHaveBeenCalledWith('{"answer":"Hello"}', 3, true)
expect(setup.setRespondingFalse).toHaveBeenCalled()
@@ -647,6 +652,7 @@ describe('createWorkflowStreamHandlers', () => {
const handlers = createWorkflowStreamHandlers({
getCompletionRes: () => '',
getWorkflowProcessData: () => existingProcess,
isPublicAPI: false,
isTimedOut: () => false,
markEnded: vi.fn(),
notify: setup.notify,

View File

@@ -351,6 +351,7 @@ describe('useResultSender', () => {
await waitFor(() => {
expect(createWorkflowStreamHandlersMock).toHaveBeenCalledWith(expect.objectContaining({
getCompletionRes: harness.runState.getCompletionRes,
isPublicAPI: true,
resetRunState: harness.runState.resetRunState,
setWorkflowProcessData: harness.runState.setWorkflowProcessData,
}))
@@ -373,6 +374,30 @@ describe('useResultSender', () => {
expect(harness.runState.clearMoreLikeThis).not.toHaveBeenCalled()
})
it('should configure workflow handlers for installed apps as non-public', async () => {
const harness = createRunStateHarness()
const { result } = renderSender({
appSourceType: AppSourceTypeEnum.installedApp,
isWorkflow: true,
runState: harness.runState,
})
await act(async () => {
expect(await result.current.handleSend()).toBe(true)
})
expect(createWorkflowStreamHandlersMock).toHaveBeenCalledWith(expect.objectContaining({
isPublicAPI: false,
}))
expect(sendWorkflowMessageMock).toHaveBeenCalledWith(
{ inputs: { name: 'Alice' } },
expect.any(Object),
AppSourceTypeEnum.installedApp,
'app-1',
)
})
it('should stringify non-Error workflow failures', async () => {
const harness = createRunStateHarness()
sendWorkflowMessageMock.mockRejectedValue('workflow failed')

View File

@@ -1,11 +1,11 @@
import type { ResultInputValue } from '../result-request'
import type { ResultRunStateController } from './use-result-run-state'
import type { PromptConfig } from '@/models/debug'
import type { AppSourceType } from '@/service/share'
import type { VisionFile, VisionSettings } from '@/types/app'
import { useCallback, useEffect, useRef } from 'react'
import { TEXT_GENERATION_TIMEOUT_MS } from '@/config'
import {
AppSourceType,
sendCompletionMessage,
sendWorkflowMessage,
} from '@/service/share'
@@ -117,6 +117,7 @@ export const useResultSender = ({
const otherOptions = createWorkflowStreamHandlers({
getCompletionRes: runState.getCompletionRes,
getWorkflowProcessData: runState.getWorkflowProcessData,
isPublicAPI: appSourceType === AppSourceType.webApp,
isTimedOut: () => isTimeout,
markEnded: () => {
isEnd = true

View File

@@ -13,6 +13,7 @@ type Translate = (key: string, options?: Record<string, unknown>) => string
type CreateWorkflowStreamHandlersParams = {
getCompletionRes: () => string
getWorkflowProcessData: () => WorkflowProcess | undefined
isPublicAPI: boolean
isTimedOut: () => boolean
markEnded: () => void
notify: Notify
@@ -255,6 +256,7 @@ const serializeWorkflowOutputs = (outputs: WorkflowFinishedResponse['data']['out
export const createWorkflowStreamHandlers = ({
getCompletionRes,
getWorkflowProcessData,
isPublicAPI,
isTimedOut,
markEnded,
notify,
@@ -287,6 +289,7 @@ export const createWorkflowStreamHandlers = ({
}
const otherOptions: IOtherOptions = {
isPublicAPI,
onWorkflowStarted: ({ workflow_run_id, task_id }) => {
const workflowProcessData = getWorkflowProcessData()
if (workflowProcessData?.tracing.length) {
@@ -378,6 +381,7 @@ export const createWorkflowStreamHandlers = ({
},
onWorkflowPaused: ({ data }) => {
tempMessageId = data.workflow_run_id
// WebApp workflows must keep using the public API namespace after pause/resume.
void sseGet(`/workflow/${data.workflow_run_id}/events`, {}, otherOptions)
setWorkflowProcessData(applyWorkflowPaused(getWorkflowProcessData()))
},