diff --git a/api/controllers/console/app/agent_app_access.py b/api/controllers/console/app/agent_app_access.py index aa236de158..4e79beac59 100644 --- a/api/controllers/console/app/agent_app_access.py +++ b/api/controllers/console/app/agent_app_access.py @@ -23,8 +23,13 @@ from services.agent.roster_service import AgentRosterService class AgentReferencingWorkflowResponse(ResponseModel): app_id: str app_name: str + app_icon_type: str | None = None + app_icon: str | None = None + app_icon_background: str | None = None app_mode: str + app_updated_at: int | None = None workflow_id: str + workflow_version: str node_ids: list[str] = Field(default_factory=list) diff --git a/api/fields/agent_fields.py b/api/fields/agent_fields.py index ce4854ce36..0f22fd9a86 100644 --- a/api/fields/agent_fields.py +++ b/api/fields/agent_fields.py @@ -44,7 +44,11 @@ class AgentConfigSnapshotSummaryResponse(ResponseModel): class AgentPublishedReferenceResponse(ResponseModel): app_id: str app_name: str + app_icon_type: str | None = None + app_icon: str | None = None + app_icon_background: str | None = None app_mode: str + app_updated_at: int | None = None workflow_id: str workflow_version: str node_ids: list[str] = Field(default_factory=list) diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md index 544b81fec4..4a737dbbdd 100644 --- a/api/openapi/markdown/console-openapi.md +++ b/api/openapi/markdown/console-openapi.md @@ -11864,9 +11864,13 @@ the current roster/workflow APIs scoped to Dify Agent. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | +| app_icon | string | | No | +| app_icon_background | string | | No | +| app_icon_type | string | | No | | app_id | string | | Yes | | app_mode | string | | Yes | | app_name | string | | Yes | +| app_updated_at | integer | | No | | node_ids | [ string ] | | No | | workflow_id | string | | Yes | | workflow_version | string | | Yes | @@ -11875,11 +11879,16 @@ the current roster/workflow APIs scoped to Dify Agent. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | +| app_icon | string | | No | +| app_icon_background | string | | No | +| app_icon_type | string | | No | | app_id | string | | Yes | | app_mode | string | | Yes | | app_name | string | | Yes | +| app_updated_at | integer | | No | | node_ids | [ string ] | | No | | workflow_id | string | | Yes | +| workflow_version | string | | Yes | #### AgentReferencingWorkflowsResponse diff --git a/api/services/agent/roster_service.py b/api/services/agent/roster_service.py index f1001637fc..ed6a24ac1d 100644 --- a/api/services/agent/roster_service.py +++ b/api/services/agent/roster_service.py @@ -37,7 +37,11 @@ class AgentReferencingWorkflow(TypedDict): app_id: str app_name: str + app_icon_type: str | None + app_icon: str | None + app_icon_background: str | None app_mode: str + app_updated_at: int | None workflow_id: str workflow_version: str node_ids: list[str] @@ -557,7 +561,11 @@ class AgentRosterService: AgentReferencingWorkflow( app_id=binding.app_id, app_name=app.name, + app_icon_type=(icon_type.value if (icon_type := getattr(app, "icon_type", None)) else None), + app_icon=getattr(app, "icon", None), + app_icon_background=getattr(app, "icon_background", None), app_mode=str(app.mode), + app_updated_at=to_timestamp(getattr(app, "updated_at", None)), workflow_id=binding.workflow_id, workflow_version=binding.workflow_version, node_ids=[], @@ -570,7 +578,7 @@ class AgentRosterService: references = list(by_workflow.values()) for reference in references: reference["node_ids"] = sorted(set(reference["node_ids"])) - references.sort(key=lambda item: (item["app_name"].lower(), item["workflow_id"])) + references.sort(key=lambda item: (-(item["app_updated_at"] or 0), item["app_name"].lower())) result[agent_id] = references return result diff --git a/api/tests/unit_tests/services/agent/test_agent_services.py b/api/tests/unit_tests/services/agent/test_agent_services.py index b14782c498..fc219e45dd 100644 --- a/api/tests/unit_tests/services/agent/test_agent_services.py +++ b/api/tests/unit_tests/services/agent/test_agent_services.py @@ -697,6 +697,62 @@ def test_invite_options_uses_db_filtered_pagination(monkeypatch): assert [item["id"] for item in result["data"]] == ["agent-2"] +def test_published_references_include_app_display_fields_and_sort_by_updated_at(): + recent_updated_at = datetime(2026, 1, 7, 3, 4, 5, tzinfo=UTC) + stale_updated_at = datetime(2026, 1, 6, 3, 4, 5, tzinfo=UTC) + bindings = [ + SimpleNamespace( + tenant_id="tenant-1", + agent_id="agent-1", + app_id="app-stale", + workflow_id="workflow-stale", + workflow_version="published-stale", + node_id="node-b", + ), + SimpleNamespace( + tenant_id="tenant-1", + agent_id="agent-1", + app_id="app-recent", + workflow_id="workflow-recent", + workflow_version="published-recent", + node_id="node-a", + ), + ] + apps = [ + SimpleNamespace( + id="app-stale", + name="Stale Workflow", + mode="advanced-chat", + workflow_id="workflow-stale", + icon_type=SimpleNamespace(value="emoji"), + icon="old", + icon_background="#F3F4F6", + updated_at=stale_updated_at, + ), + SimpleNamespace( + id="app-recent", + name="Recent Workflow", + mode="advanced-chat", + workflow_id="workflow-recent", + icon_type=SimpleNamespace(value="image"), + icon="upload-file-id", + icon_background="#E0F2FE", + updated_at=recent_updated_at, + ), + ] + service = AgentRosterService(FakeSession(scalars=[bindings, apps])) + + result = service._load_published_references_by_agent_id(tenant_id="tenant-1", agent_ids=["agent-1"]) + + references = result["agent-1"] + assert [item["app_id"] for item in references] == ["app-recent", "app-stale"] + assert references[0]["app_icon_type"] == "image" + assert references[0]["app_icon"] == "upload-file-id" + assert references[0]["app_icon_background"] == "#E0F2FE" + assert references[0]["app_updated_at"] == int(recent_updated_at.timestamp()) + assert references[0]["workflow_version"] == "published-recent" + + def test_roster_update_archive_versions_and_detail(monkeypatch): listed_version = AgentConfigSnapshot(id="version-2", agent_id="agent-1", version=2) listed_version_created_at = datetime(2026, 1, 5, 3, 4, 5, tzinfo=UTC) diff --git a/packages/contracts/generated/api/console/agent/types.gen.ts b/packages/contracts/generated/api/console/agent/types.gen.ts index 750834f123..08a470b8c5 100644 --- a/packages/contracts/generated/api/console/agent/types.gen.ts +++ b/packages/contracts/generated/api/console/agent/types.gen.ts @@ -579,11 +579,16 @@ export type MessageFile = { } export type AgentReferencingWorkflowResponse = { + app_icon?: string | null + app_icon_background?: string | null + app_icon_type?: string | null app_id: string app_mode: string app_name: string + app_updated_at?: number | null node_ids?: Array workflow_id: string + workflow_version: string } export type SandboxFileEntryResponse = { @@ -658,9 +663,13 @@ export type AgentKind = 'dify_agent' export type AgentIconType = 'emoji' | 'image' | 'link' export type AgentPublishedReferenceResponse = { + app_icon?: string | null + app_icon_background?: string | null + app_icon_type?: string | null app_id: string app_mode: string app_name: string + app_updated_at?: number | null node_ids?: Array workflow_id: string workflow_version: string diff --git a/packages/contracts/generated/api/console/agent/zod.gen.ts b/packages/contracts/generated/api/console/agent/zod.gen.ts index b79f2ba9f2..8d5ca65443 100644 --- a/packages/contracts/generated/api/console/agent/zod.gen.ts +++ b/packages/contracts/generated/api/console/agent/zod.gen.ts @@ -356,11 +356,16 @@ export const zMessageFile = z.object({ * AgentReferencingWorkflowResponse */ export const zAgentReferencingWorkflowResponse = z.object({ + app_icon: z.string().nullish(), + app_icon_background: z.string().nullish(), + app_icon_type: z.string().nullish(), app_id: z.string(), app_mode: z.string(), app_name: z.string(), + app_updated_at: z.int().nullish(), node_ids: z.array(z.string()).optional(), workflow_id: z.string(), + workflow_version: z.string(), }) /** @@ -572,9 +577,13 @@ export const zAgentIconType = z.enum(['emoji', 'image', 'link']) * AgentPublishedReferenceResponse */ export const zAgentPublishedReferenceResponse = z.object({ + app_icon: z.string().nullish(), + app_icon_background: z.string().nullish(), + app_icon_type: z.string().nullish(), app_id: z.string(), app_mode: z.string(), app_name: z.string(), + app_updated_at: z.int().nullish(), node_ids: z.array(z.string()).optional(), workflow_id: z.string(), workflow_version: z.string(),