Skip to content

Commit e3a6a7e

Browse files
authored
Fix background naming and remote runtime bootstrap
1 parent 8cb3d1e commit e3a6a7e

25 files changed

Lines changed: 591 additions & 167 deletions

apps/desktop/src/main/services/ai/aiIntegrationService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1505,7 +1505,7 @@ export function createAiIntegrationService(args: {
15051505
throw new Error("No AI provider is available. Install and authenticate Claude Code and/or Codex CLI.");
15061506
}
15071507

1508-
if (!getFeatureFlag(args.feature)) {
1508+
if (args.taskType !== "session_title" && !getFeatureFlag(args.feature)) {
15091509
logger.warn("ai.task.skipped_feature_disabled", {
15101510
requestId,
15111511
taskType: args.taskType,
@@ -2007,6 +2007,7 @@ export function createAiIntegrationService(args: {
20072007
prompt: string;
20082008
timeoutMs?: number;
20092009
model?: string;
2010+
reasoningEffort?: string | null;
20102011
jsonSchema?: unknown;
20112012
systemPrompt?: string;
20122013
taskType?: Extract<AiTaskType, "terminal_summary" | "session_title" | "session_summary" | "handoff_summary" | "continuity_summary" | "context_compaction">;
@@ -2018,6 +2019,7 @@ export function createAiIntegrationService(args: {
20182019
prompt: args.prompt,
20192020
timeoutMs: args.timeoutMs,
20202021
model: args.model,
2022+
reasoningEffort: args.reasoningEffort,
20212023
jsonSchema: args.jsonSchema,
20222024
systemPrompt: args.systemPrompt
20232025
});

apps/desktop/src/main/services/chat/agentChatService.test.ts

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18822,11 +18822,11 @@ describe("createAgentChatService", () => {
1882218822

1882318823
describe("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 },

apps/desktop/src/main/services/chat/agentChatService.ts

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ import { buildAdeCliAgentGuidance } from "../../../shared/adeCliGuidance";
261261
import { getAdeAgentSkillRootsForPrompt } from "../../../shared/agentSkillRoots";
262262
import { parseAgentChatTranscript } from "../../../shared/chatTranscript";
263263
import { extractLeadingSlashCommand, isProviderSlashCommandInput } from "../../../shared/chatSlashCommands";
264+
import {
265+
deriveDeterministicLaneNameFromPrompt,
266+
GENERIC_LANE_FALLBACK_NAME,
267+
genericLaneFallbackName,
268+
genericSuffixFromLaneFallbackName,
269+
} from "../../../shared/laneNameFallback";
264270
import { resolveSubagentCapability } from "../../../shared/subagentCapabilities";
265271
import { stripAnsi } from "../../utils/ansiStrip";
266272
import type { createCtoStateService } from "../cto/ctoStateService";
@@ -1833,9 +1839,11 @@ type ResolvedChatConfig = {
18331839
sessionBudgetUsd: number | null;
18341840
titleGenerationEnabled: boolean;
18351841
titleModelId: string | null;
1842+
titleReasoningEffort: string | null;
18361843
titleRefreshOnComplete: boolean;
18371844
summaryEnabled: boolean;
18381845
summaryModelId: string | null;
1846+
summaryReasoningEffort: string | null;
18391847
};
18401848

18411849
const MAX_PENDING_STEERS = 10;
@@ -3068,40 +3076,21 @@ function sanitizeAutoTitle(raw: string, maxChars = AUTO_TITLE_MAX_CHARS): string
30683076
return normalized.length > maxChars ? normalized.slice(0, maxChars).trimEnd() : normalized;
30693077
}
30703078

3071-
const GENERIC_PROMPT_LANE_NAME = "parallel-task";
3072-
30733079
function fallbackLaneNameFromPrompt(prompt: string): string {
3074-
const collapsed = prompt.replace(/\s+/g, " ");
3075-
if (!collapsed.length) return GENERIC_PROMPT_LANE_NAME;
3076-
const words = collapsed.split(/\s+/).filter(Boolean).slice(0, 4);
3077-
const slug = words
3078-
.join("-")
3079-
.toLowerCase()
3080-
.replace(/[^a-z0-9-]+/g, "-")
3081-
.replace(/-+/g, "-")
3082-
.replace(/^-|-$/g, "");
3083-
return slug.length ? slug.slice(0, 48) : GENERIC_PROMPT_LANE_NAME;
3080+
return deriveDeterministicLaneNameFromPrompt(prompt);
30843081
}
30853082

30863083
function uniquePromptFallbackLaneName(promptFallback: string, explicitFallback: string | null): string {
3087-
if (promptFallback === GENERIC_PROMPT_LANE_NAME) {
3088-
return explicitFallback ?? promptFallback;
3084+
if (promptFallback !== GENERIC_LANE_FALLBACK_NAME) {
3085+
return promptFallback;
30893086
}
3090-
if (!explicitFallback) return promptFallback;
3091-
3092-
const uniqueSuffix = explicitFallback
3093-
.replace(/^chat-?/u, "")
3094-
.replace(/[^a-z0-9-]+/giu, "-")
3095-
.replace(/-+/g, "-")
3096-
.replace(/^-|-$/g, "")
3097-
.toLowerCase();
3098-
if (!uniqueSuffix.length) return promptFallback;
3099-
3100-
const maxPrefixLength = Math.max(1, 60 - uniqueSuffix.length - 1);
3101-
const prefix = promptFallback
3102-
.slice(0, maxPrefixLength)
3103-
.replace(/-+$/g, "");
3104-
return `${prefix}-${uniqueSuffix}`;
3087+
const suffix = genericSuffixFromLaneFallbackName(explicitFallback);
3088+
if (suffix) {
3089+
return genericLaneFallbackName(suffix);
3090+
}
3091+
return explicitFallback && !/^chat(?:-|$)/u.test(explicitFallback)
3092+
? explicitFallback
3093+
: promptFallback;
31053094
}
31063095

31073096
function normalizeSuggestedLaneName(raw: string): string | null {
@@ -5357,14 +5346,23 @@ export function createAgentChatService(args: {
53575346
prompt: string;
53585347
systemPrompt?: string;
53595348
timeoutMs?: number;
5349+
reasoningEffort?: string | null;
53605350
taskType: "session_title" | "session_summary" | "handoff_summary" | "continuity_summary";
53615351
}) => {
5352+
const config = resolveChatConfig();
5353+
const reasoningEffort = args.reasoningEffort
5354+
?? (args.taskType === "session_title"
5355+
? config.titleReasoningEffort
5356+
: args.taskType === "session_summary"
5357+
? config.summaryReasoningEffort
5358+
: null);
53625359
return await aiIntegrationService.summarizeTerminal({
53635360
cwd: args.cwd,
53645361
model: args.modelId,
53655362
prompt: args.prompt,
53665363
systemPrompt: args.systemPrompt,
53675364
timeoutMs: args.timeoutMs,
5365+
reasoningEffort,
53685366
taskType: args.taskType,
53695367
});
53705368
};
@@ -7660,10 +7658,18 @@ export function createAgentChatService(args: {
76607658
const titleGenerationEnabled = si?.titles?.enabled
76617659
?? (typeof legacyChat.autoTitleEnabled === "boolean" ? legacyChat.autoTitleEnabled : undefined)
76627660
?? true;
7663-
const titleModelIdRaw = si?.titles?.modelId ?? legacyChat.autoTitleModelId;
7661+
const titleModelIdRaw = si?.titles && Object.prototype.hasOwnProperty.call(si.titles, "modelId")
7662+
? si.titles.modelId
7663+
: legacyChat.autoTitleModelId;
76647664
const titleModelId = typeof titleModelIdRaw === "string" && titleModelIdRaw.trim().length
76657665
? titleModelIdRaw.trim()
76667666
: null;
7667+
const titleReasoningEffortRaw = si?.titles && Object.prototype.hasOwnProperty.call(si.titles, "reasoningEffort")
7668+
? si.titles.reasoningEffort
7669+
: legacyChat.autoTitleReasoningEffort;
7670+
const titleReasoningEffort = typeof titleReasoningEffortRaw === "string" && titleReasoningEffortRaw.trim().length
7671+
? titleReasoningEffortRaw.trim()
7672+
: null;
76677673
const titleRefreshOnComplete = si?.titles?.refreshOnComplete
76687674
?? (typeof legacyChat.autoTitleRefreshOnComplete === "boolean" ? legacyChat.autoTitleRefreshOnComplete : undefined)
76697675
?? true;
@@ -7674,6 +7680,10 @@ export function createAgentChatService(args: {
76747680
const summaryModelId = typeof summaryModelIdRaw === "string" && summaryModelIdRaw.trim().length
76757681
? summaryModelIdRaw.trim()
76767682
: null;
7683+
const summaryReasoningEffortRaw = si?.summaries?.reasoningEffort;
7684+
const summaryReasoningEffort = typeof summaryReasoningEffortRaw === "string" && summaryReasoningEffortRaw.trim().length
7685+
? summaryReasoningEffortRaw.trim()
7686+
: null;
76777687

76787688
return {
76797689
codexApprovalPolicy: approvalPolicy,
@@ -7683,9 +7693,11 @@ export function createAgentChatService(args: {
76837693
sessionBudgetUsd,
76847694
titleGenerationEnabled,
76857695
titleModelId,
7696+
titleReasoningEffort,
76867697
titleRefreshOnComplete,
76877698
summaryEnabled,
76887699
summaryModelId,
7700+
summaryReasoningEffort,
76897701
};
76907702
};
76917703

0 commit comments

Comments
 (0)