fix(agent): expose publish state from composer (#39615)

This commit is contained in:
zyssyz123 2026-07-27 12:05:38 +08:00 committed by GitHub
parent d563d6e7df
commit cc01189966
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 39 additions and 36 deletions

View File

@ -257,7 +257,6 @@ class AgentAppDetailWithSite(GenericAppDetailWithSite):
debug_conversation_has_messages: bool = False
debug_conversation_message_count: int = 0
role: str | None = None
active_config_is_published: bool = False
class AgentDebugConversationRefreshResponse(BaseModel):
@ -410,10 +409,6 @@ def _serialize_agent_app_detail(
payload["debug_conversation_has_messages"] = message_count > 0
payload["debug_conversation_message_count"] = message_count
payload["role"] = agent.role or ""
payload["active_config_is_published"] = roster_service.active_config_is_published(
tenant_id=app_model.tenant_id,
agent=agent,
)
return payload

View File

@ -383,6 +383,7 @@ class AgentAppComposerResponse(ResponseModel):
variant: Literal[ComposerVariant.AGENT_APP]
agent: AgentComposerAgentResponse
active_config_snapshot: AgentConfigSnapshotSummaryResponse | None = None
active_config_is_published: bool
draft: AgentConfigDraftSummaryResponse | None = None
agent_soul: AgentSoulConfig
save_options: list[ComposerSaveStrategy]

View File

@ -13243,6 +13243,7 @@ Model class for AI model.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| active_config_is_published | boolean | | Yes |
| active_config_snapshot | [AgentConfigSnapshotSummaryResponse](#agentconfigsnapshotsummaryresponse) | | No |
| agent | [AgentComposerAgentResponse](#agentcomposeragentresponse) | | Yes |
| agent_soul | [AgentSoulConfig](#agentsoulconfig) | | Yes |
@ -13282,7 +13283,6 @@ Model class for AI model.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| access_mode | string | | No |
| active_config_is_published | boolean | | No |
| api_base_url | string | | No |
| app_id | string | | No |
| backing_app_id | string | | No |

View File

@ -405,6 +405,7 @@ class AgentComposerService:
"variant": ComposerVariant.AGENT_APP.value,
"agent": cls._serialize_agent(agent),
"active_config_snapshot": cls._serialize_version(version),
"active_config_is_published": bool(agent.active_config_snapshot_id and agent.active_config_is_published),
"draft": cls._serialize_draft(draft),
"agent_soul": draft.config_snapshot_dict,
"save_options": [ComposerSaveStrategy.SAVE_TO_CURRENT_VERSION.value],

View File

@ -115,6 +115,7 @@ def _agent_app_composer_response() -> dict:
"active_config_snapshot_id": "version-1",
},
"active_config_snapshot": _version_response(),
"active_config_is_published": True,
"agent_soul": {},
"save_options": ["save_to_current_version"],
}
@ -376,7 +377,7 @@ def test_agent_app_list_and_create_use_agent_route(
assert created["app_id"] == "app-created"
assert created["debug_conversation_id"] == "debug-conversation-created"
assert created["role"] == "Created role"
assert created["active_config_is_published"] is False
assert "active_config_is_published" not in created
assert "bound_agent_id" not in created
create_call = cast(dict[str, object], captured["create"])
create_params = cast(Any, create_call["params"])
@ -487,7 +488,7 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id(
assert detail["debug_conversation_has_messages"] is True
assert detail["debug_conversation_message_count"] == 2
assert detail["role"] == "Resolved role"
assert detail["active_config_is_published"] is False
assert "active_config_is_published" not in detail
assert "bound_agent_id" not in detail
assert captured["get_app"] == {"app": app_model, "session": session}
with app.test_request_context(
@ -502,7 +503,7 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id(
assert updated["debug_conversation_has_messages"] is True
assert updated["debug_conversation_message_count"] == 2
assert updated["role"] == "Resolved role"
assert updated["active_config_is_published"] is False
assert "active_config_is_published" not in updated
assert "bound_agent_id" not in updated
update_call = cast(dict[str, object], captured["update"])
assert update_call["app"] is app_model
@ -845,9 +846,6 @@ def test_agent_app_update_allows_empty_role(app: Flask, monkeypatch: pytest.Monk
monkeypatch.setattr(
roster_controller.AgentRosterService, "count_agent_app_debug_conversation_messages", lambda _self, **kwargs: 0
)
monkeypatch.setattr(
roster_controller.AgentRosterService, "active_config_is_published", lambda _self, **kwargs: False
)
monkeypatch.setattr(
roster_controller.FeatureService,
"get_system_features",
@ -1299,13 +1297,14 @@ def test_agent_composer_routes_resolve_app_from_agent_id(
composer_controller.AgentComposerService, "collect_validation_findings", collect_validation_findings
)
monkeypatch.setattr(composer_controller.AgentComposerService, "get_agent_app_candidates", get_agent_app_candidates)
assert unwrap(AgentComposerApi.get)(AgentComposerApi(), MagicMock(), "tenant-1", agent_id)["variant"] == "agent_app"
composer = unwrap(AgentComposerApi.get)(AgentComposerApi(), MagicMock(), "tenant-1", agent_id)
assert composer["variant"] == "agent_app"
assert composer["active_config_is_published"] is True
assert cast(dict[str, object], captured["load"])["agent_id"] == agent_id
with app.test_request_context(json=payload):
assert (
unwrap(AgentComposerApi.put)(AgentComposerApi(), MagicMock(), "tenant-1", account_id, agent_id)["variant"]
== "agent_app"
)
saved_composer = unwrap(AgentComposerApi.put)(AgentComposerApi(), MagicMock(), "tenant-1", account_id, agent_id)
assert saved_composer["variant"] == "agent_app"
assert saved_composer["active_config_is_published"] is True
assert cast(dict[str, object], captured["save"])["agent_id"] == agent_id
assert unwrap(AgentComposerValidateApi.post)(AgentComposerValidateApi(), MagicMock(), "tenant-1", agent_id) == {
"result": "success",

View File

@ -548,6 +548,7 @@ def test_load_agent_app_composer_exposes_draft_save_only(monkeypatch: pytest.Mon
agent = SimpleNamespace(
id="agent-1",
active_config_snapshot_id="version-1",
active_config_is_published=True,
updated_by="account-1",
created_by="account-1",
app_id="app-1",
@ -567,6 +568,7 @@ def test_load_agent_app_composer_exposes_draft_save_only(monkeypatch: pytest.Mon
result = AgentComposerService.load_agent_app_composer(session=session, tenant_id="tenant-1", app_id="app-1")
assert result["save_options"] == [ComposerSaveStrategy.SAVE_TO_CURRENT_VERSION.value]
assert result["active_config_is_published"] is True
def test_save_agent_app_composer_rejects_version_save_strategy():
@ -610,7 +612,11 @@ def test_save_agent_app_composer_updates_normal_draft(monkeypatch: pytest.Monkey
lambda **kwargs: saved.update(kwargs) or SimpleNamespace(id="draft-1"),
)
monkeypatch.setattr(AgentComposerService, "_get_version_if_present", lambda **_kwargs: active_version)
monkeypatch.setattr(AgentComposerService, "load_agent_composer", lambda **kwargs: {"loaded": True})
monkeypatch.setattr(
AgentComposerService,
"load_agent_composer",
lambda **kwargs: {"loaded": True, "active_config_is_published": agent.active_config_is_published},
)
payload = ComposerSavePayload.model_validate(
{
"variant": ComposerVariant.AGENT_APP.value,
@ -628,7 +634,7 @@ def test_save_agent_app_composer_updates_normal_draft(monkeypatch: pytest.Monkey
)
assert result.pop("validation") == {"warnings": [], "knowledge_retrieval_placeholder": []}
assert result == {"loaded": True}
assert result == {"loaded": True, "active_config_is_published": False}
assert saved["draft_type"] == AgentConfigDraftType.DRAFT
assert saved["agent_soul"].model_dump(mode="json") == _agent_soul_with_model().model_dump(mode="json")
assert agent.active_config_is_published is False
@ -657,7 +663,11 @@ def test_save_agent_app_composer_keeps_published_when_draft_matches_active_snaps
lambda **_kwargs: SimpleNamespace(id="draft-1"),
)
monkeypatch.setattr(AgentComposerService, "_get_version_if_present", lambda **_kwargs: active_version)
monkeypatch.setattr(AgentComposerService, "load_agent_composer", lambda **_kwargs: {"loaded": True})
monkeypatch.setattr(
AgentComposerService,
"load_agent_composer",
lambda **_kwargs: {"loaded": True, "active_config_is_published": agent.active_config_is_published},
)
payload = ComposerSavePayload.model_validate(
{
"variant": ComposerVariant.AGENT_APP.value,
@ -666,7 +676,7 @@ def test_save_agent_app_composer_keeps_published_when_draft_matches_active_snaps
}
)
AgentComposerService.save_agent_app_composer(
result = AgentComposerService.save_agent_app_composer(
session=session,
tenant_id="tenant-1",
app_id="app-1",
@ -675,6 +685,7 @@ def test_save_agent_app_composer_keeps_published_when_draft_matches_active_snaps
)
assert agent.active_config_is_published is True
assert result["active_config_is_published"] is True
assert fake_session.flushes >= 1

View File

@ -31,6 +31,7 @@ const getComposerInheritanceSnapshot = async (world: DifyWorld, agentId: string)
const knowledgeSets = asArray(asRecord(soul.knowledge).sets)
return {
activeConfigIsPublished: draft.active_config_is_published,
fileNames: files
.map((file) => asString(asRecord(file).name))
.filter(Boolean)
@ -189,8 +190,7 @@ Then(
)
const client = this.getConsoleClient()
const [sourceDetail, duplicatedDetail, sourceSnapshot, duplicatedSnapshot] = await Promise.all([
client.agent.byAgentId.get({ params: { agent_id: sourceAgent.id } }),
const [duplicatedDetail, sourceSnapshot, duplicatedSnapshot] = await Promise.all([
client.agent.byAgentId.get({ params: { agent_id: duplicatedAgentId } }),
getComposerInheritanceSnapshot(this, sourceAgent.id),
getComposerInheritanceSnapshot(this, duplicatedAgentId),
@ -198,9 +198,7 @@ Then(
expect(duplicatedDetail.id).toBe(duplicatedAgentId)
expect(duplicatedDetail.name).toBe(this.lastCreatedAgentName)
expect(duplicatedDetail.active_config_is_published).toBe(
sourceDetail.active_config_is_published,
)
expect(duplicatedSnapshot.activeConfigIsPublished).toBe(sourceSnapshot.activeConfigIsPublished)
expect(duplicatedSnapshot.model).toEqual({
name: stableModel.name,
provider: stableModel.provider,

View File

@ -32,10 +32,10 @@ Then('the Agent v2 draft should remain unpublished', async function (this: DifyW
.poll(
async () => {
const agentId = getCurrentAgentId(this)
const agent = await this.getConsoleClient().agent.byAgentId.get({
const composer = await this.getConsoleClient().agent.byAgentId.composer.get({
params: { agent_id: agentId },
})
return agent.active_config_is_published
return composer.active_config_is_published
},
{ timeout: 30_000 },
)
@ -55,10 +55,10 @@ Then('the Agent v2 draft should be published and up to date', async function (th
await expect(page.getByText('Up to date')).toBeVisible()
await expect
.poll(async () => {
const agent = await this.getConsoleClient().agent.byAgentId.get({
const composer = await this.getConsoleClient().agent.byAgentId.composer.get({
params: { agent_id: agentId },
})
return agent.active_config_is_published
return composer.active_config_is_published
})
.toBe(true)
})

View File

@ -23,7 +23,6 @@ export type AgentAppCreatePayload = {
export type AgentAppDetailWithSite = {
access_mode?: string | null
active_config_is_published?: boolean
api_base_url?: string | null
app_id?: string | null
backing_app_id?: string | null
@ -169,6 +168,7 @@ export type SuggestedQuestionsResponse = {
}
export type AgentAppComposerResponse = {
active_config_is_published: boolean
active_config_snapshot?: AgentConfigSnapshotSummaryResponse | null
agent: AgentComposerAgentResponse
agent_soul: AgentSoulConfig
@ -1892,7 +1892,6 @@ export type AgentAppPaginationWritable = {
export type AgentAppDetailWithSiteWritable = {
access_mode?: string | null
active_config_is_published?: boolean
api_base_url?: string | null
app_id?: string | null
backing_app_id?: string | null

View File

@ -374,7 +374,6 @@ export const zWorkflowPartial = z.object({
*/
export const zAgentAppDetailWithSite = z.object({
access_mode: z.string().nullish(),
active_config_is_published: z.boolean().optional().default(false),
api_base_url: z.string().nullish(),
app_id: z.string().nullish(),
backing_app_id: z.string().nullish(),
@ -2494,6 +2493,7 @@ export const zComposerSavePayload = z.object({
* AgentAppComposerResponse
*/
export const zAgentAppComposerResponse = z.object({
active_config_is_published: z.boolean(),
active_config_snapshot: zAgentConfigSnapshotSummaryResponse.nullish(),
agent: zAgentComposerAgentResponse,
agent_soul: zAgentSoulConfig,
@ -2730,7 +2730,6 @@ export const zAppDetailSiteResponseWritable = z.object({
*/
export const zAgentAppDetailWithSiteWritable = z.object({
access_mode: z.string().nullish(),
active_config_is_published: z.boolean().optional().default(false),
api_base_url: z.string().nullish(),
app_id: z.string().nullish(),
backing_app_id: z.string().nullish(),

View File

@ -447,7 +447,7 @@ function AgentConfigurePageComposerContent({
leftPanel={
<AgentOrchestratePanel
agentId={agentId}
activeConfigIsPublished={agentQuery.data?.active_config_is_published}
activeConfigIsPublished={composerQuery.data?.active_config_is_published}
activeConfigSnapshot={activeConfigSnapshot}
agentSoulConfig={buildDraft.agentSoulConfig}
agentName={agentQuery.data?.name}

View File

@ -108,7 +108,6 @@ const getRetryFn = (queryOptions: object): RetryFn => {
const createAgent = (overrides: Partial<AgentMutationResponse> = {}): AgentMutationResponse => ({
...overrides,
active_config_is_published: overrides.active_config_is_published ?? false,
debug_conversation_has_messages: overrides.debug_conversation_has_messages ?? false,
debug_conversation_message_count: overrides.debug_conversation_message_count ?? 0,
enable_api: overrides.enable_api ?? true,
@ -125,6 +124,7 @@ const createAgent = (overrides: Partial<AgentMutationResponse> = {}): AgentMutat
const createComposerState = (
overrides: Partial<AgentComposerMutationResponse> = {},
): AgentComposerMutationResponse => ({
active_config_is_published: false,
active_config_snapshot: {
id: 'snapshot-1',
version: 1,