From eb7281edf8faa0ce0d76eb3d6dcd4c2f2652ea02 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 00:21:39 +0100 Subject: [PATCH 01/32] Implementation of the new UI state (checkbox for "Provide only file tree") and adjustment of the token bar below the chat so that it reacts to this state. --- apps/editor/src/constants/state-keys.ts | 2 + .../src/views/panel/backend/panel-provider.ts | 21 ++++++++- .../src/views/panel/frontend/Main/Main.tsx | 8 ++++ .../panel/frontend/Main/MainView/MainView.tsx | 47 +++++++++++++++---- .../editor/src/views/panel/frontend/Panel.tsx | 8 ++++ .../panel/frontend/hooks/panel/use-panel.ts | 21 ++++++++- .../panel/frontend/i18n/translations/home.ts | 18 +++++++ apps/editor/src/views/panel/types/messages.ts | 17 +++++++ .../ContextUtilisation/ContextUtilisation.tsx | 21 +++++++++ 9 files changed, 152 insertions(+), 11 deletions(-) diff --git a/apps/editor/src/constants/state-keys.ts b/apps/editor/src/constants/state-keys.ts index 3ce884e57..4cfc5adc1 100644 --- a/apps/editor/src/constants/state-keys.ts +++ b/apps/editor/src/constants/state-keys.ts @@ -57,6 +57,8 @@ export const HISTORY_FIND_RELEVANT_FILES_STATE_KEY = export const HISTORY_NO_CONTEXT_STATE_KEY = 'history-no-context' export const FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY = 'find-relevant-files-shrink-source-code' +export const FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY = + 'find-relevant-files-only-file-tree' export const RECENTLY_USED_CODE_AT_CURSOR_CONFIG_IDS_STATE_KEY = 'recently-used-code-at-cursor-config-ids' diff --git a/apps/editor/src/views/panel/backend/panel-provider.ts b/apps/editor/src/views/panel/backend/panel-provider.ts index 103262e00..b5525f085 100644 --- a/apps/editor/src/views/panel/backend/panel-provider.ts +++ b/apps/editor/src/views/panel/backend/panel-provider.ts @@ -112,7 +112,8 @@ import { RECENTLY_USED_FIND_RELEVANT_FILES_CONFIG_IDS_STATE_KEY, RECENTLY_USED_EDIT_CONTEXT_CONFIG_IDS_STATE_KEY, get_recently_used_presets_or_groups_key, - FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY + FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY, + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY } from '@/constants/state-keys' import { config_preset_to_ui_format, @@ -1012,6 +1013,24 @@ export class PanelProvider implements vscode.WebviewViewProvider { this, message.shrink_source_code ) + } else if ( + message.command == 'GET_FIND_RELEVANT_FILES_ONLY_FILE_TREE' + ) { + const only_file_tree = this.context.workspaceState.get( + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY, + false + ) + this.send_message({ + command: 'FIND_RELEVANT_FILES_ONLY_FILE_TREE', + only_file_tree + }) + } else if ( + message.command == 'SAVE_FIND_RELEVANT_FILES_ONLY_FILE_TREE' + ) { + await this.context.workspaceState.update( + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY, + message.only_file_tree + ) } else if (message.command == 'RELEVANT_FILES_MODAL_RESPONSE') { if (this.relevant_files_choice_resolver) { this.relevant_files_choice_resolver(message.files) diff --git a/apps/editor/src/views/panel/frontend/Main/Main.tsx b/apps/editor/src/views/panel/frontend/Main/Main.tsx index aa9dcb16f..415ce19cb 100644 --- a/apps/editor/src/views/panel/frontend/Main/Main.tsx +++ b/apps/editor/src/views/panel/frontend/Main/Main.tsx @@ -74,6 +74,8 @@ type Props = { is_setup_complete: boolean find_relevant_files_shrink_source_code: boolean on_find_relevant_files_shrink_source_code_change: (shrink: boolean) => void + find_relevant_files_only_file_tree: boolean + on_find_relevant_files_only_file_tree_change: (only: boolean) => void tabs_count: number active_tab_index: number on_tab_change: (index: number) => void @@ -861,6 +863,12 @@ export const Main: React.FC = (props) => { on_find_relevant_files_shrink_source_code_change={ props.on_find_relevant_files_shrink_source_code_change } + find_relevant_files_only_file_tree={ + props.find_relevant_files_only_file_tree + } + on_find_relevant_files_only_file_tree_change={ + props.on_find_relevant_files_only_file_tree_change + } is_setup_complete={props.is_setup_complete} tabs_count={props.tabs_count} active_tab_index={props.active_tab_index} diff --git a/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx b/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx index 70bf06f6f..bb3befb09 100644 --- a/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx +++ b/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx @@ -119,6 +119,8 @@ type Props = { on_recording_finished: () => void find_relevant_files_shrink_source_code: boolean on_find_relevant_files_shrink_source_code_change: (shrink: boolean) => void + find_relevant_files_only_file_tree: boolean + on_find_relevant_files_only_file_tree_change: (only: boolean) => void is_setup_complete: boolean tabs_count: number active_tab_index: number @@ -198,6 +200,13 @@ export const MainView: React.FC = (props) => { configurations: props.configurations }) + const is_only_file_tree_active = + is_in_find_relevant_files_prompt_type && + props.find_relevant_files_only_file_tree + const file_tree_token_count = Math.ceil( + props.context_file_paths.join('\n').length / 4 + ) + return ( <>
= (props) => { {is_in_find_relevant_files_prompt_type && ( -
- - +
+
+ + +
+
+ + +
)} @@ -324,6 +351,8 @@ export const MainView: React.FC = (props) => { props.context_size_warning_threshold } is_context_disabled={is_in_no_context_prompt_type} + is_only_file_tree_active={is_only_file_tree_active} + file_tree_token_count={file_tree_token_count} />
diff --git a/apps/editor/src/views/panel/frontend/Panel.tsx b/apps/editor/src/views/panel/frontend/Panel.tsx index 36d443841..9002cccfc 100644 --- a/apps/editor/src/views/panel/frontend/Panel.tsx +++ b/apps/editor/src/views/panel/frontend/Panel.tsx @@ -81,6 +81,8 @@ export const Panel = () => { handle_set_recording_state, find_relevant_files_shrink_source_code, handle_find_relevant_files_shrink_source_code_change, + find_relevant_files_only_file_tree, + handle_find_relevant_files_only_file_tree_change, is_setup_complete, handle_tab_change, handle_new_tab, @@ -431,6 +433,12 @@ export const Panel = () => { on_find_relevant_files_shrink_source_code_change={ handle_find_relevant_files_shrink_source_code_change } + find_relevant_files_only_file_tree={ + find_relevant_files_only_file_tree + } + on_find_relevant_files_only_file_tree_change={ + handle_find_relevant_files_only_file_tree_change + } is_setup_complete={is_setup_complete} tabs_count={current_state?.instructions.length ?? 0} active_tab_index={current_state?.active_index ?? 0} diff --git a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts index f46ba9297..dcb10d3a4 100644 --- a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts +++ b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts @@ -57,6 +57,10 @@ export const use_panel = (vscode: any) => { find_relevant_files_shrink_source_code, set_find_relevant_files_shrink_source_code ] = useState(false) + const [ + find_relevant_files_only_file_tree, + set_find_relevant_files_only_file_tree + ] = useState(false) const handle_task_forward = (text: string) => { handle_instructions_change(text, 'edit-context') @@ -116,6 +120,16 @@ export const use_panel = (vscode: any) => { }) } + const handle_find_relevant_files_only_file_tree_change = ( + only_file_tree: boolean + ) => { + set_find_relevant_files_only_file_tree(only_file_tree) + post_message(vscode, { + command: 'SAVE_FIND_RELEVANT_FILES_ONLY_FILE_TREE', + only_file_tree + }) + } + useEffect(() => { const handle_message = (event: MessageEvent) => { const message = event.data @@ -155,6 +169,8 @@ export const use_panel = (vscode: any) => { set_setup_progress(message.setup_progress) } else if (message.command == 'FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE') { set_find_relevant_files_shrink_source_code(message.shrink_source_code) + } else if (message.command == 'FIND_RELEVANT_FILES_ONLY_FILE_TREE') { + set_find_relevant_files_only_file_tree(message.only_file_tree) } else if (message.command == 'RETURN_HOME') { set_active_view('home') } @@ -173,7 +189,8 @@ export const use_panel = (vscode: any) => { { command: 'GET_CHECKPOINTS' }, { command: 'REQUEST_CAN_UNDO' }, { command: 'GET_SETUP_PROGRESS' }, - { command: 'GET_FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE' } + { command: 'GET_FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE' }, + { command: 'GET_FIND_RELEVANT_FILES_ONLY_FILE_TREE' } ] initial_messages.forEach((message) => post_message(vscode, message)) @@ -313,6 +330,8 @@ export const use_panel = (vscode: any) => { is_setup_complete, find_relevant_files_shrink_source_code, handle_find_relevant_files_shrink_source_code_change, + find_relevant_files_only_file_tree, + handle_find_relevant_files_only_file_tree_change, handle_tab_change, handle_new_tab, handle_tab_delete diff --git a/apps/editor/src/views/panel/frontend/i18n/translations/home.ts b/apps/editor/src/views/panel/frontend/i18n/translations/home.ts index 0fbe23244..17029f94e 100644 --- a/apps/editor/src/views/panel/frontend/i18n/translations/home.ts +++ b/apps/editor/src/views/panel/frontend/i18n/translations/home.ts @@ -143,5 +143,23 @@ export const home = { cs: 'Odstranit těla funkcí a komentáře', hu: 'Függvénytestek és megjegyzések eltávolítása', bg: 'Премахване на телата на функциите и коментарите' + }, + 'home.only-file-tree': { + en: 'Provide only file tree', + pl: 'Dostarcz tylko drzewo plików', + 'zh-cn': '仅提供文件树', + ja: 'ファイルツリーのみを提供する', + 'zh-tw': '僅提供文件樹', + de: 'Nur Dateibaum bereitstellen', + es: 'Proporcionar solo el árbol de archivos', + fr: "Fournir uniquement l'arborescence des fichiers", + 'pt-br': 'Fornecer apenas a árvore de arquivos', + ru: 'Предоставить только дерево файлов', + ko: '파일 트리만 제공', + it: "Fornisci solo l'albero dei file", + tr: 'Sadece dosya ağacını sağla', + cs: 'Poskytnout pouze strom souborů', + hu: 'Csak a fájlfát biztosítsa', + bg: 'Предоставяне само на дървото на файловете' } } as const diff --git a/apps/editor/src/views/panel/types/messages.ts b/apps/editor/src/views/panel/types/messages.ts index 0aafa6597..2109b6b06 100644 --- a/apps/editor/src/views/panel/types/messages.ts +++ b/apps/editor/src/views/panel/types/messages.ts @@ -549,6 +549,15 @@ export interface SaveFindRelevantFilesShrinkSourceCodeMessage extends BaseMessag shrink_source_code: boolean } +export interface GetFindRelevantFilesOnlyFileTreeMessage extends BaseMessage { + command: 'GET_FIND_RELEVANT_FILES_ONLY_FILE_TREE' +} + +export interface SaveFindRelevantFilesOnlyFileTreeMessage extends BaseMessage { + command: 'SAVE_FIND_RELEVANT_FILES_ONLY_FILE_TREE' + only_file_tree: boolean +} + export interface GetSetupProgressMessage extends BaseMessage { command: 'GET_SETUP_PROGRESS' } @@ -664,6 +673,8 @@ export type FrontendMessage = | SetRecordingStateMessage | GetFindRelevantFilesShrinkSourceCodeMessage | SaveFindRelevantFilesShrinkSourceCodeMessage + | GetFindRelevantFilesOnlyFileTreeMessage + | SaveFindRelevantFilesOnlyFileTreeMessage | GetSetupProgressMessage | RequestReturnHomeMessage | RelevantFilesModalResponseMessage @@ -915,6 +926,11 @@ export interface FindRelevantFilesShrinkSourceCodeMessage extends BaseMessage { shrink_source_code: boolean } +export interface FindRelevantFilesOnlyFileTreeMessage extends BaseMessage { + command: 'FIND_RELEVANT_FILES_ONLY_FILE_TREE' + only_file_tree: boolean +} + export interface SetupProgressMessage { command: 'SETUP_PROGRESS' setup_progress: SetupProgress @@ -983,6 +999,7 @@ export type BackendMessage = | UpdateFileProgressMessage | RecordingStateMessage | FindRelevantFilesShrinkSourceCodeMessage + | FindRelevantFilesOnlyFileTreeMessage | SetupProgressMessage | InsertSymbolAtCursorMessage | ReturnHomeMessage diff --git a/packages/ui/src/components/editor/panel/ContextUtilisation/ContextUtilisation.tsx b/packages/ui/src/components/editor/panel/ContextUtilisation/ContextUtilisation.tsx index a59b76bd4..a326bf191 100644 --- a/packages/ui/src/components/editor/panel/ContextUtilisation/ContextUtilisation.tsx +++ b/packages/ui/src/components/editor/panel/ContextUtilisation/ContextUtilisation.tsx @@ -5,6 +5,8 @@ type Props = { current_context_size: number context_size_warning_threshold: number is_context_disabled?: boolean + is_only_file_tree_active?: boolean + file_tree_token_count?: number } const format_tokens = (tokens: number): string => { @@ -27,6 +29,25 @@ export const ContextUtilisation: React.FC = (props) => { ) } + if ( + props.is_only_file_tree_active && + props.file_tree_token_count !== undefined + ) { + return ( +
+
+
+
+ + ~{format_tokens(props.file_tree_token_count)} file tree tokens + +
+ ) + } + const is_above_threshold = props.current_context_size > props.context_size_warning_threshold const progress = Math.min( From 08db46ae2ed7eb29a357cf785de63de8e3cbb47a Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 00:33:40 +0100 Subject: [PATCH 02/32] =?UTF-8?q?Calculation=20of=20separate=20token=20val?= =?UTF-8?q?ues=20=E2=80=8B=E2=80=8Bfor=20file=20paths,=20avoidance=20of=20?= =?UTF-8?q?lag/crunching=20and=20correct=20generation=20of=20the=20prompt?= =?UTF-8?q?=20for=20the=20LLM.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workspace/modules/token-calculator.ts | 155 ++++++++++++++---- .../providers/workspace/workspace-provider.ts | 55 +++++-- apps/editor/src/utils/files-collector.ts | 45 ++--- .../message-handlers/handle-copy-prompt.ts | 14 +- .../handle-find-relevant-files.ts | 11 +- .../handle-send-to-browser.ts | 14 +- 6 files changed, 222 insertions(+), 72 deletions(-) diff --git a/apps/editor/src/context/providers/workspace/modules/token-calculator.ts b/apps/editor/src/context/providers/workspace/modules/token-calculator.ts index 620248555..181666649 100644 --- a/apps/editor/src/context/providers/workspace/modules/token-calculator.ts +++ b/apps/editor/src/context/providers/workspace/modules/token-calculator.ts @@ -6,7 +6,7 @@ import { Logger } from '@shared/utils/logger' import type { IWorkspaceProvider } from '../workspace-provider' import { shrink_file } from '@/context/utils/shrink-file' -type TokenData = [number, number, number] +type TokenData = [number, number, number, number?] type TokenCacheNode = { [key: string]: TokenCacheNode | TokenData @@ -28,11 +28,14 @@ const TOKEN_CACHE_FILE_NAME = 'token-counts-cache.json' export class TokenCalculator implements vscode.Disposable { private _file_token_counts: Map = new Map() private _file_shrink_token_counts: Map = new Map() + private _file_path_token_counts: Map = new Map() private _directory_token_counts: Map = new Map() private _directory_shrink_token_counts: Map = new Map() + private _directory_path_token_counts: Map = new Map() private _directory_selected_token_counts: Map = new Map() private _directory_selected_shrink_token_counts: Map = new Map() + private _directory_selected_path_token_counts: Map = new Map() private _token_cache: TokenCountsCache = {} private _session_cache: TokenCountsCache = {} private _token_cache_update_timeout: NodeJS.Timeout | null = null @@ -145,7 +148,8 @@ export class TokenCalculator implements vscode.Disposable { relative_path: string, mtime: number, token_count: number, - shrink_token_count: number + shrink_token_count: number, + path_token_count: number ) { if (!this._session_cache[workspace_root]) { this._session_cache[workspace_root] = { @@ -156,7 +160,8 @@ export class TokenCalculator implements vscode.Disposable { this._session_cache[workspace_root].files[relative_path] = [ mtime, token_count, - shrink_token_count + shrink_token_count, + path_token_count ] this._session_cache[workspace_root].modified_at = Date.now() this._update_token_counts_cache() @@ -198,18 +203,22 @@ export class TokenCalculator implements vscode.Disposable { if (!workspace_root) { this._file_token_counts.delete(changed_file_path) this._file_shrink_token_counts.delete(changed_file_path) + this._file_path_token_counts.delete(changed_file_path) return } this._file_token_counts.delete(changed_file_path) this._file_shrink_token_counts.delete(changed_file_path) + this._file_path_token_counts.delete(changed_file_path) let dir_path = path.dirname(changed_file_path) while (dir_path.startsWith(workspace_root)) { this._directory_token_counts.delete(dir_path) this._directory_shrink_token_counts.delete(dir_path) + this._directory_path_token_counts.delete(dir_path) this._directory_selected_token_counts.delete(dir_path) this._directory_selected_shrink_token_counts.delete(dir_path) + this._directory_selected_path_token_counts.delete(dir_path) dir_path = path.dirname(dir_path) } } @@ -217,50 +226,81 @@ export class TokenCalculator implements vscode.Disposable { public invalidate_directory_counts(dir_path: string) { this._directory_token_counts.delete(dir_path) this._directory_shrink_token_counts.delete(dir_path) + this._directory_path_token_counts.delete(dir_path) this._directory_selected_token_counts.delete(dir_path) this._directory_selected_shrink_token_counts.delete(dir_path) + this._directory_selected_path_token_counts.delete(dir_path) } public invalidate_directory_selected_count(dir_path: string) { this._directory_selected_token_counts.delete(dir_path) this._directory_selected_shrink_token_counts.delete(dir_path) + this._directory_selected_path_token_counts.delete(dir_path) } public clear_caches() { this._file_token_counts.clear() this._file_shrink_token_counts.clear() + this._file_path_token_counts.clear() this._directory_token_counts.clear() this._directory_shrink_token_counts.clear() + this._directory_path_token_counts.clear() this._directory_selected_token_counts.clear() this._directory_selected_shrink_token_counts.clear() + this._directory_selected_path_token_counts.clear() } public clear_selected_counts() { this._directory_selected_token_counts.clear() this._directory_selected_shrink_token_counts.clear() + this._directory_selected_path_token_counts.clear() } public get_cached_token_count( file_path: string - ): { total: number; shrink: number } | undefined { + ): { total: number; shrink: number; path: number } | undefined { const total = this._file_token_counts.get(file_path) const shrink = this._file_shrink_token_counts.get(file_path) - if (total !== undefined && shrink !== undefined) { - return { total, shrink } + const path_tokens = this._file_path_token_counts.get(file_path) + if ( + total !== undefined && + shrink !== undefined && + path_tokens !== undefined + ) { + return { total, shrink, path: path_tokens } } return undefined } + private _calculate_path_tokens( + workspace_root: string | undefined, + file_path: string + ): number { + let display_path = file_path + if (workspace_root) { + display_path = path + .relative(workspace_root, file_path) + .replace(/\\/g, '/') + if (this._provider.get_workspace_roots().length > 1) { + const workspace_name = this._provider.get_workspace_name(workspace_root) + display_path = `${workspace_name}/${display_path}` + } + } + return Math.floor(`- ${display_path}\n`.length / 4) + } + public async calculate_file_tokens( file_path: string - ): Promise<{ total: number; shrink: number }> { + ): Promise<{ total: number; shrink: number; path: number }> { if ( this._file_token_counts.has(file_path) && - this._file_shrink_token_counts.has(file_path) + this._file_shrink_token_counts.has(file_path) && + this._file_path_token_counts.has(file_path) ) { return { total: this._file_token_counts.get(file_path)!, - shrink: this._file_shrink_token_counts.get(file_path)! + shrink: this._file_shrink_token_counts.get(file_path)!, + path: this._file_path_token_counts.get(file_path)! } } @@ -268,6 +308,11 @@ export class TokenCalculator implements vscode.Disposable { const range = this._provider.get_range(file_path) let mtime = 0 + const path_token_count = this._calculate_path_tokens( + workspace_root, + file_path + ) + if (workspace_root && !range) { try { const stats = await fs.promises.stat(file_path) @@ -286,18 +331,25 @@ export class TokenCalculator implements vscode.Disposable { ) { const tokens = cached_file[1] const shrink_tokens = cached_file[2] + const cached_path_tokens = cached_file[3] ?? path_token_count this._file_token_counts.set(file_path, tokens) this._file_shrink_token_counts.set(file_path, shrink_tokens) + this._file_path_token_counts.set(file_path, cached_path_tokens) this._update_session_cache( workspace_root, relative_path, mtime, tokens, - shrink_tokens + shrink_tokens, + cached_path_tokens ) - return { total: tokens, shrink: shrink_tokens } + return { + total: tokens, + shrink: shrink_tokens, + path: cached_path_tokens + } } } catch { // Continue to calculate if stat fails @@ -339,6 +391,7 @@ export class TokenCalculator implements vscode.Disposable { this._file_token_counts.set(file_path, token_count) this._file_shrink_token_counts.set(file_path, shrink_token_count) + this._file_path_token_counts.set(file_path, path_token_count) if (workspace_root && !range && mtime > 0) { if ( @@ -358,7 +411,8 @@ export class TokenCalculator implements vscode.Disposable { this._token_cache[workspace_root].files[relative_path] = [ mtime, token_count, - shrink_token_count + shrink_token_count, + path_token_count ] this._token_cache[workspace_root].modified_at = Date.now() @@ -367,31 +421,38 @@ export class TokenCalculator implements vscode.Disposable { relative_path, mtime, token_count, - shrink_token_count + shrink_token_count, + path_token_count ) } - return { total: token_count, shrink: shrink_token_count } + return { + total: token_count, + shrink: shrink_token_count, + path: path_token_count + } } catch (error) { Logger.error({ function_name: 'calculate_file_tokens', message: `Error calculating tokens for ${file_path}`, data: error }) - return { total: 0, shrink: 0 } + return { total: 0, shrink: 0, path: path_token_count } } } public async calculate_directory_tokens( dir_path: string - ): Promise<{ total: number; shrink: number }> { + ): Promise<{ total: number; shrink: number; path: number }> { if ( this._directory_token_counts.has(dir_path) && - this._directory_shrink_token_counts.has(dir_path) + this._directory_shrink_token_counts.has(dir_path) && + this._directory_path_token_counts.has(dir_path) ) { return { total: this._directory_token_counts.get(dir_path)!, - shrink: this._directory_shrink_token_counts.get(dir_path)! + shrink: this._directory_shrink_token_counts.get(dir_path)!, + path: this._directory_path_token_counts.get(dir_path)! } } @@ -399,7 +460,7 @@ export class TokenCalculator implements vscode.Disposable { const workspace_root = this._provider.get_workspace_root_for_file(dir_path) if (!workspace_root) { - return { total: 0, shrink: 0 } + return { total: 0, shrink: 0, path: 0 } } const relative_dir_path = path.relative(workspace_root, dir_path) @@ -410,7 +471,8 @@ export class TokenCalculator implements vscode.Disposable { ) { this._directory_token_counts.set(dir_path, 0) this._directory_shrink_token_counts.set(dir_path, 0) - return { total: 0, shrink: 0 } + this._directory_path_token_counts.set(dir_path, 0) + return { total: 0, shrink: 0, path: 0 } } const entries = await fs.promises.readdir(dir_path, { @@ -418,6 +480,7 @@ export class TokenCalculator implements vscode.Disposable { }) let total_tokens = 0 let total_shrink_tokens = 0 + let total_path_tokens = 0 for (const entry of entries) { const full_path = path.join(dir_path, entry.name) @@ -458,53 +521,63 @@ export class TokenCalculator implements vscode.Disposable { } if (is_directory && !is_broken_link) { - // Recurse into subdirectory (including resolved symlinks that are directories) + // Recurse into subdirectory const counts = await this.calculate_directory_tokens(full_path) total_tokens += counts.total total_shrink_tokens += counts.shrink + total_path_tokens += counts.path } else if ( entry.isFile() || (is_symbolic_link && !is_broken_link && !is_directory) ) { - // Add file tokens (including resolved symlinks that are files) + // Add file tokens const counts = await this.calculate_file_tokens(full_path) total_tokens += counts.total total_shrink_tokens += counts.shrink + total_path_tokens += counts.path } } this._directory_token_counts.set(dir_path, total_tokens) this._directory_shrink_token_counts.set(dir_path, total_shrink_tokens) + this._directory_path_token_counts.set(dir_path, total_path_tokens) - return { total: total_tokens, shrink: total_shrink_tokens } + return { + total: total_tokens, + shrink: total_shrink_tokens, + path: total_path_tokens + } } catch (error) { Logger.error({ function_name: 'calculate_directory_tokens', message: `Error calculating tokens for directory ${dir_path}`, data: error }) - return { total: 0, shrink: 0 } + return { total: 0, shrink: 0, path: 0 } } } public async calculate_directory_selected_tokens( dir_path: string - ): Promise<{ total: number; shrink: number }> { + ): Promise<{ total: number; shrink: number; path: number }> { if (!dir_path) { - return { total: 0, shrink: 0 } + return { total: 0, shrink: 0, path: 0 } } if ( this._directory_selected_token_counts.has(dir_path) && - this._directory_selected_shrink_token_counts.has(dir_path) + this._directory_selected_shrink_token_counts.has(dir_path) && + this._directory_selected_path_token_counts.has(dir_path) ) { return { total: this._directory_selected_token_counts.get(dir_path)!, - shrink: this._directory_selected_shrink_token_counts.get(dir_path)! + shrink: this._directory_selected_shrink_token_counts.get(dir_path)!, + path: this._directory_selected_path_token_counts.get(dir_path)! } } let selected_tokens = 0 let selected_shrink_tokens = 0 + let selected_path_tokens = 0 try { const workspace_root = @@ -514,7 +587,7 @@ export class TokenCalculator implements vscode.Disposable { function_name: 'calculate_directory_selected_tokens', message: `No workspace root found for directory ${dir_path}` }) - return { total: 0, shrink: 0 } + return { total: 0, shrink: 0, path: 0 } } const relative_dir_path = path.relative(workspace_root, dir_path) @@ -525,7 +598,8 @@ export class TokenCalculator implements vscode.Disposable { ) { this._directory_selected_token_counts.set(dir_path, 0) this._directory_selected_shrink_token_counts.set(dir_path, 0) - return { total: 0, shrink: 0 } + this._directory_selected_path_token_counts.set(dir_path, 0) + return { total: 0, shrink: 0, path: 0 } } const entries = await fs.promises.readdir(dir_path, { @@ -569,17 +643,20 @@ export class TokenCalculator implements vscode.Disposable { const counts = await this.calculate_directory_tokens(full_path) selected_tokens += counts.total selected_shrink_tokens += counts.shrink + selected_path_tokens += counts.path } else if (this._provider.is_partially_checked(full_path)) { const counts = await this.calculate_directory_selected_tokens(full_path) selected_tokens += counts.total selected_shrink_tokens += counts.shrink + selected_path_tokens += counts.path } } else { if (checkbox_state === vscode.TreeItemCheckboxState.Checked) { const counts = await this.calculate_file_tokens(full_path) selected_tokens += counts.total selected_shrink_tokens += counts.shrink + selected_path_tokens += counts.path } } } @@ -589,21 +666,29 @@ export class TokenCalculator implements vscode.Disposable { message: `Error calculating selected tokens for dir ${dir_path}`, data: error }) - return { total: 0, shrink: 0 } + return { total: 0, shrink: 0, path: 0 } } this._directory_selected_token_counts.set(dir_path, selected_tokens) this._directory_selected_shrink_token_counts.set( dir_path, selected_shrink_tokens ) - return { total: selected_tokens, shrink: selected_shrink_tokens } + this._directory_selected_path_token_counts.set( + dir_path, + selected_path_tokens + ) + return { + total: selected_tokens, + shrink: selected_shrink_tokens, + path: selected_path_tokens + } } public async get_checked_files_token_count(options?: { exclude_file_path?: string - }): Promise<{ total: number; shrink: number }> { + }): Promise<{ total: number; shrink: number; path: number }> { const checked_files = this._provider.get_checked_files() - const result = { total: 0, shrink: 0 } + const result = { total: 0, shrink: 0, path: 0 } for (const file_path of checked_files) { try { @@ -618,10 +703,12 @@ export class TokenCalculator implements vscode.Disposable { if (this._file_token_counts.has(file_path)) { result.total += this._file_token_counts.get(file_path)! result.shrink += this._file_shrink_token_counts.get(file_path)! + result.path += this._file_path_token_counts.get(file_path)! } else { const count = await this.calculate_file_tokens(file_path) result.total += count.total result.shrink += count.shrink + result.path += count.path } } } catch (error) { diff --git a/apps/editor/src/context/providers/workspace/workspace-provider.ts b/apps/editor/src/context/providers/workspace/workspace-provider.ts index 66aaaf152..6e496b6ab 100644 --- a/apps/editor/src/context/providers/workspace/workspace-provider.ts +++ b/apps/editor/src/context/providers/workspace/workspace-provider.ts @@ -7,7 +7,8 @@ import { CONTEXT_CHECKED_TIMESTAMPS_STATE_KEY, CONTEXT_CHECKED_PATHS_FRF_STATE_KEY, CONTEXT_CHECKED_TIMESTAMPS_FRF_STATE_KEY, - RANGES_STATE_KEY + RANGES_STATE_KEY, + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY } from '@/constants/state-keys' import { IGNORE_PATTERNS } from '@/constants/ignore-patterns' import { natural_sort } from '@/utils/natural-sort' @@ -695,12 +696,23 @@ export class WorkspaceProvider element.checkboxState = checkbox_state - const total_token_count = this.use_shrink_token_count - ? element.shrinkTokenCount - : element.tokenCount - const selected_token_count = this.use_shrink_token_count - ? element.selectedShrinkTokenCount - : element.selectedTokenCount + const only_file_tree = this._context.workspaceState.get( + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY, + false + ) + const show_only_path_tokens = this._is_frf_mode && only_file_tree + + const total_token_count = show_only_path_tokens + ? element.pathTokenCount + : this.use_shrink_token_count + ? element.shrinkTokenCount + : element.tokenCount + + const selected_token_count = show_only_path_tokens + ? element.selectedPathTokenCount + : this.use_shrink_token_count + ? element.selectedShrinkTokenCount + : element.selectedTokenCount const formatted_total = total_token_count !== undefined && total_token_count > 0 @@ -726,6 +738,23 @@ export class WorkspaceProvider display_description = formatted_total ?? '' } + if ( + !show_only_path_tokens && + element.pathTokenCount !== undefined && + element.pathTokenCount > 0 + ) { + const path_token_str = `${element.pathTokenCount} path` + if (element.isDirectory && formatted_selected) { + if (selected_token_count! < total_token_count!) { + display_description += ` (${element.selectedPathTokenCount} path selected)` + } else { + display_description += ` (${path_token_str})` + } + } else { + display_description += ` (${path_token_str})` + } + } + if (!element.isDirectory && element.range) { display_description += `${ display_description ? ' · ' : '' @@ -892,8 +921,10 @@ export class WorkspaceProvider false, total_tokens.total, total_tokens.shrink, + total_tokens.path, selected_tokens.total, selected_tokens.shrink, + selected_tokens.path, undefined, true ) @@ -1006,13 +1037,13 @@ export class WorkspaceProvider public get_cached_token_count( file_path: string - ): { total: number; shrink: number } | undefined { + ): { total: number; shrink: number; path: number } | undefined { return this._token_calculator.get_cached_token_count(file_path) } public async calculate_file_tokens( file_path: string - ): Promise<{ total: number; shrink: number }> { + ): Promise<{ total: number; shrink: number; path: number }> { return this._token_calculator.calculate_file_tokens(file_path) } @@ -1158,8 +1189,10 @@ export class WorkspaceProvider false, tokens.total, tokens.shrink, + tokens.path, selected_tokens?.total, selected_tokens?.shrink, + selected_tokens?.path, undefined, false, range @@ -1701,7 +1734,7 @@ export class WorkspaceProvider public async get_checked_files_token_count(options?: { exclude_file_path?: string - }): Promise<{ total: number; shrink: number }> { + }): Promise<{ total: number; shrink: number; path: number }> { return this._token_calculator.get_checked_files_token_count(options) } @@ -1825,8 +1858,10 @@ export class FileItem extends vscode.TreeItem { public isOpenFile: boolean = false, public tokenCount?: number, public shrinkTokenCount?: number, + public pathTokenCount?: number, public selectedTokenCount?: number, public selectedShrinkTokenCount?: number, + public selectedPathTokenCount?: number, description?: string, public isWorkspaceRoot: boolean = false, public range?: string diff --git a/apps/editor/src/utils/files-collector.ts b/apps/editor/src/utils/files-collector.ts index c1f9b802a..0814b74b4 100644 --- a/apps/editor/src/utils/files-collector.ts +++ b/apps/editor/src/utils/files-collector.ts @@ -24,6 +24,7 @@ export class FilesCollector { additional_paths?: string[] no_context?: boolean shrink?: boolean + only_file_tree?: boolean }): Promise<{ other_files: string; recent_files: string }> { const additional_paths = (params?.additional_paths ?? []).map((p) => { if (this.workspace_roots.length > 0) { @@ -64,6 +65,28 @@ export class FilesCollector { if (stats.isDirectory()) continue + const workspace_root = this._get_workspace_root_for_file(file_path) + + let display_path = file_path.replace(/\\/g, '/') + if (workspace_root) { + const relative_path = path + .relative(workspace_root, file_path) + .replace(/\\/g, '/') + + if (this.workspace_roots.length > 1) { + const workspace_name = + this.workspace_provider.get_workspace_name(workspace_root) + display_path = `${workspace_name}/${relative_path}` + } else { + display_path = relative_path + } + } + + if (params?.only_file_tree) { + collected_text += `- ${display_path}\n` + continue + } + let content = fs.readFileSync(file_path, 'utf8') const range = this.workspace_provider.get_range(file_path) @@ -78,28 +101,6 @@ export class FilesCollector { content = shrink_file(content, path.extname(file_path)) } - const workspace_root = this._get_workspace_root_for_file(file_path) - - if (!workspace_root) { - collected_text += `\n\n\n` - continue - } - - const relative_path = path - .relative(workspace_root, file_path) - .replace(/\\/g, '/') - - // Get the workspace name to prefix the path if there are multiple workspaces - let display_path = relative_path - if (this.workspace_roots.length > 1) { - const workspace_name = - this.workspace_provider.get_workspace_name(workspace_root) - display_path = `${workspace_name}/${relative_path}` - } - collected_text += `\n\n\n` } catch (error) { console.error(`Error reading file ${file_path}:`, error) diff --git a/apps/editor/src/views/panel/backend/message-handlers/handle-copy-prompt.ts b/apps/editor/src/views/panel/backend/message-handlers/handle-copy-prompt.ts index b774b6c63..26dbdca85 100644 --- a/apps/editor/src/views/panel/backend/message-handlers/handle-copy-prompt.ts +++ b/apps/editor/src/views/panel/backend/message-handlers/handle-copy-prompt.ts @@ -27,7 +27,10 @@ import { EDIT_FORMAT_INSTRUCTIONS_BEFORE_AFTER, EDIT_FORMAT_INSTRUCTIONS_DIFF } from '@/constants/edit-format-instructions' -import { FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY } from '@/constants/state-keys' +import { + FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY, + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY +} from '@/constants/state-keys' export const handle_copy_prompt = async (params: { panel_provider: PanelProvider @@ -188,9 +191,16 @@ export const handle_copy_prompt = async (params: { false ) + const only_file_tree = + params.panel_provider.context.workspaceState.get( + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY, + false + ) + const collected = await files_collector.collect_files({ no_context: params.panel_provider.web_prompt_type == 'no-context', - shrink: is_in_find_relevant_files_prompt_type && shrink_source_code + shrink: is_in_find_relevant_files_prompt_type && shrink_source_code, + only_file_tree: is_in_find_relevant_files_prompt_type && only_file_tree }) const context_text = collected.other_files + collected.recent_files diff --git a/apps/editor/src/views/panel/backend/message-handlers/handle-find-relevant-files.ts b/apps/editor/src/views/panel/backend/message-handlers/handle-find-relevant-files.ts index 6d38f2f6a..c4322a429 100644 --- a/apps/editor/src/views/panel/backend/message-handlers/handle-find-relevant-files.ts +++ b/apps/editor/src/views/panel/backend/message-handlers/handle-find-relevant-files.ts @@ -10,7 +10,8 @@ import { import axios from 'axios' import { RECENTLY_USED_FIND_RELEVANT_FILES_CONFIG_IDS_STATE_KEY, - FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY + FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY, + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY } from '@/constants/state-keys' import { replace_changes_symbol, @@ -395,8 +396,14 @@ export const handle_find_relevant_files = async ( false ) + const only_file_tree = panel_provider.context.workspaceState.get( + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY, + false + ) + const collected = await files_collector.collect_files({ - shrink: shrink_source_code + shrink: shrink_source_code, + only_file_tree }) const collected_files = collected.other_files + collected.recent_files diff --git a/apps/editor/src/views/panel/backend/message-handlers/handle-send-to-browser.ts b/apps/editor/src/views/panel/backend/message-handlers/handle-send-to-browser.ts index 1a82be805..bf98af3c7 100644 --- a/apps/editor/src/views/panel/backend/message-handlers/handle-send-to-browser.ts +++ b/apps/editor/src/views/panel/backend/message-handlers/handle-send-to-browser.ts @@ -20,7 +20,8 @@ import { } from '@/constants/instructions' import { get_recently_used_presets_or_groups_key, - FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY + FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY, + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY } from '@/constants/state-keys' import { ConfigPresetFormat } from '../utils/preset-format-converters' import { MODE } from '@/views/panel/types/main-view-mode' @@ -219,12 +220,21 @@ export const handle_send_to_browser = async (params: { false ) + const only_file_tree = + params.panel_provider.context.workspaceState.get( + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY, + false + ) + const collected = await files_collector.collect_files({ additional_paths, no_context: params.panel_provider.web_prompt_type == 'no-context', shrink: params.panel_provider.web_prompt_type == 'find-relevant-files' && - shrink_source_code + shrink_source_code, + only_file_tree: + params.panel_provider.web_prompt_type == 'find-relevant-files' && + only_file_tree }) const context_text = collected.other_files + collected.recent_files From 4692484e2c5d240bb4b58bccf61e9b232be5b63d Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 00:47:08 +0100 Subject: [PATCH 03/32] The LLM should be able to output a plan in the form of subtasks in "Find Relevant Files" mode, which can then be processed phase by phase by the user/system. --- .../apply-chat-response-command.ts | 74 ++++++++++++++++++- .../clipboard-parser/clipboard-parser.ts | 15 ++++ .../parsers/subtask-directives-parser.ts | 48 ++++++++++++ apps/editor/src/constants/instructions.ts | 34 +++++++++ 4 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts diff --git a/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts b/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts index 1767c5340..493a0a81f 100644 --- a/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts +++ b/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts @@ -26,7 +26,7 @@ import { preview_document_provider, CwcPreviewProvider } from './utils/preview/virtual-document-provider' -import { parse_response } from './utils/clipboard-parser' +import { parse_response, SubtasksItem } from './utils/clipboard-parser' let in_progress = false let placeholder_created_at_for_update: number | undefined @@ -91,9 +91,13 @@ export const apply_chat_response_command = (params: { response: chat_response, is_single_root_folder_workspace }) + const is_relevant_files = clipboard_items.some( - (item) => item.type == 'relevant-files' + (item) => item.type == 'relevant-files' || item.type == 'subtasks' ) + const subtasks_item = clipboard_items.find( + (item) => item.type == 'subtasks' + ) as SubtasksItem | undefined if (response_preview_promise_resolve && !is_relevant_files) { const history = params.panel_provider.response_history @@ -188,6 +192,72 @@ export const apply_chat_response_command = (params: { command: 'HIDE_PROGRESS' }) + if (subtasks_item) { + const tasks_array = subtasks_item.subtasks.map((st) => ({ + id: Math.random().toString(36).substring(7), + description: st.instruction, + is_done: false, + files: st.files + })) + + const default_workspace = + vscode.workspace.workspaceFolders![0].uri.fsPath + + const tasks_record = + params.context.workspaceState.get>( + 'codeWebChat.tasks' + ) || {} + + tasks_record[default_workspace] = tasks_array + + await params.context.workspaceState.update( + 'codeWebChat.tasks', + tasks_record + ) + + params.panel_provider.send_message({ + command: 'TASKS', + tasks: tasks_record as any + }) + + if (tasks_array.length > 0) { + const first_task = tasks_array[0] + + const wp = params.workspace_provider as any + if (typeof wp.set_checked_files === 'function') { + wp.set_checked_files(first_task.files) + } else if (typeof wp.check_file === 'function') { + if (typeof wp.uncheck_all_files === 'function') { + wp.uncheck_all_files() + } + for (const file of first_task.files) { + wp.check_file(file) + } + } + + await params.panel_provider.switch_to_edit_context() + + params.panel_provider.edit_context_instructions.instructions[ + params.panel_provider.edit_context_instructions.active_index + ] = first_task.description + params.panel_provider.caret_position = first_task.description.length + + params.panel_provider.send_message({ + command: 'INSTRUCTIONS', + ask_about_context: + params.panel_provider.ask_about_context_instructions, + edit_context: params.panel_provider.edit_context_instructions, + no_context: params.panel_provider.no_context_instructions, + code_at_cursor: params.panel_provider.code_at_cursor_instructions, + find_relevant_files: + params.panel_provider.find_relevant_files_instructions, + caret_position: params.panel_provider.caret_position + }) + + params.panel_provider.send_context_files() + } + } + if (preview_data) { let created_at_for_preview = args?.created_at if (!args?.files_with_content) { diff --git a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts index 5c8765ffd..31f2e9075 100644 --- a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts +++ b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts @@ -4,6 +4,10 @@ import { parse_multiple_files, parse_relevant_files } from './parsers' +import { + parse_subtask_directives, + SubtaskDirective +} from './parsers/subtask-directives-parser' export type FileItem = { type: 'file' @@ -36,6 +40,11 @@ export type RelevantFilesItem = { file_paths: string[] } +export type SubtasksItem = { + type: 'subtasks' + subtasks: SubtaskDirective[] +} + export type TextItem = { type: 'text' content: string @@ -54,6 +63,7 @@ export type ClipboardItem = | TextItem | InlineFileItem | RelevantFilesItem + | SubtasksItem export const extract_workspace_and_path = (params: { raw_file_path: string @@ -91,6 +101,11 @@ export const parse_response = (params: { return code_at_cursor_items } + const subtasks = parse_subtask_directives(params.response) as SubtasksItem[] + if (subtasks && subtasks.length > 0) { + return subtasks + } + const relevant_files = parse_relevant_files({ response: params.response }) if (relevant_files) { return [relevant_files] diff --git a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts new file mode 100644 index 000000000..e301197a5 --- /dev/null +++ b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts @@ -0,0 +1,48 @@ +export interface SubtaskDirective { + instruction: string + files: string[] +} + +export const parse_subtask_directives = (response: string) => { + const items: any[] = [] + + const subtasks_match = response.match(/([\s\S]*?)<\/subtasks>/i) + if (subtasks_match) { + const subtasks_content = subtasks_match[1] + const subtask_regex = /([\s\S]*?)<\/subtask>/gi + let subtask_match + + const subtasks: SubtaskDirective[] = [] + while ((subtask_match = subtask_regex.exec(subtasks_content)) !== null) { + const subtask_content = subtask_match[1] + + const instruction_match = subtask_content.match( + /([\s\S]*?)<\/instruction>/i + ) + const instruction = instruction_match ? instruction_match[1].trim() : '' + + const files_match = subtask_content.match(/([\s\S]*?)<\/files>/i) + const files: string[] = [] + if (files_match) { + const file_regex = /([\s\S]*?)<\/file>/gi + let file_match + while ((file_match = file_regex.exec(files_match[1])) !== null) { + files.push(file_match[1].trim()) + } + } + + if (instruction || files.length > 0) { + subtasks.push({ instruction, files }) + } + } + + if (subtasks.length > 0) { + items.push({ + type: 'subtasks', + subtasks + }) + } + } + + return items +} diff --git a/apps/editor/src/constants/instructions.ts b/apps/editor/src/constants/instructions.ts index 6ec3df465..120ffcf1d 100644 --- a/apps/editor/src/constants/instructions.ts +++ b/apps/editor/src/constants/instructions.ts @@ -39,6 +39,23 @@ Your response must begin with "**Relevant files:**", then list paths one under a - \`src/index.ts\` - \`src/hello.ts\` - \`src/welcome.ts\` + +If \`only_file_tree\` is active and the request is complex, you can instead respond with a plan using the \`\` format. Each subtask should contain the files that need to be edited and a clear instruction. Example: + + + + Update the greeting logic + + src/hello.ts + + + + Export the new functions + + src/index.ts + + + ` export const find_relevant_files_format_for_panel = ` @@ -51,6 +68,23 @@ Your response must begin with "**Relevant files:**", then list paths one under a - \`src/welcome.ts\` These files contain the core greeting logic and module exports. + +If \`only_file_tree\` is active and the request is complex, you can instead respond with a plan using the \`\` format. Each subtask should contain the files that need to be edited and a clear instruction. Example: + + + + Update the greeting logic + + src/hello.ts + + + + Export the new functions + + src/index.ts + + + ` export const voice_input_instructions = From fbe87bacdb753e27bacb64501777afab7e3cbd99 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 01:32:23 +0100 Subject: [PATCH 04/32] =?UTF-8?q?We=20fix=20the=20lag=20by=20ensuring=20th?= =?UTF-8?q?at=20TokenCalculator=20calculates=20all=20three=20values=20?= =?UTF-8?q?=E2=80=8B=E2=80=8B(Total,=20Shrink,=20Path-only)=20only=20once?= =?UTF-8?q?=20and=20stores=20them=20in=20the=20array/tuple.=20Toggleing=20?= =?UTF-8?q?the=20checkbox=20will=20then=20no=20longer=20reread=20files,=20?= =?UTF-8?q?but=20only=20change=20the=20displayed=20variable=20value.=20At?= =?UTF-8?q?=20the=20same=20time,=20we=20adjust=20the=20FilesCollector=20so?= =?UTF-8?q?=20that=20it=20appends=20the=20token=20count=20to=20the=20file?= =?UTF-8?q?=20path=20in=20the=20format=20(1.2k=20tokens).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../providers/workspace/modules/token-calculator.ts | 4 ++-- apps/editor/src/utils/files-collector.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/editor/src/context/providers/workspace/modules/token-calculator.ts b/apps/editor/src/context/providers/workspace/modules/token-calculator.ts index 181666649..e957d9b73 100644 --- a/apps/editor/src/context/providers/workspace/modules/token-calculator.ts +++ b/apps/editor/src/context/providers/workspace/modules/token-calculator.ts @@ -6,7 +6,7 @@ import { Logger } from '@shared/utils/logger' import type { IWorkspaceProvider } from '../workspace-provider' import { shrink_file } from '@/context/utils/shrink-file' -type TokenData = [number, number, number, number?] +type TokenData = [number, number, number, number] type TokenCacheNode = { [key: string]: TokenCacheNode | TokenData @@ -286,7 +286,7 @@ export class TokenCalculator implements vscode.Disposable { display_path = `${workspace_name}/${display_path}` } } - return Math.floor(`- ${display_path}\n`.length / 4) + return Math.floor(display_path.length / 4) } public async calculate_file_tokens( diff --git a/apps/editor/src/utils/files-collector.ts b/apps/editor/src/utils/files-collector.ts index 0814b74b4..9bdf32d68 100644 --- a/apps/editor/src/utils/files-collector.ts +++ b/apps/editor/src/utils/files-collector.ts @@ -83,7 +83,14 @@ export class FilesCollector { } if (params?.only_file_tree) { - collected_text += `- ${display_path}\n` + const cached_tokens = + this.workspace_provider.get_cached_token_count(file_path) + const token_count = cached_tokens?.total || 0 + const count_str = + token_count >= 1000 + ? `${Number((token_count / 1000).toFixed(1))}k` + : token_count.toString() + collected_text += `- ${display_path} (${count_str} tokens)\n` continue } From f3aa189d4c6de2a80008057e85161d47278896fb Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 01:46:19 +0100 Subject: [PATCH 05/32] We are fixing the bug where the Workspace View is only updated when expanded, and implementing the requested Total (Modified) display. --- .../providers/workspace/workspace-provider.ts | 113 +++++++++--------- .../src/views/panel/backend/panel-provider.ts | 5 + 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/apps/editor/src/context/providers/workspace/workspace-provider.ts b/apps/editor/src/context/providers/workspace/workspace-provider.ts index 6e496b6ab..607b476d9 100644 --- a/apps/editor/src/context/providers/workspace/workspace-provider.ts +++ b/apps/editor/src/context/providers/workspace/workspace-provider.ts @@ -701,57 +701,59 @@ export class WorkspaceProvider false ) const show_only_path_tokens = this._is_frf_mode && only_file_tree + const show_shrink_tokens = this.use_shrink_token_count - const total_token_count = show_only_path_tokens - ? element.pathTokenCount - : this.use_shrink_token_count - ? element.shrinkTokenCount - : element.tokenCount + let active_total = element.tokenCount + let active_selected = element.selectedTokenCount - const selected_token_count = show_only_path_tokens - ? element.selectedPathTokenCount - : this.use_shrink_token_count - ? element.selectedShrinkTokenCount - : element.selectedTokenCount + if (show_only_path_tokens) { + active_total = element.pathTokenCount + } else if (show_shrink_tokens) { + active_total = element.shrinkTokenCount + } - const formatted_total = - total_token_count !== undefined && total_token_count > 0 - ? display_token_count(total_token_count) - : undefined + if (show_only_path_tokens) { + active_selected = element.selectedPathTokenCount + } else if (show_shrink_tokens) { + active_selected = element.selectedShrinkTokenCount + } - const formatted_selected = - selected_token_count !== undefined && selected_token_count > 0 - ? display_token_count(selected_token_count) + const formatted_total = + element.tokenCount !== undefined && element.tokenCount > 0 + ? display_token_count(element.tokenCount) : undefined let display_description = '' + let tooltip_tokens_info = '' - if (element.isDirectory) { - if (formatted_total) { - if (formatted_selected && selected_token_count! < total_token_count!) { - display_description = `${formatted_total} · ${formatted_selected} selected` - } else { - display_description = formatted_total - } + if (formatted_total) { + display_description = formatted_total + tooltip_tokens_info = `About ${formatted_total} tokens` + + let modified_total_str: string | undefined + if (show_only_path_tokens && element.pathTokenCount !== undefined) { + modified_total_str = display_token_count(element.pathTokenCount) + tooltip_tokens_info = `About ${formatted_total} original tokens (${modified_total_str} Tokens for path only)` + } else if (show_shrink_tokens && element.shrinkTokenCount !== undefined) { + modified_total_str = display_token_count(element.shrinkTokenCount) + tooltip_tokens_info = `About ${formatted_total} original tokens (${modified_total_str} Tokens after shrinking)` } - } else { - display_description = formatted_total ?? '' - } - if ( - !show_only_path_tokens && - element.pathTokenCount !== undefined && - element.pathTokenCount > 0 - ) { - const path_token_str = `${element.pathTokenCount} path` - if (element.isDirectory && formatted_selected) { - if (selected_token_count! < total_token_count!) { - display_description += ` (${element.selectedPathTokenCount} path selected)` - } else { - display_description += ` (${path_token_str})` - } - } else { - display_description += ` (${path_token_str})` + if (modified_total_str) { + display_description += ` (${modified_total_str})` + } + + const formatted_selected = + active_selected !== undefined && active_selected > 0 + ? display_token_count(active_selected) + : undefined + + if ( + element.isDirectory && + formatted_selected && + active_selected! < (active_total ?? 0) + ) { + display_description += ` · ${formatted_selected} selected` } } @@ -765,29 +767,32 @@ export class WorkspaceProvider trimmed_description == '' ? undefined : trimmed_description const tooltip_parts = [element.resourceUri.fsPath] - if (formatted_total) { - tooltip_parts.push(`· About ${formatted_total} tokens`) + if (tooltip_tokens_info) { + tooltip_parts.push(`· ${tooltip_tokens_info}`) } - if (element.isDirectory && formatted_selected) { - if ( - total_token_count !== undefined && - selected_token_count == total_token_count - ) { + + if ( + element.isDirectory && + active_selected !== undefined && + active_selected > 0 + ) { + if (active_total !== undefined && active_selected === active_total) { tooltip_parts.push('· Fully selected') } else { - tooltip_parts.push(`· ${formatted_selected} selected`) + const formatted_sel = display_token_count(active_selected) + tooltip_parts.push(`· ${formatted_sel} selected`) } } element.tooltip = tooltip_parts.join(' ') if (element.isWorkspaceRoot) { - // Workspace root tooltip is primarily its name and role, token info is appended if available let root_tooltip = `${element.label} (Workspace Root)` - if (formatted_total) { - root_tooltip += ` • About ${formatted_total} tokens` - if (formatted_selected) { - root_tooltip += ` (${formatted_selected} selected)` + if (tooltip_tokens_info) { + root_tooltip += ` • ${tooltip_tokens_info}` + if (active_selected !== undefined && active_selected > 0) { + const formatted_sel = display_token_count(active_selected) + root_tooltip += ` (${formatted_sel} selected)` } } element.tooltip = root_tooltip diff --git a/apps/editor/src/views/panel/backend/panel-provider.ts b/apps/editor/src/views/panel/backend/panel-provider.ts index b5525f085..14c461841 100644 --- a/apps/editor/src/views/panel/backend/panel-provider.ts +++ b/apps/editor/src/views/panel/backend/panel-provider.ts @@ -1013,6 +1013,7 @@ export class PanelProvider implements vscode.WebviewViewProvider { this, message.shrink_source_code ) + this.update_providers_shrink_mode() } else if ( message.command == 'GET_FIND_RELEVANT_FILES_ONLY_FILE_TREE' ) { @@ -1031,6 +1032,10 @@ export class PanelProvider implements vscode.WebviewViewProvider { FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY, message.only_file_tree ) + this.workspace_provider.refresh() + if ('refresh' in this.open_editors_provider) { + ;(this.open_editors_provider as any).refresh() + } } else if (message.command == 'RELEVANT_FILES_MODAL_RESPONSE') { if (this.relevant_files_choice_resolver) { this.relevant_files_choice_resolver(message.files) From 7a6752b9bd050419c7b8dd17d8eaf3c69778ad9e Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 01:57:44 +0100 Subject: [PATCH 06/32] Ensure that the React webview calculates exactly the tokens to be displayed below the chat (the sum of the modified tokens), and that the checkboxes interact smoothly with each other (e.g., exclude each other if desired). --- .../src/views/panel/backend/panel-provider.ts | 86 +++++++++++++++++-- .../panel/frontend/Main/MainView/MainView.tsx | 13 +-- .../panel/frontend/hooks/panel/use-panel.ts | 16 ++++ 3 files changed, 95 insertions(+), 20 deletions(-) diff --git a/apps/editor/src/views/panel/backend/panel-provider.ts b/apps/editor/src/views/panel/backend/panel-provider.ts index 14c461841..60f5f2da4 100644 --- a/apps/editor/src/views/panel/backend/panel-provider.ts +++ b/apps/editor/src/views/panel/backend/panel-provider.ts @@ -309,6 +309,7 @@ export class PanelProvider implements vscode.WebviewViewProvider { } this.update_providers_shrink_mode() this.update_providers_context_state() + this.send_token_count() } public async send_checkpoints() { @@ -376,6 +377,69 @@ export class PanelProvider implements vscode.WebviewViewProvider { }) } + public async send_token_count(default_token_count?: number) { + const is_find_relevant_files = + (this.mode == MODE.WEB && + this.web_prompt_type == 'find-relevant-files') || + (this.mode == MODE.API && this.api_prompt_type == 'find-relevant-files') + + if (!is_find_relevant_files && default_token_count !== undefined) { + this.send_message({ + command: 'TOKEN_COUNT_UPDATED', + token_count: default_token_count + }) + return + } + + let total = 0 + + try { + const open_editors_counts = (this.open_editors_provider as any) + .get_checked_files_token_count + ? await ( + this.open_editors_provider as any + ).get_checked_files_token_count() + : { total: 0, shrink: 0, path: 0 } + const workspace_counts = + await this.workspace_provider.get_checked_files_token_count() + + if (is_find_relevant_files) { + const only_file_tree = this.context.workspaceState.get( + FIND_RELEVANT_FILES_ONLY_FILE_TREE_STATE_KEY, + false + ) + const shrink_source_code = this.context.workspaceState.get( + FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY, + false + ) + + if (only_file_tree) { + total = (workspace_counts.path || 0) + (open_editors_counts.path || 0) + } else if (shrink_source_code) { + total = + (workspace_counts.shrink || 0) + (open_editors_counts.shrink || 0) + } else { + total = + (workspace_counts.total || 0) + (open_editors_counts.total || 0) + } + } else { + total = (workspace_counts.total || 0) + (open_editors_counts.total || 0) + } + + this.send_message({ + command: 'TOKEN_COUNT_UPDATED', + token_count: total + }) + } catch (e) { + if (default_token_count !== undefined) { + this.send_message({ + command: 'TOKEN_COUNT_UPDATED', + token_count: default_token_count + }) + } + } + } + constructor(params: { extension_uri: vscode.Uri workspace_provider: WorkspaceProvider @@ -527,16 +591,15 @@ export class PanelProvider implements vscode.WebviewViewProvider { } ) - token_count_emitter.on('token-count-updated', (token_count: number) => { - if (this._webview_view) { - this.send_message({ - command: 'TOKEN_COUNT_UPDATED', - token_count - }) - - this.send_context_files() + token_count_emitter.on( + 'token-count-updated', + async (token_count: number) => { + if (this._webview_view) { + await this.send_token_count(token_count) + this.send_context_files() + } } - }) + ) this.context.subscriptions.push(this._config_listener) @@ -885,12 +948,14 @@ export class PanelProvider implements vscode.WebviewViewProvider { await handle_save_web_prompt_type(this, message.prompt_type) this.update_providers_shrink_mode() this.update_providers_context_state() + this.send_token_count() } else if (message.command == 'GET_API_PROMPT_TYPE') { handle_get_api_prompt_type(this) } else if (message.command == 'SAVE_API_PROMPT_TYPE') { await handle_save_api_prompt_type(this, message.prompt_type) this.update_providers_shrink_mode() this.update_providers_context_state() + this.send_token_count() } else if (message.command == 'GET_EDIT_FORMAT_INSTRUCTIONS') { handle_get_edit_format_instructions(this) } else if (message.command == 'GET_EDIT_FORMAT') { @@ -903,6 +968,7 @@ export class PanelProvider implements vscode.WebviewViewProvider { await handle_save_mode(this, message) this.update_providers_shrink_mode() this.update_providers_context_state() + this.send_token_count() } else if (message.command == 'GET_MODE') { handle_get_mode(this) } else if (message.command == 'GET_VERSION') { @@ -1014,6 +1080,7 @@ export class PanelProvider implements vscode.WebviewViewProvider { message.shrink_source_code ) this.update_providers_shrink_mode() + this.send_token_count() } else if ( message.command == 'GET_FIND_RELEVANT_FILES_ONLY_FILE_TREE' ) { @@ -1036,6 +1103,7 @@ export class PanelProvider implements vscode.WebviewViewProvider { if ('refresh' in this.open_editors_provider) { ;(this.open_editors_provider as any).refresh() } + this.send_token_count() } else if (message.command == 'RELEVANT_FILES_MODAL_RESPONSE') { if (this.relevant_files_choice_resolver) { this.relevant_files_choice_resolver(message.files) diff --git a/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx b/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx index bb3befb09..49475b49c 100644 --- a/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx +++ b/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx @@ -203,9 +203,6 @@ export const MainView: React.FC = (props) => { const is_only_file_tree_active = is_in_find_relevant_files_prompt_type && props.find_relevant_files_only_file_tree - const file_tree_token_count = Math.ceil( - props.context_file_paths.join('\n').length / 4 - ) return ( <> @@ -233,14 +230,8 @@ export const MainView: React.FC = (props) => { props.on_find_relevant_files_shrink_source_code_change } id="shrink-source-code" - disabled={props.find_relevant_files_only_file_tree} /> -
@@ -352,7 +343,7 @@ export const MainView: React.FC = (props) => { } is_context_disabled={is_in_no_context_prompt_type} is_only_file_tree_active={is_only_file_tree_active} - file_tree_token_count={file_tree_token_count} + file_tree_token_count={props.token_count} /> diff --git a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts index dcb10d3a4..4f8763122 100644 --- a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts +++ b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts @@ -118,6 +118,14 @@ export const use_panel = (vscode: any) => { command: 'SAVE_FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE', shrink_source_code }) + + if (shrink_source_code) { + set_find_relevant_files_only_file_tree(false) + post_message(vscode, { + command: 'SAVE_FIND_RELEVANT_FILES_ONLY_FILE_TREE', + only_file_tree: false + }) + } } const handle_find_relevant_files_only_file_tree_change = ( @@ -128,6 +136,14 @@ export const use_panel = (vscode: any) => { command: 'SAVE_FIND_RELEVANT_FILES_ONLY_FILE_TREE', only_file_tree }) + + if (only_file_tree) { + set_find_relevant_files_shrink_source_code(false) + post_message(vscode, { + command: 'SAVE_FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE', + shrink_source_code: false + }) + } } useEffect(() => { From 229d49c4982083eda907db04e3039626d68229a2 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 02:18:51 +0100 Subject: [PATCH 07/32] fix(context): eliminate recrunching lag on mode toggle and restore token progress bar in filetree-only mode --- .../workspace/modules/token-calculator.ts | 16 ++++---- .../src/views/panel/backend/panel-provider.ts | 13 ++++--- .../ContextUtilisation/ContextUtilisation.tsx | 39 ++++++++++++------- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/apps/editor/src/context/providers/workspace/modules/token-calculator.ts b/apps/editor/src/context/providers/workspace/modules/token-calculator.ts index e957d9b73..3bdbd65aa 100644 --- a/apps/editor/src/context/providers/workspace/modules/token-calculator.ts +++ b/apps/editor/src/context/providers/workspace/modules/token-calculator.ts @@ -327,7 +327,8 @@ export class TokenCalculator implements vscode.Disposable { if ( cached_file && Array.isArray(cached_file) && - cached_file[0] == mtime + cached_file[0] == mtime && + cached_file[2] !== undefined ) { const tokens = cached_file[1] const shrink_tokens = cached_file[2] @@ -699,12 +700,13 @@ export class TokenCalculator implements vscode.Disposable { continue } - if (fs.statSync(file_path).isFile()) { - if (this._file_token_counts.has(file_path)) { - result.total += this._file_token_counts.get(file_path)! - result.shrink += this._file_shrink_token_counts.get(file_path)! - result.path += this._file_path_token_counts.get(file_path)! - } else { + // Fast path: bypass fs.statSync completely if cache hit + if (this._file_token_counts.has(file_path)) { + result.total += this._file_token_counts.get(file_path)! + result.shrink += this._file_shrink_token_counts.get(file_path)! + result.path += this._file_path_token_counts.get(file_path)! + } else { + if (fs.statSync(file_path).isFile()) { const count = await this.calculate_file_tokens(file_path) result.total += count.total result.shrink += count.shrink diff --git a/apps/editor/src/views/panel/backend/panel-provider.ts b/apps/editor/src/views/panel/backend/panel-provider.ts index 60f5f2da4..5364b8bed 100644 --- a/apps/editor/src/views/panel/backend/panel-provider.ts +++ b/apps/editor/src/views/panel/backend/panel-provider.ts @@ -93,7 +93,6 @@ import { handle_delete_configuration, handle_update_last_used_preset_or_group, handle_get_find_relevant_files_shrink_source_code, - handle_save_find_relevant_files_shrink_source_code, handle_return_home_and_switch_to_edit_context } from './message-handlers' import { SelectionState } from '../types/messages' @@ -1075,12 +1074,16 @@ export class PanelProvider implements vscode.WebviewViewProvider { } else if ( message.command == 'SAVE_FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE' ) { - await handle_save_find_relevant_files_shrink_source_code( - this, + await this.context.workspaceState.update( + FIND_RELEVANT_FILES_SHRINK_SOURCE_CODE_STATE_KEY, message.shrink_source_code ) this.update_providers_shrink_mode() - this.send_token_count() + this.workspace_provider.refresh() + if ('refresh' in this.open_editors_provider) { + ;(this.open_editors_provider as any).refresh() + } + await this.send_token_count() } else if ( message.command == 'GET_FIND_RELEVANT_FILES_ONLY_FILE_TREE' ) { @@ -1103,7 +1106,7 @@ export class PanelProvider implements vscode.WebviewViewProvider { if ('refresh' in this.open_editors_provider) { ;(this.open_editors_provider as any).refresh() } - this.send_token_count() + await this.send_token_count() } else if (message.command == 'RELEVANT_FILES_MODAL_RESPONSE') { if (this.relevant_files_choice_resolver) { this.relevant_files_choice_resolver(message.files) diff --git a/packages/ui/src/components/editor/panel/ContextUtilisation/ContextUtilisation.tsx b/packages/ui/src/components/editor/panel/ContextUtilisation/ContextUtilisation.tsx index a326bf191..48fe826d7 100644 --- a/packages/ui/src/components/editor/panel/ContextUtilisation/ContextUtilisation.tsx +++ b/packages/ui/src/components/editor/panel/ContextUtilisation/ContextUtilisation.tsx @@ -29,6 +29,19 @@ export const ContextUtilisation: React.FC = (props) => { ) } + const active_token_count = + props.is_only_file_tree_active && props.file_tree_token_count !== undefined + ? props.file_tree_token_count + : props.current_context_size + + const is_above_threshold = + active_token_count > props.context_size_warning_threshold + const progress = Math.min( + (active_token_count / props.context_size_warning_threshold) * 100, + 100 + ) + const display_progress = active_token_count > 0 ? Math.max(progress, 1) : 0 + if ( props.is_only_file_tree_active && props.file_tree_token_count !== undefined @@ -36,7 +49,12 @@ export const ContextUtilisation: React.FC = (props) => { return (
-
+
= (props) => { ) } - const is_above_threshold = - props.current_context_size > props.context_size_warning_threshold - const progress = Math.min( - (props.current_context_size / props.context_size_warning_threshold) * 100, - 100 - ) - - const formatted_current_size = format_tokens(props.current_context_size) + const formatted_current_size = format_tokens(active_token_count) const formatted_threshold = format_tokens( props.context_size_warning_threshold ) @@ -65,14 +76,14 @@ export const ContextUtilisation: React.FC = (props) => { if (!is_above_threshold) { const remaining_tokens = props.context_size_warning_threshold - - (props.current_context_size < 1000 - ? props.current_context_size - : Math.floor(props.current_context_size / 1000) * 1000) + (active_token_count < 1000 + ? active_token_count + : Math.floor(active_token_count / 1000) * 1000) const formatted_remaining_tokens = format_tokens(remaining_tokens) title_text = `${formatted_remaining_tokens} tokens remaining until threshold warning (change in settings)` } else { const exceeded_by = - props.current_context_size - props.context_size_warning_threshold + active_token_count - props.context_size_warning_threshold const formatted_exceeded_by = format_tokens(exceeded_by) title_text = `Threshold of ${formatted_threshold} tokens is exceeded by ${formatted_exceeded_by} tokens` } @@ -84,7 +95,7 @@ export const ContextUtilisation: React.FC = (props) => { className={cn(styles.bar__progress, { [styles['bar__progress--warning']]: is_above_threshold })} - style={{ width: `${progress}%` }} + style={{ width: `${display_progress}%` }} />
From 9bc5e480f90ef54db816bcfe9ad413cdf798d57a Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 02:42:48 +0100 Subject: [PATCH 08/32] fix(context): eliminate recrunching lag on mode toggle and restore token progress bar in filetree-only mode --- .../workspace/modules/token-calculator.ts | 60 +++++++++---------- .../providers/workspace/workspace-provider.ts | 35 +++++++---- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/apps/editor/src/context/providers/workspace/modules/token-calculator.ts b/apps/editor/src/context/providers/workspace/modules/token-calculator.ts index 3bdbd65aa..f17d34e15 100644 --- a/apps/editor/src/context/providers/workspace/modules/token-calculator.ts +++ b/apps/editor/src/context/providers/workspace/modules/token-calculator.ts @@ -256,6 +256,14 @@ export class TokenCalculator implements vscode.Disposable { this._directory_selected_path_token_counts.clear() } + public is_directory_cached(dir_path: string): boolean { + return this._directory_token_counts.has(dir_path) + } + + public is_file_cached(file_path: string): boolean { + return this._file_token_counts.has(file_path) + } + public get_cached_token_count( file_path: string ): { total: number; shrink: number; path: number } | undefined { @@ -688,41 +696,29 @@ export class TokenCalculator implements vscode.Disposable { public async get_checked_files_token_count(options?: { exclude_file_path?: string }): Promise<{ total: number; shrink: number; path: number }> { - const checked_files = this._provider.get_checked_files() - const result = { total: 0, shrink: 0, path: 0 } - - for (const file_path of checked_files) { - try { - if ( - options?.exclude_file_path && - file_path == options.exclude_file_path - ) { - continue - } + let result_total = 0 + let result_shrink = 0 + let result_path = 0 + + for (const root of this._provider.get_workspace_roots()) { + const counts = await this.calculate_directory_selected_tokens(root) + result_total += counts.total + result_shrink += counts.shrink + result_path += counts.path + } - // Fast path: bypass fs.statSync completely if cache hit - if (this._file_token_counts.has(file_path)) { - result.total += this._file_token_counts.get(file_path)! - result.shrink += this._file_shrink_token_counts.get(file_path)! - result.path += this._file_path_token_counts.get(file_path)! - } else { - if (fs.statSync(file_path).isFile()) { - const count = await this.calculate_file_tokens(file_path) - result.total += count.total - result.shrink += count.shrink - result.path += count.path - } - } - } catch (error) { - Logger.error({ - function_name: 'get_checked_files_token_count', - message: `Error accessing file ${file_path} for token count`, - data: error - }) - } + if ( + options?.exclude_file_path && + this._provider.get_check_state(options.exclude_file_path) === + vscode.TreeItemCheckboxState.Checked + ) { + const counts = await this.calculate_file_tokens(options.exclude_file_path) + result_total = Math.max(0, result_total - counts.total) + result_shrink = Math.max(0, result_shrink - counts.shrink) + result_path = Math.max(0, result_path - counts.path) } - return result + return { total: result_total, shrink: result_shrink, path: result_path } } public dispose() { diff --git a/apps/editor/src/context/providers/workspace/workspace-provider.ts b/apps/editor/src/context/providers/workspace/workspace-provider.ts index 607b476d9..18116e917 100644 --- a/apps/editor/src/context/providers/workspace/workspace-provider.ts +++ b/apps/editor/src/context/providers/workspace/workspace-provider.ts @@ -1463,19 +1463,28 @@ export class WorkspaceProvider public get_checked_files(): string[] { return Array.from(this._checked_items.entries()) - .filter( - ([file_path, state]) => - state == vscode.TreeItemCheckboxState.Checked && - fs.existsSync(file_path) && - (fs.lstatSync(file_path).isFile() || - fs.lstatSync(file_path).isSymbolicLink()) && - (() => { - const workspace_root = this.get_workspace_root_for_file(file_path) - return workspace_root - ? !this.is_excluded(path.relative(workspace_root, file_path)) - : false - })() - ) + .filter(([file_path, state]) => { + if (state !== vscode.TreeItemCheckboxState.Checked) return false + + // Fast path: Check caches to avoid slow synchronous fs calls + if (this._token_calculator.is_directory_cached(file_path)) return false + if (this._token_calculator.is_file_cached(file_path)) { + const workspace_root = this.get_workspace_root_for_file(file_path) + return workspace_root + ? !this.is_excluded(path.relative(workspace_root, file_path)) + : false + } + + // Fallback if not cached + if (!fs.existsSync(file_path)) return false + const stat = fs.lstatSync(file_path) + if (!stat.isFile() && !stat.isSymbolicLink()) return false + + const workspace_root = this.get_workspace_root_for_file(file_path) + return workspace_root + ? !this.is_excluded(path.relative(workspace_root, file_path)) + : false + }) .map(([path, _]) => path) } From 61cede9ddf3b72fa8e54fb32a1bdfae61f3236d9 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 03:16:39 +0100 Subject: [PATCH 09/32] fix(commands): prevent preview freeze on empty edits and improve subtask instruction generation --- .../apply-chat-response-command.ts | 20 ++++++++++++++++++- apps/editor/src/constants/instructions.ts | 12 +++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts b/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts index 493a0a81f..1bf17cde2 100644 --- a/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts +++ b/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts @@ -98,6 +98,9 @@ export const apply_chat_response_command = (params: { const subtasks_item = clipboard_items.find( (item) => item.type == 'subtasks' ) as SubtasksItem | undefined + const relevant_files_item = clipboard_items.find( + (item) => item.type == 'relevant-files' + ) as { type: 'relevant-files'; files: string[] } | undefined if (response_preview_promise_resolve && !is_relevant_files) { const history = params.panel_provider.response_history @@ -256,9 +259,24 @@ export const apply_chat_response_command = (params: { params.panel_provider.send_context_files() } + } else if (relevant_files_item) { + const wp = params.workspace_provider as any + if (typeof wp.set_checked_files === 'function') { + wp.set_checked_files(relevant_files_item.files) + } else if (typeof wp.check_file === 'function') { + if (typeof wp.uncheck_all_files === 'function') { + wp.uncheck_all_files() + } + for (const file of relevant_files_item.files) { + wp.check_file(file) + } + } + + await params.panel_provider.switch_to_edit_context() + params.panel_provider.send_context_files() } - if (preview_data) { + if (preview_data && preview_data.original_states.length > 0) { let created_at_for_preview = args?.created_at if (!args?.files_with_content) { let total_lines_added = 0 diff --git a/apps/editor/src/constants/instructions.ts b/apps/editor/src/constants/instructions.ts index 120ffcf1d..9c75ba5fb 100644 --- a/apps/editor/src/constants/instructions.ts +++ b/apps/editor/src/constants/instructions.ts @@ -40,17 +40,17 @@ Your response must begin with "**Relevant files:**", then list paths one under a - \`src/hello.ts\` - \`src/welcome.ts\` -If \`only_file_tree\` is active and the request is complex, you can instead respond with a plan using the \`\` format. Each subtask should contain the files that need to be edited and a clear instruction. Example: +If the user asks to implement a feature or perform a task, you MUST respond with a plan using the \`\` format. Each \`\` must be a highly detailed prompt for the next AI agent who will execute this subtask in 'Edit Context' mode. Include all requirements like writing a commit message if the user requested it. Example: - Update the greeting logic + Implement the greeting logic in hello.ts. Ensure you handle undefined inputs and output a commit message summarizing these changes. src/hello.ts - Export the new functions + Export the new functions in index.ts so they are available to other modules. Output a brief commit message for this change as well. src/index.ts @@ -69,17 +69,17 @@ Your response must begin with "**Relevant files:**", then list paths one under a These files contain the core greeting logic and module exports. -If \`only_file_tree\` is active and the request is complex, you can instead respond with a plan using the \`\` format. Each subtask should contain the files that need to be edited and a clear instruction. Example: +If the user asks to implement a feature or perform a task, you MUST respond with a plan using the \`\` format. Each \`\` must be a highly detailed prompt for the next AI agent who will execute this subtask in 'Edit Context' mode. Include all requirements like writing a commit message if the user requested it. Example: - Update the greeting logic + Implement the greeting logic in hello.ts. Ensure you handle undefined inputs and output a commit message summarizing these changes. src/hello.ts - Export the new functions + Export the new functions in index.ts so they are available to other modules. Output a brief commit message for this change as well. src/index.ts From 2f68ea5e5b12915361e6a7514932b36d796a447c Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 03:28:21 +0100 Subject: [PATCH 10/32] fix(context): restore relevant files popup for subtasks and prevent preview freezing --- .../apply-chat-response-command.ts | 89 +---------------- .../response-processor.ts | 97 ++++++++++++++++--- 2 files changed, 84 insertions(+), 102 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts b/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts index 1bf17cde2..e0e4a2718 100644 --- a/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts +++ b/apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts @@ -26,7 +26,7 @@ import { preview_document_provider, CwcPreviewProvider } from './utils/preview/virtual-document-provider' -import { parse_response, SubtasksItem } from './utils/clipboard-parser' +import { parse_response } from './utils/clipboard-parser' let in_progress = false let placeholder_created_at_for_update: number | undefined @@ -95,12 +95,6 @@ export const apply_chat_response_command = (params: { const is_relevant_files = clipboard_items.some( (item) => item.type == 'relevant-files' || item.type == 'subtasks' ) - const subtasks_item = clipboard_items.find( - (item) => item.type == 'subtasks' - ) as SubtasksItem | undefined - const relevant_files_item = clipboard_items.find( - (item) => item.type == 'relevant-files' - ) as { type: 'relevant-files'; files: string[] } | undefined if (response_preview_promise_resolve && !is_relevant_files) { const history = params.panel_provider.response_history @@ -195,87 +189,6 @@ export const apply_chat_response_command = (params: { command: 'HIDE_PROGRESS' }) - if (subtasks_item) { - const tasks_array = subtasks_item.subtasks.map((st) => ({ - id: Math.random().toString(36).substring(7), - description: st.instruction, - is_done: false, - files: st.files - })) - - const default_workspace = - vscode.workspace.workspaceFolders![0].uri.fsPath - - const tasks_record = - params.context.workspaceState.get>( - 'codeWebChat.tasks' - ) || {} - - tasks_record[default_workspace] = tasks_array - - await params.context.workspaceState.update( - 'codeWebChat.tasks', - tasks_record - ) - - params.panel_provider.send_message({ - command: 'TASKS', - tasks: tasks_record as any - }) - - if (tasks_array.length > 0) { - const first_task = tasks_array[0] - - const wp = params.workspace_provider as any - if (typeof wp.set_checked_files === 'function') { - wp.set_checked_files(first_task.files) - } else if (typeof wp.check_file === 'function') { - if (typeof wp.uncheck_all_files === 'function') { - wp.uncheck_all_files() - } - for (const file of first_task.files) { - wp.check_file(file) - } - } - - await params.panel_provider.switch_to_edit_context() - - params.panel_provider.edit_context_instructions.instructions[ - params.panel_provider.edit_context_instructions.active_index - ] = first_task.description - params.panel_provider.caret_position = first_task.description.length - - params.panel_provider.send_message({ - command: 'INSTRUCTIONS', - ask_about_context: - params.panel_provider.ask_about_context_instructions, - edit_context: params.panel_provider.edit_context_instructions, - no_context: params.panel_provider.no_context_instructions, - code_at_cursor: params.panel_provider.code_at_cursor_instructions, - find_relevant_files: - params.panel_provider.find_relevant_files_instructions, - caret_position: params.panel_provider.caret_position - }) - - params.panel_provider.send_context_files() - } - } else if (relevant_files_item) { - const wp = params.workspace_provider as any - if (typeof wp.set_checked_files === 'function') { - wp.set_checked_files(relevant_files_item.files) - } else if (typeof wp.check_file === 'function') { - if (typeof wp.uncheck_all_files === 'function') { - wp.uncheck_all_files() - } - for (const file of relevant_files_item.files) { - wp.check_file(file) - } - } - - await params.panel_provider.switch_to_edit_context() - params.panel_provider.send_context_files() - } - if (preview_data && preview_data.original_states.length > 0) { let created_at_for_preview = args?.created_at if (!args?.files_with_content) { diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index dffa36bc8..f7653dc77 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -8,7 +8,8 @@ import { parse_response, FileItem, DiffItem, - RelevantFilesItem + RelevantFilesItem, + SubtasksItem } from './utils/clipboard-parser' import { create_safe_path } from '@/utils/path-sanitizer' import { dictionary } from '@shared/constants/dictionary' @@ -108,16 +109,28 @@ export const process_chat_response = async ( is_single_root_folder_workspace }) - if (clipboard_items.some((item) => item.type == 'relevant-files')) { - const relevant_files_item = clipboard_items.find( - (item) => item.type == 'relevant-files' - ) as RelevantFilesItem + const relevant_files_item = clipboard_items.find( + (item) => item.type == 'relevant-files' + ) as RelevantFilesItem | undefined + const subtasks_item = clipboard_items.find( + (item) => item.type == 'subtasks' + ) as SubtasksItem | undefined + if (relevant_files_item || subtasks_item) { const current_checked_files = workspace_provider.get_export_state().regular.checked_files const workspace_roots = workspace_provider.get_workspace_roots() - const all_paths_to_process = new Set(relevant_files_item.file_paths) + + const all_paths_to_process = new Set() + if (relevant_files_item) { + relevant_files_item.file_paths.forEach((p) => all_paths_to_process.add(p)) + } + if (subtasks_item) { + subtasks_item.subtasks.forEach((st: any) => + st.files.forEach((f: string) => all_paths_to_process.add(f)) + ) + } const files_for_modal = ( await Promise.all( @@ -178,16 +191,71 @@ export const process_chat_response = async ( shared_context_state.switch_context_state(false) } - const presented_files = files_for_modal.map((f) => f.file_path) + if (subtasks_item) { + const tasks_array = subtasks_item.subtasks.map((st: any) => ({ + id: Math.random().toString(36).substring(7), + description: st.instruction, + is_done: false, + files: st.files + })) - const filtered_current_files = current_checked_files.filter( - (f) => !presented_files.includes(f) - ) + const default_workspace = + vscode.workspace.workspaceFolders![0].uri.fsPath + const tasks_record = + context.workspaceState.get>( + 'codeWebChat.tasks' + ) || {} + tasks_record[default_workspace] = tasks_array + await context.workspaceState.update('codeWebChat.tasks', tasks_record) + panel_provider.send_message({ + command: 'TASKS', + tasks: tasks_record as any + }) - const merged_files = Array.from( - new Set([...filtered_current_files, ...selected_files]) - ) - await workspace_provider.set_checked_files(merged_files) + if (tasks_array.length > 0) { + const first_task = tasks_array[0] + + const approved_first_task_files = first_task.files.filter( + (f: string) => + selected_files.some((sf) => sf.endsWith(f) || sf.includes(f)) + ) + + const presented_files = files_for_modal.map((f) => f.file_path) + const filtered_current_files = current_checked_files.filter( + (f) => !presented_files.includes(f) + ) + const merged_files = Array.from( + new Set([...filtered_current_files, ...approved_first_task_files]) + ) + + await workspace_provider.set_checked_files(merged_files) + + panel_provider.edit_context_instructions.instructions[ + panel_provider.edit_context_instructions.active_index + ] = first_task.description + panel_provider.caret_position = first_task.description.length + + panel_provider.send_message({ + command: 'INSTRUCTIONS', + ask_about_context: panel_provider.ask_about_context_instructions, + edit_context: panel_provider.edit_context_instructions, + no_context: panel_provider.no_context_instructions, + code_at_cursor: panel_provider.code_at_cursor_instructions, + find_relevant_files: + panel_provider.find_relevant_files_instructions, + caret_position: panel_provider.caret_position + }) + } + } else { + const presented_files = files_for_modal.map((f) => f.file_path) + const filtered_current_files = current_checked_files.filter( + (f) => !presented_files.includes(f) + ) + const merged_files = Array.from( + new Set([...filtered_current_files, ...selected_files]) + ) + await workspace_provider.set_checked_files(merged_files) + } if (was_frf) { shared_context_state.switch_context_state(true) @@ -200,6 +268,7 @@ export const process_chat_response = async ( }) await panel_provider.switch_to_edit_context() + panel_provider.send_context_files() } return null From 155b1b2327991acd98723b74f4ac1aec2e780bf6 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 03:40:09 +0100 Subject: [PATCH 11/32] fix(context): ensure selected files are checked and safely parse subtask descriptions --- .../response-processor.ts | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index f7653dc77..ec3b6ba39 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -194,9 +194,18 @@ export const process_chat_response = async ( if (subtasks_item) { const tasks_array = subtasks_item.subtasks.map((st: any) => ({ id: Math.random().toString(36).substring(7), - description: st.instruction, + // Safely fallback to different possible property names from the parser to prevent undefined errors + description: ( + st.instruction || + st.description || + st.title || + st.text || + 'Execute subtask' + ) + .toString() + .trim(), is_done: false, - files: st.files + files: Array.isArray(st.files) ? st.files : [] })) const default_workspace = @@ -212,23 +221,19 @@ export const process_chat_response = async ( tasks: tasks_record as any }) - if (tasks_array.length > 0) { - const first_task = tasks_array[0] - - const approved_first_task_files = first_task.files.filter( - (f: string) => - selected_files.some((sf) => sf.endsWith(f) || sf.includes(f)) - ) + // Check all files the user explicitly approved in the modal + const presented_files = files_for_modal.map((f) => f.file_path) + const filtered_current_files = current_checked_files.filter( + (f) => !presented_files.includes(f) + ) + const merged_files = Array.from( + new Set([...filtered_current_files, ...selected_files]) + ) - const presented_files = files_for_modal.map((f) => f.file_path) - const filtered_current_files = current_checked_files.filter( - (f) => !presented_files.includes(f) - ) - const merged_files = Array.from( - new Set([...filtered_current_files, ...approved_first_task_files]) - ) + await workspace_provider.set_checked_files(merged_files) - await workspace_provider.set_checked_files(merged_files) + if (tasks_array.length > 0) { + const first_task = tasks_array[0] panel_provider.edit_context_instructions.instructions[ panel_provider.edit_context_instructions.active_index From 8052fbd384e53fad3a00adb96b8c6da531dc9492 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 04:11:20 +0100 Subject: [PATCH 12/32] fix(tasks): safely parse subtask descriptions and auto-select task files in context - Fix empty subtask bugs and `trim()` crashes by properly mapping the LLM parsed output to the `text` property with robust fallbacks. - Extend the `Task` type to include an optional `files` array. - Update the `response-processor` to store parsed relative file paths into the created tasks. - Ensure that for the first auto-started subtask, only files explicitly approved by the user in the popup are checked. - Enhance the task "Forward" button to send the full task object instead of just text. - Implement the `SET_TASK_FILES` message handler in the backend to automatically resolve relative task paths to absolute paths and check them in the workspace context. --- .../response-processor.ts | 67 +++++++++++-------- .../src/views/panel/backend/panel-provider.ts | 23 +++++++ .../src/views/panel/frontend/Home/Home.tsx | 6 +- .../panel/frontend/hooks/panel/use-panel.ts | 13 +++- apps/editor/src/views/panel/types/messages.ts | 6 ++ packages/shared/src/types/task.ts | 1 + .../components/editor/panel/Tasks/Tasks.tsx | 4 +- 7 files changed, 85 insertions(+), 35 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index ec3b6ba39..240981fda 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -192,21 +192,22 @@ export const process_chat_response = async ( } if (subtasks_item) { - const tasks_array = subtasks_item.subtasks.map((st: any) => ({ - id: Math.random().toString(36).substring(7), - // Safely fallback to different possible property names from the parser to prevent undefined errors - description: ( - st.instruction || - st.description || - st.title || - st.text || - 'Execute subtask' - ) - .toString() - .trim(), - is_done: false, - files: Array.isArray(st.files) ? st.files : [] - })) + const tasks_array = subtasks_item.subtasks.map( + (st: any, index: number) => ({ + text: ( + st.instruction || + st.description || + st.title || + st.text || + 'Execute subtask' + ) + .toString() + .trim(), + is_checked: false, + created_at: Date.now() + index, + files: Array.isArray(st.files) ? st.files : [] + }) + ) const default_workspace = vscode.workspace.workspaceFolders![0].uri.fsPath @@ -221,24 +222,34 @@ export const process_chat_response = async ( tasks: tasks_record as any }) - // Check all files the user explicitly approved in the modal - const presented_files = files_for_modal.map((f) => f.file_path) - const filtered_current_files = current_checked_files.filter( - (f) => !presented_files.includes(f) - ) - const merged_files = Array.from( - new Set([...filtered_current_files, ...selected_files]) - ) - - await workspace_provider.set_checked_files(merged_files) - if (tasks_array.length > 0) { const first_task = tasks_array[0] + // Only check the absolute paths that match the first task's relative files + const approved_first_task_absolute_paths = selected_files.filter( + (sf) => + first_task.files.some( + (rel_f: string) => sf.endsWith(rel_f) || sf.includes(rel_f) + ) + ) + + const presented_files = files_for_modal.map((f) => f.file_path) + const filtered_current_files = current_checked_files.filter( + (f) => !presented_files.includes(f) + ) + const merged_files = Array.from( + new Set([ + ...filtered_current_files, + ...approved_first_task_absolute_paths + ]) + ) + + await workspace_provider.set_checked_files(merged_files) + panel_provider.edit_context_instructions.instructions[ panel_provider.edit_context_instructions.active_index - ] = first_task.description - panel_provider.caret_position = first_task.description.length + ] = first_task.text + panel_provider.caret_position = first_task.text.length panel_provider.send_message({ command: 'INSTRUCTIONS', diff --git a/apps/editor/src/views/panel/backend/panel-provider.ts b/apps/editor/src/views/panel/backend/panel-provider.ts index 5364b8bed..3a41cec96 100644 --- a/apps/editor/src/views/panel/backend/panel-provider.ts +++ b/apps/editor/src/views/panel/backend/panel-provider.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode' import * as path from 'path' +import * as fs from 'fs' import { ChildProcessWithoutNullStreams } from 'child_process' import { WebSocketManager } from '@/services/websocket-manager' import { @@ -1112,6 +1113,26 @@ export class PanelProvider implements vscode.WebviewViewProvider { this.relevant_files_choice_resolver(message.files) this.relevant_files_choice_resolver = undefined } + } else if (message.command == 'SET_TASK_FILES') { + if (message.files && message.files.length > 0) { + const workspace_roots = + this.workspace_provider.get_workspace_roots() + const absolute_paths = message.files + .map((rel_path: string) => { + for (const root of workspace_roots) { + const potential = path.join(root, rel_path) + if (fs.existsSync(potential)) return potential + } + return null + }) + .filter(Boolean) as string[] + + if (absolute_paths.length > 0) { + await this.workspace_provider.set_checked_files(absolute_paths) + this.send_context_files() + await this.send_token_count() + } + } } } catch (error: any) { Logger.error({ @@ -1295,3 +1316,5 @@ export class PanelProvider implements vscode.WebviewViewProvider { }) } } + +// Note: Ensure 'import * as fs from 'fs'' is added. diff --git a/apps/editor/src/views/panel/frontend/Home/Home.tsx b/apps/editor/src/views/panel/frontend/Home/Home.tsx index 9b84990d7..388c91b68 100644 --- a/apps/editor/src/views/panel/frontend/Home/Home.tsx +++ b/apps/editor/src/views/panel/frontend/Home/Home.tsx @@ -42,7 +42,7 @@ type Props = { tasks: Record on_tasks_change: (root: string, tasks: Task[]) => void on_task_delete: (root: string, timestamp: number) => void - on_task_forward: (text: string) => void + on_task_forward: (task: Task) => void is_setup_complete: boolean } @@ -257,8 +257,8 @@ export const Home: React.FC = (props) => { on_delete={(timestamp) => { handle_delete(workspace_root_folder, timestamp) }} - on_forward={(text) => { - props.on_task_forward(text) + on_forward={(task) => { + props.on_task_forward(task) }} placeholder={t('home.tasks.placeholder')} /> diff --git a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts index 4f8763122..48ba25b49 100644 --- a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts +++ b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts @@ -7,6 +7,7 @@ import { import { Checkpoint } from '../../../types/messages' import { Mode, MODE } from '../../../types/main-view-mode' import { ApiPromptType, WebPromptType } from '@shared/types/prompt-types' +import { Task } from '@shared/types/task' import { post_message } from '../../utils/post_message' import { use_instructions } from './use-instructions' @@ -62,8 +63,16 @@ export const use_panel = (vscode: any) => { set_find_relevant_files_only_file_tree ] = useState(false) - const handle_task_forward = (text: string) => { - handle_instructions_change(text, 'edit-context') + const handle_task_forward = (task: Task) => { + handle_instructions_change(task.text, 'edit-context') + + if (task.files && task.files.length > 0) { + post_message(vscode, { + command: 'SET_TASK_FILES', + files: task.files + }) + } + set_active_view('main') set_main_view_scroll_reset_key((k) => k + 1) } diff --git a/apps/editor/src/views/panel/types/messages.ts b/apps/editor/src/views/panel/types/messages.ts index 2109b6b06..ad80667af 100644 --- a/apps/editor/src/views/panel/types/messages.ts +++ b/apps/editor/src/views/panel/types/messages.ts @@ -469,6 +469,11 @@ export interface SaveTasksMessage extends BaseMessage { tasks: Record } +export interface SetTaskFilesMessage extends BaseMessage { + command: 'SET_TASK_FILES' + files: string[] +} + export interface DeleteTaskMessage extends BaseMessage { command: 'DELETE_TASK' root: string @@ -658,6 +663,7 @@ export type FrontendMessage = | PreviewSwitchChoiceMessage | GetTasksMessage | SaveTasksMessage + | SetTaskFilesMessage | DeleteTaskMessage | PreviewGeneratedCodeMessage | UpdateFileProgressMessage diff --git a/packages/shared/src/types/task.ts b/packages/shared/src/types/task.ts index d3b3c16ec..92fe0ca70 100644 --- a/packages/shared/src/types/task.ts +++ b/packages/shared/src/types/task.ts @@ -4,4 +4,5 @@ export type Task = { created_at: number is_collapsed?: boolean children?: Task[] + files?: string[] } diff --git a/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx b/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx index 0b3ba2f58..ab91b20b0 100644 --- a/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx +++ b/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx @@ -20,7 +20,7 @@ type Props = { on_change: (task: Task) => void on_add: () => void on_add_subtask?: (parent_task: Task) => void - on_forward: (text: string) => void + on_forward: (task: Task) => void on_delete: (created_at: number) => void placeholder: string } @@ -164,7 +164,7 @@ export const Tasks: React.FC = (props) => { codicon_icon="forward" on_click={(e) => { e.stopPropagation() - props.on_forward(params.task.text) + props.on_forward(params.task) set_forwarded_timestamp(params.task.created_at) }} title="Use" From 3ecf1884c273339f2f211f6544b0bbda74fd9df8 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 04:24:24 +0100 Subject: [PATCH 13/32] feat(tasks): smart subtask routing and file auto-selection - Auto-fill prompt and automatically check task-specific files when only 1 subtask is generated. - Route user to the Home view (Tasks list) without auto-filling the prompt when multiple subtasks are generated, improving the planning workflow. - Ensure that tasks only save file paths that the user explicitly approved in the Relevant Files modal. --- .../response-processor.ts | 85 +++++++++++++------ 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index 240981fda..01b54a57a 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -193,20 +193,30 @@ export const process_chat_response = async ( if (subtasks_item) { const tasks_array = subtasks_item.subtasks.map( - (st: any, index: number) => ({ - text: ( - st.instruction || - st.description || - st.title || - st.text || - 'Execute subtask' + (st: any, index: number) => { + const raw_files = Array.isArray(st.files) ? st.files : [] + // Ensure we only store files in the task that the user actually approved in the modal + const approved_files = raw_files.filter((rel_f: string) => + selected_files.some( + (sf) => sf.endsWith(rel_f) || sf.includes(rel_f) + ) ) - .toString() - .trim(), - is_checked: false, - created_at: Date.now() + index, - files: Array.isArray(st.files) ? st.files : [] - }) + + return { + text: ( + st.instruction || + st.description || + st.title || + st.text || + 'Execute subtask' + ) + .toString() + .trim(), + is_checked: false, + created_at: Date.now() + index, + files: approved_files + } + } ) const default_workspace = @@ -217,15 +227,25 @@ export const process_chat_response = async ( ) || {} tasks_record[default_workspace] = tasks_array await context.workspaceState.update('codeWebChat.tasks', tasks_record) + panel_provider.send_message({ command: 'TASKS', tasks: tasks_record as any }) - if (tasks_array.length > 0) { + if (tasks_array.length > 1) { + if (was_frf) { + shared_context_state.switch_context_state(true) + } + panel_provider.send_message({ command: 'RETURN_HOME' }) + panel_provider.send_message({ + command: 'SHOW_AUTO_CLOSING_MODAL', + title: 'Subtasks saved. Select one to start.', + type: 'success' + }) + } else if (tasks_array.length === 1) { const first_task = tasks_array[0] - // Only check the absolute paths that match the first task's relative files const approved_first_task_absolute_paths = selected_files.filter( (sf) => first_task.files.some( @@ -261,6 +281,19 @@ export const process_chat_response = async ( panel_provider.find_relevant_files_instructions, caret_position: panel_provider.caret_position }) + + if (was_frf) { + shared_context_state.switch_context_state(true) + } + + panel_provider.send_message({ + command: 'SHOW_AUTO_CLOSING_MODAL', + title: 'Subtask ready.', + type: 'success' + }) + + await panel_provider.switch_to_edit_context() + panel_provider.send_context_files() } } else { const presented_files = files_for_modal.map((f) => f.file_path) @@ -271,20 +304,20 @@ export const process_chat_response = async ( new Set([...filtered_current_files, ...selected_files]) ) await workspace_provider.set_checked_files(merged_files) - } - if (was_frf) { - shared_context_state.switch_context_state(true) - } + if (was_frf) { + shared_context_state.switch_context_state(true) + } - panel_provider.send_message({ - command: 'SHOW_AUTO_CLOSING_MODAL', - title: t('command.apply-chat-response.relevant-files.success'), - type: 'success' - }) + panel_provider.send_message({ + command: 'SHOW_AUTO_CLOSING_MODAL', + title: t('command.apply-chat-response.relevant-files.success'), + type: 'success' + }) - await panel_provider.switch_to_edit_context() - panel_provider.send_context_files() + await panel_provider.switch_to_edit_context() + panel_provider.send_context_files() + } } return null From 162e51a795df679878fe542ad6b5248bb41c62e5 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 04:33:03 +0100 Subject: [PATCH 14/32] fix(tasks): normalize paths for file matching and auto-switch to edit mode on task forward --- .../response-processor.ts | 14 ++++++++------ .../views/panel/frontend/hooks/panel/use-panel.ts | 2 ++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index 01b54a57a..c7c46e028 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -192,14 +192,18 @@ export const process_chat_response = async ( } if (subtasks_item) { + const is_path_match = (absolute: string, relative: string) => { + const norm_abs = absolute.replace(/\\/g, '/') + const norm_rel = relative.replace(/\\/g, '/') + return norm_abs.endsWith(norm_rel) || norm_abs.includes(norm_rel) + } + const tasks_array = subtasks_item.subtasks.map( (st: any, index: number) => { const raw_files = Array.isArray(st.files) ? st.files : [] // Ensure we only store files in the task that the user actually approved in the modal const approved_files = raw_files.filter((rel_f: string) => - selected_files.some( - (sf) => sf.endsWith(rel_f) || sf.includes(rel_f) - ) + selected_files.some((sf) => is_path_match(sf, rel_f)) ) return { @@ -248,9 +252,7 @@ export const process_chat_response = async ( const approved_first_task_absolute_paths = selected_files.filter( (sf) => - first_task.files.some( - (rel_f: string) => sf.endsWith(rel_f) || sf.includes(rel_f) - ) + first_task.files.some((rel_f: string) => is_path_match(sf, rel_f)) ) const presented_files = files_for_modal.map((f) => f.file_path) diff --git a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts index 48ba25b49..e58f11815 100644 --- a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts +++ b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts @@ -64,6 +64,8 @@ export const use_panel = (vscode: any) => { ] = useState(false) const handle_task_forward = (task: Task) => { + handle_mode_change(MODE.WEB) + handle_web_prompt_type_change('edit-context') handle_instructions_change(task.text, 'edit-context') if (task.files && task.files.length > 0) { From 32cd6e0ffb25b20886a38b23cf9cf57d947397c0 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 04:57:30 +0100 Subject: [PATCH 15/32] fix(tasks): do not save single subtask to task list to prevent redundancy, and Append new multiple tasks to existing ones to prevent data loss --- .../response-processor.ts | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index c7c46e028..f88802cc8 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -223,21 +223,27 @@ export const process_chat_response = async ( } ) - const default_workspace = - vscode.workspace.workspaceFolders![0].uri.fsPath - const tasks_record = - context.workspaceState.get>( - 'codeWebChat.tasks' - ) || {} - tasks_record[default_workspace] = tasks_array - await context.workspaceState.update('codeWebChat.tasks', tasks_record) + if (tasks_array.length > 1) { + // Only save to the task list if there are multiple subtasks + const default_workspace = + vscode.workspace.workspaceFolders![0].uri.fsPath + const tasks_record = + context.workspaceState.get>( + 'codeWebChat.tasks' + ) || {} + + // Append new tasks to existing ones to prevent data loss + tasks_record[default_workspace] = [ + ...(tasks_record[default_workspace] || []), + ...tasks_array + ] + await context.workspaceState.update('codeWebChat.tasks', tasks_record) - panel_provider.send_message({ - command: 'TASKS', - tasks: tasks_record as any - }) + panel_provider.send_message({ + command: 'TASKS', + tasks: tasks_record as any + }) - if (tasks_array.length > 1) { if (was_frf) { shared_context_state.switch_context_state(true) } From c23391a0fe72c03543cf75e86d8dc2112736d4dc Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 05:04:54 +0100 Subject: [PATCH 16/32] fix(tasks): use TasksUtils for unified task state management to prevent ghost tasks --- .../apply-chat-response-command/response-processor.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index f88802cc8..e17330c31 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -30,6 +30,7 @@ import { WorkspaceProvider } from '@/context/providers/workspace/workspace-provi import { natural_sort } from '@/utils/natural-sort' import { t } from '@/i18n' import { is_truncation_line } from './utils/edit-formats/truncations' +import { TasksUtils } from '@/utils/tasks-utils' export type PreviewData = { original_states: OriginalFileState[] @@ -227,17 +228,17 @@ export const process_chat_response = async ( // Only save to the task list if there are multiple subtasks const default_workspace = vscode.workspace.workspaceFolders![0].uri.fsPath - const tasks_record = - context.workspaceState.get>( - 'codeWebChat.tasks' - ) || {} + + // Use TasksUtils instead of workspaceState to prevent desync with deletion logic + const tasks_record = TasksUtils.load_all(context) // Append new tasks to existing ones to prevent data loss tasks_record[default_workspace] = [ ...(tasks_record[default_workspace] || []), ...tasks_array ] - await context.workspaceState.update('codeWebChat.tasks', tasks_record) + + TasksUtils.save_all({ context, tasks: tasks_record }) panel_provider.send_message({ command: 'TASKS', From abbec6d18f6758abfd9fa726d0d0dfdbcd42edef Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 05:28:03 +0100 Subject: [PATCH 17/32] feat(parser): parse commit messages from subtasks --- .../parsers/subtask-directives-parser.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts index e301197a5..edc617062 100644 --- a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts +++ b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts @@ -21,6 +21,16 @@ export const parse_subtask_directives = (response: string) => { ) const instruction = instruction_match ? instruction_match[1].trim() : '' + const commit_match = subtask_content.match( + /([\s\S]*?)<\/commit_message>/i + ) + const commit = commit_match ? commit_match[1].trim() : '' + + let final_instruction = instruction + if (commit) { + final_instruction += `\n\nCommit message: ${commit}` + } + const files_match = subtask_content.match(/([\s\S]*?)<\/files>/i) const files: string[] = [] if (files_match) { @@ -31,8 +41,8 @@ export const parse_subtask_directives = (response: string) => { } } - if (instruction || files.length > 0) { - subtasks.push({ instruction, files }) + if (final_instruction || files.length > 0) { + subtasks.push({ instruction: final_instruction, files }) } } From d965d2fc8ca8796c526477032a2ace005ce69dd5 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 05:30:20 +0100 Subject: [PATCH 18/32] feat(prompts): format subtasks as markdown xml blocks and require commit messages --- apps/editor/src/constants/instructions.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/editor/src/constants/instructions.ts b/apps/editor/src/constants/instructions.ts index 9c75ba5fb..e3103499b 100644 --- a/apps/editor/src/constants/instructions.ts +++ b/apps/editor/src/constants/instructions.ts @@ -40,22 +40,26 @@ Your response must begin with "**Relevant files:**", then list paths one under a - \`src/hello.ts\` - \`src/welcome.ts\` -If the user asks to implement a feature or perform a task, you MUST respond with a plan using the \`\` format. Each \`\` must be a highly detailed prompt for the next AI agent who will execute this subtask in 'Edit Context' mode. Include all requirements like writing a commit message if the user requested it. Example: +If the user asks to implement a feature or perform a task, you MUST respond with a plan using the \`\` format. Wrap the XML block in a markdown code block so it formats nicely in the chat. Each subtask must contain a highly detailed \`\` for the next AI agent, a brief \`\`, and the relevant \`\`. Example: +\`\`\`xml - Implement the greeting logic in hello.ts. Ensure you handle undefined inputs and output a commit message summarizing these changes. + Implement the greeting logic in hello.ts. Ensure you handle undefined inputs. + feat: add greeting logic and handle undefined inputs src/hello.ts - Export the new functions in index.ts so they are available to other modules. Output a brief commit message for this change as well. + Export the new functions in index.ts so they are available to other modules. + feat: export greeting functions in index.ts src/index.ts +\`\`\` ` export const find_relevant_files_format_for_panel = ` @@ -69,22 +73,26 @@ Your response must begin with "**Relevant files:**", then list paths one under a These files contain the core greeting logic and module exports. -If the user asks to implement a feature or perform a task, you MUST respond with a plan using the \`\` format. Each \`\` must be a highly detailed prompt for the next AI agent who will execute this subtask in 'Edit Context' mode. Include all requirements like writing a commit message if the user requested it. Example: +If the user asks to implement a feature or perform a task, you MUST respond with a plan using the \`\` format. Wrap the XML block in a markdown code block so it formats nicely in the chat. Each subtask must contain a highly detailed \`\` for the next AI agent, a brief \`\`, and the relevant \`\`. Example: +\`\`\`xml - Implement the greeting logic in hello.ts. Ensure you handle undefined inputs and output a commit message summarizing these changes. + Implement the greeting logic in hello.ts. Ensure you handle undefined inputs. + feat: add greeting logic and handle undefined inputs src/hello.ts - Export the new functions in index.ts so they are available to other modules. Output a brief commit message for this change as well. + Export the new functions in index.ts so they are available to other modules. + feat: export greeting functions in index.ts src/index.ts +\`\`\` ` export const voice_input_instructions = From d9c654fccb3f369eaacf7431a323f7a660d063dc Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 05:43:46 +0100 Subject: [PATCH 19/32] feat(prompts): switch subtasks output format from xml to native markdown --- apps/editor/src/constants/instructions.ts | 70 ++++++++++------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/apps/editor/src/constants/instructions.ts b/apps/editor/src/constants/instructions.ts index e3103499b..b801de5a2 100644 --- a/apps/editor/src/constants/instructions.ts +++ b/apps/editor/src/constants/instructions.ts @@ -40,26 +40,21 @@ Your response must begin with "**Relevant files:**", then list paths one under a - \`src/hello.ts\` - \`src/welcome.ts\` -If the user asks to implement a feature or perform a task, you MUST respond with a plan using the \`\` format. Wrap the XML block in a markdown code block so it formats nicely in the chat. Each subtask must contain a highly detailed \`\` for the next AI agent, a brief \`\`, and the relevant \`\`. Example: - -\`\`\`xml - - - Implement the greeting logic in hello.ts. Ensure you handle undefined inputs. - feat: add greeting logic and handle undefined inputs - - src/hello.ts - - - - Export the new functions in index.ts so they are available to other modules. - feat: export greeting functions in index.ts - - src/index.ts - - - -\`\`\` +If the user asks to implement a feature or perform a task, you MUST respond with a plan formatted strictly as a Markdown list of subtasks. Do NOT use XML. Use the following exact headings: + +**Subtasks:** + +### Subtask 1 +**Instruction:** Implement the greeting logic in hello.ts. Ensure you handle undefined inputs. +**Commit message:** feat: add greeting logic and handle undefined inputs +**Files:** +- \`src/hello.ts\` + +### Subtask 2 +**Instruction:** Export the new functions in index.ts so they are available to other modules. +**Commit message:** feat: export greeting functions in index.ts +**Files:** +- \`src/index.ts\` ` export const find_relevant_files_format_for_panel = ` @@ -73,26 +68,21 @@ Your response must begin with "**Relevant files:**", then list paths one under a These files contain the core greeting logic and module exports. -If the user asks to implement a feature or perform a task, you MUST respond with a plan using the \`\` format. Wrap the XML block in a markdown code block so it formats nicely in the chat. Each subtask must contain a highly detailed \`\` for the next AI agent, a brief \`\`, and the relevant \`\`. Example: - -\`\`\`xml - - - Implement the greeting logic in hello.ts. Ensure you handle undefined inputs. - feat: add greeting logic and handle undefined inputs - - src/hello.ts - - - - Export the new functions in index.ts so they are available to other modules. - feat: export greeting functions in index.ts - - src/index.ts - - - -\`\`\` +If the user asks to implement a feature or perform a task, you MUST respond with a plan formatted strictly as a Markdown list of subtasks. Do NOT use XML. Use the following exact headings: + +**Subtasks:** + +### Subtask 1 +**Instruction:** Implement the greeting logic in hello.ts. Ensure you handle undefined inputs. +**Commit message:** feat: add greeting logic and handle undefined inputs +**Files:** +- \`src/hello.ts\` + +### Subtask 2 +**Instruction:** Export the new functions in index.ts so they are available to other modules. +**Commit message:** feat: export greeting functions in index.ts +**Files:** +- \`src/index.ts\` ` export const voice_input_instructions = From 81a99987179709cb950418540a5f16cacd5b378c Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 05:45:42 +0100 Subject: [PATCH 20/32] feat(parser): parse subtasks from markdown headings and lists --- .../parsers/subtask-directives-parser.ts | 64 ++++++++++++++++--- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts index edc617062..ed452b980 100644 --- a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts +++ b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts @@ -5,14 +5,15 @@ export interface SubtaskDirective { export const parse_subtask_directives = (response: string) => { const items: any[] = [] + const subtasks: SubtaskDirective[] = [] - const subtasks_match = response.match(/([\s\S]*?)<\/subtasks>/i) - if (subtasks_match) { - const subtasks_content = subtasks_match[1] + // 1. Try old XML format (for backward compatibility with history) + const xml_subtasks_match = response.match(/([\s\S]*?)<\/subtasks>/i) + if (xml_subtasks_match) { + const subtasks_content = xml_subtasks_match[1] const subtask_regex = /([\s\S]*?)<\/subtask>/gi let subtask_match - const subtasks: SubtaskDirective[] = [] while ((subtask_match = subtask_regex.exec(subtasks_content)) !== null) { const subtask_content = subtask_match[1] @@ -45,14 +46,59 @@ export const parse_subtask_directives = (response: string) => { subtasks.push({ instruction: final_instruction, files }) } } + } else { + // 2. Try new Markdown format + const md_subtasks_match = response.match(/\*\*Subtasks:\*\*([\s\S]+)/i) + if (md_subtasks_match) { + const content = md_subtasks_match[1] + // Split safely by LLM generated headings (### Subtask 1, **Subtask 1**, or 1. **Instruction:**) + const blocks = content.split( + /(?:###\s*Subtask\s*\d+|\*\*Subtask\s*\d+\*\*|(?:\n|^)\d+\.\s*(?=\*\*Instruction:\*\*))/i + ) + + for (const block of blocks) { + if (!block.trim()) continue + + const instruction_match = block.match( + /\*\*Instruction:\*\*\s*([\s\S]*?)(?=\n\*\*Commit message:\*\*|\n\*\*Files:\*\*|$)/i + ) + const commit_match = block.match( + /\*\*Commit message:\*\*\s*([\s\S]*?)(?=\n\*\*Files:\*\*|$)/i + ) + const files_match = block.match(/\*\*Files:\*\*([\s\S]*)/i) + + const instruction = instruction_match ? instruction_match[1].trim() : '' + const commit = commit_match ? commit_match[1].trim() : '' + + let final_instruction = instruction + if (commit) { + final_instruction += `\n\nCommit message: ${commit}` + } - if (subtasks.length > 0) { - items.push({ - type: 'subtasks', - subtasks - }) + const files: string[] = [] + if (files_match) { + // Extracts paths from standard markdown lists like: - `src/file.ts` or * src/file.ts + const file_regex = /^[\s]*[-*]\s*`?([^`\n]+)`?/gm + let file_match + while ((file_match = file_regex.exec(files_match[1])) !== null) { + const path = file_match[1].trim() + if (path) files.push(path) + } + } + + if (final_instruction || files.length > 0) { + subtasks.push({ instruction: final_instruction, files }) + } + } } } + if (subtasks.length > 0) { + items.push({ + type: 'subtasks', + subtasks + }) + } + return items } From 470a18546064d88735509438ebae67d65b170dfa Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 06:05:06 +0100 Subject: [PATCH 21/32] feat(tasks): display affected files under each task in the tasks view - Update `Tasks.tsx` to render the list of affected file paths beneath the task description. - Add corresponding styles in `Tasks.module.scss` for a dimmed, native VS Code look with file icons. --- .../editor/panel/Tasks/Tasks.module.scss | 30 +++++++++++++++++++ .../components/editor/panel/Tasks/Tasks.tsx | 14 +++++++++ 2 files changed, 44 insertions(+) diff --git a/packages/ui/src/components/editor/panel/Tasks/Tasks.module.scss b/packages/ui/src/components/editor/panel/Tasks/Tasks.module.scss index 2576e3ec6..927ceea3a 100644 --- a/packages/ui/src/components/editor/panel/Tasks/Tasks.module.scss +++ b/packages/ui/src/components/editor/panel/Tasks/Tasks.module.scss @@ -102,6 +102,36 @@ opacity: 0.5; } } + + &__files { + display: flex; + flex-direction: column; + gap: 2px; + margin-top: 6px; + + &--checked { + opacity: 0.5; + } + } + + &__file { + font-size: 11px; + color: var(--cwc-text-color-dimmed); + display: flex; + align-items: flex-start; + gap: 4px; + word-break: break-word; + line-height: 1.3; + + &::before { + content: '\ea6f'; + font-family: 'codicon'; + font-size: 12px; + opacity: 0.7; + flex-shrink: 0; + margin-top: 1px; + } + } } .add-button { diff --git a/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx b/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx index ab91b20b0..e13ce298c 100644 --- a/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx +++ b/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx @@ -259,6 +259,20 @@ export const Tasks: React.FC = (props) => { {params.task.text || props.placeholder}
)} + + {params.task.files && params.task.files.length > 0 && ( +
+ {params.task.files.map((file, index) => ( +
+ {file} +
+ ))} +
+ )}
) From 2a95951202d5e6087607dc1d69b69b01f0989109 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 06:09:48 +0100 Subject: [PATCH 22/32] refactor(prompts): make subtask generation optional for simple requests` --- apps/editor/src/constants/instructions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/editor/src/constants/instructions.ts b/apps/editor/src/constants/instructions.ts index b801de5a2..ffc5809c5 100644 --- a/apps/editor/src/constants/instructions.ts +++ b/apps/editor/src/constants/instructions.ts @@ -40,7 +40,7 @@ Your response must begin with "**Relevant files:**", then list paths one under a - \`src/hello.ts\` - \`src/welcome.ts\` -If the user asks to implement a feature or perform a task, you MUST respond with a plan formatted strictly as a Markdown list of subtasks. Do NOT use XML. Use the following exact headings: +If the task is complex and requires multiple logical steps, break it down into a plan formatted strictly as a Markdown list of subtasks. For simple requests, you can provide just a single subtask. Do NOT use XML. Use the following exact headings: **Subtasks:** @@ -68,7 +68,7 @@ Your response must begin with "**Relevant files:**", then list paths one under a These files contain the core greeting logic and module exports. -If the user asks to implement a feature or perform a task, you MUST respond with a plan formatted strictly as a Markdown list of subtasks. Do NOT use XML. Use the following exact headings: +If the task is complex and requires multiple logical steps, break it down into a plan formatted strictly as a Markdown list of subtasks. For simple requests, you can provide just a single subtask. Do NOT use XML. Use the following exact headings: **Subtasks:** From ca1880e101792679737fce461e6d162a9e3c0e09 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 06:15:31 +0100 Subject: [PATCH 23/32] fix(parser): ignore system keywords inside markdown code blocks --- .../clipboard-parser/parsers/relevant-files-parser.ts | 5 +++-- .../parsers/subtask-directives-parser.ts | 11 +++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/relevant-files-parser.ts b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/relevant-files-parser.ts index 79460c7f5..82d670446 100644 --- a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/relevant-files-parser.ts +++ b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/relevant-files-parser.ts @@ -3,8 +3,9 @@ import { RelevantFilesItem } from '../clipboard-parser' export const parse_relevant_files = (params: { response: string }): RelevantFilesItem | null => { - const trimmed_response = params.response.trim() - const lines = trimmed_response.split('\n') + // Strip out everything inside markdown code blocks to prevent false positive keyword matches + const clean_response = params.response.replace(/```[\s\S]*?```/g, '') + const lines = clean_response.trim().split('\n') if (lines.length == 0) { return null diff --git a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts index ed452b980..cacdcb312 100644 --- a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts +++ b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts @@ -7,8 +7,13 @@ export const parse_subtask_directives = (response: string) => { const items: any[] = [] const subtasks: SubtaskDirective[] = [] + // Strip out everything inside markdown code blocks to prevent false positive keyword matches + const clean_response = response.replace(/```[\s\S]*?```/g, '') + // 1. Try old XML format (for backward compatibility with history) - const xml_subtasks_match = response.match(/([\s\S]*?)<\/subtasks>/i) + const xml_subtasks_match = clean_response.match( + /([\s\S]*?)<\/subtasks>/i + ) if (xml_subtasks_match) { const subtasks_content = xml_subtasks_match[1] const subtask_regex = /([\s\S]*?)<\/subtask>/gi @@ -48,7 +53,9 @@ export const parse_subtask_directives = (response: string) => { } } else { // 2. Try new Markdown format - const md_subtasks_match = response.match(/\*\*Subtasks:\*\*([\s\S]+)/i) + const md_subtasks_match = clean_response.match( + /\*\*Subtasks:\*\*([\s\S]+)/i + ) if (md_subtasks_match) { const content = md_subtasks_match[1] // Split safely by LLM generated headings (### Subtask 1, **Subtask 1**, or 1. **Instruction:**) From b63eb574c55edb204ba543bc43dd79e027b51ecc Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 06:23:03 +0100 Subject: [PATCH 24/32] fix(tasks): prevent context race condition by delaying task file selection --- .../views/panel/frontend/hooks/panel/use-panel.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts index e58f11815..62fdb2f06 100644 --- a/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts +++ b/apps/editor/src/views/panel/frontend/hooks/panel/use-panel.ts @@ -69,10 +69,15 @@ export const use_panel = (vscode: any) => { handle_instructions_change(task.text, 'edit-context') if (task.files && task.files.length > 0) { - post_message(vscode, { - command: 'SET_TASK_FILES', - files: task.files - }) + const files = task.files + // Delay setting the files slightly to allow the backend to finish switching + // the workspace context state (which involves async state loading). + setTimeout(() => { + post_message(vscode, { + command: 'SET_TASK_FILES', + files: files + }) + }, 150) } set_active_view('main') From 7c2ae619ab26f83b496c690cf62cf79171d4400f Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 06:45:12 +0100 Subject: [PATCH 25/32] feat(tasks): separate commit messages, add token counts, and make task files clickable - Update `Task` type to support token counts per file and a separate `commit_message` property. - Update `subtask-directives-parser` to extract `commit_message` cleanly instead of appending it to the instruction. - Map matched files in `response-processor` to include their pre-calculated token counts. - Enhance `Tasks.tsx` to render the separate commit message, show formatted token counts, and trigger `on_go_to_file` when a task file is clicked. - Add `FILL_SCM_COMMIT` command in `PanelProvider` to interact natively with the VS Code Git extension. --- .../response-processor.ts | 40 ++++++++++++++++++- .../parsers/subtask-directives-parser.ts | 19 +++------ .../src/views/panel/backend/panel-provider.ts | 25 +++++++++++- packages/shared/src/types/task.ts | 8 +++- .../editor/panel/Tasks/Tasks.module.scss | 32 +++++++++++++++ .../components/editor/panel/Tasks/Tasks.tsx | 33 ++++++++++++++- 6 files changed, 136 insertions(+), 21 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index e17330c31..8655f93fc 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -207,6 +207,16 @@ export const process_chat_response = async ( selected_files.some((sf) => is_path_match(sf, rel_f)) ) + const task_files = approved_files.map((rel_f: string) => { + const matched_modal_file = files_for_modal.find((f) => + is_path_match(f.file_path, rel_f) + ) + return { + path: rel_f, + tokens: matched_modal_file?.token_count + } + }) + return { text: ( st.instruction || @@ -217,9 +227,10 @@ export const process_chat_response = async ( ) .toString() .trim(), + commit_message: st.commit_message, is_checked: false, created_at: Date.now() + index, - files: approved_files + files: task_files } } ) @@ -259,7 +270,9 @@ export const process_chat_response = async ( const approved_first_task_absolute_paths = selected_files.filter( (sf) => - first_task.files.some((rel_f: string) => is_path_match(sf, rel_f)) + first_task.files.some((rel_f: any) => + is_path_match(sf, rel_f.path) + ) ) const presented_files = files_for_modal.map((f) => f.file_path) @@ -291,6 +304,13 @@ export const process_chat_response = async ( caret_position: panel_provider.caret_position }) + if (first_task.commit_message) { + panel_provider.send_message({ + command: 'SET_ACTIVE_COMMIT_MESSAGE', + commit_message: first_task.commit_message + } as any) + } + if (was_frf) { shared_context_state.switch_context_state(true) } @@ -314,6 +334,22 @@ export const process_chat_response = async ( ) await workspace_provider.set_checked_files(merged_files) + // Clear the old prompt from cache + panel_provider.edit_context_instructions.instructions[ + panel_provider.edit_context_instructions.active_index + ] = '' + panel_provider.caret_position = 0 + + panel_provider.send_message({ + command: 'INSTRUCTIONS', + ask_about_context: panel_provider.ask_about_context_instructions, + edit_context: panel_provider.edit_context_instructions, + no_context: panel_provider.no_context_instructions, + code_at_cursor: panel_provider.code_at_cursor_instructions, + find_relevant_files: panel_provider.find_relevant_files_instructions, + caret_position: panel_provider.caret_position + }) + if (was_frf) { shared_context_state.switch_context_state(true) } diff --git a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts index cacdcb312..07ee64b14 100644 --- a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts +++ b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/parsers/subtask-directives-parser.ts @@ -1,5 +1,6 @@ export interface SubtaskDirective { instruction: string + commit_message?: string files: string[] } @@ -32,11 +33,6 @@ export const parse_subtask_directives = (response: string) => { ) const commit = commit_match ? commit_match[1].trim() : '' - let final_instruction = instruction - if (commit) { - final_instruction += `\n\nCommit message: ${commit}` - } - const files_match = subtask_content.match(/([\s\S]*?)<\/files>/i) const files: string[] = [] if (files_match) { @@ -47,8 +43,8 @@ export const parse_subtask_directives = (response: string) => { } } - if (final_instruction || files.length > 0) { - subtasks.push({ instruction: final_instruction, files }) + if (instruction || files.length > 0) { + subtasks.push({ instruction, commit_message: commit, files }) } } } else { @@ -77,11 +73,6 @@ export const parse_subtask_directives = (response: string) => { const instruction = instruction_match ? instruction_match[1].trim() : '' const commit = commit_match ? commit_match[1].trim() : '' - let final_instruction = instruction - if (commit) { - final_instruction += `\n\nCommit message: ${commit}` - } - const files: string[] = [] if (files_match) { // Extracts paths from standard markdown lists like: - `src/file.ts` or * src/file.ts @@ -93,8 +84,8 @@ export const parse_subtask_directives = (response: string) => { } } - if (final_instruction || files.length > 0) { - subtasks.push({ instruction: final_instruction, files }) + if (instruction || files.length > 0) { + subtasks.push({ instruction, commit_message: commit, files }) } } } diff --git a/apps/editor/src/views/panel/backend/panel-provider.ts b/apps/editor/src/views/panel/backend/panel-provider.ts index 3a41cec96..b56123248 100644 --- a/apps/editor/src/views/panel/backend/panel-provider.ts +++ b/apps/editor/src/views/panel/backend/panel-provider.ts @@ -1133,6 +1133,29 @@ export class PanelProvider implements vscode.WebviewViewProvider { await this.send_token_count() } } + } else if ((message as any).command == 'FILL_SCM_COMMIT') { + try { + const gitExtension = vscode.extensions.getExtension('vscode.git') + if (gitExtension) { + const git = gitExtension.exports.getAPI(1) + if (git.repositories.length > 0) { + git.repositories[0].inputBox.value = ( + message as any + ).commit_message + vscode.commands.executeCommand('workbench.view.scm') + } else { + vscode.window.showInformationMessage( + 'No active Git repositories found.' + ) + } + } + } catch (error) { + Logger.error({ + function_name: 'FILL_SCM_COMMIT', + message: 'Failed to fill SCM commit message', + data: error + }) + } } } catch (error: any) { Logger.error({ @@ -1316,5 +1339,3 @@ export class PanelProvider implements vscode.WebviewViewProvider { }) } } - -// Note: Ensure 'import * as fs from 'fs'' is added. diff --git a/packages/shared/src/types/task.ts b/packages/shared/src/types/task.ts index 92fe0ca70..86a8306f1 100644 --- a/packages/shared/src/types/task.ts +++ b/packages/shared/src/types/task.ts @@ -1,8 +1,14 @@ +export type TaskFile = { + path: string + tokens?: number +} + export type Task = { text: string is_checked: boolean created_at: number is_collapsed?: boolean children?: Task[] - files?: string[] + files?: TaskFile[] + commit_message?: string } diff --git a/packages/ui/src/components/editor/panel/Tasks/Tasks.module.scss b/packages/ui/src/components/editor/panel/Tasks/Tasks.module.scss index 927ceea3a..ece471673 100644 --- a/packages/ui/src/components/editor/panel/Tasks/Tasks.module.scss +++ b/packages/ui/src/components/editor/panel/Tasks/Tasks.module.scss @@ -103,6 +103,22 @@ } } + &__commit { + font-size: 11px; + color: var(--cwc-text-color); + background: var(--vscode-textBlockQuote-background); + border-left: 2px solid var(--vscode-textBlockQuote-border); + padding: 4px 6px; + margin-top: 6px; + border-radius: 2px; + word-break: break-word; + + &-label { + font-weight: bold; + opacity: 0.8; + } + } + &__files { display: flex; flex-direction: column; @@ -122,6 +138,12 @@ gap: 4px; word-break: break-word; line-height: 1.3; + cursor: pointer; + + &:hover { + color: var(--vscode-textLink-activeForeground); + text-decoration: underline; + } &::before { content: '\ea6f'; @@ -131,6 +153,16 @@ flex-shrink: 0; margin-top: 1px; } + + &-path { + flex-grow: 1; + } + + &-tokens { + opacity: 0.5; + font-size: 10px; + white-space: nowrap; + } } } diff --git a/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx b/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx index e13ce298c..b550c342a 100644 --- a/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx +++ b/packages/ui/src/components/editor/panel/Tasks/Tasks.tsx @@ -22,6 +22,7 @@ type Props = { on_add_subtask?: (parent_task: Task) => void on_forward: (task: Task) => void on_delete: (created_at: number) => void + on_go_to_file?: (file_path: string) => void placeholder: string } @@ -77,6 +78,12 @@ export const Tasks: React.FC = (props) => { props.on_change({ ...rest, is_checked: checked }) } + const format_tokens = (tokens?: number) => { + if (tokens === undefined) return '' + if (tokens >= 1000) return `(${(tokens / 1000).toFixed(1)}k tokens)` + return `(${tokens} tokens)` + } + const render_item = (params: { task: Task is_visually_checked: boolean @@ -260,6 +267,13 @@ export const Tasks: React.FC = (props) => { )} + {params.task.commit_message && ( +
+ Commit:{' '} + {params.task.commit_message} +
+ )} + {params.task.files && params.task.files.length > 0 && (
= (props) => { })} > {params.task.files.map((file, index) => ( -
- {file} +
{ + e.stopPropagation() + if (props.on_go_to_file) { + props.on_go_to_file(file.path) + } + }} + title={props.on_go_to_file ? 'Click to open file' : undefined} + > + {file.path} + {file.tokens !== undefined && ( + + {format_tokens(file.tokens)} + + )}
))}
From 42689f5d3f06c3ce48e8a5e81a1cb10270ce27de Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 06:50:09 +0100 Subject: [PATCH 26/32] feat(ui): add fill scm commit button and fix task files typescript error - Fix TypeScript error in `use-panel.ts` by mapping `TaskFile` objects to path strings before sending them to the backend. - Introduce `active_commit_message` state in `use-panel.ts` and listen for backend updates (`SET_ACTIVE_COMMIT_MESSAGE`). - Render a new "Fill SCM Commit" banner and button in `MainView.tsx` when a commit message is available for the current task. - Trigger the backend `FILL_SCM_COMMIT` command when the user clicks the button. --- .../src/views/panel/frontend/Main/Main.tsx | 4 ++ .../panel/frontend/Main/MainView/MainView.tsx | 51 +++++++++++++++++++ .../panel/frontend/hooks/panel/use-panel.ts | 38 ++++++++++++-- apps/editor/src/views/panel/types/messages.ts | 2 + 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/apps/editor/src/views/panel/frontend/Main/Main.tsx b/apps/editor/src/views/panel/frontend/Main/Main.tsx index 415ce19cb..7fc8343b7 100644 --- a/apps/editor/src/views/panel/frontend/Main/Main.tsx +++ b/apps/editor/src/views/panel/frontend/Main/Main.tsx @@ -82,6 +82,8 @@ type Props = { on_new_tab: () => void on_tab_delete: (index: number) => void missing_preset?: boolean + active_commit_message?: string + on_fill_scm_commit: () => void } export const Main: React.FC = (props) => { @@ -875,6 +877,8 @@ export const Main: React.FC = (props) => { on_tab_change={props.on_tab_change} on_new_tab={props.on_new_tab} on_tab_delete={props.on_tab_delete} + active_commit_message={props.active_commit_message} + on_fill_scm_commit={props.on_fill_scm_commit} /> ) } diff --git a/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx b/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx index 49475b49c..26e97fbbb 100644 --- a/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx +++ b/apps/editor/src/views/panel/frontend/Main/MainView/MainView.tsx @@ -127,6 +127,8 @@ type Props = { on_tab_change: (index: number) => void on_new_tab: () => void on_tab_delete: (index: number) => void + active_commit_message?: string + on_fill_scm_commit?: () => void } export const MainView: React.FC = (props) => { @@ -272,6 +274,55 @@ export const MainView: React.FC = (props) => { /> )} + {props.active_commit_message && ( +
+
+ + Pending Commit: + {' '} + {props.active_commit_message} +
+ +
+ )} +
{ set_find_relevant_files_only_file_tree ] = useState(false) + const [active_commit_message, set_active_commit_message] = useState< + string | undefined + >() + + const handle_instructions_change_with_commit_clear = ( + value: string, + prompt_type: any + ) => { + // If user manually types a new prompt, we keep the commit message unless they clear it entirely + if (!value) { + set_active_commit_message(undefined) + } + handle_instructions_change(value, prompt_type) + } + const handle_task_forward = (task: Task) => { handle_mode_change(MODE.WEB) handle_web_prompt_type_change('edit-context') - handle_instructions_change(task.text, 'edit-context') + handle_instructions_change_with_commit_clear(task.text, 'edit-context') + set_active_commit_message(task.commit_message) if (task.files && task.files.length > 0) { const files = task.files @@ -75,7 +91,7 @@ export const use_panel = (vscode: any) => { setTimeout(() => { post_message(vscode, { command: 'SET_TASK_FILES', - files: files + files: files.map((f) => f.path) }) }, 150) } @@ -162,6 +178,15 @@ export const use_panel = (vscode: any) => { } } + const handle_fill_scm_commit = () => { + if (active_commit_message) { + post_message(vscode, { + command: 'FILL_SCM_COMMIT', + commit_message: active_commit_message + }) + } + } + useEffect(() => { const handle_message = (event: MessageEvent) => { const message = event.data @@ -203,8 +228,11 @@ export const use_panel = (vscode: any) => { set_find_relevant_files_shrink_source_code(message.shrink_source_code) } else if (message.command == 'FIND_RELEVANT_FILES_ONLY_FILE_TREE') { set_find_relevant_files_only_file_tree(message.only_file_tree) + } else if (message.command == 'SET_ACTIVE_COMMIT_MESSAGE') { + set_active_commit_message(message.commit_message) } else if (message.command == 'RETURN_HOME') { set_active_view('home') + set_active_commit_message(undefined) } } window.addEventListener('message', handle_message) @@ -345,7 +373,7 @@ export const use_panel = (vscode: any) => { : false, is_timeline_collapsed, handle_task_forward, - handle_instructions_change, + set_instructions: handle_instructions_change_with_commit_clear, handle_web_prompt_type_change, handle_api_prompt_type_change, handle_mode_change, @@ -366,6 +394,8 @@ export const use_panel = (vscode: any) => { handle_find_relevant_files_only_file_tree_change, handle_tab_change, handle_new_tab, - handle_tab_delete + handle_tab_delete, + active_commit_message, + handle_fill_scm_commit } } diff --git a/apps/editor/src/views/panel/types/messages.ts b/apps/editor/src/views/panel/types/messages.ts index ad80667af..2a16cdfd6 100644 --- a/apps/editor/src/views/panel/types/messages.ts +++ b/apps/editor/src/views/panel/types/messages.ts @@ -684,6 +684,7 @@ export type FrontendMessage = | GetSetupProgressMessage | RequestReturnHomeMessage | RelevantFilesModalResponseMessage + | { command: 'FILL_SCM_COMMIT'; commit_message: string } // === FROM BACKEND TO FRONTEND === export interface InstructionsMessage extends BaseMessage { @@ -1010,3 +1011,4 @@ export type BackendMessage = | InsertSymbolAtCursorMessage | ReturnHomeMessage | ShowRelevantFilesModalMessage + | { command: 'SET_ACTIVE_COMMIT_MESSAGE'; commit_message: string } From 74d1073fd5228992c7d6e49240920eff63f761b2 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 07:09:20 +0100 Subject: [PATCH 27/32] fix(ui): pass commit message and file click handlers down the component tree --- apps/editor/src/views/panel/frontend/Home/Home.tsx | 8 ++++++++ apps/editor/src/views/panel/frontend/Panel.tsx | 10 +++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/editor/src/views/panel/frontend/Home/Home.tsx b/apps/editor/src/views/panel/frontend/Home/Home.tsx index 388c91b68..b3230b2cb 100644 --- a/apps/editor/src/views/panel/frontend/Home/Home.tsx +++ b/apps/editor/src/views/panel/frontend/Home/Home.tsx @@ -82,6 +82,13 @@ export const Home: React.FC = (props) => { } as FrontendMessage) } + const handle_go_to_file = (file_path: string) => { + post_message(props.vscode, { + command: 'GO_TO_FILE', + file_path + } as FrontendMessage) + } + const handle_create_checkpoint_click = () => { post_message(props.vscode, { command: 'CREATE_CHECKPOINT' @@ -260,6 +267,7 @@ export const Home: React.FC = (props) => { on_forward={(task) => { props.on_task_forward(task) }} + on_go_to_file={handle_go_to_file} placeholder={t('home.tasks.placeholder')} /> )} diff --git a/apps/editor/src/views/panel/frontend/Panel.tsx b/apps/editor/src/views/panel/frontend/Panel.tsx index 9002cccfc..0d063e44e 100644 --- a/apps/editor/src/views/panel/frontend/Panel.tsx +++ b/apps/editor/src/views/panel/frontend/Panel.tsx @@ -63,7 +63,7 @@ export const Panel = () => { presets_collapsed, send_with_shift_enter, configurations_collapsed, - handle_instructions_change, + set_instructions, handle_web_prompt_type_change, handle_api_prompt_type_change, handle_mode_change, @@ -86,7 +86,9 @@ export const Panel = () => { is_setup_complete, handle_tab_change, handle_new_tab, - handle_tab_delete + handle_tab_delete, + active_commit_message, + handle_fill_scm_commit } = use_panel(vscode) const { @@ -380,7 +382,7 @@ export const Panel = () => { find_relevant_files_instructions.active_index ] || '' } - set_instructions={handle_instructions_change} + set_instructions={set_instructions} mode={mode} web_prompt_type={web_prompt_type} api_prompt_type={api_prompt_type} @@ -445,6 +447,8 @@ export const Panel = () => { on_tab_change={handle_tab_change} on_new_tab={handle_new_tab} on_tab_delete={handle_tab_delete} + active_commit_message={active_commit_message} + on_fill_scm_commit={handle_fill_scm_commit} />
Date: Thu, 12 Mar 2026 08:20:42 +0100 Subject: [PATCH 28/32] remove 'token' string for every filetree element --- apps/editor/src/utils/files-collector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/editor/src/utils/files-collector.ts b/apps/editor/src/utils/files-collector.ts index 9bdf32d68..373381916 100644 --- a/apps/editor/src/utils/files-collector.ts +++ b/apps/editor/src/utils/files-collector.ts @@ -90,7 +90,7 @@ export class FilesCollector { token_count >= 1000 ? `${Number((token_count / 1000).toFixed(1))}k` : token_count.toString() - collected_text += `- ${display_path} (${count_str} tokens)\n` + collected_text += `- ${display_path} (${count_str})\n` continue } From a337543d9803433c96bb5e9edb03c5be16b53a74 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 09:45:15 +0100 Subject: [PATCH 29/32] refactor: allow mixed content parsing in chat responses --- .../utils/clipboard-parser/clipboard-parser.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts index 31f2e9075..d1ab8556d 100644 --- a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts +++ b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts @@ -91,6 +91,7 @@ export const parse_response = (params: { }): ClipboardItem[] => { const is_single_root_folder_workspace = params.is_single_root_folder_workspace ?? true + const items: ClipboardItem[] = [] const code_at_cursor_items = parse_code_at_cursor({ response: params.response, @@ -98,17 +99,17 @@ export const parse_response = (params: { }) if (code_at_cursor_items && code_at_cursor_items.length > 0) { - return code_at_cursor_items + items.push(...code_at_cursor_items) } const subtasks = parse_subtask_directives(params.response) as SubtasksItem[] if (subtasks && subtasks.length > 0) { - return subtasks + items.push(...subtasks) } const relevant_files = parse_relevant_files({ response: params.response }) if (relevant_files) { - return [relevant_files] + items.push(relevant_files) } const processed_response = params.response.replace(/``````/g, '```\n```') @@ -129,14 +130,15 @@ export const parse_response = (params: { is_single_root: is_single_root_folder_workspace }) if (patches_or_text.length) { - return patches_or_text + items.push(...patches_or_text) } } - const items = parse_multiple_files({ + const file_items = parse_multiple_files({ response: processed_response, is_single_root_folder_workspace }) + items.push(...file_items) return items } From 4b46d557b7f489d557f41a279f14b7e885422702 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 09:48:30 +0100 Subject: [PATCH 30/32] fix: process edits and subtasks sequentially when both are present --- .../response-processor.ts | 414 ++++++++++-------- 1 file changed, 228 insertions(+), 186 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index 8655f93fc..c5c6d5b98 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -110,6 +110,34 @@ export const process_chat_response = async ( is_single_root_folder_workspace }) + // 1. Handle Meta Items (Relevant Files / Subtasks) + await handle_meta_items( + clipboard_items, + context, + panel_provider, + workspace_provider, + is_single_root_folder_workspace + ) + + // 2. Handle Code Items (Diffs / Files / Code-at-cursor) + return await handle_code_items({ + clipboard_items, + chat_response, + context, + panel_provider, + args, + is_single_root_folder_workspace, + on_progress + }) +} + +const handle_meta_items = async ( + clipboard_items: any[], + context: vscode.ExtensionContext, + panel_provider: PanelProvider, + workspace_provider: WorkspaceProvider, + is_single_root_folder_workspace: boolean +): Promise => { const relevant_files_item = clipboard_items.find( (item) => item.type == 'relevant-files' ) as RelevantFilesItem | undefined @@ -117,228 +145,177 @@ export const process_chat_response = async ( (item) => item.type == 'subtasks' ) as SubtasksItem | undefined - if (relevant_files_item || subtasks_item) { - const current_checked_files = - workspace_provider.get_export_state().regular.checked_files + if (!relevant_files_item && !subtasks_item) { + return + } - const workspace_roots = workspace_provider.get_workspace_roots() + const current_checked_files = + workspace_provider.get_export_state().regular.checked_files - const all_paths_to_process = new Set() - if (relevant_files_item) { - relevant_files_item.file_paths.forEach((p) => all_paths_to_process.add(p)) - } - if (subtasks_item) { - subtasks_item.subtasks.forEach((st: any) => - st.files.forEach((f: string) => all_paths_to_process.add(f)) - ) - } + const workspace_roots = workspace_provider.get_workspace_roots() - const files_for_modal = ( - await Promise.all( - Array.from(all_paths_to_process).map(async (rel_path) => { - let absolute_path: string | undefined - for (const root of workspace_roots) { - const potential = path.join(root, rel_path) - if (fs.existsSync(potential)) { - absolute_path = potential - break - } - } + const all_paths_to_process = new Set() + if (relevant_files_item) { + relevant_files_item.file_paths.forEach((p) => all_paths_to_process.add(p)) + } + if (subtasks_item) { + subtasks_item.subtasks.forEach((st: any) => + st.files.forEach((f: string) => all_paths_to_process.add(f)) + ) + } - let token_count: number | undefined - if (absolute_path) { - const count = - await workspace_provider.calculate_file_tokens(absolute_path) - token_count = count.total + const files_for_modal = ( + await Promise.all( + Array.from(all_paths_to_process).map(async (rel_path) => { + let absolute_path: string | undefined + for (const root of workspace_roots) { + const potential = path.join(root, rel_path) + if (fs.existsSync(potential)) { + absolute_path = potential + break } + } - return { - file_path: absolute_path, - relative_path: rel_path, - token_count - } - }) - ) - ).filter( - ( - f - ): f is { - file_path: string - relative_path: string - token_count: number - } => !!f.file_path - ) + let token_count: number | undefined + if (absolute_path) { + const count = + await workspace_provider.calculate_file_tokens(absolute_path) + token_count = count.total + } - files_for_modal.sort((a, b) => - natural_sort(a.relative_path, b.relative_path) + return { + file_path: absolute_path, + relative_path: rel_path, + token_count + } + }) ) + ).filter( + ( + f + ): f is { + file_path: string + relative_path: string + token_count: number + } => !!f.file_path + ) + + files_for_modal.sort((a, b) => natural_sort(a.relative_path, b.relative_path)) + + panel_provider.send_message({ + command: 'SHOW_RELEVANT_FILES_MODAL', + files: files_for_modal + }) - panel_provider.send_message({ - command: 'SHOW_RELEVANT_FILES_MODAL', - files: files_for_modal - }) + const selected_files = await new Promise((resolve) => { + panel_provider.relevant_files_choice_resolver = resolve + }) - const selected_files = await new Promise( - (resolve) => { - panel_provider.relevant_files_choice_resolver = resolve - } - ) + if (selected_files) { + const shared_context_state = panel_provider.shared_context_state + const was_frf = workspace_provider.is_frf_mode - if (selected_files) { - const shared_context_state = panel_provider.shared_context_state - const was_frf = workspace_provider.is_frf_mode + if (was_frf) { + shared_context_state.switch_context_state(false) + } - if (was_frf) { - shared_context_state.switch_context_state(false) + if (subtasks_item) { + const is_path_match = (absolute: string, relative: string) => { + const norm_abs = absolute.replace(/\\/g, '/') + const norm_rel = relative.replace(/\\/g, '/') + return norm_abs.endsWith(norm_rel) || norm_abs.includes(norm_rel) } - if (subtasks_item) { - const is_path_match = (absolute: string, relative: string) => { - const norm_abs = absolute.replace(/\\/g, '/') - const norm_rel = relative.replace(/\\/g, '/') - return norm_abs.endsWith(norm_rel) || norm_abs.includes(norm_rel) - } + const tasks_array = subtasks_item.subtasks.map( + (st: any, index: number) => { + const raw_files = Array.isArray(st.files) ? st.files : [] + // Ensure we only store files in the task that the user actually approved in the modal + const approved_files = raw_files.filter((rel_f: string) => + selected_files.some((sf) => is_path_match(sf, rel_f)) + ) - const tasks_array = subtasks_item.subtasks.map( - (st: any, index: number) => { - const raw_files = Array.isArray(st.files) ? st.files : [] - // Ensure we only store files in the task that the user actually approved in the modal - const approved_files = raw_files.filter((rel_f: string) => - selected_files.some((sf) => is_path_match(sf, rel_f)) + const task_files = approved_files.map((rel_f: string) => { + const matched_modal_file = files_for_modal.find((f) => + is_path_match(f.file_path, rel_f) ) - - const task_files = approved_files.map((rel_f: string) => { - const matched_modal_file = files_for_modal.find((f) => - is_path_match(f.file_path, rel_f) - ) - return { - path: rel_f, - tokens: matched_modal_file?.token_count - } - }) - return { - text: ( - st.instruction || - st.description || - st.title || - st.text || - 'Execute subtask' - ) - .toString() - .trim(), - commit_message: st.commit_message, - is_checked: false, - created_at: Date.now() + index, - files: task_files + path: rel_f, + tokens: matched_modal_file?.token_count } - } - ) - - if (tasks_array.length > 1) { - // Only save to the task list if there are multiple subtasks - const default_workspace = - vscode.workspace.workspaceFolders![0].uri.fsPath - - // Use TasksUtils instead of workspaceState to prevent desync with deletion logic - const tasks_record = TasksUtils.load_all(context) - - // Append new tasks to existing ones to prevent data loss - tasks_record[default_workspace] = [ - ...(tasks_record[default_workspace] || []), - ...tasks_array - ] - - TasksUtils.save_all({ context, tasks: tasks_record }) - - panel_provider.send_message({ - command: 'TASKS', - tasks: tasks_record as any }) - if (was_frf) { - shared_context_state.switch_context_state(true) + return { + text: ( + st.instruction || + st.description || + st.title || + st.text || + 'Execute subtask' + ) + .toString() + .trim(), + commit_message: st.commit_message, + is_checked: false, + created_at: Date.now() + index, + files: task_files } - panel_provider.send_message({ command: 'RETURN_HOME' }) - panel_provider.send_message({ - command: 'SHOW_AUTO_CLOSING_MODAL', - title: 'Subtasks saved. Select one to start.', - type: 'success' - }) - } else if (tasks_array.length === 1) { - const first_task = tasks_array[0] - - const approved_first_task_absolute_paths = selected_files.filter( - (sf) => - first_task.files.some((rel_f: any) => - is_path_match(sf, rel_f.path) - ) - ) + } + ) - const presented_files = files_for_modal.map((f) => f.file_path) - const filtered_current_files = current_checked_files.filter( - (f) => !presented_files.includes(f) - ) - const merged_files = Array.from( - new Set([ - ...filtered_current_files, - ...approved_first_task_absolute_paths - ]) - ) + if (tasks_array.length > 1) { + // Only save to the task list if there are multiple subtasks + const default_workspace = + vscode.workspace.workspaceFolders![0].uri.fsPath - await workspace_provider.set_checked_files(merged_files) + // Use TasksUtils instead of workspaceState to prevent desync with deletion logic + const tasks_record = TasksUtils.load_all(context) - panel_provider.edit_context_instructions.instructions[ - panel_provider.edit_context_instructions.active_index - ] = first_task.text - panel_provider.caret_position = first_task.text.length + // Append new tasks to existing ones to prevent data loss + tasks_record[default_workspace] = [ + ...(tasks_record[default_workspace] || []), + ...tasks_array + ] - panel_provider.send_message({ - command: 'INSTRUCTIONS', - ask_about_context: panel_provider.ask_about_context_instructions, - edit_context: panel_provider.edit_context_instructions, - no_context: panel_provider.no_context_instructions, - code_at_cursor: panel_provider.code_at_cursor_instructions, - find_relevant_files: - panel_provider.find_relevant_files_instructions, - caret_position: panel_provider.caret_position - }) + TasksUtils.save_all({ context, tasks: tasks_record }) - if (first_task.commit_message) { - panel_provider.send_message({ - command: 'SET_ACTIVE_COMMIT_MESSAGE', - commit_message: first_task.commit_message - } as any) - } + panel_provider.send_message({ + command: 'TASKS', + tasks: tasks_record as any + }) - if (was_frf) { - shared_context_state.switch_context_state(true) - } + if (was_frf) { + shared_context_state.switch_context_state(true) + } + panel_provider.send_message({ command: 'RETURN_HOME' }) + panel_provider.send_message({ + command: 'SHOW_AUTO_CLOSING_MODAL', + title: 'Subtasks saved. Select one to start.', + type: 'success' + }) + } else if (tasks_array.length === 1) { + const first_task = tasks_array[0] - panel_provider.send_message({ - command: 'SHOW_AUTO_CLOSING_MODAL', - title: 'Subtask ready.', - type: 'success' - }) + const approved_first_task_absolute_paths = selected_files.filter((sf) => + first_task.files.some((rel_f: any) => is_path_match(sf, rel_f.path)) + ) - await panel_provider.switch_to_edit_context() - panel_provider.send_context_files() - } - } else { const presented_files = files_for_modal.map((f) => f.file_path) const filtered_current_files = current_checked_files.filter( (f) => !presented_files.includes(f) ) const merged_files = Array.from( - new Set([...filtered_current_files, ...selected_files]) + new Set([ + ...filtered_current_files, + ...approved_first_task_absolute_paths + ]) ) + await workspace_provider.set_checked_files(merged_files) - // Clear the old prompt from cache panel_provider.edit_context_instructions.instructions[ panel_provider.edit_context_instructions.active_index - ] = '' - panel_provider.caret_position = 0 + ] = first_task.text + panel_provider.caret_position = first_task.text.length panel_provider.send_message({ command: 'INSTRUCTIONS', @@ -350,23 +327,87 @@ export const process_chat_response = async ( caret_position: panel_provider.caret_position }) + if (first_task.commit_message) { + panel_provider.send_message({ + command: 'SET_ACTIVE_COMMIT_MESSAGE', + commit_message: first_task.commit_message + } as any) + } + if (was_frf) { shared_context_state.switch_context_state(true) } panel_provider.send_message({ command: 'SHOW_AUTO_CLOSING_MODAL', - title: t('command.apply-chat-response.relevant-files.success'), + title: 'Subtask ready.', type: 'success' }) await panel_provider.switch_to_edit_context() panel_provider.send_context_files() } + } else { + const presented_files = files_for_modal.map((f) => f.file_path) + const filtered_current_files = current_checked_files.filter( + (f) => !presented_files.includes(f) + ) + const merged_files = Array.from( + new Set([...filtered_current_files, ...selected_files]) + ) + await workspace_provider.set_checked_files(merged_files) + + // Clear the old prompt from cache + panel_provider.edit_context_instructions.instructions[ + panel_provider.edit_context_instructions.active_index + ] = '' + panel_provider.caret_position = 0 + + panel_provider.send_message({ + command: 'INSTRUCTIONS', + ask_about_context: panel_provider.ask_about_context_instructions, + edit_context: panel_provider.edit_context_instructions, + no_context: panel_provider.no_context_instructions, + code_at_cursor: panel_provider.code_at_cursor_instructions, + find_relevant_files: panel_provider.find_relevant_files_instructions, + caret_position: panel_provider.caret_position + }) + + if (was_frf) { + shared_context_state.switch_context_state(true) + } + + panel_provider.send_message({ + command: 'SHOW_AUTO_CLOSING_MODAL', + title: t('command.apply-chat-response.relevant-files.success'), + type: 'success' + }) + + await panel_provider.switch_to_edit_context() + panel_provider.send_context_files() } + } +} - return null - } else if (clipboard_items.some((item) => item.type == 'diff')) { +const handle_code_items = async ({ + clipboard_items, + chat_response, + context, + panel_provider, + args, + is_single_root_folder_workspace, + on_progress +}: { + clipboard_items: any[] + chat_response: string + context: vscode.ExtensionContext + panel_provider: PanelProvider + args: CommandArgs | undefined + is_single_root_folder_workspace: boolean + on_progress: (progress: number) => void +}): Promise => { + // Logic for applying code, diffs, etc. + if (clipboard_items.some((item) => item.type == 'diff')) { const patches = clipboard_items.filter( (item): item is DiffItem => item.type == 'diff' ) @@ -488,6 +529,7 @@ export const process_chat_response = async ( return null } else { + // Check for code-at-cursor if (clipboard_items.some((item) => item.type == 'code-at-cursor')) { const completion = clipboard_items.find( (item) => item.type == 'code-at-cursor' From bf165daee26f8e87ce76049e27d8e230e6d343af Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 10:00:55 +0100 Subject: [PATCH 31/32] refactor: allow mixed subtask and edit processing to support prefilled commit messages --- .../response-processor.ts | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts index c5c6d5b98..da45c4ec4 100644 --- a/apps/editor/src/commands/apply-chat-response-command/response-processor.ts +++ b/apps/editor/src/commands/apply-chat-response-command/response-processor.ts @@ -110,6 +110,26 @@ export const process_chat_response = async ( is_single_root_folder_workspace }) + // Check for single subtask scenario alongside code edits + const subtasks_item = clipboard_items.find( + (item) => item.type == 'subtasks' + ) as SubtasksItem | undefined + + const has_code = clipboard_items.some( + (item) => + item.type == 'diff' || + item.type == 'file' || + item.type == 'code-at-cursor' + ) + + let extracted_commit_message: string | undefined + + if (subtasks_item && subtasks_item.subtasks.length === 1 && has_code) { + extracted_commit_message = subtasks_item.subtasks[0].commit_message + // Remove subtasks item so it is ignored by handle_meta_items + clipboard_items = clipboard_items.filter((item) => item !== subtasks_item) + } + // 1. Handle Meta Items (Relevant Files / Subtasks) await handle_meta_items( clipboard_items, @@ -120,7 +140,7 @@ export const process_chat_response = async ( ) // 2. Handle Code Items (Diffs / Files / Code-at-cursor) - return await handle_code_items({ + const result = await handle_code_items({ clipboard_items, chat_response, context, @@ -129,6 +149,16 @@ export const process_chat_response = async ( is_single_root_folder_workspace, on_progress }) + + // 3. Apply extracted commit message if operation succeeded + if (result && extracted_commit_message) { + panel_provider.send_message({ + command: 'SET_ACTIVE_COMMIT_MESSAGE', + commit_message: extracted_commit_message + } as any) + } + + return result } const handle_meta_items = async ( From ddb44d562745bf9f4c2bc6c4604cf0ea1c24f774 Mon Sep 17 00:00:00 2001 From: seedlord Date: Thu, 12 Mar 2026 10:02:30 +0100 Subject: [PATCH 32/32] fix: return combined items from parse_response to support mixed content --- .../utils/clipboard-parser/clipboard-parser.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts index d1ab8556d..7cfd72946 100644 --- a/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts +++ b/apps/editor/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.ts @@ -91,10 +91,15 @@ export const parse_response = (params: { }): ClipboardItem[] => { const is_single_root_folder_workspace = params.is_single_root_folder_workspace ?? true + + // Standardize the response by cleaning up common code block formatting issues + const processed_response = params.response.replace(/``````/g, '```\n```') + const items: ClipboardItem[] = [] + // Use processed_response for all parsers to ensure consistency and robustness const code_at_cursor_items = parse_code_at_cursor({ - response: params.response, + response: processed_response, is_single_root_folder_workspace }) @@ -102,18 +107,18 @@ export const parse_response = (params: { items.push(...code_at_cursor_items) } - const subtasks = parse_subtask_directives(params.response) as SubtasksItem[] + const subtasks = parse_subtask_directives( + processed_response + ) as SubtasksItem[] if (subtasks && subtasks.length > 0) { items.push(...subtasks) } - const relevant_files = parse_relevant_files({ response: params.response }) + const relevant_files = parse_relevant_files({ response: processed_response }) if (relevant_files) { items.push(relevant_files) } - const processed_response = params.response.replace(/``````/g, '```\n```') - const hunk_header_regex = /^(@@\s+-\d+(?:,\d+)?\s+\+\d+(?:,\d+)?\s+@@)/m const diff_header_regex = /^---\s+.+\n\+\+\+\s+.+/m const git_diff_header_regex = /^diff --git /m