fix: generate session and thread titles in sentence case (#123389)

Generated titles came back in Title Case because none of the title prompts stated a capitalization style; asked for a "title" with no case rule, models default to Title Case and nothing downstream corrects it. The tool-call purpose titles already ask for sentence case, so session titles, Discord auto-thread titles, and Telegram DM topic labels were the outliers.

Sets sentence case in each title prompt rather than post-processing: the titles follow the user's language, and sentence-case rules are language-specific, so a mechanical normalizer would mangle German nouns and proper nouns like OpenClaw or GitHub.
This commit is contained in:
Vyctor H. Brzezowski 2026-08-15 01:38:41 -03:00 committed by GitHub
parent 94a24f1585
commit 82efd1f0ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 6 additions and 6 deletions

View file

@ -72,7 +72,7 @@ describe("generateThreadTitle", () => {
userMessage:
"Channel: release-status\n\nChannel description: Deploy updates and incident notes\n\nMessage:\nSummarize deployment blockers and owner follow-ups.",
prompt:
"Generate a concise Discord thread title (3-6 words). Return only the title. Use channel context when provided and avoid redundant channel-name words unless needed for clarity.",
"Generate a concise Discord thread title (3-6 words) in sentence case: capitalize only the first word and words that are always capitalized. Return only the title. Use channel context when provided and avoid redundant channel-name words unless needed for clarity.",
modelRef: "openai/gpt-4.1-mini@local",
timeoutMs: 60_000,
maxLength: 600,

View file

@ -9,7 +9,7 @@ const MAX_THREAD_TITLE_SOURCE_CHARS = 600;
const MAX_THREAD_TITLE_CHANNEL_NAME_CHARS = 120;
const MAX_THREAD_TITLE_CHANNEL_DESCRIPTION_CHARS = 320;
const DISCORD_THREAD_TITLE_SYSTEM_PROMPT =
"Generate a concise Discord thread title (3-6 words). Return only the title. Use channel context when provided and avoid redundant channel-name words unless needed for clarity.";
"Generate a concise Discord thread title (3-6 words) in sentence case: capitalize only the first word and words that are always capitalized. Return only the title. Use channel context when provided and avoid redundant channel-name words unless needed for clarity.";
export async function generateThreadTitle(params: {
cfg: OpenClawConfig;

View file

@ -5,7 +5,7 @@ import type {
} from "openclaw/plugin-sdk/config-contracts";
const AUTO_TOPIC_LABEL_DEFAULT_PROMPT =
"Generate a very short topic label (2-4 words, max 25 chars) for a chat conversation based on the user's first message below. No emoji. Use the same language as the message. Be concise and descriptive. Return ONLY the topic name, nothing else.";
"Generate a very short topic label (2-4 words, max 25 chars) for a chat conversation based on the user's first message below. No emoji. Use the same language as the message, in sentence case: capitalize only the first word and words that language always capitalizes. Be concise and descriptive. Return ONLY the topic name, nothing else.";
export function resolveAutoTopicLabelConfig(
directConfig?: TelegramDirectConfig["autoTopicLabel"],

View file

@ -11,7 +11,7 @@ import { resolveAutoTopicLabelConfig } from "./auto-topic-label-config.js";
import { generateTelegramTopicLabel } from "./auto-topic-label.js";
const EXPECTED_DEFAULT_PROMPT =
"Generate a very short topic label (2-4 words, max 25 chars) for a chat conversation based on the user's first message below. No emoji. Use the same language as the message. Be concise and descriptive. Return ONLY the topic name, nothing else.";
"Generate a very short topic label (2-4 words, max 25 chars) for a chat conversation based on the user's first message below. No emoji. Use the same language as the message, in sentence case: capitalize only the first word and words that language always capitalizes. Be concise and descriptive. Return ONLY the topic name, nothing else.";
describe("resolveAutoTopicLabelConfig", () => {
it("returns enabled with default prompt when configs are undefined", () => {

View file

@ -75,7 +75,7 @@ describe("maybeGenerateDashboardSessionTitle", () => {
expect(generateConversationLabelWithFallback).toHaveBeenCalledWith({
userMessage: "Help me plan the release",
prompt:
"Generate a concise session title (3-6 words, max 60 characters) from the user's first message. Use the same language as the message. No emoji. Return only the title.",
"Generate a concise session title (3-6 words, max 60 characters) from the user's first message. Use the same language as the message, in sentence case: capitalize only the first word and words that language always capitalizes. No emoji. Return only the title.",
cfg,
agentId: "main",
utilityModelRef: "openai/gpt-5.6-luna",

View file

@ -30,7 +30,7 @@ type DashboardSessionTitleModelEntry = Pick<
const DASHBOARD_SESSION_TITLE_MAX_CHARS = 60;
const DASHBOARD_SESSION_TITLE_SOURCE_MAX_CHARS = 1_000;
const DASHBOARD_SESSION_TITLE_PROMPT =
"Generate a concise session title (3-6 words, max 60 characters) from the user's first message. Use the same language as the message. No emoji. Return only the title.";
"Generate a concise session title (3-6 words, max 60 characters) from the user's first message. Use the same language as the message, in sentence case: capitalize only the first word and words that language always capitalizes. No emoji. Return only the title.";
// One title request per session generation. Concurrent triggers cannot race duplicate model
// calls or metadata writes; late callers receive the in-flight promise so they