mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 19:47:10 +08:00
feat(cursor): sync local conversation history to Cursor transcripts
Project sidecar context history into Cursor-compatible JSONL transcripts so previous conversations remain readable when referenced from new chats. Backfill existing sessions on startup and preserve Cursor-managed turn status entries during atomic updates.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -438,7 +439,11 @@ func (store *ConversationFileStore) writeConversationLocked(conversationID strin
|
||||
if err := store.writeContextLocked(conversationID, conversation); err != nil {
|
||||
return err
|
||||
}
|
||||
return store.writeConversationMetaLocked(conversationID, conversation)
|
||||
if err := store.writeConversationMetaLocked(conversationID, conversation); err != nil {
|
||||
return err
|
||||
}
|
||||
store.syncCursorTranscriptBestEffort(conversationID, conversation)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *ConversationFileStore) writeConversationMetaLocked(conversationID string, conversation *ConversationFile) error {
|
||||
@@ -477,6 +482,70 @@ func (store *ConversationFileStore) writeContextLocked(conversationID string, co
|
||||
return writeJSONFileAtomic(store.contextPath(conversationID), context)
|
||||
}
|
||||
|
||||
func (store *ConversationFileStore) syncCursorTranscriptBestEffort(conversationID string, conversation *ConversationFile) {
|
||||
if store == nil || conversation == nil {
|
||||
return
|
||||
}
|
||||
folder := normalizeAgentTranscriptsFolder(conversation.AgentTranscriptsFolder)
|
||||
if folder == "" {
|
||||
return
|
||||
}
|
||||
if err := store.syncCursorTranscript(conversationID, conversation, folder); err != nil {
|
||||
log.Printf("forwarder transcript sync failed conversation_id=%s err=%v", strings.TrimSpace(conversationID), err)
|
||||
}
|
||||
}
|
||||
|
||||
func (store *ConversationFileStore) syncCursorTranscript(conversationID string, conversation *ConversationFile, transcriptsFolder string) error {
|
||||
return store.syncCursorTranscriptWithLatestStatus(conversationID, conversation, transcriptsFolder, false)
|
||||
}
|
||||
|
||||
func (store *ConversationFileStore) syncCursorTranscriptWithLatestStatus(conversationID string, conversation *ConversationFile, transcriptsFolder string, includeLatestStatus bool) error {
|
||||
if store == nil || conversation == nil {
|
||||
return nil
|
||||
}
|
||||
path, err := cursorTranscriptPath(transcriptsFolder, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := projectCursorTranscriptJSONLWithLatestStatus(conversation, includeLatestStatus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
data = preserveCursorAppendedTurnEnded(path, data)
|
||||
return writeCursorTranscriptAtomic(path, data)
|
||||
}
|
||||
|
||||
func (store *ConversationFileStore) SyncAllCursorTranscriptsBestEffort() {
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
conversationIDs, err := store.ListConversationIDs()
|
||||
if err != nil {
|
||||
log.Printf("forwarder transcript backfill scan failed err=%v", err)
|
||||
return
|
||||
}
|
||||
for _, conversationID := range conversationIDs {
|
||||
conversation, err := store.LoadConversation(conversationID)
|
||||
if err != nil {
|
||||
log.Printf("forwarder transcript backfill load failed conversation_id=%s err=%v", conversationID, err)
|
||||
continue
|
||||
}
|
||||
if conversation == nil || conversation.AgentTranscriptsFolder == "" {
|
||||
continue
|
||||
}
|
||||
info, err := os.Stat(conversation.AgentTranscriptsFolder)
|
||||
if err != nil || !info.IsDir() {
|
||||
continue
|
||||
}
|
||||
if err := store.syncCursorTranscriptWithLatestStatus(conversationID, conversation, conversation.AgentTranscriptsFolder, true); err != nil {
|
||||
log.Printf("forwarder transcript backfill failed conversation_id=%s err=%v", conversationID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contextVersionForEntries(entries []HistoryEntry) int64 {
|
||||
var version int64
|
||||
for _, entry := range entries {
|
||||
@@ -663,6 +732,9 @@ func mergeConversationMetadata(target *ConversationFile, source *ConversationFil
|
||||
target.ParentConversationID = strings.TrimSpace(source.ParentConversationID)
|
||||
target.ParentToolCallID = strings.TrimSpace(source.ParentToolCallID)
|
||||
target.SubagentTypeName = strings.TrimSpace(source.SubagentTypeName)
|
||||
if folder := normalizeAgentTranscriptsFolder(source.AgentTranscriptsFolder); folder != "" {
|
||||
target.AgentTranscriptsFolder = folder
|
||||
}
|
||||
if strings.TrimSpace(source.Mode) != "" {
|
||||
target.Mode = strings.TrimSpace(source.Mode)
|
||||
}
|
||||
@@ -718,6 +790,10 @@ func normalizeLoadedConversation(conversationID string, conversation *Conversati
|
||||
if conversation.Entries == nil {
|
||||
conversation.Entries = make([]HistoryEntry, 0, 16)
|
||||
}
|
||||
conversation.AgentTranscriptsFolder = normalizeAgentTranscriptsFolder(conversation.AgentTranscriptsFolder)
|
||||
if conversation.AgentTranscriptsFolder == "" {
|
||||
conversation.AgentTranscriptsFolder = agentTranscriptsFolderFromEntries(conversation.Entries)
|
||||
}
|
||||
for _, entry := range conversation.Entries {
|
||||
if entry.Seq >= conversation.NextEntrySeq {
|
||||
conversation.NextEntrySeq = entry.Seq + 1
|
||||
|
||||
Reference in New Issue
Block a user