forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdesktopChatPrompt.test.ts
More file actions
189 lines (173 loc) · 9.23 KB
/
Copy pathdesktopChatPrompt.test.ts
File metadata and controls
189 lines (173 loc) · 9.23 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
import { describe, expect, it } from 'vitest'
import { buildDesktopChatSystemPrompt, buildDesktopChatPersonalization } from './desktopChatPrompt'
describe('buildDesktopChatSystemPrompt', () => {
it('carries the <initiative> block that routes long/coding work to spawn_agent', () => {
const prompt = buildDesktopChatSystemPrompt()
// The whole point of the port: the model must be told to hand slow work to a
// background agent instead of answering in text.
expect(prompt).toContain('<initiative>')
expect(prompt).toContain('spawn_agent')
// The threshold is ported verbatim from macOS (the proven wording) so the
// spawn trigger behaves like Mac's — not a more aggressive one.
expect(prompt).toContain(
'Work needing more than ~30 seconds of tool calls or research: start a background agent with spawn_agent'
)
})
it('does NOT use a more aggressive spawn trigger than Mac (~30s threshold only)', () => {
const prompt = buildDesktopChatSystemPrompt().toLowerCase()
// Guard against over-eager wording that would spawn agents for normal chat —
// the no-regressions constraint. Only the gated ~30s path may mention spawning.
expect(prompt).not.toContain('always spawn')
expect(prompt).not.toContain('spawn an agent for every')
expect(prompt).not.toContain('spawn for any')
// spawn_agent is referenced exactly once (the initiative bullet), not sprinkled
// as a default action across the prompt.
expect(prompt.match(/spawn_agent/g)).toHaveLength(1)
})
it('establishes the Omi persona and keeps normal replies conversational', () => {
const prompt = buildDesktopChatSystemPrompt()
expect(prompt).toContain('You are Omi')
// Response-style guidance keeps ordinary questions as short text answers — the
// regression guard that a system prompt does not turn every message into a spawn.
expect(prompt).toContain('<response_style>')
expect(prompt).toContain('Write like a smart friend texting')
})
it('carries the full ported persona: response_style, mentor, and critical-accuracy rules', () => {
// The brief's parity gap — every typed reply must get the concise 2-8-line
// register, the "mentor, not a yes-man" pushback, AND the anti-fabrication
// guardrails, exactly as Mac front-loads them (ChatPrompts.desktopChat).
const prompt = buildDesktopChatSystemPrompt()
expect(prompt).toContain('<response_style>')
expect(prompt).toContain('Default 2-8 lines')
expect(prompt).toContain('<mentor_behavior>')
expect(prompt).toContain("You're a mentor, not a yes-man")
expect(prompt).toContain('<critical_accuracy_rules>')
expect(prompt).toContain('never from plausible invention')
})
it('interpolates a name when provided, else reads as "the user"', () => {
expect(buildDesktopChatSystemPrompt({ userName: 'Ada' })).toContain(
'an AI assistant & mentor for Ada'
)
expect(buildDesktopChatSystemPrompt()).toContain('an AI assistant & mentor for the user')
// No unreplaced template tokens leak through in either case.
expect(buildDesktopChatSystemPrompt({ userName: 'Ada' })).not.toContain('{user_name}')
expect(buildDesktopChatSystemPrompt()).not.toContain('{user_name}')
})
it('interpolates the timezone when provided and drops the parenthetical otherwise', () => {
expect(buildDesktopChatSystemPrompt({ timezone: 'America/New_York' })).toContain(
'timezone (America/New_York)'
)
const noTz = buildDesktopChatSystemPrompt()
expect(noTz).toContain('timezone, in a natural')
expect(noTz).not.toContain('{tz}')
})
it('is byte-stable for identical inputs (binding-reuse / no per-turn pi restart)', () => {
// The kernel keys binding reuse on the system-prompt hash; a prompt that
// varied turn-to-turn would restart the pi subprocess every message.
const a = buildDesktopChatSystemPrompt({ timezone: 'UTC' })
const b = buildDesktopChatSystemPrompt({ timezone: 'UTC' })
expect(a).toBe(b)
})
it('carries NO volatile personalization (that rides the per-turn prompt, not here)', () => {
// Personalization lives in the per-turn <user_context> block so the system
// prompt stays byte-stable. Prove none of it leaked into the system prompt.
const prompt = buildDesktopChatSystemPrompt({ userName: 'Ada' })
expect(prompt).not.toContain('<user_context>')
expect(prompt).not.toContain('<user_facts>')
expect(prompt).not.toContain('<user_tasks>')
expect(prompt).not.toContain('<ai_user_profile>')
})
})
describe('buildDesktopChatPersonalization', () => {
it('renders memories, tasks, and AI profile into one <user_context> block', () => {
const block = buildDesktopChatPersonalization({
userName: 'Ada',
memories: ['prefers dark mode', 'lives in Berlin'],
tasks: [
{ description: 'ship the installer', priority: 'high', category: 'work' },
{ description: 'call the bank' }
],
aiProfileText: 'Ada is a systems engineer shipping a desktop app.'
})
expect(block).toContain('<user_context>')
expect(block).toContain('</user_context>')
// Faithful to Mac's formatMemoriesSection wording.
expect(block).toContain('<user_facts>')
expect(block).toContain('Facts about Ada:')
expect(block).toContain('- [memory] prefers dark mode')
expect(block).toContain('- [memory] lives in Berlin')
// Faithful to Mac's formatTasksSection wording.
expect(block).toContain('<user_tasks>')
expect(block).toContain('Current tasks:')
expect(block).toContain('- ship the installer [priority: high] [category: work]')
expect(block).toContain('- call the bank')
// Faithful to Mac's formatAIProfileSection.
expect(block).toContain('<ai_user_profile>')
expect(block).toContain('Ada is a systems engineer shipping a desktop app.')
})
it('renders a due date in the USER timezone (local wall-clock, no misleading UTC)', () => {
// 15:30 UTC on 2026-07-20. New York is EDT (UTC-4) → 11:30 local; the model
// must see the local wall-clock, not the raw UTC time, or a non-UTC user gets
// a due time off by their offset. Deterministic: fixed epoch + fixed tz.
const ny = buildDesktopChatPersonalization({
timezone: 'America/New_York',
tasks: [{ description: 'submit report', dueAt: Date.UTC(2026, 6, 20, 15, 30) }]
})
expect(ny).toContain('- submit report [due: 2026-07-20 11:30]')
// A fractional-offset zone (Kolkata, UTC+5:30) → 21:00 local, proving it is a
// real tz conversion and not a fixed slice.
const kolkata = buildDesktopChatPersonalization({
timezone: 'Asia/Kolkata',
tasks: [{ description: 'submit report', dueAt: Date.UTC(2026, 6, 20, 15, 30) }]
})
expect(kolkata).toContain('- submit report [due: 2026-07-20 21:00]')
})
it('marks the due date as UTC when no timezone is known (never unlabeled)', () => {
// No tz → deterministic UTC with an explicit marker so the model can convert
// instead of reading it as-if-local. Also proves the pure builder never falls
// back to the non-deterministic runtime-local zone.
const block = buildDesktopChatPersonalization({
tasks: [{ description: 'submit report', dueAt: Date.UTC(2026, 6, 20, 15, 30) }]
})
expect(block).toContain('- submit report [due: 2026-07-20 15:30 UTC]')
})
it('falls back to marked UTC when the timezone id is invalid', () => {
const block = buildDesktopChatPersonalization({
timezone: 'Not/AZone',
tasks: [{ description: 'submit report', dueAt: Date.UTC(2026, 6, 20, 15, 30) }]
})
expect(block).toContain('- submit report [due: 2026-07-20 15:30 UTC]')
})
it('falls back to "the user" in the facts header when no name is given', () => {
const block = buildDesktopChatPersonalization({ memories: ['likes tea'] })
expect(block).toContain('Facts about the user:')
})
it('drops empty sections and returns "" when there is nothing to say', () => {
// Whole-empty input → no wrapper at all (never inject an empty shell).
expect(buildDesktopChatPersonalization()).toBe('')
expect(buildDesktopChatPersonalization({ userName: 'Ada', memories: [], tasks: [] })).toBe('')
// Only one source present → only that section, still wrapped.
const onlyProfile = buildDesktopChatPersonalization({ aiProfileText: 'engineer' })
expect(onlyProfile).toContain('<ai_user_profile>')
expect(onlyProfile).not.toContain('<user_facts>')
expect(onlyProfile).not.toContain('<user_tasks>')
})
it('caps memories at 30 and tasks at 20 (Mac parity)', () => {
const block = buildDesktopChatPersonalization({
memories: Array.from({ length: 50 }, (_, i) => `memory ${i}`),
tasks: Array.from({ length: 40 }, (_, i) => ({ description: `task ${i}` }))
})
expect((block.match(/\[memory\]/g) ?? []).length).toBe(30)
expect((block.match(/^- task /gm) ?? []).length).toBe(20)
})
it('ignores blank memories and blank-description tasks', () => {
const block = buildDesktopChatPersonalization({
memories: [' ', 'real memory', ''],
tasks: [{ description: ' ' }, { description: 'real task' }]
})
expect(block).toContain('- [memory] real memory')
expect(block).toContain('- real task')
// The blank entries did not produce empty bullets.
expect(block).not.toContain('- [memory] \n')
})
})