From a5f4b5822158f89619d6541eb67c96803fddd19c Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Mon, 9 Mar 2026 20:12:00 -0700 Subject: [PATCH] chat titles --- .../coding-agent/src/core/gateway/runtime.ts | 33 +++--- .../test/gateway-session-titles.test.ts | 101 ++++++++++++++++++ .../coding-agent/test/gateway-steer.test.ts | 13 ++- 3 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 packages/coding-agent/test/gateway-session-titles.test.ts diff --git a/packages/coding-agent/src/core/gateway/runtime.ts b/packages/coding-agent/src/core/gateway/runtime.ts index 8527190..daa8519 100644 --- a/packages/coding-agent/src/core/gateway/runtime.ts +++ b/packages/coding-agent/src/core/gateway/runtime.ts @@ -177,15 +177,14 @@ export class GatewayRuntime { request: GatewayMessageRequest; onStart?: () => void; onFinish?: () => void; - }): - Promise< - | { accepted: false; errorResult: GatewayMessageResult } - | { - accepted: true; - managedSession: ManagedGatewaySession; - completion: Promise; - } - > { + }): Promise< + | { accepted: false; errorResult: GatewayMessageResult } + | { + accepted: true; + managedSession: ManagedGatewaySession; + completion: Promise; + } + > { const managedSession = await this.ensureSession( queuedMessage.request.sessionKey, ); @@ -640,6 +639,7 @@ export class GatewayRuntime { managedSession: ManagedGatewaySession, ): GatewaySessionSnapshot { const messages = managedSession.session.messages; + const name = managedSession.session.sessionName?.trim() || undefined; let lastMessagePreview: string | undefined; for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; @@ -676,6 +676,7 @@ export class GatewayRuntime { lastActiveAt: managedSession.lastActiveAt, createdAt: managedSession.createdAt, updatedAt: managedSession.lastActiveAt, + name, lastMessagePreview, }; } @@ -1292,15 +1293,13 @@ export class GatewayRuntime { ): Promise { const managed = await this.requireExistingSession(sessionKey); if (patch.name !== undefined) { - // Labels in pi-mono are per-entry; we label the current leaf entry - const leafId = managed.session.sessionManager.getLeafId(); - if (!leafId) { - throw new HttpError( - 409, - `Cannot rename session without an active leaf entry: ${sessionKey}`, - ); + const name = patch.name.trim(); + if (!name) { + throw new HttpError(400, "Session name cannot be empty"); } - managed.session.sessionManager.appendLabelChange(leafId, patch.name); + managed.session.sessionManager.appendSessionInfo(name); + managed.lastActiveAt = Date.now(); + this.emitState(managed); } } diff --git a/packages/coding-agent/test/gateway-session-titles.test.ts b/packages/coding-agent/test/gateway-session-titles.test.ts new file mode 100644 index 0000000..641eecc --- /dev/null +++ b/packages/coding-agent/test/gateway-session-titles.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it, vi } from "vitest"; +import { GatewayRuntime } from "../src/core/gateway/runtime.js"; + +function createMockSession(options?: { sessionName?: string }) { + return { + sessionId: "session-1", + sessionName: options?.sessionName, + messages: [], + prompt: vi.fn().mockResolvedValue(undefined), + steer: vi.fn().mockResolvedValue(undefined), + abort: vi.fn().mockResolvedValue(undefined), + dispose: vi.fn(), + subscribe: vi.fn(() => () => {}), + sessionManager: { + getSessionDir: () => "/tmp/pi-gateway-test", + appendSessionInfo: vi.fn(), + appendLabelChange: vi.fn(), + }, + }; +} + +function createRuntime(session = createMockSession()) { + return new GatewayRuntime({ + config: { + bind: "127.0.0.1", + port: 0, + session: { + idleMinutes: 5, + maxQueuePerSession: 4, + }, + webhook: { + enabled: false, + basePath: "/webhooks", + }, + }, + primarySessionKey: "primary", + primarySession: session as never, + createSession: async () => session as never, + }); +} + +function addManagedSession( + runtime: GatewayRuntime, + sessionKey: string, + session: ReturnType, +) { + const managedSession = { + sessionKey, + session, + queue: [], + processing: false, + activeAssistantMessage: null, + pendingToolResults: [], + createdAt: Date.now(), + lastActiveAt: Date.now(), + listeners: new Set(), + unsubscribe: () => {}, + }; + + (runtime as unknown as { sessions: Map }).sessions.set( + sessionKey, + managedSession, + ); + + return managedSession; +} + +describe("GatewayRuntime session titles", () => { + it("includes the session name in snapshots", () => { + const session = createMockSession({ sessionName: "Generated title" }); + const runtime = createRuntime(session); + addManagedSession(runtime, "chat", session); + + expect(runtime.listSessions()).toEqual([ + expect.objectContaining({ + sessionKey: "chat", + name: "Generated title", + }), + ]); + }); + + it("persists renamed sessions via session_info entries", async () => { + const session = createMockSession(); + const runtime = createRuntime(session); + addManagedSession(runtime, "chat", session); + + await ( + runtime as unknown as { + handlePatchSession: ( + sessionKey: string, + patch: { name?: string }, + ) => Promise; + } + ).handlePatchSession("chat", { name: "Renamed title" }); + + expect(session.sessionManager.appendSessionInfo).toHaveBeenCalledWith( + "Renamed title", + ); + expect(session.sessionManager.appendLabelChange).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/coding-agent/test/gateway-steer.test.ts b/packages/coding-agent/test/gateway-steer.test.ts index 851f25a..7b0dc21 100644 --- a/packages/coding-agent/test/gateway-steer.test.ts +++ b/packages/coding-agent/test/gateway-steer.test.ts @@ -4,6 +4,7 @@ import { GatewayRuntime } from "../src/core/gateway/runtime.js"; function createMockSession() { return { sessionId: "session-1", + sessionName: undefined, messages: [], prompt: vi.fn().mockResolvedValue(undefined), steer: vi.fn().mockResolvedValue(undefined), @@ -74,7 +75,11 @@ describe("GatewayRuntime steer handling", () => { handleSteer: ( sessionKey: string, text: string, - ) => Promise<{ ok: true; mode: "steer" | "queued"; sessionKey: string }>; + ) => Promise<{ + ok: true; + mode: "steer" | "queued"; + sessionKey: string; + }>; } ).handleSteer("chat", "keep going"); @@ -97,7 +102,11 @@ describe("GatewayRuntime steer handling", () => { handleSteer: ( sessionKey: string, text: string, - ) => Promise<{ ok: true; mode: "steer" | "queued"; sessionKey: string }>; + ) => Promise<{ + ok: true; + mode: "steer" | "queued"; + sessionKey: string; + }>; } ).handleSteer("chat", "pick this up next");