|
|
@@ -9,7 +9,7 @@ import SessionStore, {
|
|
|
SessionId,
|
|
|
snapshotSessionEvent,
|
|
|
} from '@deepseek-ai/dsh-session'
|
|
|
-import type { CreateSessionOptions, SessionEventType, SessionHeader, SessionSurface, TodoItem } from '@deepseek-ai/dsh-session'
|
|
|
+import type { CreateSessionOptions, SessionEventType, SessionHeader, SessionSurface } from '@deepseek-ai/dsh-session'
|
|
|
|
|
|
describe('Session', () => {
|
|
|
it('exposes one stable readonly surface view', () => {
|
|
|
@@ -787,7 +787,7 @@ describe('Session', () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const event = session.append('todo/write', data as never)
|
|
|
+ const event = session.append('request/context', data as never)
|
|
|
|
|
|
expect(reads).toBe(1)
|
|
|
expect(event.data).toEqual({ value: 'accepted' })
|
|
|
@@ -914,14 +914,14 @@ describe('Session', () => {
|
|
|
expect(() => { seededEvent.data.turn = 99 }).toThrow(TypeError)
|
|
|
|
|
|
const appended = Session.create(SessionId('append-frozen'))
|
|
|
- const appendedEvent = appended.append('todo/write', {
|
|
|
- todos: [{ content: 'first', status: 'pending' }],
|
|
|
- })
|
|
|
+ const appendedEvent = appended.append('user/message', createUserMessage({
|
|
|
+ content: [{ type: 'text', text: 'first' }], source: { kind: 'user' },
|
|
|
+ }), { surfaceOp: 'append' })
|
|
|
expect(Object.isFrozen(appendedEvent)).toBe(true)
|
|
|
expect(Object.isFrozen(appendedEvent.data)).toBe(true)
|
|
|
- expect(Object.isFrozen(appendedEvent.data.todos)).toBe(true)
|
|
|
- expect(Object.isFrozen(appendedEvent.data.todos[0])).toBe(true)
|
|
|
- expect(() => { appendedEvent.data.todos[0]!.content = 'mutated' }).toThrow(TypeError)
|
|
|
+ expect(Object.isFrozen(appendedEvent.data.content)).toBe(true)
|
|
|
+ expect(Object.isFrozen(appendedEvent.data.content[0])).toBe(true)
|
|
|
+ expect(() => { (appendedEvent.data.content[0] as { text: string }).text = 'mutated' }).toThrow(TypeError)
|
|
|
})
|
|
|
|
|
|
it('iteratively freezes deeply nested restored event data', () => {
|
|
|
@@ -1531,7 +1531,7 @@ describe('SessionStore', () => {
|
|
|
const session = ctx.sessions.create(SessionId('reentrant-observer'))
|
|
|
const heard: SessionEvent[] = []
|
|
|
ctx.on('session/event', (observedSession) => {
|
|
|
- observedSession.append('todo/write', { todos: [] })
|
|
|
+ observedSession.append('request/context', { provider: 'mock', model: 'mock' })
|
|
|
})
|
|
|
ctx.on('session/event', (_observedSession, event) => { heard.push(event) })
|
|
|
|
|
|
@@ -1663,68 +1663,3 @@ describe('SessionStore', () => {
|
|
|
expect(heard).toEqual([session])
|
|
|
})
|
|
|
})
|
|
|
-
|
|
|
-describe('todo/write event', () => {
|
|
|
- it('appends the whole-list snapshot and isolates the log from later mutation', () => {
|
|
|
- const session = Session.create(SessionId('t1'))
|
|
|
- const todos: TodoItem[] = [
|
|
|
- { content: 'plan the work', status: 'in_progress' },
|
|
|
- { content: 'write the code', status: 'pending' },
|
|
|
- ]
|
|
|
- session.append('todo/write', { todos })
|
|
|
-
|
|
|
- const event = session.events.findLast(e => e.type === 'todo/write')!
|
|
|
- expect(event.type).toBe('todo/write')
|
|
|
- expect(event.data.todos).toEqual(todos)
|
|
|
-
|
|
|
- // The append snapshots its input: mutating the caller's array afterward must
|
|
|
- // not change what the log holds (the durable-source-of-truth contract).
|
|
|
- todos.push({ content: 'sneak in', status: 'pending' })
|
|
|
- todos[0]!.status = 'completed'
|
|
|
- expect(event.data.todos).toEqual([
|
|
|
- { content: 'plan the work', status: 'in_progress' },
|
|
|
- { content: 'write the code', status: 'pending' },
|
|
|
- ])
|
|
|
- })
|
|
|
-
|
|
|
- it('is last-write-wins: the current list is the most recent todo/write', () => {
|
|
|
- const session = Session.create(SessionId('t2'))
|
|
|
- session.append('todo/write', { todos: [{ content: 'first', status: 'pending' }] })
|
|
|
- session.append('todo/write', { todos: [
|
|
|
- { content: 'first', status: 'completed' },
|
|
|
- { content: 'second', status: 'in_progress' },
|
|
|
- ] })
|
|
|
-
|
|
|
- const current = session.events.findLast(e => e.type === 'todo/write')!.data.todos
|
|
|
- expect(current).toEqual([
|
|
|
- { content: 'first', status: 'completed' },
|
|
|
- { content: 'second', status: 'in_progress' },
|
|
|
- ])
|
|
|
- })
|
|
|
-
|
|
|
- it('is NOT a surface event: it produces no derived message and joins no surface node', () => {
|
|
|
- const session = Session.create(SessionId('t3'))
|
|
|
- session.append('user/message', createUserMessage({
|
|
|
- content: [{ type: 'text', text: 'q' }], source: { kind: 'user' },
|
|
|
- }), { surfaceOp: 'append' })
|
|
|
- const before = session.deriveMessages().length
|
|
|
- session.append('todo/write', { todos: [{ content: 'a task', status: 'pending' }] })
|
|
|
- // The todo event must not add a message to the derived history…
|
|
|
- expect(session.deriveMessages()).toHaveLength(before)
|
|
|
- // …and must not appear on the ordered surface.
|
|
|
- expect(session.surface.nodes).not.toContain(session.seq - 1)
|
|
|
- })
|
|
|
-
|
|
|
- it('round-trips through a seeded replay identically (durable, no surfaceOp needed)', () => {
|
|
|
- const original = Session.create(SessionId('t4'))
|
|
|
- original.append('turn/start', { turn: 1 })
|
|
|
- original.append('todo/write', { todos: [{ content: 'only', status: 'completed' }] })
|
|
|
- original.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
|
|
- // Seeding a non-surface event with no surfaceOp must not throw.
|
|
|
- const replayed = Session.create(SessionId('t4-replay'), [...original.events])
|
|
|
- expect(replayed.events.findLast(e => e.type === 'todo/write')!.data.todos)
|
|
|
- .toEqual([{ content: 'only', status: 'completed' }])
|
|
|
- expect(replayed.events.slice(0, original.seq)).toEqual(original.events)
|
|
|
- expect(replayed.firstLiveSeq).toBe(original.seq)
|
|
|
- })
|
|
|
-})
|