forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkernelSupport.test.ts
More file actions
148 lines (132 loc) · 5.25 KB
/
Copy pathkernelSupport.test.ts
File metadata and controls
148 lines (132 loc) · 5.25 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
// Unit tests for the pure kernelSupport.ts utilities ported in PR #3a:
// deterministic hashing / JSON canonicalization, MCP-config normalization
// (request-scoped env stripping + ordering), the generic column-patch UPDATE
// helper, and value coercers.
import { describe, expect, it, vi } from 'vitest'
import {
KERNEL_MCP_PROTOCOL_VERSION,
boundedLimit,
buildDelegatedPrompt,
nullableNumber,
nullableText,
parseJsonObject,
placeholders,
requiredChildSessionId,
stableJsonHash,
stableJsonStringify,
stableMcpServerConfig,
updateByColumns
} from './kernelSupport'
import { PROTOCOL_VERSION as AUTOMATION_HELPER_PROTOCOL_VERSION } from '../automation/protocol'
import type { AgentStore } from './types'
describe('KERNEL_MCP_PROTOCOL_VERSION', () => {
// The agent runtime's MCP tool-context contract is macOS protocol v2. Windows
// ALSO has an unrelated `automation/protocol.ts` exporting PROTOCOL_VERSION = 1
// — that is the C# UI-automation helper's wire format and has nothing to do with
// the agent runtime. Importing it here would silently stamp protocolVersion: 1
// into the MCP context file every adapter reads. Pin the value, and pin that the
// two are NOT the same number, so the mistake cannot be made quietly.
it('is 2, matching the macOS agent protocol', () => {
expect(KERNEL_MCP_PROTOCOL_VERSION).toBe(2)
})
it('is not the UI-automation helper protocol version', () => {
expect(AUTOMATION_HELPER_PROTOCOL_VERSION).toBe(1)
expect(KERNEL_MCP_PROTOCOL_VERSION).not.toBe(AUTOMATION_HELPER_PROTOCOL_VERSION)
})
})
describe('kernelSupport — deterministic serialization', () => {
it('canonicalizes object key order', () => {
expect(stableJsonStringify({ b: 1, a: 2 })).toBe(stableJsonStringify({ a: 2, b: 1 }))
expect(stableJsonStringify({ b: 1, a: 2 })).toBe('{"a":2,"b":1}')
})
it('produces a stable hash irrespective of key order', () => {
expect(stableJsonHash({ x: [1, 2], y: 'z' })).toBe(stableJsonHash({ y: 'z', x: [1, 2] }))
})
it('strips request-scoped MCP env entries and sorts servers by env name', () => {
const normalized = stableMcpServerConfig([
{
name: 'omi',
env: [
{ name: 'OMI_REQUEST_ID', value: 'req-123' },
{ name: 'OMI_STATIC', value: 'keep' },
{ name: 'OMI_CONTEXT_FILE', value: '/tmp/ctx.json' }
]
}
]) as Array<{ env: Array<{ name: string }> }>
const envNames = normalized[0].env.map((e) => e.name)
expect(envNames).toEqual(['OMI_STATIC'])
})
it('produces identical hashes when only request-scoped env differs', () => {
const a = stableJsonHash(
stableMcpServerConfig([{ name: 'omi', env: [{ name: 'OMI_RUN_ID', value: 'run-a' }] }])
)
const b = stableJsonHash(
stableMcpServerConfig([{ name: 'omi', env: [{ name: 'OMI_RUN_ID', value: 'run-b' }] }])
)
expect(a).toBe(b)
})
it('treats non-array MCP config as empty', () => {
expect(stableMcpServerConfig(undefined)).toEqual([])
expect(stableMcpServerConfig({})).toEqual([])
})
})
describe('kernelSupport — helpers', () => {
it('parses JSON objects and rejects arrays / malformed input', () => {
expect(parseJsonObject('{"a":1}')).toEqual({ a: 1 })
expect(parseJsonObject('[1,2]')).toEqual({})
expect(parseJsonObject('not json')).toEqual({})
expect(parseJsonObject(null)).toEqual({})
})
it('bounds limits with a fallback and ceiling', () => {
expect(boundedLimit(undefined, 50, 500)).toBe(50)
expect(boundedLimit(1000, 50, 500)).toBe(500)
expect(boundedLimit(0, 50, 500)).toBe(1)
expect(boundedLimit(NaN, 50, 500)).toBe(50)
})
it('emits N sql placeholders', () => {
expect(placeholders(3)).toBe('?, ?, ?')
expect(placeholders(0)).toBe('')
})
it('builds a delegated prompt with optional context', () => {
expect(buildDelegatedPrompt('do it', undefined)).toBe('do it')
expect(buildDelegatedPrompt('do it', 'because')).toBe('Objective:\ndo it\n\nContext:\nbecause')
})
it('requires a child session id in continue mode', () => {
expect(requiredChildSessionId('ses_1')).toBe('ses_1')
expect(() => requiredChildSessionId(undefined)).toThrow(/requires childSessionId/)
})
it('coerces nullable values', () => {
expect(nullableText(undefined)).toBeNull()
expect(nullableText(5)).toBe('5')
expect(nullableNumber(null)).toBeNull()
expect(nullableNumber('7')).toBe(7)
})
it('updateByColumns skips undefined fields and maps camelCase to columns', () => {
const execute = vi.fn().mockReturnValue(1)
const store = { execute } as unknown as AgentStore
updateByColumns(
store,
'runs',
'run_id',
'run_1',
{ statusText: 'status', updatedAtMs: 'updated_at_ms' },
{
statusText: 'running',
updatedAtMs: undefined
} as Record<string, unknown>
)
expect(execute).toHaveBeenCalledWith('UPDATE runs SET status = ? WHERE run_id = ?', [
'running',
'run_1'
])
})
it('updateByColumns is a no-op when the patch has no defined fields', () => {
const execute = vi.fn()
const store = { execute } as unknown as AgentStore
updateByColumns(store, 'runs', 'run_id', 'run_1', {}, { a: undefined } as Record<
string,
unknown
>)
expect(execute).not.toHaveBeenCalled()
})
})