fix(agent-service): store proper title in session metadata, exclude systemPrompt from list API
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 }.
This commit is contained in:
parent
9546dab93d
commit
6ca8aab243
|
|
@ -53,17 +53,33 @@ export class AgentController {
|
||||||
: this.engineRegistry.getActiveEngine();
|
: this.engineRegistry.getActiveEngine();
|
||||||
|
|
||||||
// Reuse existing session or create new one
|
// Reuse existing session or create new one
|
||||||
|
const isVoice = body.voiceMode ?? false;
|
||||||
let session: AgentSession;
|
let session: AgentSession;
|
||||||
if (body.sessionId) {
|
if (body.sessionId) {
|
||||||
const existing = await this.sessionRepository.findById(body.sessionId);
|
const existing = await this.sessionRepository.findById(body.sessionId);
|
||||||
if (existing && existing.status === 'active' && existing.tenantId === tenantId) {
|
if (existing && existing.status === 'active' && existing.tenantId === tenantId) {
|
||||||
session = existing;
|
session = existing;
|
||||||
} else {
|
} else {
|
||||||
session = this.createNewSession(tenantId, engine.engineType, body.systemPrompt);
|
session = this.createNewSession(tenantId, engine.engineType, body.systemPrompt, isVoice);
|
||||||
}
|
}
|
||||||
} else {
|
} 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<string, unknown>).title && !(session.metadata as Record<string, unknown>).titleSet) {
|
||||||
|
session.metadata = {
|
||||||
|
...session.metadata as Record<string, unknown>,
|
||||||
|
voiceMode: isVoice,
|
||||||
|
title: isVoice ? '' : body.prompt.substring(0, 40).trim(),
|
||||||
|
titleSet: true, // prevent overwrite on subsequent turns
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Keep session active for multi-turn
|
// Keep session active for multi-turn
|
||||||
session.status = 'active';
|
session.status = 'active';
|
||||||
session.updatedAt = new Date();
|
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();
|
const session = new AgentSession();
|
||||||
session.id = crypto.randomUUID();
|
session.id = crypto.randomUUID();
|
||||||
session.tenantId = tenantId;
|
session.tenantId = tenantId;
|
||||||
session.engineType = engineType;
|
session.engineType = engineType;
|
||||||
session.status = 'active';
|
session.status = 'active';
|
||||||
session.systemPrompt = systemPrompt;
|
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.createdAt = new Date();
|
||||||
session.updatedAt = new Date();
|
session.updatedAt = new Date();
|
||||||
return session;
|
return session;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,18 @@ export class SessionController {
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async listSessions(@TenantId() tenantId: string) {
|
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')
|
@Get(':sessionId')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue