diff --git a/src/server/sessions/piSessionService.ts b/src/server/sessions/piSessionService.ts
index c8a4003..ecf10cc 100644
--- a/src/server/sessions/piSessionService.ts
+++ b/src/server/sessions/piSessionService.ts
@@ -15,7 +15,7 @@ import { BUILTIN_COMMANDS } from "./builtinCommands.js";
import { SessionCommandService } from "./sessionCommandService.js";
import { SessionArchiveStore } from "./sessionArchiveStore.js";
import type { ActiveSession } from "./sessionRuntimeStore.js";
-import { generateShortSessionName } from "./sessionNameGenerator.js";
+import { fallbackSessionName, generateShortSessionName } from "./sessionNameGenerator.js";
function noop(): void {
// Intentionally empty default unsubscribe callback.
@@ -263,14 +263,18 @@ export class PiSessionService {
if (model === undefined) return;
void generateShortSessionName(this.modelRegistry, model, firstMessage).then((name) => {
- if (name === undefined || session.sessionName !== undefined) return;
- session.setSessionName(name);
- this.publishSessionName(session);
+ this.applyGeneratedSessionName(session, name ?? fallbackSessionName(firstMessage));
}).catch(() => {
- // Session naming is best-effort and must not affect prompt handling.
+ this.applyGeneratedSessionName(session, fallbackSessionName(firstMessage));
});
}
+ private applyGeneratedSessionName(session: AgentSession, name: string | undefined): void {
+ if (name === undefined || session.sessionName !== undefined) return;
+ session.setSessionName(name);
+ this.publishSessionName(session);
+ }
+
private publishSessionName(session: AgentSession): void {
const event = session.sessionName === undefined
? { type: "session.name", sessionId: session.sessionId } as const
diff --git a/src/server/sessions/sessionNameGenerator.test.ts b/src/server/sessions/sessionNameGenerator.test.ts
new file mode 100644
index 0000000..be26d68
--- /dev/null
+++ b/src/server/sessions/sessionNameGenerator.test.ts
@@ -0,0 +1,18 @@
+import { describe, expect, it } from "vitest";
+import { cleanSessionName, fallbackSessionName } from "./sessionNameGenerator.js";
+
+describe("sessionNameGenerator", () => {
+ it("cleans model-generated titles", () => {
+ expect(cleanSessionName('Title: "Fix Session Naming."\nextra')).toBe("Fix Session Naming");
+ });
+
+ it("builds a concise fallback from the first request", () => {
+ expect(fallbackSessionName("Seems like auto name for sessions is not working, I still get the first message as a name."))
+ .toBe("Seems like auto name for sessions");
+ });
+
+ it("ignores skill blocks in fallback names", () => {
+ expect(fallbackSessionName('\nDo x\n\n\nCheck the UI now'))
+ .toBe("Check the UI now");
+ });
+});
diff --git a/src/server/sessions/sessionNameGenerator.ts b/src/server/sessions/sessionNameGenerator.ts
index e81d56f..0c5ed07 100644
--- a/src/server/sessions/sessionNameGenerator.ts
+++ b/src/server/sessions/sessionNameGenerator.ts
@@ -4,6 +4,7 @@ import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
const SESSION_NAME_TIMEOUT_MS = 10_000;
const SESSION_NAME_MAX_INPUT_CHARS = 4_000;
const SESSION_NAME_MAX_LENGTH = 60;
+const FALLBACK_SESSION_NAME_MAX_WORDS = 6;
export async function generateShortSessionName(modelRegistry: ModelRegistry, model: Model, firstMessage: string): Promise {
const provider = getApiProvider(model.api);
@@ -23,7 +24,6 @@ export async function generateShortSessionName(modelRegistry:
}],
},
{
- temperature: 0.2,
maxTokens: 24,
reasoning: "minimal",
signal: AbortSignal.timeout(SESSION_NAME_TIMEOUT_MS),
@@ -43,10 +43,21 @@ export async function generateShortSessionName(modelRegistry:
return cleanSessionName(finalMessage === undefined ? streamedText : textFromAssistant(finalMessage));
}
+export function fallbackSessionName(firstMessage: string): string | undefined {
+ return cleanSessionName(firstMessage
+ .replace(/[\s\S]*?<\/skill>/g, "")
+ .replace(/```[\s\S]*?```/g, " ")
+ .replace(/[`*_#[\](){}<>]/g, " ")
+ .split(/\s+/)
+ .filter(Boolean)
+ .slice(0, FALLBACK_SESSION_NAME_MAX_WORDS)
+ .join(" "));
+}
+
export function cleanSessionName(value: string): string | undefined {
const title = (value.split("\n", 1)[0] ?? "")
+ .replace(/^\s*(title|session title)\s*:\s*/i, "")
.replace(/^\s*["'`]+|["'`.]+\s*$/g, "")
- .replace(/^(title|session title)\s*:\s*/i, "")
.replace(/\s+/g, " ")
.trim()
.slice(0, SESSION_NAME_MAX_LENGTH)