fix(sync): keep session renames stable (#2043)
* fix(sync): keep session renames stable * fix(sync): clarify rename mirror flow * fix(sync): clarify archive comment --------- Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
bashrusakh
Bohdan Triapitsyn
parent
9bfc5bf0be
commit
d5745aaac9
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { Session } from "@opencode-ai/sdk/v2"
|
||||
import type { Event, Part, PermissionRequest, QuestionRequest, SessionStatus } from "@opencode-ai/sdk/v2/client"
|
||||
import { applyDirectoryEvent } from "../event-reducer"
|
||||
import { INITIAL_STATE, type State } from "../types"
|
||||
@@ -55,6 +56,14 @@ function topLevelSessionOnlyPartUpdatedEvent(): Event {
|
||||
} as Event
|
||||
}
|
||||
|
||||
function buildSession(title: string, time: Session["time"]): Session {
|
||||
return {
|
||||
id: "ses_1",
|
||||
title,
|
||||
time,
|
||||
} as Session
|
||||
}
|
||||
|
||||
describe("applyDirectoryEvent", () => {
|
||||
test("returns typed materialization when delta arrives before parts", () => {
|
||||
const result = applyDirectoryEvent(state(), deltaEvent())
|
||||
@@ -129,6 +138,20 @@ describe("applyDirectoryEvent", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("skips stale session.updated events so a newer title survives", () => {
|
||||
const draft = state({ session: [buildSession("New Title", { created: 1, updated: 20 })] })
|
||||
|
||||
const result = applyDirectoryEvent(draft, {
|
||||
type: "session.updated",
|
||||
properties: {
|
||||
info: buildSession("Old Title", { created: 1, updated: 10 }),
|
||||
},
|
||||
} as Event)
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(draft.session[0]?.title).toBe("New Title")
|
||||
})
|
||||
|
||||
test("applies part update without materialization when owning message exists", () => {
|
||||
const draft = state({
|
||||
message: { ses_1: [{ id: "msg_1", sessionID: "ses_1", role: "assistant", time: { created: 1 } } as never] },
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { Session } from "@opencode-ai/sdk/v2"
|
||||
|
||||
import { shouldSkipStaleSessionEvent } from "../session-event-freshness"
|
||||
|
||||
const buildSession = (title: string, time: Partial<NonNullable<Session["time"]>>): Session => ({
|
||||
id: "ses_1",
|
||||
title,
|
||||
time: time as Session["time"],
|
||||
} as Session)
|
||||
|
||||
describe("shouldSkipStaleSessionEvent", () => {
|
||||
test("skips a stale SSE session update after a newer local rename", () => {
|
||||
const current = buildSession("New Title", { created: 1, updated: 20 })
|
||||
const incoming = buildSession("Old Title", { created: 1, updated: 10 })
|
||||
|
||||
expect(shouldSkipStaleSessionEvent(current, incoming)).toBe(true)
|
||||
})
|
||||
|
||||
test("allows a fresher SSE update to apply", () => {
|
||||
const current = buildSession("Old Title", { created: 1, updated: 10 })
|
||||
const incoming = buildSession("New Title", { created: 1, updated: 20 })
|
||||
|
||||
expect(shouldSkipStaleSessionEvent(current, incoming)).toBe(false)
|
||||
})
|
||||
|
||||
test("falls back to created timestamp when updated is missing", () => {
|
||||
const current = buildSession("Current", { created: 20 })
|
||||
const incoming = buildSession("Incoming", { created: 10 })
|
||||
|
||||
expect(shouldSkipStaleSessionEvent(current, incoming)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,46 @@
|
||||
import { beforeEach, describe, expect, mock, test } from "bun:test"
|
||||
import type { Event, Session } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
let currentSessions: Session[] = []
|
||||
const upsertedSessions: Session[] = []
|
||||
|
||||
mock.module("@/stores/useGlobalSessionsStore", () => ({
|
||||
useGlobalSessionsStore: {
|
||||
getState: () => ({
|
||||
activeSessions: currentSessions,
|
||||
archivedSessions: [] as Session[],
|
||||
upsertSession: (session: Session) => {
|
||||
upsertedSessions.push(session)
|
||||
},
|
||||
}),
|
||||
},
|
||||
}))
|
||||
import { applySessionEventToGlobalSessions } from "../session-event-router"
|
||||
|
||||
const buildSession = (title: string, time: Session["time"]): Session => ({
|
||||
id: "ses_1",
|
||||
title,
|
||||
time,
|
||||
} as Session)
|
||||
|
||||
const buildEvent = (session: Session): Event => ({
|
||||
type: "session.updated",
|
||||
properties: {
|
||||
info: session,
|
||||
},
|
||||
} as Event)
|
||||
|
||||
describe("applySessionEventToGlobalSessions", () => {
|
||||
beforeEach(() => {
|
||||
currentSessions = []
|
||||
upsertedSessions.length = 0
|
||||
})
|
||||
|
||||
test("skips stale global session.updated echoes after a newer rename", () => {
|
||||
currentSessions = [buildSession("New Title", { created: 1, updated: 20 })]
|
||||
|
||||
applySessionEventToGlobalSessions(buildEvent(buildSession("Old Title", { created: 1, updated: 10 })))
|
||||
|
||||
expect(upsertedSessions).toEqual([])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user