@vellumai/assistant 0.4.23 → 0.4.25
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/bun.lock +3 -0
- package/package.json +2 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -15
- package/src/__tests__/assistant-events-sse-hardening.test.ts +9 -3
- package/src/__tests__/config-schema.test.ts +38 -178
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +4 -1
- package/src/__tests__/credential-security-invariants.test.ts +0 -2
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +2 -2
- package/src/__tests__/ipc-snapshot.test.ts +0 -9
- package/src/__tests__/onboarding-template-contract.test.ts +10 -20
- package/src/__tests__/relay-server.test.ts +3 -3
- package/src/__tests__/runtime-events-sse-parity.test.ts +10 -0
- package/src/__tests__/runtime-events-sse.test.ts +7 -0
- package/src/__tests__/session-runtime-assembly.test.ts +34 -8
- package/src/__tests__/system-prompt.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +12 -8
- package/src/__tests__/twilio-routes-twiml.test.ts +2 -2
- package/src/__tests__/twilio-routes.test.ts +2 -3
- package/src/__tests__/voice-quality.test.ts +21 -132
- package/src/calls/relay-server.ts +11 -5
- package/src/calls/twilio-routes.ts +4 -38
- package/src/calls/voice-quality.ts +7 -63
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +7 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -5
- package/src/config/bundled-skills/phone-calls/SKILL.md +143 -82
- package/src/config/bundled-skills/sms-setup/SKILL.md +0 -20
- package/src/config/bundled-skills/twilio-setup/SKILL.md +9 -17
- package/src/config/bundled-skills/voice-setup/SKILL.md +36 -1
- package/src/config/bundled-skills/voice-setup/icon.svg +20 -0
- package/src/config/calls-schema.ts +3 -53
- package/src/config/elevenlabs-schema.ts +33 -0
- package/src/config/schema.ts +183 -137
- package/src/config/types.ts +0 -1
- package/src/daemon/handlers/browser.ts +1 -6
- package/src/daemon/ipc-contract/browser.ts +5 -14
- package/src/daemon/ipc-contract-inventory.json +0 -2
- package/src/daemon/session-agent-loop-handlers.ts +3 -0
- package/src/daemon/session-runtime-assembly.ts +9 -7
- package/src/mcp/client.ts +2 -1
- package/src/memory/conversation-crud.ts +339 -166
- package/src/runtime/routes/events-routes.ts +7 -0
- package/src/runtime/routes/inbound-message-handler.ts +3 -4
- package/src/schedule/scheduler.ts +159 -45
- package/src/security/secure-keys.ts +3 -3
- package/src/tools/browser/browser-manager.ts +72 -228
- package/src/tools/browser/browser-screencast.ts +0 -5
- package/src/tools/network/script-proxy/certs.ts +7 -237
- package/src/tools/network/script-proxy/connect-tunnel.ts +1 -82
- package/src/tools/network/script-proxy/http-forwarder.ts +2 -151
- package/src/tools/network/script-proxy/logging.ts +12 -196
- package/src/tools/network/script-proxy/mitm-handler.ts +2 -270
- package/src/tools/network/script-proxy/policy.ts +4 -152
- package/src/tools/network/script-proxy/router.ts +2 -60
- package/src/tools/network/script-proxy/server.ts +5 -137
- package/src/tools/network/script-proxy/types.ts +19 -125
- package/src/tools/system/voice-config.ts +23 -1
- package/src/util/logger.ts +4 -1
- package/src/__tests__/elevenlabs-config.test.ts +0 -95
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +0 -407
- package/src/calls/elevenlabs-config.ts +0 -32
|
@@ -116,6 +116,13 @@ export function handleSubscribeAssistantEvents(
|
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// Immediately enqueue a heartbeat comment so the HTTP status line and
|
|
120
|
+
// headers are flushed to the client without waiting for a real event.
|
|
121
|
+
// Without this, Bun may buffer the headers until the first data chunk
|
|
122
|
+
// arrives, causing clients (e.g. Python `requests`) to hang until the
|
|
123
|
+
// periodic heartbeat fires or an event is published.
|
|
124
|
+
controller.enqueue(encoder.encode(formatSseHeartbeat()));
|
|
125
|
+
|
|
119
126
|
// Send a keep-alive comment on each interval to prevent proxies and
|
|
120
127
|
// load-balancers from treating idle connections as timed out.
|
|
121
128
|
heartbeatTimer = setInterval(() => {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { ChannelId, InterfaceId } from '../../channels/types.js';
|
|
10
10
|
import { CHANNEL_IDS, INTERFACE_IDS, isChannelId, parseInterfaceId } from '../../channels/types.js';
|
|
11
11
|
import { getGatewayInternalBaseUrl } from '../../config/env.js';
|
|
12
|
+
import { resolveUserReference } from '../../config/user-reference.js';
|
|
12
13
|
import { RESEND_COOLDOWN_MS } from '../../daemon/handlers/config-channels.js';
|
|
13
14
|
import * as attachmentsStore from '../../memory/attachments-store.js';
|
|
14
15
|
import {
|
|
@@ -1577,10 +1578,8 @@ function startTrustedContactApprovalNotifier(params: {
|
|
|
1577
1578
|
const guardianName = resolveGuardianDisplayName(
|
|
1578
1579
|
assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
1579
1580
|
sourceChannel,
|
|
1580
|
-
);
|
|
1581
|
-
const waitingText = guardianName
|
|
1582
|
-
? `Waiting for ${guardianName}'s approval...`
|
|
1583
|
-
: 'Waiting for your guardian\'s approval...';
|
|
1581
|
+
) ?? resolveUserReference();
|
|
1582
|
+
const waitingText = `Waiting for ${guardianName}'s approval...`;
|
|
1584
1583
|
try {
|
|
1585
1584
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1586
1585
|
chatId: externalChatId,
|
|
@@ -1,18 +1,31 @@
|
|
|
1
|
-
import { createConversation } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import { createConversation } from "../memory/conversation-store.js";
|
|
2
|
+
import {
|
|
3
|
+
GENERATING_TITLE,
|
|
4
|
+
queueGenerateConversationTitle,
|
|
5
|
+
} from "../memory/conversation-title-service.js";
|
|
6
|
+
import { invalidateAssistantInferredItemsForConversation } from "../memory/task-memory-cleanup.js";
|
|
7
|
+
import { runSequencesOnce } from "../sequence/engine.js";
|
|
8
|
+
import {
|
|
9
|
+
claimDueReminders,
|
|
10
|
+
completeReminder,
|
|
11
|
+
failReminder,
|
|
12
|
+
type RoutingIntent,
|
|
13
|
+
setReminderConversationId,
|
|
14
|
+
} from "../tools/reminder/reminder-store.js";
|
|
15
|
+
import { getLogger } from "../util/logger.js";
|
|
16
|
+
import {
|
|
17
|
+
runWatchersOnce,
|
|
18
|
+
type WatcherEscalator,
|
|
19
|
+
type WatcherNotifier,
|
|
20
|
+
} from "../watcher/engine.js";
|
|
21
|
+
import { hasSetConstructs } from "./recurrence-engine.js";
|
|
9
22
|
import {
|
|
10
23
|
claimDueSchedules,
|
|
11
24
|
completeScheduleRun,
|
|
12
25
|
createScheduleRun,
|
|
13
|
-
} from
|
|
26
|
+
} from "./schedule-store.js";
|
|
14
27
|
|
|
15
|
-
const log = getLogger(
|
|
28
|
+
const log = getLogger("scheduler");
|
|
16
29
|
|
|
17
30
|
export type ScheduleMessageProcessor = (
|
|
18
31
|
conversationId: string,
|
|
@@ -50,21 +63,35 @@ export function startScheduler(
|
|
|
50
63
|
if (stopped || tickRunning) return;
|
|
51
64
|
tickRunning = true;
|
|
52
65
|
try {
|
|
53
|
-
await runScheduleOnce(
|
|
66
|
+
await runScheduleOnce(
|
|
67
|
+
processMessage,
|
|
68
|
+
notifyReminder,
|
|
69
|
+
notifySchedule,
|
|
70
|
+
watcherNotifier,
|
|
71
|
+
watcherEscalator,
|
|
72
|
+
);
|
|
54
73
|
} catch (err) {
|
|
55
|
-
log.error({ err },
|
|
74
|
+
log.error({ err }, "Schedule tick failed");
|
|
56
75
|
} finally {
|
|
57
76
|
tickRunning = false;
|
|
58
77
|
}
|
|
59
78
|
};
|
|
60
79
|
|
|
61
|
-
const timer = setInterval(() => {
|
|
80
|
+
const timer = setInterval(() => {
|
|
81
|
+
void tick();
|
|
82
|
+
}, TICK_INTERVAL_MS);
|
|
62
83
|
timer.unref();
|
|
63
84
|
void tick();
|
|
64
85
|
|
|
65
86
|
return {
|
|
66
87
|
async runOnce(): Promise<number> {
|
|
67
|
-
return runScheduleOnce(
|
|
88
|
+
return runScheduleOnce(
|
|
89
|
+
processMessage,
|
|
90
|
+
notifyReminder,
|
|
91
|
+
notifySchedule,
|
|
92
|
+
watcherNotifier,
|
|
93
|
+
watcherEscalator,
|
|
94
|
+
);
|
|
68
95
|
},
|
|
69
96
|
stop(): void {
|
|
70
97
|
stopped = true;
|
|
@@ -90,62 +117,123 @@ async function runScheduleOnce(
|
|
|
90
117
|
const taskMatch = job.message.match(/^run_task:(\S+)$/);
|
|
91
118
|
if (taskMatch) {
|
|
92
119
|
const taskId = taskMatch[1];
|
|
93
|
-
const isRruleSet =
|
|
120
|
+
const isRruleSet =
|
|
121
|
+
job.syntax === "rrule" && hasSetConstructs(job.expression);
|
|
94
122
|
try {
|
|
95
|
-
log.info(
|
|
96
|
-
|
|
123
|
+
log.info(
|
|
124
|
+
{
|
|
125
|
+
jobId: job.id,
|
|
126
|
+
name: job.name,
|
|
127
|
+
taskId,
|
|
128
|
+
syntax: job.syntax,
|
|
129
|
+
expression: job.expression,
|
|
130
|
+
isRruleSet,
|
|
131
|
+
},
|
|
132
|
+
"Executing scheduled task",
|
|
133
|
+
);
|
|
134
|
+
const { runTask } = await import("../tasks/task-runner.js");
|
|
97
135
|
const result = await runTask(
|
|
98
|
-
{ taskId, workingDir: process.cwd(), source:
|
|
99
|
-
processMessage as (
|
|
136
|
+
{ taskId, workingDir: process.cwd(), source: "schedule" },
|
|
137
|
+
processMessage as (
|
|
138
|
+
conversationId: string,
|
|
139
|
+
message: string,
|
|
140
|
+
taskRunId: string,
|
|
141
|
+
) => Promise<void>,
|
|
100
142
|
);
|
|
101
143
|
|
|
102
144
|
// Track the schedule run using the task's conversation
|
|
103
145
|
const runId = createScheduleRun(job.id, result.conversationId);
|
|
104
|
-
if (result.status ===
|
|
105
|
-
completeScheduleRun(runId, {
|
|
146
|
+
if (result.status === "failed") {
|
|
147
|
+
completeScheduleRun(runId, {
|
|
148
|
+
status: "error",
|
|
149
|
+
error: result.error ?? "Task run failed",
|
|
150
|
+
});
|
|
106
151
|
} else {
|
|
107
|
-
completeScheduleRun(runId, { status:
|
|
152
|
+
completeScheduleRun(runId, { status: "ok" });
|
|
108
153
|
notifySchedule({ id: job.id, name: job.name });
|
|
109
154
|
}
|
|
110
155
|
processed += 1;
|
|
111
156
|
} catch (err) {
|
|
112
157
|
const message = err instanceof Error ? err.message : String(err);
|
|
113
|
-
log.warn(
|
|
158
|
+
log.warn(
|
|
159
|
+
{
|
|
160
|
+
err,
|
|
161
|
+
jobId: job.id,
|
|
162
|
+
name: job.name,
|
|
163
|
+
taskId,
|
|
164
|
+
syntax: job.syntax,
|
|
165
|
+
expression: job.expression,
|
|
166
|
+
isRruleSet,
|
|
167
|
+
},
|
|
168
|
+
"Scheduled task execution failed",
|
|
169
|
+
);
|
|
114
170
|
// Create a fallback conversation for the schedule run record
|
|
115
|
-
const fallbackConversation = createConversation({
|
|
171
|
+
const fallbackConversation = createConversation({
|
|
172
|
+
title: GENERATING_TITLE,
|
|
173
|
+
source: "schedule",
|
|
174
|
+
scheduleJobId: job.id,
|
|
175
|
+
});
|
|
116
176
|
queueGenerateConversationTitle({
|
|
117
177
|
conversationId: fallbackConversation.id,
|
|
118
|
-
context: { origin:
|
|
178
|
+
context: { origin: "schedule", systemHint: `Schedule: ${job.name}` },
|
|
119
179
|
});
|
|
120
180
|
const runId = createScheduleRun(job.id, fallbackConversation.id);
|
|
121
|
-
completeScheduleRun(runId, { status:
|
|
181
|
+
completeScheduleRun(runId, { status: "error", error: message });
|
|
122
182
|
}
|
|
123
183
|
continue;
|
|
124
184
|
}
|
|
125
185
|
|
|
126
|
-
const conversation = createConversation({
|
|
186
|
+
const conversation = createConversation({
|
|
187
|
+
title: GENERATING_TITLE,
|
|
188
|
+
source: "schedule",
|
|
189
|
+
scheduleJobId: job.id,
|
|
190
|
+
});
|
|
127
191
|
queueGenerateConversationTitle({
|
|
128
192
|
conversationId: conversation.id,
|
|
129
|
-
context: { origin:
|
|
193
|
+
context: { origin: "schedule", systemHint: `Schedule: ${job.name}` },
|
|
130
194
|
});
|
|
131
195
|
const runId = createScheduleRun(job.id, conversation.id);
|
|
132
|
-
const isRruleSetMsg =
|
|
196
|
+
const isRruleSetMsg =
|
|
197
|
+
job.syntax === "rrule" && hasSetConstructs(job.expression);
|
|
133
198
|
|
|
134
199
|
try {
|
|
135
|
-
log.info(
|
|
200
|
+
log.info(
|
|
201
|
+
{
|
|
202
|
+
jobId: job.id,
|
|
203
|
+
name: job.name,
|
|
204
|
+
syntax: job.syntax,
|
|
205
|
+
expression: job.expression,
|
|
206
|
+
isRruleSet: isRruleSetMsg,
|
|
207
|
+
conversationId: conversation.id,
|
|
208
|
+
},
|
|
209
|
+
"Executing schedule",
|
|
210
|
+
);
|
|
136
211
|
await processMessage(conversation.id, job.message);
|
|
137
|
-
completeScheduleRun(runId, { status:
|
|
212
|
+
completeScheduleRun(runId, { status: "ok" });
|
|
138
213
|
notifySchedule({ id: job.id, name: job.name });
|
|
139
214
|
processed += 1;
|
|
140
215
|
} catch (err) {
|
|
141
216
|
const message = err instanceof Error ? err.message : String(err);
|
|
142
|
-
log.warn(
|
|
143
|
-
|
|
217
|
+
log.warn(
|
|
218
|
+
{
|
|
219
|
+
err,
|
|
220
|
+
jobId: job.id,
|
|
221
|
+
name: job.name,
|
|
222
|
+
syntax: job.syntax,
|
|
223
|
+
expression: job.expression,
|
|
224
|
+
isRruleSet: isRruleSetMsg,
|
|
225
|
+
},
|
|
226
|
+
"Schedule execution failed",
|
|
227
|
+
);
|
|
228
|
+
completeScheduleRun(runId, { status: "error", error: message });
|
|
144
229
|
|
|
145
230
|
try {
|
|
146
231
|
invalidateAssistantInferredItemsForConversation(conversation.id);
|
|
147
232
|
} catch (cleanupErr) {
|
|
148
|
-
log.warn(
|
|
233
|
+
log.warn(
|
|
234
|
+
{ err: cleanupErr, conversationId: conversation.id },
|
|
235
|
+
"Failed to invalidate assistant-inferred memory items",
|
|
236
|
+
);
|
|
149
237
|
}
|
|
150
238
|
}
|
|
151
239
|
}
|
|
@@ -153,24 +241,43 @@ async function runScheduleOnce(
|
|
|
153
241
|
// ── One-shot reminders ──────────────────────────────────────────────
|
|
154
242
|
const dueReminders = claimDueReminders(now);
|
|
155
243
|
for (const reminder of dueReminders) {
|
|
156
|
-
if (reminder.mode ===
|
|
157
|
-
const conversation = createConversation({
|
|
244
|
+
if (reminder.mode === "execute") {
|
|
245
|
+
const conversation = createConversation({
|
|
246
|
+
title: GENERATING_TITLE,
|
|
247
|
+
source: "reminder",
|
|
248
|
+
});
|
|
158
249
|
queueGenerateConversationTitle({
|
|
159
250
|
conversationId: conversation.id,
|
|
160
|
-
context: {
|
|
251
|
+
context: {
|
|
252
|
+
origin: "reminder",
|
|
253
|
+
systemHint: `Reminder: ${reminder.label}`,
|
|
254
|
+
},
|
|
161
255
|
});
|
|
162
256
|
setReminderConversationId(reminder.id, conversation.id);
|
|
163
257
|
try {
|
|
164
|
-
log.info(
|
|
258
|
+
log.info(
|
|
259
|
+
{
|
|
260
|
+
reminderId: reminder.id,
|
|
261
|
+
label: reminder.label,
|
|
262
|
+
conversationId: conversation.id,
|
|
263
|
+
},
|
|
264
|
+
"Executing reminder",
|
|
265
|
+
);
|
|
165
266
|
await processMessage(conversation.id, reminder.message);
|
|
166
267
|
completeReminder(reminder.id);
|
|
167
268
|
} catch (err) {
|
|
168
|
-
log.warn(
|
|
269
|
+
log.warn(
|
|
270
|
+
{ err, reminderId: reminder.id },
|
|
271
|
+
"Reminder execution failed, reverting to pending",
|
|
272
|
+
);
|
|
169
273
|
failReminder(reminder.id);
|
|
170
274
|
}
|
|
171
275
|
} else {
|
|
172
276
|
try {
|
|
173
|
-
log.info(
|
|
277
|
+
log.info(
|
|
278
|
+
{ reminderId: reminder.id, label: reminder.label },
|
|
279
|
+
"Firing reminder notification",
|
|
280
|
+
);
|
|
174
281
|
notifyReminder({
|
|
175
282
|
id: reminder.id,
|
|
176
283
|
label: reminder.label,
|
|
@@ -180,7 +287,10 @@ async function runScheduleOnce(
|
|
|
180
287
|
});
|
|
181
288
|
completeReminder(reminder.id);
|
|
182
289
|
} catch (err) {
|
|
183
|
-
log.warn(
|
|
290
|
+
log.warn(
|
|
291
|
+
{ err, reminderId: reminder.id },
|
|
292
|
+
"Reminder notification failed, reverting to pending",
|
|
293
|
+
);
|
|
184
294
|
failReminder(reminder.id);
|
|
185
295
|
}
|
|
186
296
|
}
|
|
@@ -190,10 +300,14 @@ async function runScheduleOnce(
|
|
|
190
300
|
// ── Watchers (event-driven polling) ────────────────────────────────
|
|
191
301
|
if (watcherNotifier && watcherEscalator) {
|
|
192
302
|
try {
|
|
193
|
-
const watcherProcessed = await runWatchersOnce(
|
|
303
|
+
const watcherProcessed = await runWatchersOnce(
|
|
304
|
+
processMessage,
|
|
305
|
+
watcherNotifier,
|
|
306
|
+
watcherEscalator,
|
|
307
|
+
);
|
|
194
308
|
processed += watcherProcessed;
|
|
195
309
|
} catch (err) {
|
|
196
|
-
log.error({ err },
|
|
310
|
+
log.error({ err }, "Watcher tick failed");
|
|
197
311
|
}
|
|
198
312
|
}
|
|
199
313
|
|
|
@@ -202,11 +316,11 @@ async function runScheduleOnce(
|
|
|
202
316
|
const sequenceProcessed = await runSequencesOnce(processMessage);
|
|
203
317
|
processed += sequenceProcessed;
|
|
204
318
|
} catch (err) {
|
|
205
|
-
log.error({ err },
|
|
319
|
+
log.error({ err }, "Sequence engine tick failed");
|
|
206
320
|
}
|
|
207
321
|
|
|
208
322
|
if (processed > 0) {
|
|
209
|
-
log.info({ processed },
|
|
323
|
+
log.info({ processed }, "Schedule tick complete");
|
|
210
324
|
}
|
|
211
325
|
return processed;
|
|
212
326
|
}
|
|
@@ -161,7 +161,7 @@ export function setSecureKey(account: string, value: string): boolean {
|
|
|
161
161
|
try {
|
|
162
162
|
// Only attempt deletion if the key actually exists in keychain to
|
|
163
163
|
// avoid spawning a subprocess on every write.
|
|
164
|
-
if (keychain.getKey(account)
|
|
164
|
+
if (keychain.getKey(account) != null) {
|
|
165
165
|
keychain.deleteKey(account);
|
|
166
166
|
}
|
|
167
167
|
} catch { /* best-effort */ }
|
|
@@ -301,7 +301,7 @@ export async function setSecureKeyAsync(
|
|
|
301
301
|
// Only attempt deletion if the key actually exists in keychain to
|
|
302
302
|
// avoid spawning a subprocess on every write.
|
|
303
303
|
const exists = await keychain.getKeyAsync(account);
|
|
304
|
-
if (exists
|
|
304
|
+
if (exists != null) {
|
|
305
305
|
await keychain.deleteKeyAsync(account);
|
|
306
306
|
}
|
|
307
307
|
} catch { /* best-effort */ }
|
|
@@ -323,7 +323,7 @@ export async function setSecureKeyAsync(
|
|
|
323
323
|
keychainMissCache.delete(account);
|
|
324
324
|
try {
|
|
325
325
|
const exists = await keychain.getKeyAsync(account);
|
|
326
|
-
if (exists
|
|
326
|
+
if (exists != null) {
|
|
327
327
|
await keychain.deleteKeyAsync(account);
|
|
328
328
|
}
|
|
329
329
|
} catch { /* best-effort */ }
|