fix(kimi): sync native local session titles

This commit is contained in:
weishu
2026-07-27 10:19:25 +08:00
parent 84323496c6
commit 042ffced8d
10 changed files with 440 additions and 24 deletions
+1
View File
@@ -19,6 +19,7 @@ describe('registerAcpSessionTitleSync', () => {
listener!({ sessionId: 'session-1', title: '' });
listener!({ sessionId: 'session-1', title: null });
listener!({ sessionId: 'session-1', title: 'Untitled' });
listener!({ sessionId: 'session-1', title: 'New Session' });
listener!({ sessionId: 'session-1', title: 'New session - 2026-07-12T15:30:03.251Z' });
expect(sendClaudeSessionMessage).toHaveBeenCalledTimes(1);
+18 -16
View File
@@ -1,28 +1,18 @@
import { randomUUID } from 'node:crypto';
import type { ApiSessionClient } from '@/api/apiSession';
import type { AcpSdkBackend } from '@/agent/backends/acp';
import { normalizeNativeSessionTitle } from '@/agent/nativeSessionTitle';
type AcpSessionTitleBackend = Pick<AcpSdkBackend, 'setSessionInfoUpdateListener'>;
type AcpSessionTitleClient = Pick<ApiSessionClient, 'sendClaudeSessionMessage'>;
function isPlaceholderTitle(title: string): boolean {
return title === 'Untitled'
|| /^(?:New|Child) session - \d{4}-\d{2}-\d{2}T/.test(title);
}
/** Syncs agent-generated ACP session titles into HAPI session metadata. */
export function registerAcpSessionTitleSync(
backend: AcpSessionTitleBackend,
client: AcpSessionTitleClient
): void {
/** Creates a normalized, deduplicated native-title sink for a HAPI session. */
function createSessionTitleSync(client: AcpSessionTitleClient): (title: unknown) => void {
let lastTitle: string | null = null;
backend.setSessionInfoUpdateListener(({ title }) => {
if (typeof title !== 'string') {
return;
}
const normalizedTitle = title.trim();
if (!normalizedTitle || isPlaceholderTitle(normalizedTitle) || normalizedTitle === lastTitle) {
return (title) => {
const normalizedTitle = normalizeNativeSessionTitle(title);
if (!normalizedTitle || normalizedTitle === lastTitle) {
return;
}
lastTitle = normalizedTitle;
@@ -31,5 +21,17 @@ export function registerAcpSessionTitleSync(
summary: normalizedTitle,
leafUuid: randomUUID()
});
};
}
/** Syncs agent-generated ACP session titles into HAPI session metadata. */
export function registerAcpSessionTitleSync(
backend: AcpSessionTitleBackend,
client: AcpSessionTitleClient
): void {
const syncTitle = createSessionTitleSync(client);
backend.setSessionInfoUpdateListener(({ title }) => {
syncTitle(title);
});
}
+52
View File
@@ -0,0 +1,52 @@
import type { ApiSessionClient } from '@/api/apiSession';
import type { Metadata } from '@/api/types';
type NativeSessionTitleMetadataClient = Pick<ApiSessionClient, 'getMetadata' | 'updateMetadata'>;
export function normalizeNativeSessionTitle(title: unknown): string | null {
if (typeof title !== 'string') {
return null;
}
const normalizedTitle = title.trim();
if (!normalizedTitle
|| /^(?:Untitled|New Session)$/i.test(normalizedTitle)
|| /^(?:New|Child) session - \d{4}-\d{2}-\d{2}T/i.test(normalizedTitle)) {
return null;
}
return normalizedTitle;
}
/** Syncs a native agent title into HAPI metadata without creating a chat row. */
export function createNativeSessionTitleMetadataSync(
client: NativeSessionTitleMetadataClient
): (title: unknown) => void {
let lastTitle: string | null = null;
return (title) => {
const normalizedTitle = normalizeNativeSessionTitle(title);
if (!normalizedTitle || normalizedTitle === lastTitle) {
return;
}
lastTitle = normalizedTitle;
if (metadataHasTitle(client.getMetadata(), normalizedTitle)) {
return;
}
client.updateMetadata((metadata) => {
if (metadataHasTitle(metadata, normalizedTitle)) {
return metadata;
}
return {
...metadata,
summary: {
text: normalizedTitle,
updatedAt: Date.now()
}
};
});
};
}
function metadataHasTitle(metadata: Readonly<Metadata> | null, title: string): boolean {
return metadata?.name?.trim() === title || metadata?.summary?.text.trim() === title;
}