From 6ca8aab243f415bc1174493cffc5523281f746f9 Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 4 Mar 2026 02:39:47 -0800 Subject: [PATCH] fix(agent-service): store proper title in session metadata, exclude systemPrompt from list API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues fixed: 1. agent.controller.ts — on the FIRST task of each session, write title+voiceMode into session.metadata so the client can display a meaningful conversation title: - Text sessions: metadata.title = first 40 chars of user prompt - Voice sessions: metadata.title = '' + metadata.voiceMode = true (Flutter renders these as '语音对话 M/D HH:mm') titleSet flag prevents overwriting the title on subsequent turns of the same session. 2. session.controller.ts — listSessions() now returns a DTO instead of the raw entity. systemPrompt is an internal engine instruction and is explicitly excluded from the response. The client receives { id, status, engineType, metadata, createdAt, updatedAt }. --- .../rest/controllers/agent.controller.ts | 30 ++++++++++++++++--- .../rest/controllers/session.controller.ts | 13 +++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/services/agent-service/src/interfaces/rest/controllers/agent.controller.ts b/packages/services/agent-service/src/interfaces/rest/controllers/agent.controller.ts index bffce03..e6bf31f 100644 --- a/packages/services/agent-service/src/interfaces/rest/controllers/agent.controller.ts +++ b/packages/services/agent-service/src/interfaces/rest/controllers/agent.controller.ts @@ -53,17 +53,33 @@ export class AgentController { : this.engineRegistry.getActiveEngine(); // Reuse existing session or create new one + const isVoice = body.voiceMode ?? false; let session: AgentSession; if (body.sessionId) { const existing = await this.sessionRepository.findById(body.sessionId); if (existing && existing.status === 'active' && existing.tenantId === tenantId) { session = existing; } else { - session = this.createNewSession(tenantId, engine.engineType, body.systemPrompt); + session = this.createNewSession(tenantId, engine.engineType, body.systemPrompt, isVoice); } } else { - session = this.createNewSession(tenantId, engine.engineType, body.systemPrompt); + session = this.createNewSession(tenantId, engine.engineType, body.systemPrompt, isVoice); } + + // Set a human-readable title on the FIRST task of this session. + // This title is stored in metadata so the session list can display it + // without ever exposing the internal systemPrompt string to the client. + // - Text sessions: truncate the first user prompt to 40 chars + // - Voice sessions: leave title empty; Flutter renders "语音对话 M/D HH:mm" + if (!(session.metadata as Record).title && !(session.metadata as Record).titleSet) { + session.metadata = { + ...session.metadata as Record, + voiceMode: isVoice, + title: isVoice ? '' : body.prompt.substring(0, 40).trim(), + titleSet: true, // prevent overwrite on subsequent turns + }; + } + // Keep session active for multi-turn session.status = 'active'; session.updatedAt = new Date(); @@ -619,14 +635,20 @@ export class AgentController { }); } - private createNewSession(tenantId: string, engineType: string, systemPrompt?: string): AgentSession { + private createNewSession( + tenantId: string, + engineType: string, + systemPrompt?: string, + voiceMode?: boolean, + ): AgentSession { const session = new AgentSession(); session.id = crypto.randomUUID(); session.tenantId = tenantId; session.engineType = engineType; session.status = 'active'; session.systemPrompt = systemPrompt; - session.metadata = {}; + // Pre-populate voiceMode so it's available even before the first task saves it + session.metadata = { voiceMode: voiceMode ?? false }; session.createdAt = new Date(); session.updatedAt = new Date(); return session; diff --git a/packages/services/agent-service/src/interfaces/rest/controllers/session.controller.ts b/packages/services/agent-service/src/interfaces/rest/controllers/session.controller.ts index c9388de..0805797 100644 --- a/packages/services/agent-service/src/interfaces/rest/controllers/session.controller.ts +++ b/packages/services/agent-service/src/interfaces/rest/controllers/session.controller.ts @@ -14,7 +14,18 @@ export class SessionController { @Get() async listSessions(@TenantId() tenantId: string) { - return this.sessionRepository.findByTenant(tenantId); + const sessions = await this.sessionRepository.findByTenant(tenantId); + // Return a safe DTO: systemPrompt is an internal engine instruction and must + // NOT be sent to the client (it would be displayed as the conversation title). + // The client derives the display title from metadata.title / metadata.voiceMode. + return sessions.map((s) => ({ + id: s.id, + status: s.status, + engineType: s.engineType, + metadata: s.metadata, // contains { title, voiceMode, titleSet, sdkSessionId? } + createdAt: s.createdAt, + updatedAt: s.updatedAt, + })); } @Get(':sessionId')