mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Slash-command and skill prompts can enter the title-generation path as internal XML breadcrumbs. The async title request previously treated that transport metadata as user prose, so a generated ai-title could persist raw command tags and override the normal session title. This routes title sources and generated title output through a shared sanitizer, preserving user-visible command names and arguments while dropping unrelated internal XML metadata. Desktop fallback title rendering now applies the same cleanup so existing transcripts with bad ai-title entries recover on read. Constraint: Session titles can be produced by both desktop server logic and the SDK generate_session_title control request. Rejected: Only clean desktop session display | leaves CLI and remote title generation able to persist bad titles again Confidence: high Scope-risk: moderate Directive: Keep generated-title input and persisted-title readback on the shared sanitizer; do not add a title path that reads command XML directly. Tested: bun test src/utils/__tests__/sessionTitle.test.ts src/utils/__tests__/sessionTitleText.test.ts Tested: bun run check:server Tested: bun run check:desktop Tested: bun run verify (7 passed, 1 failed on existing aggregate agent-utils coverage baseline) Not-tested: live model title-generation smoke
42 lines
1.4 KiB
TypeScript
42 lines
1.4 KiB
TypeScript
const XML_TAG_BLOCK_PATTERN = /<([a-z][\w-]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>\n?/g
|
|
|
|
function decodeXmlText(text: string): string {
|
|
return text
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, "'")
|
|
.replace(/&/g, '&')
|
|
}
|
|
|
|
function extractXmlTag(text: string, tag: string): string | undefined {
|
|
const match = text.match(new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${tag}>`, 'i'))
|
|
const value = match?.[1]?.trim()
|
|
return value ? decodeXmlText(value) : undefined
|
|
}
|
|
|
|
function normalizeTitleWhitespace(text: string): string {
|
|
return text.replace(/\s+/g, ' ').trim()
|
|
}
|
|
|
|
/**
|
|
* Convert system-injected XML wrappers into user-facing text before using a
|
|
* message as a session title source. Slash commands are stored in transcripts
|
|
* as command-name/command-args breadcrumbs; titles should show the command the
|
|
* user typed, not the internal XML transport.
|
|
*/
|
|
export function cleanSessionTitleSource(raw: string): string {
|
|
const commandName = extractXmlTag(raw, 'command-name')
|
|
const commandArgs = extractXmlTag(raw, 'command-args')
|
|
if (commandName) {
|
|
return normalizeTitleWhitespace([commandName, commandArgs].filter(Boolean).join(' '))
|
|
}
|
|
|
|
const stripped = raw.replace(XML_TAG_BLOCK_PATTERN, ' ')
|
|
return normalizeTitleWhitespace(stripped)
|
|
}
|
|
|
|
export function hasSessionTitleMarkup(raw: string): boolean {
|
|
return /<([a-z][\w-]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/.test(raw)
|
|
}
|