@@ -18822,11 +18822,11 @@ describe("createAgentChatService", () => {
1882218822
1882318823describe("suggestLaneNameFromPrompt", () => {
1882418824 function createProjectConfigServiceWithTitleOptions(
18825- options: { titleGenerationEnabled?: boolean; titleModelId?: string } = {},
18825+ options: { titleGenerationEnabled?: boolean; titleModelId?: string | null; legacyTitleModelId?: string } = {},
1882618826 ) {
1882718827 const titleOptions: Record<string, unknown> = {};
1882818828 if (typeof options.titleGenerationEnabled === "boolean") titleOptions.enabled = options.titleGenerationEnabled;
18829- if (options.titleModelId) titleOptions.modelId = options.titleModelId;
18829+ if (options.titleModelId !== undefined ) titleOptions.modelId = options.titleModelId;
1883018830 const sessionIntelligence = Object.keys(titleOptions).length ? { titles: titleOptions } : {};
1883118831 return {
1883218832 get: vi.fn(() => ({
@@ -18836,7 +18836,9 @@ describe("suggestLaneNameFromPrompt", () => {
1883618836 cli: { mode: "edit" },
1883718837 inProcess: { mode: "edit" },
1883818838 },
18839- chat: {},
18839+ chat: {
18840+ ...(options.legacyTitleModelId ? { autoTitleModelId: options.legacyTitleModelId } : {}),
18841+ },
1884018842 sessionIntelligence,
1884118843 },
1884218844 },
@@ -18846,7 +18848,7 @@ describe("suggestLaneNameFromPrompt", () => {
1884618848 } as any;
1884718849 }
1884818850
18849- function createSuggestService(options: { titleGenerationEnabled?: boolean; titleModelId?: string } = {}) {
18851+ function createSuggestService(options: { titleGenerationEnabled?: boolean; titleModelId?: string | null; legacyTitleModelId?: string } = {}) {
1885018852 return createService({
1885118853 projectConfigService: createProjectConfigServiceWithTitleOptions(options),
1885218854 });
@@ -18879,17 +18881,17 @@ describe("suggestLaneNameFromPrompt", () => {
1887918881 modelId: "anthropic/claude-haiku-4-5",
1888018882 laneId: "lane-1",
1888118883 });
18882- expect(result).toBe("fix-the- login-bug");
18884+ expect(result).toBe("fix-login-bug");
1888318885 });
1888418886
18885- it("takes only first 4 words of a long prompt", async () => {
18887+ it("takes the first 5 meaningful words of a long prompt", async () => {
1888618888 const { service } = createSuggestService();
1888718889 const result = await service.suggestLaneNameFromPrompt({
1888818890 prompt: "Refactor the authentication service to use JWT tokens",
1888918891 modelId: "anthropic/claude-haiku-4-5",
1889018892 laneId: "lane-1",
1889118893 });
18892- expect(result).toBe("refactor-the- authentication-service");
18894+ expect(result).toBe("refactor-authentication-service-jwt-tokens ");
1889318895 });
1889418896
1889518897 it("strips special characters from the prompt slug", async () => {
@@ -18899,7 +18901,7 @@ describe("suggestLaneNameFromPrompt", () => {
1889918901 modelId: "anthropic/claude-haiku-4-5",
1890018902 laneId: "lane-1",
1890118903 });
18902- expect(result).toBe("fix-bug-123-in ");
18904+ expect(result).toBe("fix-bug-123-module ");
1890318905 });
1890418906
1890518907 it("truncates the fallback slug to 48 characters", async () => {
@@ -18919,7 +18921,7 @@ describe("suggestLaneNameFromPrompt", () => {
1891918921 modelId: "anthropic/claude-haiku-4-5",
1892018922 laneId: "lane-1",
1892118923 });
18922- expect(result).toBe("fix-the- bug-now");
18924+ expect(result).toBe("fix-bug-now");
1892318925 });
1892418926
1892518927 it("falls back when the model runtime throws an error", async () => {
@@ -18935,14 +18937,14 @@ describe("suggestLaneNameFromPrompt", () => {
1893518937 laneId: "lane-1",
1893618938 });
1893718939
18938- expect(result).toBe("write-a- test-suite");
18940+ expect(result).toBe("write-test-suite");
1893918941 expect(logger.warn).toHaveBeenCalledWith(
1894018942 "agent_chat.suggest_lane_name_failed",
1894118943 expect.objectContaining({ error: "API rate limited" }),
1894218944 );
1894318945 });
1894418946
18945- it("keeps the prompt fallback readable while adding the temporary suffix when title generation is disabled", async () => {
18947+ it("uses the deterministic prompt fallback when title generation is disabled", async () => {
1894618948 vi.mocked(detectAllAuth).mockResolvedValue([
1894718949 { type: "cli-subscription" as any, cli: "claude", authenticated: true, path: "/usr/bin/claude", verified: true },
1894818950 ]);
@@ -18955,11 +18957,11 @@ describe("suggestLaneNameFromPrompt", () => {
1895518957 fallbackName: "chat-20260514-010203",
1895618958 });
1895718959
18958- expect(result).toBe("fix-the- authentication-login-20260514-010203 ");
18960+ expect(result).toBe("fix-authentication-login-failure-dashboard ");
1895918961 expect(aiIntegrationService.summarizeTerminal).not.toHaveBeenCalled();
1896018962 });
1896118963
18962- it("uses the explicit fallback directly when the prompt fallback is generic", async () => {
18964+ it("preserves the generated suffix when the prompt fallback is generic", async () => {
1896318965 const { service } = createSuggestService();
1896418966 const result = await service.suggestLaneNameFromPrompt({
1896518967 prompt: "!!!",
@@ -18968,7 +18970,7 @@ describe("suggestLaneNameFromPrompt", () => {
1896818970 fallbackName: "chat-20260514-010203",
1896918971 });
1897018972
18971- expect(result).toBe("chat -20260514-010203");
18973+ expect(result).toBe("parallel-task -20260514-010203");
1897218974 });
1897318975
1897418976 it("uses AI-generated name when the model runtime succeeds", async () => {
@@ -19022,6 +19024,35 @@ describe("suggestLaneNameFromPrompt", () => {
1902219024 }));
1902319025 });
1902419026
19027+ it("does not fall back to a legacy title model when session intelligence model is explicitly cleared", async () => {
19028+ vi.mocked(detectAllAuth).mockResolvedValue([
19029+ { type: "cli-subscription" as any, cli: "claude", authenticated: true, path: "/usr/bin/claude", verified: true },
19030+ ]);
19031+ const { service, aiIntegrationService } = createSuggestService({
19032+ titleModelId: null,
19033+ legacyTitleModelId: "openai/gpt-5.4-mini",
19034+ });
19035+ vi.mocked(aiIntegrationService.summarizeTerminal).mockResolvedValueOnce({
19036+ text: "Fallback Title",
19037+ inputTokens: 10,
19038+ outputTokens: 5,
19039+ } as any);
19040+
19041+ const result = await service.suggestLaneNameFromPrompt({
19042+ prompt: "Fix null model clearing for background jobs",
19043+ modelId: "",
19044+ laneId: "lane-1",
19045+ });
19046+
19047+ expect(result).toBe("fallback-title");
19048+ expect(aiIntegrationService.summarizeTerminal).not.toHaveBeenCalledWith(expect.objectContaining({
19049+ model: "openai/gpt-5.4-mini",
19050+ }));
19051+ expect(aiIntegrationService.summarizeTerminal).toHaveBeenNthCalledWith(1, expect.objectContaining({
19052+ model: "anthropic/claude-haiku-4-5",
19053+ }));
19054+ });
19055+
1902519056 it("normalizes AI-generated name: strips special chars and lowercases", async () => {
1902619057 vi.mocked(detectAllAuth).mockResolvedValue([
1902719058 { type: "cli-subscription" as any, cli: "claude", authenticated: true, path: "/usr/bin/claude", verified: true },
0 commit comments