Repository navigation
feat(task): generate a session title from the first message #2349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
53d6bdf
840b344
85ef95d
999bb26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1024,6 +1024,32 @@ | |
| }) | ||
| } | ||
|
|
||
| /** | ||
| * Set the title of a history item only when it has no title yet, inside a | ||
| * single lock acquisition (in-process write lock plus the per-task file | ||
| * lock in `safeWriteJson`). Used by the background session-title flow so a | ||
| * generated title can never overwrite a title the user set by hand. | ||
| * | ||
| * @returns The updated item when a title was written, or `undefined` when | ||
| * the item already has a title (which is left untouched). | ||
| * @throws If the task ID is not present in the cache. | ||
| */ | ||
| public setTitleIfEmpty(taskId: string, title: string): Promise<HistoryItem | undefined> { | ||
| return this.withLock(async () => { | ||
| const current = this.cache.get(taskId) | ||
| if (!current) { | ||
| throw new Error(`[TaskHistoryStore] setTitleIfEmpty: task ${taskId} not found in cache`) | ||
| } | ||
| if (current.title && current.title.trim()) { | ||
|
Check warning on line 1043 in src/core/task-persistence/TaskHistoryStore.ts
|
||
| return undefined | ||
| } | ||
| // Deep-copy so the write cannot mutate cached state before persistence. | ||
| const updated = { ...structuredClone(current), title } | ||
| await this.upsertCore(updated) | ||
|
Comment on lines
+1037
to
+1048
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return updated | ||
|
Comment on lines
+1048
to
+1049
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * Update two related HistoryItems within a single in-process lock acquisition. | ||
| * Both updaters run synchronously (no I/O, no lock re-entry). Both writes | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
|
|
||
| import { AskIgnoredError } from "./AskIgnoredError" | ||
| import { RateLimitClock, createRateLimitClock } from "./RateLimitClock" | ||
| import { generateSessionTitle } from "./session-title" | ||
|
|
||
| import { Anthropic } from "@anthropic-ai/sdk" | ||
| import OpenAI from "openai" | ||
|
|
@@ -286,6 +287,9 @@ | |
| } | ||
| } | ||
|
|
||
| // Literal, not the vscode enum: specs that mock "vscode" without ExtensionMode would throw on the read. | ||
| const EXTENSION_MODE_TEST = 3 | ||
|
|
||
| export class Task extends EventEmitter<TaskEvents> implements TaskLike { | ||
| readonly taskId: string | ||
| readonly rootTaskId?: string | ||
|
|
@@ -3110,6 +3114,11 @@ | |
|
|
||
| await this.say("text", task, images) | ||
|
|
||
| // Generate a session title in the background (fire-and-forget) so the | ||
| // task list shows a short title without delaying the task. Subtasks and | ||
| // resumed tasks keep their existing names. | ||
| this.generateSessionTitleInBackground(task ?? "") | ||
|
Check warning on line 3120 in src/core/task/Task.ts
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this auxiliary request participate in provider rate limiting and usage/cost accounting before the main task request starts? |
||
|
|
||
| // Check for too many MCP tools and warn the user | ||
| const { enabledToolCount, enabledServerCount } = await this.getEnabledMcpToolsCount() | ||
| if (enabledToolCount > MAX_MCP_TOOLS_THRESHOLD) { | ||
|
|
@@ -3183,6 +3192,31 @@ | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Fire-and-forget session title generation for a new root task. | ||
| * | ||
| * Runs after the first user message is stored. A generated title is written | ||
| * only when the history item has no title yet, so a title the user set by | ||
| * hand is never replaced. Subtasks and resumed tasks keep their existing | ||
| * names. Failures are logged and never shown in the chat. | ||
| */ | ||
| private generateSessionTitleInBackground(firstMessage: string): void { | ||
| if (!firstMessage || this.parentTaskId || this._isHistoryTask) { | ||
| return | ||
| } | ||
|
|
||
| const provider = this.providerRef.deref() | ||
| if (!provider || provider.context.extensionMode === EXTENSION_MODE_TEST) { | ||
| return | ||
| } | ||
|
|
||
| void generateSessionTitle(this.api, firstMessage, { taskId: this.taskId }) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| .then((title) => provider.applyGeneratedSessionTitle(this.taskId, title)) | ||
| .catch((error) => { | ||
| console.error(`[Task#generateSessionTitleInBackground] failed for ${this.taskId}:`, error) | ||
| }) | ||
| } | ||
|
|
||
| private async resumeTaskFromHistory() { | ||
| try { | ||
| const modifiedClineMessages = [...(await this.getSavedClineMessages())] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this condition be evaluated against the locked disk record and durable manual-edit provenance so stale hosts cannot overwrite titles, refill clears, or recreate deleted tasks?