Skip to content
Closed
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
19 changes: 19 additions & 0 deletions apps/server/src/project/AgentSessionScanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2861,6 +2861,25 @@ describe("parseAgentSessionTranscript", () => {
expect(thread?.messages.map((message) => message.text)).toEqual([prompt]);
});

it("does not split an emoji when shortening a long first message into a title", () => {
const prompt = `${"a".repeat(99)}🚀 then deploy`;
const thread = AgentSessionScanner.parseAgentSessionTranscript({
contents: [
encodeTranscriptRecord({ type: "session_meta", payload: { id: "codex-session" } }),
encodeTranscriptRecord({
type: "event_msg",
payload: { type: "user_message", message: prompt },
}),
].join("\n"),
source: "codex",
providerInstanceId: ProviderInstanceId.make("codex"),
fallbackSessionId: "fallback",
lastActiveAtMs: Date.parse("2026-08-25T08:00:00.000Z"),
});

expect(thread?.title).toBe("a".repeat(99));
});

it("skips sessions without a visible user message", () => {
const thread = AgentSessionScanner.parseAgentSessionTranscript({
contents: JSON.stringify({
Expand Down
9 changes: 8 additions & 1 deletion apps/server/src/project/AgentSessionScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,9 +366,16 @@ function codexHistoryBase(records: ReadonlyArray<DecodedTranscriptRecord>) {
: undefined;
}

const MAX_DERIVED_TITLE_LENGTH = 100;

function deriveTitle(source: AgentSessionSource, text: string): string | undefined {
const request = source === "codex" ? text.replace(CODEX_TITLE_PREAMBLE, "") : text;
return request.trim().split("\n")[0]?.slice(0, 100).trim() || undefined;
const line = request.trim().split("\n")[0] ?? "";
// Cutting between an emoji's surrogates leaves a lone half that persists as U+FFFD.
const last = line.charCodeAt(MAX_DERIVED_TITLE_LENGTH - 1);
const end =
last >= 0xd800 && last <= 0xdbff ? MAX_DERIVED_TITLE_LENGTH - 1 : MAX_DERIVED_TITLE_LENGTH;
return line.slice(0, end).trim() || undefined;
}

// Pi keeps abandoned branches in the file; its active branch runs from the last entry to the root.
Expand Down
Loading