1+ import { useAuthStore } from "@features/auth/stores/authStore" ;
12import { buildPromptBlocks } from "@features/editor/utils/prompt-builder" ;
23import { getSessionService } from "@features/sessions/service/service" ;
34import { useWorkspaceStore } from "@features/workspace/stores/workspaceStore" ;
45import { Saga , type SagaLogger } from "@posthog/shared" ;
56import type { PostHogAPIClient } from "@renderer/api/posthogClient" ;
67import { logger } from "@renderer/lib/logger" ;
8+ import { queryClient } from "@renderer/lib/queryClient" ;
79import { useTaskDirectoryStore } from "@renderer/stores/taskDirectoryStore" ;
810import { trpcVanilla } from "@renderer/trpc" ;
911import { getTaskRepository } from "@renderer/utils/repository" ;
12+ import { getCloudUrlFromRegion } from "@shared/constants/oauth" ;
1013import type {
1114 ExecutionMode ,
1215 Task ,
@@ -16,6 +19,81 @@ import type {
1619
1720const log = logger . scope ( "task-creation-saga" ) ;
1821
22+ function truncateToTitle ( content : string ) : string {
23+ // Strip XML/HTML tags
24+ const stripped = content . replace ( / < [ ^ > ] * > / g, "" ) . trim ( ) ;
25+ if ( ! stripped ) return "Untitled" ;
26+ if ( stripped . length <= 80 ) return stripped ;
27+ // Truncate at word boundary
28+ const truncated = stripped . slice ( 0 , 80 ) ;
29+ const lastSpace = truncated . lastIndexOf ( " " ) ;
30+ return lastSpace > 20
31+ ? `${ truncated . slice ( 0 , lastSpace ) } ...`
32+ : `${ truncated } ...` ;
33+ }
34+
35+ const TITLE_SYSTEM_PROMPT = `You are a title generator. You output ONLY a task title. Nothing else.
36+
37+ Convert the task description into a concise task title.
38+ - The title should be clear, concise, and accurately reflect the content of the task.
39+ - You should keep it short and simple, ideally no more than 6 words.
40+ - Avoid using jargon or overly technical terms unless absolutely necessary.
41+ - The title should be easy to understand for anyone reading it.
42+ - Use sentence case (capitalize only first word and proper nouns)
43+ -Remove: the, this, my, a, an
44+ - If possible, start with action verbs (Fix, Implement, Analyze, Debug, Update, Research, Review)
45+ - Keep exact: technical terms, numbers, filenames, HTTP codes, PR numbers
46+ - Never assume tech stack
47+ - Only output "Untitled" if the input is completely null/missing, not just unclear
48+
49+ Examples:
50+ - "Fix the login bug in the authentication system" → Fix authentication login bug
51+ - "Schedule a meeting with stakeholders to discuss Q4 budget planning" → Schedule Q4 budget meeting
52+ - "Update user documentation for new API endpoints" → Update API documentation
53+ - "Research competitor pricing strategies for our product" → Research competitor pricing
54+ - "Review pull request #123" → Review pull request #123
55+ - "debug 500 errors in production" → Debug production 500 errors
56+ - "why is the payment flow failing" → Analyze payment flow failure
57+ - "So how about that weather huh" → "Weather chat"
58+ - "dsfkj sdkfj help me code" → "Coding help request"
59+ - "👋😊" → "Friendly greeting"
60+ - "aaaaaaaaaa" → "Repeated letters"
61+ - " " → "Empty message"
62+ - "What's the best restaurant in NYC?" → "NYC restaurant recommendations"` ;
63+
64+ async function generateTaskTitle (
65+ taskId : string ,
66+ description : string ,
67+ posthogClient : PostHogAPIClient ,
68+ ) : Promise < void > {
69+ try {
70+ const authState = useAuthStore . getState ( ) ;
71+ const apiKey = authState . oauthAccessToken ;
72+ const cloudRegion = authState . cloudRegion ;
73+ if ( ! apiKey || ! cloudRegion ) return ;
74+
75+ const apiHost = getCloudUrlFromRegion ( cloudRegion ) ;
76+
77+ const result = await trpcVanilla . llmGateway . prompt . mutate ( {
78+ credentials : { apiKey, apiHost } ,
79+ system : TITLE_SYSTEM_PROMPT ,
80+ messages : [ { role : "user" , content : description } ] ,
81+ } ) ;
82+
83+ const title = result . content . trim ( ) ;
84+ if ( ! title ) return ;
85+
86+ await posthogClient . updateTask ( taskId , { title } ) ;
87+
88+ // Update all cached task lists so the sidebar reflects the new title instantly
89+ queryClient . setQueriesData < Task [ ] > ( { queryKey : [ "tasks" , "list" ] } , ( old ) =>
90+ old ?. map ( ( task ) => ( task . id === taskId ? { ...task , title } : task ) ) ,
91+ ) ;
92+ } catch ( error ) {
93+ log . error ( "Failed to generate task title" , { taskId, error } ) ;
94+ }
95+ }
96+
1997// Adapt our logger to SagaLogger interface
2098const sagaLogger : SagaLogger = {
2199 info : ( message , data ) => log . info ( message , data ) ,
@@ -78,6 +156,11 @@ export class TaskCreationSaga extends Saga<
78156 )
79157 : await this . createTask ( input ) ;
80158
159+ // Fire-and-forget: generate a proper LLM title for new tasks
160+ if ( ! taskId ) {
161+ generateTaskTitle ( task . id , input . content ?? "" , this . deps . posthogClient ) ;
162+ }
163+
81164 // Step 2: Resolve repoPath - input takes precedence, then stored mappings
82165 // Wait for workspace store to load first (it loads async on init)
83166 await this . readOnlyStep ( "wait_workspaces_loaded" , ( ) =>
@@ -282,6 +365,7 @@ export class TaskCreationSaga extends Saga<
282365 name : "task_creation" ,
283366 execute : async ( ) => {
284367 const result = await this . deps . posthogClient . createTask ( {
368+ title : truncateToTitle ( input . content ?? "" ) ,
285369 description : input . content ?? "" ,
286370 repository : repository ?? undefined ,
287371 github_integration :
0 commit comments