Skip to content

Commit b574656

Browse files
committed
Upgrade chat history storage to a timestamped format for enhanced management
1 parent 55fd479 commit b574656

9 files changed

Lines changed: 240 additions & 57 deletions

File tree

packages/vscode/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,7 @@
13551355
"axios": "^1.10.0",
13561356
"bufferutil": "^4.0.9",
13571357
"classnames": "^2.5.1",
1358+
"dayjs": "^1.11.10",
13581359
"glob": "^11.0.3",
13591360
"he": "^1.2.0",
13601361
"ignore": "^6.0.2",

packages/vscode/src/constants/state-keys.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ export const PINNED_HISTORY_EDIT_STATE_KEY = 'pinned-history-edit'
4545
export const PINNED_HISTORY_CODE_COMPLETIONS_STATE_KEY =
4646
'pinned-history-code-completions'
4747
export const PINNED_HISTORY_NO_CONTEXT_STATE_KEY = 'pinned-history-no-context'
48+
49+
export interface HistoryEntry {
50+
text: string
51+
createdAt: number
52+
}

packages/vscode/src/extension.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
migrate_chat_code_completion_instructions,
1111
migrate_refactoring_to_intelligent_update,
1212
migrate_presets_to_chat_presets_for_edit_context,
13-
migrate_edit_to_edit_context
13+
migrate_edit_to_edit_context,
14+
migrate_clear_history
1415
} from './migrations'
1516
import {
1617
apply_chat_response_command,
@@ -76,6 +77,8 @@ export async function activate(context: vscode.ExtensionContext) {
7677
await migrate_commit_messages_config_to_array(context)
7778
// 21 July 2025
7879
await migrate_edit_to_edit_context(context)
80+
// 25 July 2025
81+
await migrate_clear_history(context)
7982
}
8083

8184
await migrations()

packages/vscode/src/migrations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './migrate-chat-code-completion-instructions'
66
export * from './migrate-refactoring-to-intelligent-update'
77
export * from './migrate-presets-to-chat-presets-for-edit-context'
88
export * from './migrate-edit-to-edit-context'
9+
export * from './migrate-clear-history'
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as vscode from 'vscode'
2+
import { Logger } from '../utils/logger'
3+
import {
4+
HISTORY_ASK_STATE_KEY,
5+
HISTORY_CODE_COMPLETIONS_STATE_KEY,
6+
HISTORY_EDIT_STATE_KEY,
7+
HISTORY_NO_CONTEXT_STATE_KEY,
8+
PINNED_HISTORY_ASK_STATE_KEY,
9+
PINNED_HISTORY_CODE_COMPLETIONS_STATE_KEY,
10+
PINNED_HISTORY_EDIT_STATE_KEY,
11+
PINNED_HISTORY_NO_CONTEXT_STATE_KEY
12+
} from '../constants/state-keys'
13+
14+
const MIGRATION_ID = 'clear-history-for-object-migration-20250725'
15+
16+
/**
17+
* Migration to clear all history due to a data structure change from string[] to HistoryEntry[].
18+
* This migration runs only once per workspace.
19+
*/
20+
export async function migrate_clear_history(
21+
context: vscode.ExtensionContext
22+
): Promise<void> {
23+
try {
24+
if (context.workspaceState.get(MIGRATION_ID)) {
25+
return
26+
}
27+
28+
const history_keys = [
29+
HISTORY_ASK_STATE_KEY,
30+
HISTORY_EDIT_STATE_KEY,
31+
HISTORY_CODE_COMPLETIONS_STATE_KEY,
32+
HISTORY_NO_CONTEXT_STATE_KEY,
33+
PINNED_HISTORY_ASK_STATE_KEY,
34+
PINNED_HISTORY_EDIT_STATE_KEY,
35+
PINNED_HISTORY_CODE_COMPLETIONS_STATE_KEY,
36+
PINNED_HISTORY_NO_CONTEXT_STATE_KEY
37+
]
38+
39+
for (const key of history_keys) {
40+
await context.workspaceState.update(key, undefined)
41+
}
42+
43+
Logger.log({
44+
function_name: 'migrate_clear_history',
45+
message: 'Successfully cleared old string-based history.'
46+
})
47+
48+
await context.workspaceState.update(MIGRATION_ID, true)
49+
} catch (error) {
50+
Logger.error({
51+
function_name: 'migrate_clear_history',
52+
message: 'Error clearing history',
53+
data: error instanceof Error ? error.message : String(error)
54+
})
55+
}
56+
}

packages/vscode/src/view/backend/message-handlers/handle-get-history.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,32 @@ import {
22
HISTORY_ASK_STATE_KEY,
33
HISTORY_CODE_COMPLETIONS_STATE_KEY,
44
HISTORY_EDIT_STATE_KEY,
5-
HISTORY_NO_CONTEXT_STATE_KEY
5+
HISTORY_NO_CONTEXT_STATE_KEY,
6+
HistoryEntry
67
} from '@/constants/state-keys'
78
import { ViewProvider } from '@/view/backend/view-provider'
89

910
export const handle_get_history = (provider: ViewProvider): void => {
10-
const ask_history = provider.context.workspaceState.get<string[]>(
11+
const ask_history = provider.context.workspaceState.get<HistoryEntry[]>(
1112
HISTORY_ASK_STATE_KEY,
1213
[]
1314
)
14-
const edit_history = provider.context.workspaceState.get<string[]>(
15+
const edit_history = provider.context.workspaceState.get<HistoryEntry[]>(
1516
HISTORY_EDIT_STATE_KEY,
1617
[]
1718
)
1819
const code_completions_history = provider.context.workspaceState.get<
19-
string[]
20+
HistoryEntry[]
2021
>(HISTORY_CODE_COMPLETIONS_STATE_KEY, [])
21-
const no_context_history = provider.context.workspaceState.get<string[]>(
22-
HISTORY_NO_CONTEXT_STATE_KEY,
23-
[]
24-
)
22+
const no_context_history = provider.context.workspaceState.get<
23+
HistoryEntry[]
24+
>(HISTORY_NO_CONTEXT_STATE_KEY, [])
2525

2626
provider.send_message({
2727
command: 'CHAT_HISTORY',
28-
ask: ask_history,
29-
edit_context: edit_history,
30-
no_context: no_context_history,
31-
code_completions: code_completions_history
28+
ask: ask_history.map((h) => h.text),
29+
edit_context: edit_history.map((h) => h.text),
30+
no_context: no_context_history.map((h) => h.text),
31+
code_completions: code_completions_history.map((h) => h.text)
3232
})
3333
}

packages/vscode/src/view/backend/message-handlers/handle-save-history.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import {
22
HISTORY_ASK_STATE_KEY,
33
HISTORY_CODE_COMPLETIONS_STATE_KEY,
44
HISTORY_EDIT_STATE_KEY,
5-
HISTORY_NO_CONTEXT_STATE_KEY
5+
HISTORY_NO_CONTEXT_STATE_KEY,
6+
HistoryEntry,
7+
PINNED_HISTORY_ASK_STATE_KEY,
8+
PINNED_HISTORY_CODE_COMPLETIONS_STATE_KEY,
9+
PINNED_HISTORY_EDIT_STATE_KEY,
10+
PINNED_HISTORY_NO_CONTEXT_STATE_KEY
611
} from '@/constants/state-keys'
712
import { ViewProvider } from '@/view/backend/view-provider'
813
import { SaveHistoryMessage } from '@/view/types/messages'
@@ -12,21 +17,75 @@ export const handle_save_history = async (
1217
message: SaveHistoryMessage
1318
): Promise<void> => {
1419
let key: string | undefined
20+
let pinned_key: string | undefined
1521
switch (message.mode) {
1622
case 'ask':
1723
key = HISTORY_ASK_STATE_KEY
24+
pinned_key = PINNED_HISTORY_ASK_STATE_KEY
1825
break
1926
case 'edit-context':
2027
key = HISTORY_EDIT_STATE_KEY
28+
pinned_key = PINNED_HISTORY_EDIT_STATE_KEY
2129
break
2230
case 'no-context':
2331
key = HISTORY_NO_CONTEXT_STATE_KEY
32+
pinned_key = PINNED_HISTORY_NO_CONTEXT_STATE_KEY
2433
break
2534
case 'code-completions':
2635
key = HISTORY_CODE_COMPLETIONS_STATE_KEY
36+
pinned_key = PINNED_HISTORY_CODE_COMPLETIONS_STATE_KEY
2737
break
2838
}
2939
if (key) {
30-
await provider.context.workspaceState.update(key, message.messages)
40+
const text_history: string[] = message.messages
41+
if (text_history.length === 0) {
42+
await provider.context.workspaceState.update(key, [])
43+
return
44+
}
45+
46+
const old_history =
47+
provider.context.workspaceState.get<HistoryEntry[]>(key, []) || []
48+
const old_history_map = new Map(
49+
old_history.map((entry) => [entry.text, entry])
50+
)
51+
52+
const new_history: HistoryEntry[] = []
53+
54+
// Using a Set to ensure no duplicates are added, though frontend should handle this.
55+
const processedTexts = new Set<string>()
56+
57+
for (const text of text_history) {
58+
if (processedTexts.has(text)) continue
59+
processedTexts.add(text)
60+
61+
// The first item in text_history is the one that was just used.
62+
// Its timestamp is updated to reflect recent use.
63+
if (text === text_history[0]) {
64+
new_history.push({ text, createdAt: Date.now() })
65+
} else {
66+
const old_entry = old_history_map.get(text)
67+
if (old_entry) {
68+
new_history.push(old_entry)
69+
} else {
70+
// Fallback for entries not in old history
71+
new_history.push({ text, createdAt: Date.now() })
72+
}
73+
}
74+
}
75+
76+
await provider.context.workspaceState.update(key, new_history)
77+
78+
if (pinned_key) {
79+
const used_text = text_history[0]
80+
const pinned_history =
81+
provider.context.workspaceState.get<HistoryEntry[]>(pinned_key, []) ||
82+
[]
83+
const pinned_index = pinned_history.findIndex((p) => p.text === used_text)
84+
85+
if (pinned_index > -1) {
86+
pinned_history[pinned_index].createdAt = Date.now()
87+
await provider.context.workspaceState.update(pinned_key, pinned_history)
88+
}
89+
}
3190
}
3291
}

0 commit comments

Comments
 (0)