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
61 changes: 56 additions & 5 deletions apps/server/src/project/AgentSessionScanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1474,7 +1474,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => {
contents: [
encodeTranscriptRecord({
type: "session_meta",
payload: { id: "codex-recent", cwd: workspace },
payload: { id: "codex-recent", cwd: workspace, name: "Saved code review" },
}),
encodeTranscriptRecord({
type: "event_msg",
Expand Down Expand Up @@ -1504,6 +1504,10 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => {
"codex-recent",
"claude-recent",
]);
expect(threads.map((thread) => thread.title)).toEqual([
"Saved code review",
"Fix the project",
]);
expect(threads.map((thread) => thread.messages.map((message) => message.text))).toEqual([
["Review this code", "Looks good"],
["Fix the project", "Done"],
Expand Down Expand Up @@ -2965,7 +2969,7 @@ describe("parseAgentSessionTranscript", () => {
}),
encodeTranscriptRecord({
type: "session_meta",
payload: { id: "parent-session" },
payload: { id: "parent-session", name: "Parent title" },
}),
encodeTranscriptRecord({
type: "event_msg",
Expand All @@ -2979,6 +2983,7 @@ describe("parseAgentSessionTranscript", () => {
});

expect(thread?.providerSessionId).toBe("fork-session");
expect(thread?.title).toBe("Continue in the fork");
});

it("skips Codex transcripts without a resumable session ID", () => {
Expand Down Expand Up @@ -3071,6 +3076,52 @@ describe("parseAgentSessionTranscript", () => {
]);
});

it.each([
[
"<recommended_plugins>\nAvailable plugins\n</recommended_plugins>Fix the import",
{},
"Fix the import",
],
[
"<recommended_plugins>\nPlugins\n</recommended_plugins>\n<environment_context>\n<cwd>/tmp</cwd>\n</environment_context>\n## My request for Codex:\n\nFix the import",
{},
"Fix the import",
],
[
"# AGENTS.md instructions for /tmp\n\n<INSTRUCTIONS>\nRules\n</INSTRUCTIONS>\n<user_instructions>Rules</user_instructions>\nFix the import",
{},
"Fix the import",
],
["<recommended_plugins>Plugins</recommended_plugins>", {}, "Imported thread"],
["<recommended_plugins>Unclosed example", {}, "<recommended_plugins>Unclosed example"],
["<example>Keep this XML</example>", {}, "<example>Keep this XML</example>"],
["Fix the import\nMore detail", { name: " Saved title " }, "Saved title"],
["Fix the import", { threadName: "Renamed thread" }, "Renamed thread"],
["Fix the import", { name: " ", threadName: " " }, "Fix the import"],
["Fix the import", { name: null, threadName: null }, "Fix the import"],
["x".repeat(120), {}, "x".repeat(100)],
])("derives a Codex title without changing prompt %s", (prompt, metadata, title) => {
const thread = AgentSessionScanner.parseAgentSessionTranscript({
contents: [
encodeTranscriptRecord({
type: "session_meta",
payload: { id: "codex-session", ...metadata },
}),
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(title);
expect(thread?.messages.map((message) => message.text)).toEqual([prompt]);
});

it("preserves context markup in response-only Codex messages", () => {
const context = "<environment_context>\n<cwd>/tmp/project</cwd>\n</environment_context>";
const thread = AgentSessionScanner.parseAgentSessionTranscript({
Expand Down Expand Up @@ -3104,7 +3155,7 @@ describe("parseAgentSessionTranscript", () => {
lastActiveAtMs: Date.parse("2026-08-25T08:00:00.000Z"),
});

expect(thread?.title).toBe("<environment_context>");
expect(thread?.title).toBe("Initialize Git and add a README.");
expect(thread?.messages.map((message) => message.text)).toEqual([
context,
"Initialize Git and add a README.",
Expand All @@ -3131,7 +3182,7 @@ describe("parseAgentSessionTranscript", () => {
lastActiveAtMs: Date.parse("2026-08-25T08:00:00.000Z"),
});

expect(thread?.title).toBe("<environment_context>");
expect(thread?.title).toBe("Create a useful project.");
expect(thread?.messages.map((message) => message.text)).toEqual([prompt]);
});

Expand All @@ -3154,7 +3205,7 @@ describe("parseAgentSessionTranscript", () => {
lastActiveAtMs: Date.parse("2026-08-25T08:00:00.000Z"),
});

expect(thread?.title).toBe("## My request for Codex:");
expect(thread?.title).toBe("Fix the visible bug");
expect(thread?.messages.map((message) => message.text)).toEqual([prompt]);
});

Expand Down
20 changes: 18 additions & 2 deletions apps/server/src/project/AgentSessionScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ const TranscriptRecord = Schema.Struct({
Schema.Struct({
id: Schema.optional(Schema.String),
session_id: Schema.optional(Schema.String),
name: Schema.optional(Schema.NullOr(Schema.String)),
threadName: Schema.optional(Schema.NullOr(Schema.String)),
type: Schema.optional(Schema.String),
role: Schema.optional(Schema.String),
message: Schema.optional(Schema.String),
Expand Down Expand Up @@ -304,6 +306,7 @@ function parseAgentSessionRecords(
// timestamp text, so only transcript metadata can provide a resumable ID.
let providerSessionId = input.source === "codex" ? "" : input.fallbackSessionId;
let title: string | null = null;
let derivedTitle: string | undefined;
let model: string | null = null;
let hasCodexSessionId = false;
const messages: Array<AgentSessionThreadMessage & { readonly codexResponseUser: boolean }> = [];
Expand Down Expand Up @@ -372,6 +375,19 @@ function parseAgentSessionRecords(
if (firstUserMessage === undefined && message.role === "user") {
firstUserMessage = message;
}
if (derivedTitle === undefined && message.role === "user") {
// Strip only known leading Codex preambles, and only for the title.
const prompt =
input.source === "codex"
? message.text
.trim()
.replace(
/^(?:(?:# AGENTS\.md instructions for [^\n]*\n\s*)?<(recommended_plugins|environment_context|user_instructions|INSTRUCTIONS)>[\s\S]*?<\/\1>\s*|## My request for Codex:\s*)+/,
"",
)
: message.text.trim();
derivedTitle = prompt.split("\n")[0]?.slice(0, 100).trim() || undefined;
}
messages.push(message);
if (messages.length > MAX_IMPORTED_MESSAGES) messages.shift();
};
Expand Down Expand Up @@ -429,6 +445,7 @@ function parseAgentSessionRecords(
if (!hasCodexSessionId && sessionId) {
providerSessionId = sessionId;
hasCodexSessionId = true;
title = record.payload?.name?.trim() || record.payload?.threadName?.trim() || null;
}
continue;
}
Expand Down Expand Up @@ -492,13 +509,12 @@ function parseAgentSessionRecords(
const retainedMessages = firstUserMessageRetained
? visibleMessages
: [visibleFirstUserMessage, ...visibleMessages.slice(-(MAX_IMPORTED_MESSAGES - 1))];
const derivedTitle = visibleFirstUserMessage.text.trim().split("\n")[0]?.slice(0, 100).trim();

return {
source: input.source,
providerInstanceId: input.providerInstanceId,
providerSessionId,
title: title ?? (derivedTitle && derivedTitle.length > 0 ? derivedTitle : "Imported thread"),
title: title ?? derivedTitle ?? "Imported thread",
model,
createdAt: retainedMessages[0]?.createdAt ?? fallbackTimestamp,
updatedAt: fallbackTimestamp,
Expand Down
Loading