Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/core/src/sessions/chatTitle.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Task } from "@posthog/shared/domain-types";
import { describe, expect, it } from "vitest";
import {
canApplyTitleFromPrompts,
decideTitleGeneration,
formatPromptsForTitleInput,
getFallbackTaskTitle,
Expand Down Expand Up @@ -182,6 +183,32 @@ describe("decideTitleGeneration", () => {
);
});

describe("canApplyTitleFromPrompts", () => {
it("allows the first-prompt fire to write the title", () => {
expect(
canApplyTitleFromPrompts(1, { title: "Custom", description: "d" }),
).toBe(true);
});

it("blocks later fires from rewriting a real title", () => {
expect(
canApplyTitleFromPrompts(1 + REGENERATE_INTERVAL, {
title: "Fix login bug",
description: "the login page 500s",
}),
).toBe(false);
});

it("allows later fires to replace a placeholder title", () => {
expect(
canApplyTitleFromPrompts(1 + REGENERATE_INTERVAL, {
title: "Fix login",
description: "Fix login",
}),
).toBe(true);
});
});

describe("selectPromptsForTitle", () => {
it("returns all prompts on the first prompt", () => {
expect(selectPromptsForTitle(["a"], 1)).toEqual(["a"]);
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/sessions/chatTitle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ export function decideTitleGeneration(input: {
return { shouldGenerateFromPrompts, shouldGenerateFromTaskDescription };
}

// Prompt-window fires past the first prompt describe the recent conversation,
// not the task, so the title must stay pinned to the original prompt context.
// Later fires may only fill in a title that is still the raw-description
// placeholder (e.g. an earlier generation failed); the summary always
// refreshes regardless.
export function canApplyTitleFromPrompts(
promptCount: number,
task: Pick<Task, "title" | "description">,
): boolean {
return promptCount <= 1 || isPlaceholderTaskTitle(task);
}

export function selectPromptsForTitle(
prompts: string[],
promptCount: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,54 @@ describe("useChatTitleGenerator", () => {
expect(mockGenerateTitle).toHaveBeenCalledTimes(1);
});

it("does not rewrite an unlocked real title from later prompts, but still refreshes the summary", async () => {
// Auto-generated at creation: real title, title_manually_set false.
const unlockedTask = createTask({
title: "Fix login bug",
description: "the login page 500s for SSO users",
});
cacheTask(unlockedTask);
mockGenerateTitle.mockResolvedValue({
title: "Discuss deploy schedule",
summary: "User is coordinating a deploy",
});
mockPrompts.value = Array.from({ length: 8 }, (_, i) => `prompt ${i}`);

renderHook(() => useChatTitleGenerator(unlockedTask));

await waitFor(() => {
expect(mockGenerateTitle).toHaveBeenCalledTimes(1);
});
await waitFor(() => {
expect(mockSessionStoreSetters.updateSession).toHaveBeenCalledWith(
"run-1",
{ conversationSummary: "User is coordinating a deploy" },
);
});
expect(mockUpdateTask).not.toHaveBeenCalled();
});

it("replaces a placeholder title from later prompts", async () => {
const placeholderTask = createTask({
title: "Attached files: pasted-text.txt",
description: "Attached files: pasted-text.txt",
});
cacheTask(placeholderTask);
mockGenerateTitle.mockResolvedValue({
title: "Refactor auth flow",
summary: "",
});
mockPrompts.value = Array.from({ length: 8 }, (_, i) => `prompt ${i}`);

renderHook(() => useChatTitleGenerator(placeholderTask));

await waitFor(() => {
expect(mockUpdateTask).toHaveBeenCalledWith(TASK_ID, {
title: "Refactor auth flow",
});
});
});

it("skips catch-up generation when the title is locked and a summary exists", async () => {
const lockedTask = createTask({
title: "Custom auth title",
Expand Down
12 changes: 12 additions & 0 deletions packages/ui/src/features/sessions/hooks/useChatTitleGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Schemas } from "@posthog/api-client";
import {
canApplyTitleFromPrompts,
decideTitleGeneration,
formatPromptsForTitleInput,
isAutoTitleLocked,
Expand Down Expand Up @@ -120,6 +121,17 @@ export function useChatTitleGenerator(task: Task): void {

if (title && isTitleLocked()) {
log.debug("Skipping auto-title, user renamed task", { taskId });
} else if (
title &&
!canApplyTitleFromPrompts(
promptCount,
getCachedTask(queryClient, taskId) ?? task,
)
) {
log.debug("Skipping auto-title, keeping original-context title", {
taskId,
promptCount,
});
} else if (title) {
if (client) {
await client.updateTask(taskId, { title });
Expand Down
Loading