@vellumai/assistant 0.10.8-dev.202607142125.4cfef21 → 0.10.8-dev.202607142322.265489c
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/package.json +1 -1
- package/src/__tests__/config-schema.test.ts +3 -2
- package/src/calls/media-turn-detector.ts +11 -1
- package/src/cli/lib/bundled-marketplace.json +64 -35
- package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
- package/src/config/schemas/live-voice.ts +10 -4
- package/src/config/schemas/memory-retrospective.ts +8 -0
- package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
- package/src/live-voice/__tests__/live-voice-vad.test.ts +85 -7
- package/src/live-voice/__tests__/protocol.test.ts +122 -0
- package/src/live-voice/live-voice-session.ts +65 -13
- package/src/live-voice/protocol.ts +143 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +66 -6
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +180 -0
- package/src/plugins/defaults/memory/graph/tools.ts +47 -13
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +2 -122
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
- package/src/plugins/defaults/memory/tools.test.ts +34 -0
- package/src/plugins/defaults/memory/tools.ts +12 -2
- package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
- package/src/telemetry/AGENTS.md +27 -1
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +35 -37
- package/src/telemetry/telemetry-event-sources.test.ts +36 -2
- package/src/telemetry/telemetry-event-sources.ts +37 -17
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire-validation.test.ts +18 -10
- package/src/telemetry/telemetry-wire.generated.ts +60 -9
- package/src/telemetry/types.test.ts +0 -12
- package/src/telemetry/types.ts +59 -22
package/package.json
CHANGED
|
@@ -896,11 +896,12 @@ describe("AssistantConfigSchema", () => {
|
|
|
896
896
|
mode: "open-mic",
|
|
897
897
|
vad: {
|
|
898
898
|
speechEnergyThreshold: 800,
|
|
899
|
-
silenceThresholdMs:
|
|
899
|
+
silenceThresholdMs: 1200,
|
|
900
900
|
maxTurnDurationMs: 30000,
|
|
901
|
-
bargeInMinSpeechMs:
|
|
901
|
+
bargeInMinSpeechMs: 250,
|
|
902
902
|
},
|
|
903
903
|
maxSessionDurationSeconds: 1800,
|
|
904
|
+
archiveAudio: false,
|
|
904
905
|
});
|
|
905
906
|
});
|
|
906
907
|
|
|
@@ -75,7 +75,7 @@ export interface TurnDetectorCallbacks {
|
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
76
76
|
|
|
77
77
|
export class MediaTurnDetector {
|
|
78
|
-
private
|
|
78
|
+
private silenceThresholdMs: number;
|
|
79
79
|
private readonly maxTurnDurationMs: number;
|
|
80
80
|
private readonly callbacks: TurnDetectorCallbacks;
|
|
81
81
|
|
|
@@ -116,6 +116,16 @@ export class MediaTurnDetector {
|
|
|
116
116
|
return this.active;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Update the trailing-silence threshold mid-session (the "pause before
|
|
121
|
+
* reply" live-tuning path). Applies from the next silence-timer arm — a
|
|
122
|
+
* countdown already in flight keeps its current duration until it resets on
|
|
123
|
+
* the next speech chunk — so the change takes effect on the next utterance.
|
|
124
|
+
*/
|
|
125
|
+
setSilenceThresholdMs(silenceThresholdMs: number): void {
|
|
126
|
+
this.silenceThresholdMs = silenceThresholdMs;
|
|
127
|
+
}
|
|
128
|
+
|
|
119
129
|
/**
|
|
120
130
|
* Feed an inbound audio chunk to the detector with speech activity info.
|
|
121
131
|
*
|
|
@@ -15,20 +15,22 @@
|
|
|
15
15
|
"description": "Proactive chief-of-staff: a morning digest, propose-then-confirm inbox triage, calendar prep, and a weekly competitor brief.",
|
|
16
16
|
"category": "productivity",
|
|
17
17
|
"homepage": "https://github.com/vellum-ai/admin-copilot",
|
|
18
|
-
"license": "MIT"
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"icon": "🧑💼"
|
|
19
20
|
},
|
|
20
21
|
{
|
|
21
22
|
"name": "agent-wrapped",
|
|
22
23
|
"source": {
|
|
23
24
|
"source": "github",
|
|
24
25
|
"repo": "vellum-ai/agent-wrapped",
|
|
25
|
-
"ref": "
|
|
26
|
+
"ref": "be38bd9cd57aaac6bf06b22d82885c09219320bf"
|
|
26
27
|
},
|
|
27
28
|
"description": "Your agent's year in review: conversations, days together, memories formed, swear count, top topics, and the receipt (total tokens + LLM calls). Works with Vellum agents and Claude Code. Ships a stats collector tool, a CLI, a Claude Code plugin, and a skill for the shareable cards experience.",
|
|
28
29
|
"category": "creative",
|
|
29
30
|
"license": "MIT",
|
|
30
31
|
"submittedBy": "marina",
|
|
31
|
-
"homepage": "https://agent-wrapped.com"
|
|
32
|
+
"homepage": "https://agent-wrapped.com",
|
|
33
|
+
"icon": "🎁"
|
|
32
34
|
},
|
|
33
35
|
{
|
|
34
36
|
"name": "ai-hero-engineer-kit",
|
|
@@ -37,10 +39,11 @@
|
|
|
37
39
|
"repo": "marinatrajk/ai-hero-engineer-kit",
|
|
38
40
|
"ref": "18d33c62b15a3e2cc22dc070723fe3077749dcec"
|
|
39
41
|
},
|
|
40
|
-
"description": "Five engineering skills adapted from Matt Pocock's '5 Agent Skills I Use Every Day'
|
|
42
|
+
"description": "Five engineering skills adapted from Matt Pocock's '5 Agent Skills I Use Every Day' — plans, PRDs, vertical-slice issues, TDD loop, and codebase deepening in one install.",
|
|
41
43
|
"category": "developer",
|
|
42
44
|
"homepage": "https://github.com/marinatrajk/ai-hero-engineer-kit",
|
|
43
|
-
"license": "MIT"
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"icon": "🧰"
|
|
44
47
|
},
|
|
45
48
|
{
|
|
46
49
|
"name": "amex-perk-reminder",
|
|
@@ -52,7 +55,8 @@
|
|
|
52
55
|
"description": "Track every credit on the Amex cards you hold (Platinum, Gold, Business Platinum, Delta Gold/Platinum/Reserve) with status-aware reminders before any window closes.",
|
|
53
56
|
"category": "lifestyle",
|
|
54
57
|
"homepage": "https://github.com/AnitaKirkovska/amex-perk-reminder",
|
|
55
|
-
"license": "MIT"
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"icon": "💳"
|
|
56
60
|
},
|
|
57
61
|
{
|
|
58
62
|
"name": "astrology-horoscope",
|
|
@@ -64,7 +68,8 @@
|
|
|
64
68
|
"description": "Western tropical natal chart and transit computation. Local ephemeris (astronomy-engine, no API keys) for planet positions, ascendant/midheaven, whole-sign houses, and aspects, plus a natal-reading skill that interprets the raw chart into a readable report.",
|
|
65
69
|
"category": "hobby",
|
|
66
70
|
"homepage": "https://github.com/vellum-ai/astrology-horoscope",
|
|
67
|
-
"license": "MIT"
|
|
71
|
+
"license": "MIT",
|
|
72
|
+
"icon": "🔮"
|
|
68
73
|
},
|
|
69
74
|
{
|
|
70
75
|
"name": "caveman",
|
|
@@ -77,7 +82,7 @@
|
|
|
77
82
|
"category": "productivity",
|
|
78
83
|
"homepage": "https://github.com/JuliusBrussee/caveman",
|
|
79
84
|
"license": "MIT",
|
|
80
|
-
"icon": "
|
|
85
|
+
"icon": "🦴"
|
|
81
86
|
},
|
|
82
87
|
{
|
|
83
88
|
"name": "coffee-aficionado",
|
|
@@ -89,8 +94,7 @@
|
|
|
89
94
|
"description": "Find great coffee beans and machines matched to your taste profile, budget, and kitchen. Persistent memory learns your preferences over time.",
|
|
90
95
|
"category": "lifestyle",
|
|
91
96
|
"homepage": "https://github.com/vellum-ai/coffee-aficionado",
|
|
92
|
-
"license": "MIT"
|
|
93
|
-
"icon": "\u2615"
|
|
97
|
+
"license": "MIT"
|
|
94
98
|
},
|
|
95
99
|
{
|
|
96
100
|
"name": "cofounder",
|
|
@@ -102,7 +106,8 @@
|
|
|
102
106
|
"description": "AI co-founder that remembers your company context, challenges your thinking, and tracks your decisions.",
|
|
103
107
|
"category": "productivity",
|
|
104
108
|
"homepage": "https://github.com/vellum-ai/cofounder",
|
|
105
|
-
"license": "MIT"
|
|
109
|
+
"license": "MIT",
|
|
110
|
+
"icon": "🤝"
|
|
106
111
|
},
|
|
107
112
|
{
|
|
108
113
|
"name": "cognee",
|
|
@@ -114,7 +119,8 @@
|
|
|
114
119
|
},
|
|
115
120
|
"description": "Cognee knowledge graph memory for Vellum Assistant: session-aware storage, auto-routing recall, and persistent learning across sessions. Supports local mode and Cognee Cloud.",
|
|
116
121
|
"category": "memory",
|
|
117
|
-
"homepage": "https://github.com/topoteretes/cognee-integrations/tree/main/integrations/vellum-assistant"
|
|
122
|
+
"homepage": "https://github.com/topoteretes/cognee-integrations/tree/main/integrations/vellum-assistant",
|
|
123
|
+
"icon": "🕸️"
|
|
118
124
|
},
|
|
119
125
|
{
|
|
120
126
|
"name": "dynamic-notch",
|
|
@@ -126,7 +132,8 @@
|
|
|
126
132
|
"description": "Put your Vellum assistant inside your MacBook's notch: click to chat, hold Ctrl+Option to talk, and get a floating task bulb while it works, with live-streamed replies and per-sentence voice.",
|
|
127
133
|
"category": "interface",
|
|
128
134
|
"homepage": "https://github.com/AnitaKirkovska/dynamic-notch",
|
|
129
|
-
"license": "MIT"
|
|
135
|
+
"license": "MIT",
|
|
136
|
+
"icon": "💻"
|
|
130
137
|
},
|
|
131
138
|
{
|
|
132
139
|
"name": "family-briefing",
|
|
@@ -138,7 +145,8 @@
|
|
|
138
145
|
"description": "Daily morning briefing for parents: merges family calendars, triages school emails, and detects scheduling conflicts.",
|
|
139
146
|
"category": "productivity",
|
|
140
147
|
"homepage": "https://github.com/AnitaKirkovska/family-briefing",
|
|
141
|
-
"license": "MIT"
|
|
148
|
+
"license": "MIT",
|
|
149
|
+
"icon": "👨👩👧👦"
|
|
142
150
|
},
|
|
143
151
|
{
|
|
144
152
|
"name": "fitness-companion",
|
|
@@ -150,7 +158,8 @@
|
|
|
150
158
|
"description": "Fitness and nutrition companion. Logs meals, workouts, and weight; looks up nutrition data from OpenFoodFacts; calculates macro targets; and injects daily fitness context into every conversation.",
|
|
151
159
|
"category": "health",
|
|
152
160
|
"homepage": "https://github.com/vellum-ai/fitness-companion",
|
|
153
|
-
"license": "MIT"
|
|
161
|
+
"license": "MIT",
|
|
162
|
+
"icon": "💪"
|
|
154
163
|
},
|
|
155
164
|
{
|
|
156
165
|
"name": "git-workflow",
|
|
@@ -162,7 +171,8 @@
|
|
|
162
171
|
"description": "Git workflow tools and multi-perspective code review: branch management, push, stash, PR creation/review/merge/checkout, diffs, logs, changelog generation, and parallel subagent code review with confidence scoring.",
|
|
163
172
|
"category": "developer",
|
|
164
173
|
"homepage": "https://github.com/vellum-ai/git-workflow",
|
|
165
|
-
"license": "MIT"
|
|
174
|
+
"license": "MIT",
|
|
175
|
+
"icon": "🔀"
|
|
166
176
|
},
|
|
167
177
|
{
|
|
168
178
|
"name": "google-drive-search",
|
|
@@ -174,7 +184,8 @@
|
|
|
174
184
|
"description": "Local full-text search across your Google Drive. Indexes file metadata and document content into SQLite FTS5 for instant, private search.",
|
|
175
185
|
"category": "productivity",
|
|
176
186
|
"homepage": "https://github.com/ZeebBoyBlue/google-drive-search",
|
|
177
|
-
"license": "Apache-2.0"
|
|
187
|
+
"license": "Apache-2.0",
|
|
188
|
+
"icon": "🔍"
|
|
178
189
|
},
|
|
179
190
|
{
|
|
180
191
|
"name": "influencer-marketing",
|
|
@@ -186,7 +197,8 @@
|
|
|
186
197
|
"description": "Find and research influencers on Instagram, TikTok, and X/Twitter. Includes a strategy builder that walks from campaign brief to research criteria, a research skill that discovers real profiles via browser automation, scores candidates, and exports ranked shortlists, and a dashboard skill that turns the shortlist into an interactive outreach tracker.",
|
|
187
198
|
"category": "marketing",
|
|
188
199
|
"homepage": "https://github.com/ZeebBoyBlue/influencer",
|
|
189
|
-
"license": "MIT"
|
|
200
|
+
"license": "MIT",
|
|
201
|
+
"icon": "📣"
|
|
190
202
|
},
|
|
191
203
|
{
|
|
192
204
|
"name": "kitchen-companion",
|
|
@@ -198,7 +210,8 @@
|
|
|
198
210
|
"description": "Full kitchen companion. Scan your fridge with a photo to stock a pantry inventory and get 3 makeable-now recipes, get alerts before food expires, clip recipes from any URL into a clean format, and generate meal plans with grocery lists from what you actually have.",
|
|
199
211
|
"category": "lifestyle",
|
|
200
212
|
"homepage": "https://github.com/vellum-ai/kitchen-companion",
|
|
201
|
-
"license": "MIT"
|
|
213
|
+
"license": "MIT",
|
|
214
|
+
"icon": "🍳"
|
|
202
215
|
},
|
|
203
216
|
{
|
|
204
217
|
"name": "level-up",
|
|
@@ -210,7 +223,8 @@
|
|
|
210
223
|
"description": "Surfaces a first-class \"Level Up\" diff card whenever the assistant edits its own skills or plugins, so you can see how it improved itself after the fact.",
|
|
211
224
|
"category": "developer",
|
|
212
225
|
"homepage": "https://github.com/vellum-ai/level-up",
|
|
213
|
-
"license": "MIT"
|
|
226
|
+
"license": "MIT",
|
|
227
|
+
"icon": "🆙"
|
|
214
228
|
},
|
|
215
229
|
{
|
|
216
230
|
"name": "marketing-expert",
|
|
@@ -222,7 +236,8 @@
|
|
|
222
236
|
"description": "Acts as a full-stack marketing expert for any business (B2B or B2C): an on-demand persona plus playbook skills (positioning, demand planning, launches, content & copywriting, brand voice, email sequences, SEO & GEO, competitive teardown, board reporting) and deterministic funnel/positioning/GTM/competitive tools.",
|
|
223
237
|
"category": "marketing",
|
|
224
238
|
"homepage": "https://github.com/vellum-ai/marketing-expert",
|
|
225
|
-
"license": "MIT"
|
|
239
|
+
"license": "MIT",
|
|
240
|
+
"icon": "📈"
|
|
226
241
|
},
|
|
227
242
|
{
|
|
228
243
|
"name": "model-router",
|
|
@@ -234,7 +249,8 @@
|
|
|
234
249
|
"description": "Routes each user-facing message to one of your workspace's inference profiles, so model selection can vary per call instead of staying pinned to a single profile.",
|
|
235
250
|
"category": "productivity",
|
|
236
251
|
"homepage": "https://github.com/AnitaKirkovska/model-router",
|
|
237
|
-
"license": "MIT"
|
|
252
|
+
"license": "MIT",
|
|
253
|
+
"icon": "🚦"
|
|
238
254
|
},
|
|
239
255
|
{
|
|
240
256
|
"name": "motion-design",
|
|
@@ -246,7 +262,8 @@
|
|
|
246
262
|
"description": "Teaches the assistant to build polished UI animations with Motion (motion.dev) for React: animated product mockups, chat replays, typing effects, and staggered reveals, with a complete animated chat example included.",
|
|
247
263
|
"category": "creative",
|
|
248
264
|
"homepage": "https://github.com/vellum-ai/motion-design",
|
|
249
|
-
"license": "MIT"
|
|
265
|
+
"license": "MIT",
|
|
266
|
+
"icon": "🎬"
|
|
250
267
|
},
|
|
251
268
|
{
|
|
252
269
|
"name": "originkit",
|
|
@@ -258,7 +275,8 @@
|
|
|
258
275
|
"description": "Browse and import animated UI components from Originkit, a free library of 50 animated components. Browse by category or search term without an API key; fetch component source code (React, Next.js, Vite, Framer) with a free key from originkit.dev.",
|
|
259
276
|
"category": "productivity",
|
|
260
277
|
"homepage": "https://github.com/vellum-ai/originkit",
|
|
261
|
-
"license": "MIT"
|
|
278
|
+
"license": "MIT",
|
|
279
|
+
"icon": "🧱"
|
|
262
280
|
},
|
|
263
281
|
{
|
|
264
282
|
"name": "personal-finance",
|
|
@@ -270,7 +288,8 @@
|
|
|
270
288
|
"description": "Track expenses, income, recurring bills, and savings funds from your assistant. Multi-currency, receipt OCR, Plaid bank sync, and financial summaries. All data stored locally in SQLite.",
|
|
271
289
|
"category": "productivity",
|
|
272
290
|
"homepage": "https://github.com/AnitaKirkovska/personal-finance",
|
|
273
|
-
"license": "MIT"
|
|
291
|
+
"license": "MIT",
|
|
292
|
+
"icon": "💰"
|
|
274
293
|
},
|
|
275
294
|
{
|
|
276
295
|
"name": "plant-doctor",
|
|
@@ -282,7 +301,8 @@
|
|
|
282
301
|
"description": "Diagnose plants from photos. Identifies the species, assesses health with visible evidence, and returns a care plan. Named plants get a persistent checkup history so you can track whether they are improving.",
|
|
283
302
|
"category": "lifestyle",
|
|
284
303
|
"homepage": "https://github.com/vellum-ai/plant-doctor",
|
|
285
|
-
"license": "MIT"
|
|
304
|
+
"license": "MIT",
|
|
305
|
+
"icon": "🪴"
|
|
286
306
|
},
|
|
287
307
|
{
|
|
288
308
|
"name": "polyglot",
|
|
@@ -294,7 +314,8 @@
|
|
|
294
314
|
"description": "Ambient language acquisition through your assistant. Three graduated modes (practice, ambient, immersive) weave a target language into your real conversations, with SM-2 spaced repetition, error-pattern tracking, and CEFR level history.",
|
|
295
315
|
"category": "education",
|
|
296
316
|
"homepage": "https://github.com/vellum-ai/polyglot",
|
|
297
|
-
"license": "MIT"
|
|
317
|
+
"license": "MIT",
|
|
318
|
+
"icon": "🗣️"
|
|
298
319
|
},
|
|
299
320
|
{
|
|
300
321
|
"name": "proactive-slacks",
|
|
@@ -306,7 +327,8 @@
|
|
|
306
327
|
"description": "Let your assistant reach you on Slack on purpose. Route notifications by topic to the right channel at the right volume, with heartbeat-based change detection so it only pings when something actually changed.",
|
|
307
328
|
"category": "productivity",
|
|
308
329
|
"homepage": "https://github.com/AnitaKirkovska/proactive-slacks",
|
|
309
|
-
"license": "MIT"
|
|
330
|
+
"license": "MIT",
|
|
331
|
+
"icon": "💬"
|
|
310
332
|
},
|
|
311
333
|
{
|
|
312
334
|
"name": "reading-pal",
|
|
@@ -318,7 +340,8 @@
|
|
|
318
340
|
"description": "Your reading life in one plugin. Track your TBR pile, get guilt-tripped about your backlog, find your next read, and sync from Goodreads.",
|
|
319
341
|
"category": "hobby",
|
|
320
342
|
"homepage": "https://github.com/vellum-ai/reading-pal",
|
|
321
|
-
"license": "MIT"
|
|
343
|
+
"license": "MIT",
|
|
344
|
+
"icon": "📚"
|
|
322
345
|
},
|
|
323
346
|
{
|
|
324
347
|
"name": "simple-memory",
|
|
@@ -330,7 +353,8 @@
|
|
|
330
353
|
"description": "Reference memory plugin that exercises the full plugin API with low-cost variants of Vellum's memory defaults.",
|
|
331
354
|
"category": "memory",
|
|
332
355
|
"homepage": "https://github.com/vellum-ai/simple-memory",
|
|
333
|
-
"license": "MIT"
|
|
356
|
+
"license": "MIT",
|
|
357
|
+
"icon": "🧠"
|
|
334
358
|
},
|
|
335
359
|
{
|
|
336
360
|
"name": "smb-inbox-brief",
|
|
@@ -342,7 +366,8 @@
|
|
|
342
366
|
"description": "Find urgent replies, overdue invoices, quotes, and customer follow-ups before they slip. Scans email, logs business items, delivers urgency-ranked daily briefs.",
|
|
343
367
|
"category": "productivity",
|
|
344
368
|
"homepage": "https://github.com/AnitaKirkovska/smb-inbox-brief",
|
|
345
|
-
"license": "MIT"
|
|
369
|
+
"license": "MIT",
|
|
370
|
+
"icon": "📧"
|
|
346
371
|
},
|
|
347
372
|
{
|
|
348
373
|
"name": "tennis-companion",
|
|
@@ -354,7 +379,8 @@
|
|
|
354
379
|
"description": "Tennis journal, drill sessions, court finder, and progress tracking. Log matches with opponent tendencies, generate 30-minute practice sessions with progressions, find nearby courts via OpenStreetMap, and track win rate by surface and conditions.",
|
|
355
380
|
"category": "health",
|
|
356
381
|
"homepage": "https://github.com/vellum-ai/tennis-companion",
|
|
357
|
-
"license": "MIT"
|
|
382
|
+
"license": "MIT",
|
|
383
|
+
"icon": "🎾"
|
|
358
384
|
},
|
|
359
385
|
{
|
|
360
386
|
"name": "travel-planner",
|
|
@@ -366,7 +392,8 @@
|
|
|
366
392
|
"description": "A travel EA for your assistant. Remembers how you travel (carrier, miles, hotel budget, card benefits), keeps per-trip records, watches Gmail for booking changes, syncs your calendar, and emails an EA-style trip brief 48 hours before departure.",
|
|
367
393
|
"category": "lifestyle",
|
|
368
394
|
"homepage": "https://github.com/AnitaKirkovska/travel-planner",
|
|
369
|
-
"license": "MIT"
|
|
395
|
+
"license": "MIT",
|
|
396
|
+
"icon": "✈️"
|
|
370
397
|
},
|
|
371
398
|
{
|
|
372
399
|
"name": "vellum-client-qa",
|
|
@@ -378,7 +405,8 @@
|
|
|
378
405
|
"description": "Cross-platform QA rig for vellum-assistant clients: mock the platform backend, boot the real chat UI, and verify feature branches in headless Chromium (web), an iOS simulator, or Electron with screenshot and video proof.",
|
|
379
406
|
"category": "developer",
|
|
380
407
|
"homepage": "https://github.com/vellum-ai/vellum-client-qa",
|
|
381
|
-
"license": "MIT"
|
|
408
|
+
"license": "MIT",
|
|
409
|
+
"icon": "🧪"
|
|
382
410
|
},
|
|
383
411
|
{
|
|
384
412
|
"name": "writing-coach",
|
|
@@ -390,7 +418,8 @@
|
|
|
390
418
|
"description": "A writing coach in your pocket. Daily prompts that escalate with your streak, savage-but-constructive draft roasts, query letter critiques, and a word count enforcer that guilt-trips you when you skip.",
|
|
391
419
|
"category": "hobby",
|
|
392
420
|
"homepage": "https://github.com/vellum-ai/writing-coach",
|
|
393
|
-
"license": "MIT"
|
|
421
|
+
"license": "MIT",
|
|
422
|
+
"icon": "✍️"
|
|
394
423
|
}
|
|
395
424
|
]
|
|
396
425
|
}
|
|
@@ -11,9 +11,9 @@ describe("LiveVoiceVadConfigSchema", () => {
|
|
|
11
11
|
const parsed = LiveVoiceVadConfigSchema.parse({});
|
|
12
12
|
expect(parsed).toEqual({
|
|
13
13
|
speechEnergyThreshold: 800,
|
|
14
|
-
silenceThresholdMs:
|
|
14
|
+
silenceThresholdMs: 1200,
|
|
15
15
|
maxTurnDurationMs: 30_000,
|
|
16
|
-
bargeInMinSpeechMs:
|
|
16
|
+
bargeInMinSpeechMs: 250,
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -64,22 +64,40 @@ describe("LiveVoiceConfigSchema", () => {
|
|
|
64
64
|
mode: "open-mic",
|
|
65
65
|
vad: {
|
|
66
66
|
speechEnergyThreshold: 800,
|
|
67
|
-
silenceThresholdMs:
|
|
67
|
+
silenceThresholdMs: 1200,
|
|
68
68
|
maxTurnDurationMs: 30_000,
|
|
69
|
-
bargeInMinSpeechMs:
|
|
69
|
+
bargeInMinSpeechMs: 250,
|
|
70
70
|
},
|
|
71
71
|
maxSessionDurationSeconds: 1800,
|
|
72
|
+
// Off by default: voice turns carry only their transcript, no audio
|
|
73
|
+
// artifacts on the conversation messages (JARVIS-1283).
|
|
74
|
+
archiveAudio: false,
|
|
72
75
|
});
|
|
73
76
|
});
|
|
74
77
|
|
|
78
|
+
test("archiveAudio can be enabled", () => {
|
|
79
|
+
expect(
|
|
80
|
+
LiveVoiceConfigSchema.parse({ archiveAudio: true }).archiveAudio,
|
|
81
|
+
).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("rejects a non-boolean archiveAudio", () => {
|
|
85
|
+
const result = LiveVoiceConfigSchema.safeParse({ archiveAudio: "yes" });
|
|
86
|
+
expect(result.success).toBe(false);
|
|
87
|
+
if (!result.success) {
|
|
88
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
89
|
+
expect(msgs.some((m) => m.includes("liveVoice.archiveAudio"))).toBe(true);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
75
93
|
test("accepts overrides", () => {
|
|
76
94
|
const parsed = LiveVoiceConfigSchema.parse({
|
|
77
95
|
mode: "ptt",
|
|
78
|
-
vad: { silenceThresholdMs:
|
|
96
|
+
vad: { silenceThresholdMs: 900 },
|
|
79
97
|
maxSessionDurationSeconds: 600,
|
|
80
98
|
});
|
|
81
99
|
expect(parsed.mode).toBe("ptt");
|
|
82
|
-
expect(parsed.vad.silenceThresholdMs).toBe(
|
|
100
|
+
expect(parsed.vad.silenceThresholdMs).toBe(900);
|
|
83
101
|
// Unspecified vad fields still get defaults
|
|
84
102
|
expect(parsed.vad.speechEnergyThreshold).toBe(800);
|
|
85
103
|
expect(parsed.vad.maxTurnDurationMs).toBe(30_000);
|
|
@@ -20,9 +20,9 @@ export const LiveVoiceVadConfigSchema = z
|
|
|
20
20
|
.number({ error: "liveVoice.vad.silenceThresholdMs must be a number" })
|
|
21
21
|
.int("liveVoice.vad.silenceThresholdMs must be an integer")
|
|
22
22
|
.positive("liveVoice.vad.silenceThresholdMs must be a positive integer")
|
|
23
|
-
.default(
|
|
23
|
+
.default(1200)
|
|
24
24
|
.describe(
|
|
25
|
-
"Trailing silence duration (ms) after speech that ends the user's turn",
|
|
25
|
+
"Trailing silence duration (ms) after speech that ends the user's turn — the default 'pause before reply'. Clients may override it per-session via the start frame.",
|
|
26
26
|
),
|
|
27
27
|
maxTurnDurationMs: z
|
|
28
28
|
.number({ error: "liveVoice.vad.maxTurnDurationMs must be a number" })
|
|
@@ -38,9 +38,9 @@ export const LiveVoiceVadConfigSchema = z
|
|
|
38
38
|
.nonnegative(
|
|
39
39
|
"liveVoice.vad.bargeInMinSpeechMs must be a nonnegative integer",
|
|
40
40
|
)
|
|
41
|
-
.default(
|
|
41
|
+
.default(250)
|
|
42
42
|
.describe(
|
|
43
|
-
"Sustained speech (ms) required before speech during assistant playback interrupts it
|
|
43
|
+
"Sustained speech (ms) required before speech during assistant playback interrupts it — the default 'interrupt sensitivity' (higher = harder to interrupt). 0 disables the guard. Clients may override it per-session via the start frame. Raised from 60 so brief TTS bleed through imperfect echo cancellation no longer self-interrupts the assistant.",
|
|
44
44
|
),
|
|
45
45
|
})
|
|
46
46
|
.describe(
|
|
@@ -68,6 +68,12 @@ export const LiveVoiceConfigSchema = z
|
|
|
68
68
|
)
|
|
69
69
|
.default(1800)
|
|
70
70
|
.describe("Maximum duration of a single live voice session in seconds"),
|
|
71
|
+
archiveAudio: z
|
|
72
|
+
.boolean({ error: "liveVoice.archiveAudio must be a boolean" })
|
|
73
|
+
.default(false)
|
|
74
|
+
.describe(
|
|
75
|
+
"Persist the recorded user + assistant audio of each voice turn as attachments on the conversation messages. Off by default: voice turns carry only their transcribed text, so no audio-file artifacts land in the conversation history. Enable for playback/debugging.",
|
|
76
|
+
),
|
|
71
77
|
})
|
|
72
78
|
.describe(
|
|
73
79
|
"Live voice (in-app duplex audio) configuration — mic mode, VAD tuning, and session limits",
|
|
@@ -57,6 +57,14 @@ export const MemoryRetrospectiveConfigSchema = z
|
|
|
57
57
|
.describe(
|
|
58
58
|
"When true, fork-based retrospectives run under the source conversation's inference profile (which forkConversation copies onto the fork) instead of the call site's default. Provider prompt caches are byte-exact prefix matches scoped per model, and a thinking enable/disable mismatch invalidates the messages cache tier — so reusing the source's cached prefix requires the retrospective to resolve the SAME model/thinking/effort as the conversation's own turns. Falls back to the call site's default when the conversation has no profile or the referenced profile no longer exists.",
|
|
59
59
|
),
|
|
60
|
+
|
|
61
|
+
promptPath: z
|
|
62
|
+
.string({ error: "memory.retrospective.promptPath must be a string" })
|
|
63
|
+
.nullable()
|
|
64
|
+
.default(null)
|
|
65
|
+
.describe(
|
|
66
|
+
"Optional path to a file whose contents replace the bundled retrospective fork-instruction prompt. Absolute paths are used as-is, a leading `~/` is expanded to the home directory, otherwise the path is resolved under the workspace root. The loaded contents may include `{{AVAILABLE_TOOLS_LINE}}`, `{{WINDOW_ANCHOR}}`, `{{ALREADY_REMEMBERED}}`, and `{{SKILL_AUTHORING_SECTION}}`, which are substituted at runtime. If the file is missing, unreadable, or empty, the bundled prompt is used and a warning is logged.",
|
|
67
|
+
),
|
|
60
68
|
})
|
|
61
69
|
.describe(
|
|
62
70
|
"Controls the memory-retrospective background pass. Model selection lives under llm.callSites.memoryRetrospective.",
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { describe, expect, mock, test } from "bun:test";
|
|
1
|
+
import { describe, expect, mock, spyOn, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
VoiceTurnCallbacks,
|
|
5
5
|
VoiceTurnOptions,
|
|
6
6
|
} from "../../calls/voice-session-bridge.js";
|
|
7
|
+
import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
|
|
7
8
|
import type {
|
|
8
9
|
StreamingTranscriber,
|
|
9
10
|
SttStreamServerEvent,
|
|
10
11
|
} from "../../stt/types.js";
|
|
11
12
|
import type { LiveVoiceAudioArchiveResult } from "../live-voice-archive.js";
|
|
13
|
+
import * as liveVoiceArchive from "../live-voice-archive.js";
|
|
12
14
|
import {
|
|
13
15
|
createLiveVoiceSession,
|
|
14
16
|
type LiveVoiceSessionArchiveAudioInput,
|
|
@@ -217,7 +219,9 @@ async function waitFor(
|
|
|
217
219
|
message = "Timed out waiting for live voice integration condition",
|
|
218
220
|
): Promise<void> {
|
|
219
221
|
for (let attempt = 0; attempt < 80; attempt += 1) {
|
|
220
|
-
if (predicate())
|
|
222
|
+
if (predicate()) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
221
225
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
222
226
|
}
|
|
223
227
|
throw new Error(message);
|
|
@@ -672,3 +676,113 @@ describe("LiveVoiceSession integration smoke harness", () => {
|
|
|
672
676
|
]);
|
|
673
677
|
});
|
|
674
678
|
});
|
|
679
|
+
|
|
680
|
+
// ---------------------------------------------------------------------------
|
|
681
|
+
// Audio archiving is config-gated and OFF by default (JARVIS-1283): a voice
|
|
682
|
+
// turn persists only its transcribed text, so no audio-file artifact lands on
|
|
683
|
+
// the conversation messages unless `liveVoice.archiveAudio` is enabled. These
|
|
684
|
+
// exercise the production factory's config resolution (no injected archiver).
|
|
685
|
+
// ---------------------------------------------------------------------------
|
|
686
|
+
|
|
687
|
+
describe("live-voice audio archiving default (JARVIS-1283)", () => {
|
|
688
|
+
function makeSingleTurnLegs() {
|
|
689
|
+
const startVoiceTurn = mock(async (options: VoiceTurnOptions) => {
|
|
690
|
+
options.callbacks?.persisted_user_message_id?.("user-message-123");
|
|
691
|
+
options.callbacks?.assistant_text_delta?.(
|
|
692
|
+
makeTextDelta("Hello from the assistant."),
|
|
693
|
+
);
|
|
694
|
+
options.callbacks?.message_complete?.(makeMessageComplete());
|
|
695
|
+
return { turnId: "bridge-turn-1", abort: mock() };
|
|
696
|
+
});
|
|
697
|
+
const streamTtsAudio = mock(async (options: LiveVoiceTtsOptions) => {
|
|
698
|
+
options.onAudioChunk(makeTtsChunk(`audio:${options.text}`));
|
|
699
|
+
return makeTtsResult(options.text);
|
|
700
|
+
});
|
|
701
|
+
return { startVoiceTurn, streamTtsAudio };
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async function driveOneTurn(
|
|
705
|
+
session: ReturnType<typeof createLiveVoiceSession>,
|
|
706
|
+
frames: LiveVoiceServerFrame[],
|
|
707
|
+
) {
|
|
708
|
+
await session.start();
|
|
709
|
+
await session.handleBinaryAudio(new Uint8Array([1, 2, 3, 4]));
|
|
710
|
+
await session.handleClientFrame({ type: "ptt_release" });
|
|
711
|
+
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
test("with no injected archiver and default config, nothing is archived", async () => {
|
|
715
|
+
const { startVoiceTurn, streamTtsAudio } = makeSingleTurnLegs();
|
|
716
|
+
const { context, frames } = createContext();
|
|
717
|
+
// No `archiveAudio` option → the factory consults config, which defaults
|
|
718
|
+
// off in the test workspace.
|
|
719
|
+
const session = createLiveVoiceSession(context, {
|
|
720
|
+
resolveCredentialReadiness: null,
|
|
721
|
+
resolveTranscriber: mock(async () => new FakeStreamingTranscriber()),
|
|
722
|
+
startVoiceTurn,
|
|
723
|
+
streamTtsAudio,
|
|
724
|
+
metricsClock: createClock(),
|
|
725
|
+
createTurnId: () => "live-turn-1",
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
await driveOneTurn(session, frames);
|
|
729
|
+
|
|
730
|
+
// The turn completed, but no `archived` frame was emitted — no audio
|
|
731
|
+
// attachment was written to either message.
|
|
732
|
+
expect(startVoiceTurn).toHaveBeenCalledTimes(1);
|
|
733
|
+
expect(frames.some((frame) => frame.type === "archived")).toBe(false);
|
|
734
|
+
expect(frameTypes(frames)).not.toContain("archived");
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test("liveVoice.archiveAudio=true wires the default archiver through the factory", async () => {
|
|
738
|
+
// The real linkers write to the DB; these are role-less inputs, so stub
|
|
739
|
+
// them (synchronously — the linkers are sync) with a valid archived result
|
|
740
|
+
// per role. The point under test is the config→default-archiver wiring, not
|
|
741
|
+
// the DB write, which the injected-archiver tests above already cover.
|
|
742
|
+
const audioInput = {
|
|
743
|
+
sessionId: "session-123",
|
|
744
|
+
turnId: "live-turn-1",
|
|
745
|
+
mimeType: "audio/pcm",
|
|
746
|
+
audio: {
|
|
747
|
+
type: "base64" as const,
|
|
748
|
+
dataBase64: Buffer.from([1, 2, 3, 4]).toString("base64"),
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
const userSpy = spyOn(
|
|
752
|
+
liveVoiceArchive,
|
|
753
|
+
"linkLiveVoiceUserUtteranceAudioToMessage",
|
|
754
|
+
).mockReturnValue(makeArchiveResult({ ...audioInput, role: "user" }));
|
|
755
|
+
const assistantSpy = spyOn(
|
|
756
|
+
liveVoiceArchive,
|
|
757
|
+
"linkLiveVoiceAssistantResponseAudioToMessage",
|
|
758
|
+
).mockReturnValue(makeArchiveResult({ ...audioInput, role: "assistant" }));
|
|
759
|
+
const originalRaw = loadRawConfig();
|
|
760
|
+
saveRawConfig({ ...originalRaw, liveVoice: { archiveAudio: true } });
|
|
761
|
+
|
|
762
|
+
try {
|
|
763
|
+
const { startVoiceTurn, streamTtsAudio } = makeSingleTurnLegs();
|
|
764
|
+
const { context, frames } = createContext();
|
|
765
|
+
// Still no injected `archiveAudio` — the config default must supply it.
|
|
766
|
+
const session = createLiveVoiceSession(context, {
|
|
767
|
+
resolveCredentialReadiness: null,
|
|
768
|
+
resolveTranscriber: mock(async () => new FakeStreamingTranscriber()),
|
|
769
|
+
startVoiceTurn,
|
|
770
|
+
streamTtsAudio,
|
|
771
|
+
metricsClock: createClock(),
|
|
772
|
+
createTurnId: () => "live-turn-1",
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
await driveOneTurn(session, frames);
|
|
776
|
+
|
|
777
|
+
expect(userSpy).toHaveBeenCalledTimes(1);
|
|
778
|
+
expect(assistantSpy).toHaveBeenCalledTimes(1);
|
|
779
|
+
expect(frames.filter((frame) => frame.type === "archived")).toHaveLength(
|
|
780
|
+
2,
|
|
781
|
+
);
|
|
782
|
+
} finally {
|
|
783
|
+
saveRawConfig(originalRaw);
|
|
784
|
+
userSpy.mockRestore();
|
|
785
|
+
assistantSpy.mockRestore();
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
});
|