Compare commits

...

2 Commits

Author SHA1 Message Date
QuantumGhost
f943f1e39b chore: bump version to 1.13.1 (#33505) 2026-03-17 18:31:12 +08:00
-LAN-
239e09473e fix(web): preserve public workflow SSE reconnect after pause (#33552) 2026-03-17 16:41:08 +08:00
9 changed files with 51 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
[project]
name = "dify-api"
version = "1.13.0"
version = "1.13.1"
requires-python = ">=3.11,<3.13"
dependencies = [

2
api/uv.lock generated
View File

@@ -1533,7 +1533,7 @@ wheels = [
[[package]]
name = "dify-api"
version = "1.13.0"
version = "1.13.1"
source = { virtual = "." }
dependencies = [
{ name = "aliyun-log-python-sdk" },

View File

@@ -21,7 +21,7 @@ services:
# API service
api:
image: langgenius/dify-api:1.13.0
image: langgenius/dify-api:1.13.1
restart: always
environment:
# Use the shared environment variables.
@@ -63,7 +63,7 @@ services:
# worker service
# The Celery worker for processing all queues (dataset, workflow, mail, etc.)
worker:
image: langgenius/dify-api:1.13.0
image: langgenius/dify-api:1.13.1
restart: always
environment:
# Use the shared environment variables.
@@ -102,7 +102,7 @@ services:
# worker_beat service
# Celery beat for scheduling periodic tasks.
worker_beat:
image: langgenius/dify-api:1.13.0
image: langgenius/dify-api:1.13.1
restart: always
environment:
# Use the shared environment variables.
@@ -132,7 +132,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:1.13.0
image: langgenius/dify-web:1.13.1
restart: always
environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-}

View File

@@ -728,7 +728,7 @@ services:
# API service
api:
image: langgenius/dify-api:1.13.0
image: langgenius/dify-api:1.13.1
restart: always
environment:
# Use the shared environment variables.
@@ -770,7 +770,7 @@ services:
# worker service
# The Celery worker for processing all queues (dataset, workflow, mail, etc.)
worker:
image: langgenius/dify-api:1.13.0
image: langgenius/dify-api:1.13.1
restart: always
environment:
# Use the shared environment variables.
@@ -809,7 +809,7 @@ services:
# worker_beat service
# Celery beat for scheduling periodic tasks.
worker_beat:
image: langgenius/dify-api:1.13.0
image: langgenius/dify-api:1.13.1
restart: always
environment:
# Use the shared environment variables.
@@ -839,7 +839,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:1.13.0
image: langgenius/dify-web:1.13.1
restart: always
environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-}

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()))
},

View File

@@ -1,7 +1,7 @@
{
"name": "dify-web",
"type": "module",
"version": "1.13.0",
"version": "1.13.1",
"private": true,
"packageManager": "pnpm@10.32.1",
"imports": {