@vellumai/assistant 0.3.13 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +17 -3
- package/Dockerfile +1 -1
- package/README.md +2 -0
- package/docs/architecture/scheduling.md +81 -0
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +22 -0
- package/src/__tests__/channel-policy.test.ts +19 -0
- package/src/__tests__/guardian-control-plane-policy.test.ts +582 -0
- package/src/__tests__/guardian-outbound-http.test.ts +8 -8
- package/src/__tests__/intent-routing.test.ts +22 -0
- package/src/__tests__/ipc-snapshot.test.ts +10 -0
- package/src/__tests__/notification-routing-intent.test.ts +185 -0
- package/src/__tests__/recording-handler.test.ts +191 -31
- package/src/__tests__/recording-intent-fallback.test.ts +180 -0
- package/src/__tests__/recording-intent-handler.test.ts +597 -74
- package/src/__tests__/recording-intent.test.ts +738 -342
- package/src/__tests__/recording-state-machine.test.ts +1109 -0
- package/src/__tests__/reminder-store.test.ts +20 -18
- package/src/__tests__/reminder.test.ts +2 -1
- package/src/channels/config.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -11
- package/src/config/bundled-skills/screen-recording/SKILL.md +91 -12
- package/src/config/system-prompt.ts +5 -0
- package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
- package/src/daemon/handlers/config-channels.ts +6 -6
- package/src/daemon/handlers/index.ts +1 -1
- package/src/daemon/handlers/misc.ts +258 -102
- package/src/daemon/handlers/recording.ts +417 -5
- package/src/daemon/handlers/sessions.ts +142 -68
- package/src/daemon/ipc-contract/computer-use.ts +23 -3
- package/src/daemon/ipc-contract/messages.ts +3 -1
- package/src/daemon/ipc-contract/shared.ts +6 -0
- package/src/daemon/ipc-contract-inventory.json +2 -0
- package/src/daemon/lifecycle.ts +2 -0
- package/src/daemon/recording-executor.ts +180 -0
- package/src/daemon/recording-intent-fallback.ts +132 -0
- package/src/daemon/recording-intent.ts +306 -15
- package/src/daemon/session-tool-setup.ts +4 -0
- package/src/memory/conversation-attention-store.ts +5 -5
- package/src/notifications/README.md +69 -1
- package/src/notifications/adapters/sms.ts +80 -0
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +70 -1
- package/src/notifications/decisions-store.ts +24 -0
- package/src/notifications/destination-resolver.ts +2 -1
- package/src/notifications/emit-signal.ts +35 -3
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/types.ts +3 -0
- package/src/runtime/guardian-outbound-actions.ts +9 -9
- package/src/runtime/http-server.ts +7 -7
- package/src/runtime/routes/conversation-attention-routes.ts +3 -3
- package/src/runtime/routes/integration-routes.ts +5 -5
- package/src/schedule/scheduler.ts +15 -3
- package/src/tools/executor.ts +29 -0
- package/src/tools/guardian-control-plane-policy.ts +141 -0
- package/src/tools/types.ts +2 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
let mockProvider: object | null = null;
|
|
4
|
+
let llmResponseText = '';
|
|
5
|
+
|
|
6
|
+
mock.module('../providers/provider-send-message.js', () => ({
|
|
7
|
+
getConfiguredProvider: () => mockProvider,
|
|
8
|
+
createTimeout: (ms: number) => {
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
11
|
+
return {
|
|
12
|
+
signal: controller.signal,
|
|
13
|
+
cleanup: () => clearTimeout(timer),
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
extractText: (response: { content: Array<{ type: string; text?: string }> }) => {
|
|
17
|
+
const block = response.content.find((b) => b.type === 'text');
|
|
18
|
+
return block && 'text' in block ? (block.text ?? '').trim() : '';
|
|
19
|
+
},
|
|
20
|
+
userMessage: (text: string) => ({ role: 'user', content: [{ type: 'text', text }] }),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
mock.module('../util/logger.js', () => ({
|
|
24
|
+
getLogger: () => ({
|
|
25
|
+
info: () => {},
|
|
26
|
+
warn: () => {},
|
|
27
|
+
debug: () => {},
|
|
28
|
+
error: () => {},
|
|
29
|
+
}),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
classifyRecordingIntentFallback,
|
|
34
|
+
containsRecordingKeywords,
|
|
35
|
+
} from '../daemon/recording-intent-fallback.js';
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
mockProvider = null;
|
|
39
|
+
llmResponseText = '';
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function setMockProvider(responseText: string) {
|
|
43
|
+
llmResponseText = responseText;
|
|
44
|
+
mockProvider = {
|
|
45
|
+
sendMessage: async () => ({
|
|
46
|
+
content: [{ type: 'text' as const, text: llmResponseText }],
|
|
47
|
+
model: 'test-model',
|
|
48
|
+
stopReason: 'end_turn',
|
|
49
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function setMockProviderThatThrows() {
|
|
55
|
+
mockProvider = {
|
|
56
|
+
sendMessage: async () => {
|
|
57
|
+
throw new Error('LLM call failed');
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Module exports ──────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
describe('recording-intent-fallback exports', () => {
|
|
65
|
+
test('exports classifyRecordingIntentFallback function', () => {
|
|
66
|
+
expect(typeof classifyRecordingIntentFallback).toBe('function');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('exports containsRecordingKeywords function', () => {
|
|
70
|
+
expect(typeof containsRecordingKeywords).toBe('function');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ─── containsRecordingKeywords ───────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
describe('containsRecordingKeywords', () => {
|
|
77
|
+
test.each([
|
|
78
|
+
'can you record this',
|
|
79
|
+
'start a recording please',
|
|
80
|
+
'I want screen capture',
|
|
81
|
+
'do a screencast of this',
|
|
82
|
+
'capture my screen now',
|
|
83
|
+
'how does screen rec work',
|
|
84
|
+
])('returns true for text containing recording keyword: "%s"', (text) => {
|
|
85
|
+
expect(containsRecordingKeywords(text)).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test.each([
|
|
89
|
+
'hello world',
|
|
90
|
+
'open Safari',
|
|
91
|
+
'take a screenshot',
|
|
92
|
+
'what time is it?',
|
|
93
|
+
'start the timer',
|
|
94
|
+
'play a song',
|
|
95
|
+
])('returns false for text without recording keywords: "%s"', (text) => {
|
|
96
|
+
expect(containsRecordingKeywords(text)).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ─── classifyRecordingIntentFallback ─────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
describe('classifyRecordingIntentFallback', () => {
|
|
103
|
+
test('returns safe default when no provider is configured', async () => {
|
|
104
|
+
mockProvider = null;
|
|
105
|
+
const result = await classifyRecordingIntentFallback('record this please');
|
|
106
|
+
expect(result).toEqual({ action: 'none', confidence: 'low' });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('returns safe default when LLM call throws', async () => {
|
|
110
|
+
setMockProviderThatThrows();
|
|
111
|
+
const result = await classifyRecordingIntentFallback('record this please');
|
|
112
|
+
expect(result).toEqual({ action: 'none', confidence: 'low' });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('parses valid start response', async () => {
|
|
116
|
+
setMockProvider('{"action": "start", "confidence": "high"}');
|
|
117
|
+
const result = await classifyRecordingIntentFallback('kick off a recording');
|
|
118
|
+
expect(result).toEqual({ action: 'start', confidence: 'high' });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('parses valid stop response', async () => {
|
|
122
|
+
setMockProvider('{"action": "stop", "confidence": "high"}');
|
|
123
|
+
const result = await classifyRecordingIntentFallback('end the recording now');
|
|
124
|
+
expect(result).toEqual({ action: 'stop', confidence: 'high' });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('parses valid none response for questions', async () => {
|
|
128
|
+
setMockProvider('{"action": "none", "confidence": "high"}');
|
|
129
|
+
const result = await classifyRecordingIntentFallback('how do I record my screen?');
|
|
130
|
+
expect(result).toEqual({ action: 'none', confidence: 'high' });
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('handles medium confidence correctly', async () => {
|
|
134
|
+
setMockProvider('{"action": "start", "confidence": "medium"}');
|
|
135
|
+
const result = await classifyRecordingIntentFallback('maybe record something');
|
|
136
|
+
expect(result).toEqual({ action: 'start', confidence: 'medium' });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('handles JSON embedded in surrounding text', async () => {
|
|
140
|
+
setMockProvider('Here is my classification: {"action": "restart", "confidence": "high"} based on the input.');
|
|
141
|
+
const result = await classifyRecordingIntentFallback('restart the recording');
|
|
142
|
+
expect(result).toEqual({ action: 'restart', confidence: 'high' });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('returns safe default for malformed JSON response', async () => {
|
|
146
|
+
setMockProvider('this is not json at all');
|
|
147
|
+
const result = await classifyRecordingIntentFallback('record something');
|
|
148
|
+
expect(result).toEqual({ action: 'none', confidence: 'low' });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('returns safe default for invalid action in response', async () => {
|
|
152
|
+
setMockProvider('{"action": "explode", "confidence": "high"}');
|
|
153
|
+
const result = await classifyRecordingIntentFallback('record something');
|
|
154
|
+
expect(result).toEqual({ action: 'none', confidence: 'low' });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('returns safe default for missing confidence field', async () => {
|
|
158
|
+
setMockProvider('{"action": "start"}');
|
|
159
|
+
const result = await classifyRecordingIntentFallback('record something');
|
|
160
|
+
expect(result).toEqual({ action: 'none', confidence: 'low' });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('returns safe default for empty response', async () => {
|
|
164
|
+
setMockProvider('');
|
|
165
|
+
const result = await classifyRecordingIntentFallback('record something');
|
|
166
|
+
expect(result).toEqual({ action: 'none', confidence: 'low' });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('correctly classifies pause action', async () => {
|
|
170
|
+
setMockProvider('{"action": "pause", "confidence": "high"}');
|
|
171
|
+
const result = await classifyRecordingIntentFallback('hold the recording');
|
|
172
|
+
expect(result).toEqual({ action: 'pause', confidence: 'high' });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('correctly classifies resume action', async () => {
|
|
176
|
+
setMockProvider('{"action": "resume", "confidence": "high"}');
|
|
177
|
+
const result = await classifyRecordingIntentFallback('continue the recording');
|
|
178
|
+
expect(result).toEqual({ action: 'resume', confidence: 'high' });
|
|
179
|
+
});
|
|
180
|
+
});
|