Skip to content

Commit b0d8922

Browse files
wesmmariusvniekerk
authored andcommitted
fix(sync): persist visual studio archive fingerprints
1 parent ff15509 commit b0d8922

3 files changed

Lines changed: 102 additions & 11 deletions

File tree

internal/sync/engine.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6116,10 +6116,6 @@ func applyVisualStudioCopilotArchiveSessionFields(
61166116
}
61176117
s.StartedAt = earlierSessionTime(archived.StartedAt, s.StartedAt)
61186118
s.EndedAt = laterSessionTime(archived.EndedAt, s.EndedAt)
6119-
if storedSize, currentSize := derefInt64(archived.FileSize),
6120-
derefInt64(s.FileSize); storedSize > currentSize {
6121-
s.FileSize = int64Ptr(storedSize)
6122-
}
61236119
}
61246120

61256121
func visualStudioCopilotMergedFirstMessageFromParsed(
@@ -6251,7 +6247,7 @@ func (e *Engine) reconcileVisualStudioCopilotArchive(
62516247
agent, sessionID, len(storedMsgs), len(currentMsgs),
62526248
storedSize, currentSize,
62536249
)
6254-
return nil, true, nil
6250+
return storedMsgs, false, stored
62556251
}
62566252
if decision.merged != nil {
62576253
log.Printf(
@@ -6315,7 +6311,8 @@ func visualStudioCopilotArchiveDecision(
63156311
updates[storedIndex] = parsedMsg
63166312
}
63176313
}
6318-
additions = visualStudioCopilotResolveArchiveAdditions(
6314+
fallbackMatched := false
6315+
additions, fallbackMatched = visualStudioCopilotResolveArchiveAdditions(
63196316
stored, matchedStored, updates, additions, &hasIncomplete,
63206317
)
63216318
hasArchiveOnly := false
@@ -6325,8 +6322,9 @@ func visualStudioCopilotArchiveDecision(
63256322
break
63266323
}
63276324
}
6328-
if hasIncomplete || hasArchiveOnly {
6329-
if len(updates) > 0 || len(additions) > 0 {
6325+
if hasIncomplete || hasArchiveOnly || fallbackMatched {
6326+
if len(updates) > 0 || len(additions) > 0 ||
6327+
(fallbackMatched && !hasIncomplete) {
63306328
return visualStudioCopilotArchiveReconcile{
63316329
merged: visualStudioCopilotMergeArchiveMessages(
63326330
stored, updates, additions,
@@ -6344,7 +6342,8 @@ func visualStudioCopilotResolveArchiveAdditions(
63446342
updates map[int]db.Message,
63456343
additions []db.Message,
63466344
hasIncomplete *bool,
6347-
) []db.Message {
6345+
) ([]db.Message, bool) {
6346+
matched := false
63486347
unresolved := additions[:0]
63496348
for _, parsedMsg := range additions {
63506349
storedIndex, ok := visualStudioCopilotArchiveFallbackMatch(
@@ -6354,6 +6353,7 @@ func visualStudioCopilotResolveArchiveAdditions(
63546353
unresolved = append(unresolved, parsedMsg)
63556354
continue
63566355
}
6356+
matched = true
63576357
matchedStored[storedIndex] = true
63586358
storedMsg := stored[storedIndex]
63596359
incomplete := visualStudioCopilotMessageLooksIncomplete(
@@ -6370,7 +6370,7 @@ func visualStudioCopilotResolveArchiveAdditions(
63706370
updates[storedIndex] = update
63716371
}
63726372
}
6373-
return unresolved
6373+
return unresolved, matched
63746374
}
63756375

63766376
func visualStudioCopilotArchiveFallbackMatch(
@@ -6448,7 +6448,9 @@ func visualStudioCopilotMessagesShareContentIdentity(
64486448
if len(parsed.ToolCalls) > 0 || len(stored.ToolCalls) > 0 {
64496449
return false
64506450
}
6451-
if parsed.Role != string(parser.RoleAssistant) {
6451+
switch parsed.Role {
6452+
case string(parser.RoleAssistant), string(parser.RoleUser):
6453+
default:
64526454
return false
64536455
}
64546456
return parsed.Content != "" && parsed.Content == stored.Content

internal/sync/engine_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,71 @@ func TestVisualStudioCopilotArchiveDecisionMatchesTimestampShiftedToolCall(t *te
15471547
assert.Equal(t, "Archived prompt.", decision.merged[1].Content)
15481548
}
15491549

1550+
func TestVisualStudioCopilotArchiveDecisionMergesOnlyTimestampShiftedToolCall(t *testing.T) {
1551+
stored := []db.Message{{
1552+
Ordinal: 0,
1553+
Role: "assistant",
1554+
Content: "Run command: dotnet build",
1555+
ContentLength: len("Run command: dotnet build"),
1556+
Timestamp: "2026-06-12T19:46:40Z",
1557+
ToolCalls: []db.ToolCall{{
1558+
ToolName: "run_command_in_terminal",
1559+
ToolUseID: "call_build",
1560+
}},
1561+
}}
1562+
parsed := []db.Message{{
1563+
Ordinal: 0,
1564+
Role: "assistant",
1565+
Content: "Run command: dotnet build",
1566+
ContentLength: len("Run command: dotnet build"),
1567+
Timestamp: "2026-06-12T19:47:40Z",
1568+
ToolCalls: []db.ToolCall{{
1569+
ToolName: "run_command_in_terminal",
1570+
ToolUseID: "call_build",
1571+
}},
1572+
}}
1573+
1574+
decision := visualStudioCopilotArchiveDecision(parsed, stored)
1575+
1576+
require.False(t, decision.preserve)
1577+
require.Len(t, decision.merged, 1)
1578+
assert.Equal(t, "2026-06-12T19:46:40Z", decision.merged[0].Timestamp)
1579+
}
1580+
1581+
func TestVisualStudioCopilotArchiveDecisionMatchesTimestampShiftedUserPrompt(t *testing.T) {
1582+
stored := []db.Message{
1583+
{
1584+
Ordinal: 0,
1585+
Role: "user",
1586+
Content: "Archived prompt.",
1587+
ContentLength: len("Archived prompt."),
1588+
Timestamp: "2026-06-12T19:46:40Z",
1589+
},
1590+
{
1591+
Ordinal: 1,
1592+
Role: "assistant",
1593+
Content: "Archived answer.",
1594+
ContentLength: len("Archived answer."),
1595+
Timestamp: "2026-06-12T19:47:00Z",
1596+
},
1597+
}
1598+
parsed := []db.Message{{
1599+
Ordinal: 0,
1600+
Role: "user",
1601+
Content: "Archived prompt.",
1602+
ContentLength: len("Archived prompt."),
1603+
Timestamp: "2026-06-12T19:47:40Z",
1604+
}}
1605+
1606+
decision := visualStudioCopilotArchiveDecision(parsed, stored)
1607+
1608+
require.False(t, decision.preserve)
1609+
require.Len(t, decision.merged, 2)
1610+
assert.Equal(t, "Archived prompt.", decision.merged[0].Content)
1611+
assert.Equal(t, "2026-06-12T19:46:40Z", decision.merged[0].Timestamp)
1612+
assert.Equal(t, "Archived answer.", decision.merged[1].Content)
1613+
}
1614+
15501615
// fakeEmitter records scopes passed to Emit. Thread-safe so it
15511616
// can be called from engine goroutines under test.
15521617
type fakeEmitter struct {

internal/sync/visualstudio_copilot_integration_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,16 +548,30 @@ func TestSyncEngineVisualStudioCopilotPreservesSessionWhenSiblingDeleted(t *test
548548
})
549549
require.NotZero(t, engine.SyncAll(context.Background(), nil).Synced)
550550
assertSessionMessageCount(t, database, sessionID, 2)
551+
require.NoError(t, database.SetSessionDataVersion(
552+
sessionID, db.CurrentDataVersion()-1,
553+
))
551554

552555
// The sibling that contributed the second turn is rotated away. A reparse now
553556
// sees only the first turn; the archived two-turn transcript must be
554557
// preserved rather than force-replaced with the partial one.
555558
require.NoError(t, os.Remove(sibling))
559+
currentSize, currentMtime := parser.VisualStudioCopilotTraceFingerprint(
560+
primary,
561+
)
556562

557563
require.NoError(t, engine.SyncSingleSessionContext(
558564
context.Background(), sessionID,
559565
))
560566
assertSessionMessageCount(t, database, sessionID, 2)
567+
sess, err := database.GetSessionFull(context.Background(), sessionID)
568+
require.NoError(t, err, "GetSessionFull")
569+
require.NotNil(t, sess)
570+
require.NotNil(t, sess.FileSize)
571+
assert.Equal(t, currentSize, *sess.FileSize)
572+
require.NotNil(t, sess.FileMtime)
573+
assert.Equal(t, currentMtime, *sess.FileMtime)
574+
assert.Equal(t, db.CurrentDataVersion(), sess.DataVersion)
561575
}
562576

563577
// TestSyncEngineVisualStudioCopilotPreservesToolResultsWhenTraceShrinks verifies
@@ -687,6 +701,9 @@ func TestSyncEngineVisualStudioCopilotMergesRicherMatchedMessageWhenTraceShrinks
687701
), 0o644))
688702
later := time.Unix(1781293800, 0)
689703
require.NoError(t, os.Chtimes(primary, later, later))
704+
currentSize, currentMtime := parser.VisualStudioCopilotTraceFingerprint(
705+
primary,
706+
)
690707

691708
require.NoError(t, engine.SyncSingleSessionContext(
692709
context.Background(), sessionID,
@@ -712,6 +729,13 @@ func TestSyncEngineVisualStudioCopilotMergesRicherMatchedMessageWhenTraceShrinks
712729
assert.Equal(t, time.Unix(0, 1781293630000000000).UTC().
713730
Format(time.RFC3339Nano), *sess.EndedAt)
714731
})
732+
sess, err := database.GetSessionFull(context.Background(), sessionID)
733+
require.NoError(t, err, "GetSessionFull")
734+
require.NotNil(t, sess)
735+
require.NotNil(t, sess.FileSize)
736+
assert.Equal(t, currentSize, *sess.FileSize)
737+
require.NotNil(t, sess.FileMtime)
738+
assert.Equal(t, currentMtime, *sess.FileMtime)
715739
}
716740

717741
func TestSyncEngineVisualStudioCopilotMergesUpdateAndPreservesIncompleteSameCount(t *testing.T) {

0 commit comments

Comments
 (0)