forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsurfaceSession.test.ts
More file actions
212 lines (195 loc) · 7.01 KB
/
Copy pathsurfaceSession.test.ts
File metadata and controls
212 lines (195 loc) · 7.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// Tests for surfaceSession.ts (macOS surface-session.ts). Exercises the real
// SqliteAgentStore (node:sqlite driver, the store's test seam) so the surface
// resolution + floating->main merge run against actual SQL and partial-unique
// indexes.
import { mkdtempSync, rmSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import { DatabaseSync } from 'node:sqlite'
import { afterEach, describe, expect, it } from 'vitest'
import { SqliteAgentStore, type DatabaseFactory } from './store'
import {
clearOwnerSurfaceState,
mergeFloatingChatIntoMainChat,
resolveSurfaceSession,
type SurfaceRef
} from './surfaceSession'
const nodeSqliteFactory = DatabaseSync as unknown as DatabaseFactory
const createdDirs: string[] = []
const openStores: SqliteAgentStore[] = []
// Close stores before rmSync: on Windows an open SQLite handle blocks the temp
// dir delete (EPERM), which would otherwise mask the real assertion on failure.
afterEach(() => {
for (const store of openStores.splice(0)) {
try {
store.close()
} catch {
// already closed
}
}
for (const dir of createdDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true })
}
})
function newStore(): SqliteAgentStore {
const dir = mkdtempSync(join(tmpdir(), 'omi-surface-'))
createdDirs.push(dir)
const store = new SqliteAgentStore({
databaseFactory: nodeSqliteFactory,
databasePath: join(dir, 'omi-agentd.sqlite3'),
reconcileOnOpen: false
})
openStores.push(store)
return store
}
const mainRef: SurfaceRef = {
surfaceKind: 'main_chat',
externalRefKind: 'chat',
externalRefId: 'default'
}
describe('resolveSurfaceSession', () => {
it('creates a session + conversation on first resolve and is idempotent', () => {
const store = newStore()
const first = resolveSurfaceSession(
store,
{ ownerId: 'owner', surfaceRef: mainRef },
() => 1000
)
expect(first.agentSessionId).toBeTruthy()
expect(first.conversationId).toBeTruthy()
const second = resolveSurfaceSession(
store,
{ ownerId: 'owner', surfaceRef: mainRef },
() => 2000
)
expect(second).toEqual(first)
// Exactly one session + one surface mapping exist.
expect(Number(store.getRow('SELECT COUNT(*) AS c FROM sessions').c)).toBe(1)
expect(Number(store.getRow('SELECT COUNT(*) AS c FROM surface_conversations').c)).toBe(1)
store.close()
})
it('shares a session across surfaces with the same external ref but scopes by owner', () => {
const store = newStore()
const main = resolveSurfaceSession(store, { ownerId: 'owner', surfaceRef: mainRef }, () => 1000)
const floating = resolveSurfaceSession(
store,
{
ownerId: 'owner',
surfaceRef: {
surfaceKind: 'floating_chat',
externalRefKind: 'chat',
externalRefId: 'default'
}
},
() => 1000
)
const otherOwner = resolveSurfaceSession(
store,
{ ownerId: 'other', surfaceRef: mainRef },
() => 1000
)
// Same owner + external ref (chat/default) => one shared agent session, but a
// distinct conversation per surface_kind. A different owner is fully isolated.
expect(floating.agentSessionId).toBe(main.agentSessionId)
expect(floating.conversationId).not.toBe(main.conversationId)
expect(otherOwner.agentSessionId).not.toBe(main.agentSessionId)
expect(Number(store.getRow('SELECT COUNT(*) AS c FROM sessions').c)).toBe(2)
expect(Number(store.getRow('SELECT COUNT(*) AS c FROM surface_conversations').c)).toBe(3)
store.close()
})
it('pins the provider boundary from the requested adapter for a fresh main chat', () => {
const store = newStore()
const resolved = resolveSurfaceSession(
store,
{ ownerId: 'owner', surfaceRef: mainRef, defaultAdapterId: 'openclaw' },
() => 1000
)
const row = store.getRow(
'SELECT default_adapter_id, provider_boundary FROM sessions WHERE session_id = ?',
[resolved.agentSessionId]
)
expect(row.default_adapter_id).toBe('openclaw')
expect(row.provider_boundary).toBe('local_user:openclaw')
store.close()
})
})
describe('mergeFloatingChatIntoMainChat', () => {
it('folds the floating transcript into main chat and removes the floating mapping', () => {
const store = newStore()
const floatingRef: SurfaceRef = {
surfaceKind: 'floating_chat',
externalRefKind: 'chat',
externalRefId: 'default'
}
const floating = resolveSurfaceSession(
store,
{ ownerId: 'owner', surfaceRef: floatingRef },
() => 1000
)
store.insertConversationTurn({
conversationId: floating.conversationId,
role: 'user',
surfaceKind: 'floating_chat',
content: 'hello from floating',
createdAtMs: 1001,
metadataJson: '{}'
})
store.insertConversationTurn({
conversationId: floating.conversationId,
role: 'assistant',
surfaceKind: 'floating_chat',
content: 'reply from floating',
createdAtMs: 1002,
metadataJson: '{}'
})
const result = mergeFloatingChatIntoMainChat(store, { ownerId: 'owner' }, () => 2000)
expect(result.mergedTurns).toBe(2)
expect(result.removedFloatingMapping).toBe(true)
// Floating mapping is gone; main chat now holds the two turns.
const floatingCount = Number(
store.getRow(
"SELECT COUNT(*) AS c FROM surface_conversations WHERE surface_kind = 'floating_chat'"
).c
)
expect(floatingCount).toBe(0)
const main = resolveSurfaceSession(store, { ownerId: 'owner', surfaceRef: mainRef }, () => 3000)
const mainTurns = store.allRows(
'SELECT surface_kind, content FROM conversation_turns WHERE conversation_id = ? ORDER BY created_at_ms ASC',
[main.conversationId]
)
expect(mainTurns.map((t) => t.content)).toEqual(['hello from floating', 'reply from floating'])
expect(mainTurns.every((t) => t.surface_kind === 'main_chat')).toBe(true)
store.close()
})
it('is a no-op when there is no floating chat', () => {
const store = newStore()
const result = mergeFloatingChatIntoMainChat(store, { ownerId: 'owner' }, () => 2000)
expect(result).toEqual({ mergedTurns: 0, removedFloatingMapping: false })
store.close()
})
})
describe('clearOwnerSurfaceState', () => {
it('invalidates active bindings for the owner', () => {
const store = newStore()
const resolved = resolveSurfaceSession(
store,
{ ownerId: 'owner', surfaceRef: mainRef },
() => 1000
)
const b = store.insertAdapterBinding({
sessionId: resolved.agentSessionId,
adapterId: 'acp',
bindingGeneration: 1,
adapterNativeSessionId: 'native-1',
adapterInstanceId: 'node',
resumeFidelity: 'native',
status: 'active'
})
const cleared = clearOwnerSurfaceState(store, 'owner', () => 5000)
expect(cleared.invalidatedBindingIds).toEqual([b.bindingId])
expect(
store.getRow('SELECT status FROM adapter_bindings WHERE binding_id = ?', [b.bindingId]).status
).toBe('invalid')
store.close()
})
})