@vellumai/assistant 0.10.3-dev.202606301109.f6f8c0c → 0.10.3-dev.202606301343.ebf8f0b

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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/src/__tests__/credential-security-invariants.test.ts +0 -2
  3. package/src/__tests__/schedule-retry.test.ts +38 -14
  4. package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
  5. package/src/__tests__/scheduler-recurrence.test.ts +57 -107
  6. package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
  7. package/src/__tests__/scheduler-wake.test.ts +13 -23
  8. package/src/__tests__/task-scheduler.test.ts +68 -89
  9. package/src/background-wake/wake-intent-hooks.test.ts +13 -7
  10. package/src/config/schemas/services.ts +0 -37
  11. package/src/daemon/lifecycle.ts +2 -93
  12. package/src/daemon/shutdown-handlers.ts +0 -2
  13. package/src/persistence/conversation-row-batch-delete.ts +8 -1
  14. package/src/schedule/scheduler.ts +129 -65
  15. package/src/daemon/__tests__/meet-host-supervisor.test.ts +0 -584
  16. package/src/daemon/__tests__/meet-manifest-loader.test.ts +0 -476
  17. package/src/daemon/meet-host-supervisor.ts +0 -755
  18. package/src/daemon/meet-manifest-loader.ts +0 -396
  19. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
  20. package/src/ipc/__tests__/skill-server.test.ts +0 -182
  21. package/src/ipc/skill-ipc-types.ts +0 -54
  22. package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
  23. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
  24. package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
  25. package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
  26. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
  27. package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
  28. package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
  29. package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
  30. package/src/ipc/skill-routes/config.ts +0 -47
  31. package/src/ipc/skill-routes/events.ts +0 -147
  32. package/src/ipc/skill-routes/identity.ts +0 -21
  33. package/src/ipc/skill-routes/index.ts +0 -37
  34. package/src/ipc/skill-routes/log.ts +0 -40
  35. package/src/ipc/skill-routes/memory.ts +0 -92
  36. package/src/ipc/skill-routes/platform.ts +0 -39
  37. package/src/ipc/skill-routes/providers.ts +0 -162
  38. package/src/ipc/skill-routes/registries.ts +0 -340
  39. package/src/ipc/skill-server.ts +0 -831
  40. package/src/ipc/skill-socket-path.ts +0 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.10.3-dev.202606301109.f6f8c0c",
3
+ "version": "0.10.3-dev.202606301343.ebf8f0b",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -221,13 +221,11 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
221
221
  "runtime/routes/credential-routes.ts", // CLI credential management routes (CLI-migrated to IPC)
222
222
  "runtime/routes/sanity-routes.ts", // Sanity connect/discover routes (reads stored api_token from credential store)
223
223
  "runtime/routes/platform-routes.ts", // CLI platform connect/disconnect/status routes (CLI-migrated to IPC)
224
- "ipc/skill-routes/providers.ts", // host.providers.secureKeys.getProviderKey IPC route (out-of-process SkillHost companion)
225
224
  "inbound/platform-callback-registration.ts", // managed credential lookup for platform base URL, assistant ID, and API key
226
225
  "tts/providers/elevenlabs-provider.ts", // ElevenLabs TTS API key lookup
227
226
  "tts/providers/deepgram-provider.ts", // Deepgram TTS API key lookup
228
227
  "tts/providers/xai-provider.ts", // xAI TTS API key lookup
229
228
  "credential-health/credential-health-service.ts", // credential health check reads access tokens for liveness pings
230
- "ipc/skill-routes/providers.ts", // skill IPC route exposes provider key lookup to hosted skills
231
229
  "runtime/routes/avatar-routes.ts", // avatar generate route reads platform_base_url from credential store
232
230
  "cli/commands/keys.ts", // CLI provider key management
233
231
  "cli/commands/oauth/connect.ts", // CLI OAuth connect stored-secret verification
@@ -43,6 +43,27 @@ mock.module("../runtime/background-job-runner.js", () => ({
43
43
  },
44
44
  }));
45
45
 
46
+ // The scheduler's conversation-reuse path dispatches through `processMessage`;
47
+ // route it to the same per-test delegate the runner mock uses.
48
+ let processMessageImpl: (
49
+ conversationId: string,
50
+ message: string,
51
+ ) => Promise<unknown> = async () => {};
52
+ mock.module("../daemon/process-message.js", () => ({
53
+ processMessage: (conversationId: string, message: string) =>
54
+ processMessageImpl(conversationId, message),
55
+ }));
56
+
57
+ // Notify-mode firings dispatch through `emitNotificationSignal`; a per-test
58
+ // delegate lets a scenario make the notification throw to drive failure paths.
59
+ let emitNotificationSignalImpl: (
60
+ payload: unknown,
61
+ ) => Promise<unknown> = async () => {};
62
+ mock.module("../notifications/emit-signal.js", () => ({
63
+ emitNotificationSignal: (payload: unknown) =>
64
+ emitNotificationSignalImpl(payload),
65
+ }));
66
+
46
67
  import { getDb } from "../persistence/db-connection.js";
47
68
  import { initializeDb } from "../persistence/db-init.js";
48
69
  import { applyRetryDecision, decideRetry } from "../schedule/retry-policy.js";
@@ -62,24 +83,23 @@ import { startScheduler as startSchedulerReal } from "../schedule/scheduler.js";
62
83
  import type { ScheduleMessageProcessor } from "../schedule/scheduler-types.js";
63
84
 
64
85
  /**
65
- * Wrap `startScheduler` so the same `processMessage` the scheduler holds for
66
- * its conversation-reuse path is also handed to the mocked
67
- * `runBackgroundJob` for the fresh-bootstrap path. The two paths in scheduler
68
- * production code dispatch through different mechanisms; tests exercise both
69
- * deterministically through this single callback.
86
+ * Wrap `startScheduler` so a single per-test `processMessage` callback drives
87
+ * both scheduler dispatch paths: the conversation-reuse path (which calls the
88
+ * mocked `daemon/process-message`) and the fresh-bootstrap path (which calls
89
+ * the mocked `runBackgroundJob`). Tests exercise both deterministically through
90
+ * this one callback.
70
91
  */
71
92
  function startScheduler(
72
93
  processMessage: ScheduleMessageProcessor,
73
- notifyScheduleOneShot: Parameters<typeof startSchedulerReal>[1],
74
- options?: Parameters<typeof startSchedulerReal>[2],
94
+ _notifyScheduleOneShot?: unknown,
95
+ _options?: unknown,
75
96
  ): SchedulerHandle {
76
- injectedProcessMessageForRunner = async (
77
- conversationId: string,
78
- message: string,
79
- ) => {
97
+ const dispatch = async (conversationId: string, message: string) => {
80
98
  await processMessage(conversationId, message, { trustClass: "guardian" });
81
99
  };
82
- return startSchedulerReal(processMessage, notifyScheduleOneShot, options);
100
+ processMessageImpl = dispatch;
101
+ injectedProcessMessageForRunner = dispatch;
102
+ return startSchedulerReal();
83
103
  }
84
104
 
85
105
  await initializeDb();
@@ -102,6 +122,7 @@ function forceScheduleDue(scheduleId: string): void {
102
122
 
103
123
  describe("schedule retry store", () => {
104
124
  beforeEach(() => {
125
+ emitNotificationSignalImpl = async () => {};
105
126
  const db = getDb();
106
127
  db.run("DELETE FROM cron_runs");
107
128
  db.run("DELETE FROM cron_jobs");
@@ -203,6 +224,7 @@ describe("scheduler retry integration", () => {
203
224
  let scheduler: SchedulerHandle;
204
225
 
205
226
  beforeEach(() => {
227
+ emitNotificationSignalImpl = async () => {};
206
228
  const db = getDb();
207
229
  db.run("DELETE FROM cron_runs");
208
230
  db.run("DELETE FROM cron_jobs");
@@ -389,11 +411,11 @@ describe("scheduler retry integration", () => {
389
411
  maxRetries: 2,
390
412
  });
391
413
 
392
- const notifyScheduleOneShot = async () => {
414
+ emitNotificationSignalImpl = async () => {
393
415
  throw new Error("notify failed");
394
416
  };
395
417
 
396
- scheduler = startScheduler(async () => {}, notifyScheduleOneShot);
418
+ scheduler = startScheduler(async () => {});
397
419
  await new Promise((resolve) => setTimeout(resolve, 500));
398
420
  scheduler.stop();
399
421
 
@@ -439,6 +461,7 @@ describe("scheduler retry integration", () => {
439
461
 
440
462
  describe("crash recovery", () => {
441
463
  beforeEach(() => {
464
+ emitNotificationSignalImpl = async () => {};
442
465
  const db = getDb();
443
466
  db.run("DELETE FROM cron_runs");
444
467
  db.run("DELETE FROM cron_jobs");
@@ -574,6 +597,7 @@ describe("scheduler-recovery equivalence", () => {
574
597
  let scheduler: SchedulerHandle;
575
598
 
576
599
  beforeEach(() => {
600
+ emitNotificationSignalImpl = async () => {};
577
601
  const db = getDb();
578
602
  db.run("DELETE FROM cron_runs");
579
603
  db.run("DELETE FROM cron_jobs");
@@ -89,6 +89,18 @@ mock.module("../daemon/disk-pressure-background-gate.js", () => ({
89
89
  shouldLogDiskPressureBackgroundSkip: () => true,
90
90
  }));
91
91
 
92
+ const mockProcessMessage = mock((..._args: unknown[]) => Promise.resolve());
93
+ mock.module("../daemon/process-message.js", () => ({
94
+ processMessage: mockProcessMessage,
95
+ }));
96
+
97
+ const mockEmitNotificationSignal = mock((..._args: unknown[]) =>
98
+ Promise.resolve(),
99
+ );
100
+ mock.module("../notifications/emit-signal.js", () => ({
101
+ emitNotificationSignal: mockEmitNotificationSignal,
102
+ }));
103
+
92
104
  import { getDb } from "../persistence/db-connection.js";
93
105
  import { initializeDb } from "../persistence/db-init.js";
94
106
  import { createSchedule } from "../schedule/schedule-store.js";
@@ -104,6 +116,8 @@ function rawDb(): import("bun:sqlite").Database {
104
116
  describe("scheduler disk pressure gate", () => {
105
117
  beforeEach(() => {
106
118
  locked = true;
119
+ mockProcessMessage.mockClear();
120
+ mockEmitNotificationSignal.mockClear();
107
121
  const db = getDb();
108
122
  db.run("DELETE FROM cron_runs");
109
123
  db.run("DELETE FROM cron_jobs");
@@ -122,14 +136,11 @@ describe("scheduler disk pressure gate", () => {
122
136
  nextRunAt: dueAt,
123
137
  });
124
138
 
125
- const processMessage = mock(() => Promise.resolve());
126
- const notify = mock(() => Promise.resolve());
127
-
128
- const processed = await runScheduleOnce(processMessage, notify);
139
+ const processed = await runScheduleOnce();
129
140
 
130
141
  expect(processed).toBe(0);
131
- expect(processMessage).not.toHaveBeenCalled();
132
- expect(notify).not.toHaveBeenCalled();
142
+ expect(mockProcessMessage).not.toHaveBeenCalled();
143
+ expect(mockEmitNotificationSignal).not.toHaveBeenCalled();
133
144
 
134
145
  const row = rawDb()
135
146
  .query("SELECT status, next_run_at FROM cron_jobs WHERE id = ?")
@@ -50,6 +50,25 @@ mock.module("../runtime/background-job-runner.js", () => ({
50
50
  },
51
51
  }));
52
52
 
53
+ // The scheduler dispatches reuse/task-path messages through `processMessage`;
54
+ // route them to a per-test delegate so tests can capture those calls.
55
+ let processMessageImpl: (
56
+ conversationId: string,
57
+ message: string,
58
+ ) => Promise<void> = async () => {};
59
+ mock.module("../daemon/process-message.js", () => ({
60
+ processMessage: (conversationId: string, message: string) =>
61
+ processMessageImpl(conversationId, message),
62
+ }));
63
+
64
+ // Notify-mode firings go through `emitNotificationSignal`; capture them.
65
+ const notifySignalCalls: Array<Record<string, unknown>> = [];
66
+ mock.module("../notifications/emit-signal.js", () => ({
67
+ emitNotificationSignal: async (payload: Record<string, unknown>) => {
68
+ notifySignalCalls.push(payload);
69
+ },
70
+ }));
71
+
53
72
  import { getDb } from "../persistence/db-connection.js";
54
73
  import { initializeDb } from "../persistence/db-init.js";
55
74
  import {
@@ -138,6 +157,8 @@ describe("scheduler RRULE execution", () => {
138
157
  db.run("DELETE FROM conversations");
139
158
  onRunBackgroundJobPrompt = null;
140
159
  runBackgroundJobShouldFail = false;
160
+ processMessageImpl = async () => {};
161
+ notifySignalCalls.length = 0;
141
162
  });
142
163
 
143
164
  test("RRULE schedule fires and creates cron_runs entry", async () => {
@@ -163,10 +184,7 @@ describe("scheduler RRULE execution", () => {
163
184
  processedMessages.push({ conversationId, message: prompt });
164
185
  };
165
186
 
166
- const scheduler = startScheduler(
167
- async () => {},
168
- () => {},
169
- );
187
+ const scheduler = startScheduler();
170
188
  await new Promise((resolve) => setTimeout(resolve, 500));
171
189
  scheduler.stop();
172
190
 
@@ -201,12 +219,12 @@ describe("scheduler RRULE execution", () => {
201
219
  const directCalls: { conversationId: string; message: string }[] = [];
202
220
  let onMessage: (() => void) | undefined;
203
221
  const messageReceived = new Promise<void>((r) => (onMessage = r));
204
- const processMessage = async (conversationId: string, message: string) => {
222
+ processMessageImpl = async (conversationId, message) => {
205
223
  directCalls.push({ conversationId, message });
206
224
  onMessage?.();
207
225
  };
208
226
 
209
- const scheduler = startScheduler(processMessage, () => {});
227
+ const scheduler = startScheduler();
210
228
  // The run_task path involves a dynamic import which can take >50ms in CI,
211
229
  // exceeding the patched setTimeout delay. Await the actual callback instead
212
230
  // of relying on a fixed timeout.
@@ -271,10 +289,7 @@ describe("scheduler RRULE execution", () => {
271
289
  };
272
290
 
273
291
  // First tick: the expired schedule should fire its final due run
274
- const scheduler1 = startScheduler(
275
- async () => {},
276
- () => {},
277
- );
292
+ const scheduler1 = startScheduler();
278
293
  await new Promise((resolve) => setTimeout(resolve, 500));
279
294
  scheduler1.stop();
280
295
 
@@ -294,10 +309,7 @@ describe("scheduler RRULE execution", () => {
294
309
 
295
310
  // Second tick: the disabled schedule must NOT fire again
296
311
  processedMessages.length = 0;
297
- const scheduler2 = startScheduler(
298
- async () => {},
299
- () => {},
300
- );
312
+ const scheduler2 = startScheduler();
301
313
  await new Promise((resolve) => setTimeout(resolve, 500));
302
314
  scheduler2.stop();
303
315
 
@@ -327,10 +339,7 @@ describe("scheduler RRULE execution", () => {
327
339
  processedMessages.push({ conversationId, message: prompt });
328
340
  };
329
341
 
330
- const scheduler = startScheduler(
331
- async () => {},
332
- () => {},
333
- );
342
+ const scheduler = startScheduler();
334
343
  await new Promise((resolve) => setTimeout(resolve, 500));
335
344
  scheduler.stop();
336
345
 
@@ -385,10 +394,7 @@ describe("scheduler RRULE execution", () => {
385
394
  // Force the schedule to be due
386
395
  forceScheduleDue(schedule.id);
387
396
 
388
- const scheduler = startScheduler(
389
- async () => {},
390
- () => {},
391
- );
397
+ const scheduler = startScheduler();
392
398
  await new Promise((resolve) => setTimeout(resolve, 500));
393
399
  scheduler.stop();
394
400
 
@@ -437,10 +443,7 @@ describe("scheduler RRULE execution", () => {
437
443
  processedMessages.push(prompt);
438
444
  };
439
445
 
440
- const scheduler = startScheduler(
441
- async () => {},
442
- () => {},
443
- );
446
+ const scheduler = startScheduler();
444
447
  await new Promise((resolve) => setTimeout(resolve, 500));
445
448
  scheduler.stop();
446
449
 
@@ -516,10 +519,7 @@ describe("scheduler RRULE execution", () => {
516
519
  processedMessages.push(prompt);
517
520
  };
518
521
 
519
- const scheduler = startScheduler(
520
- async () => {},
521
- () => {},
522
- );
522
+ const scheduler = startScheduler();
523
523
  await new Promise((resolve) => setTimeout(resolve, 500));
524
524
  scheduler.stop();
525
525
 
@@ -557,8 +557,7 @@ describe("scheduler RRULE execution", () => {
557
557
  forceScheduleDue(schedule.id);
558
558
  const forcedDueAt = getSchedule(schedule.id)!.nextRunAt;
559
559
 
560
- const processMessage = async () => {};
561
- const scheduler = startScheduler(processMessage, () => {});
560
+ const scheduler = startScheduler();
562
561
  await new Promise((resolve) => setTimeout(resolve, 500));
563
562
  scheduler.stop();
564
563
 
@@ -589,10 +588,7 @@ describe("scheduler RRULE execution", () => {
589
588
  processedMessages.push({ conversationId, message: prompt });
590
589
  };
591
590
 
592
- const scheduler = startScheduler(
593
- async () => {},
594
- () => {},
595
- );
591
+ const scheduler = startScheduler();
596
592
  await new Promise((resolve) => setTimeout(resolve, 500));
597
593
  scheduler.stop();
598
594
 
@@ -623,32 +619,19 @@ describe("scheduler RRULE execution", () => {
623
619
 
624
620
  expect(getSchedule(schedule.id)!.status).toBe("active");
625
621
 
626
- const notifyCalls: Array<{
627
- id: string;
628
- label: string;
629
- message: string;
630
- routingIntent: string;
631
- routingHints: Record<string, unknown>;
632
- }> = [];
633
- const notifyScheduleOneShot = (payload: {
634
- id: string;
635
- label: string;
636
- message: string;
637
- routingIntent: string;
638
- routingHints: Record<string, unknown>;
639
- }) => {
640
- notifyCalls.push(payload);
641
- };
642
-
643
- const scheduler = startScheduler(async () => {}, notifyScheduleOneShot);
622
+ const scheduler = startScheduler();
644
623
  await new Promise((resolve) => setTimeout(resolve, 500));
645
624
  scheduler.stop();
646
625
 
647
- expect(notifyCalls).toHaveLength(1);
648
- expect(notifyCalls[0]).toEqual({
649
- id: schedule.id,
650
- label: "One-shot notify",
651
- message: "Notify about this",
626
+ expect(notifySignalCalls).toHaveLength(1);
627
+ expect(notifySignalCalls[0]).toMatchObject({
628
+ sourceEventName: "schedule.notify",
629
+ sourceContextId: schedule.id,
630
+ contextPayload: {
631
+ scheduleId: schedule.id,
632
+ label: "One-shot notify",
633
+ message: "Notify about this",
634
+ },
652
635
  routingIntent: "multi_channel",
653
636
  routingHints: { channel: "slack" },
654
637
  });
@@ -671,10 +654,7 @@ describe("scheduler RRULE execution", () => {
671
654
 
672
655
  runBackgroundJobShouldFail = true;
673
656
 
674
- const scheduler = startScheduler(
675
- async () => {},
676
- () => {},
677
- );
657
+ const scheduler = startScheduler();
678
658
  await new Promise((resolve) => setTimeout(resolve, 500));
679
659
  scheduler.stop();
680
660
 
@@ -699,32 +679,19 @@ describe("scheduler RRULE execution", () => {
699
679
 
700
680
  forceScheduleDue(schedule.id);
701
681
 
702
- const notifyCalls: Array<{
703
- id: string;
704
- label: string;
705
- message: string;
706
- routingIntent: string;
707
- routingHints: Record<string, unknown>;
708
- }> = [];
709
- const notifyScheduleOneShot = (payload: {
710
- id: string;
711
- label: string;
712
- message: string;
713
- routingIntent: string;
714
- routingHints: Record<string, unknown>;
715
- }) => {
716
- notifyCalls.push(payload);
717
- };
718
-
719
- const scheduler = startScheduler(async () => {}, notifyScheduleOneShot);
682
+ const scheduler = startScheduler();
720
683
  await new Promise((resolve) => setTimeout(resolve, 500));
721
684
  scheduler.stop();
722
685
 
723
- expect(notifyCalls).toHaveLength(1);
724
- expect(notifyCalls[0]).toEqual({
725
- id: schedule.id,
726
- label: "Recurring notify",
727
- message: "Recurring notification",
686
+ expect(notifySignalCalls).toHaveLength(1);
687
+ expect(notifySignalCalls[0]).toMatchObject({
688
+ sourceEventName: "schedule.notify",
689
+ sourceContextId: schedule.id,
690
+ contextPayload: {
691
+ scheduleId: schedule.id,
692
+ label: "Recurring notify",
693
+ message: "Recurring notification",
694
+ },
728
695
  routingIntent: "single_channel",
729
696
  routingHints: { preferred: "email" },
730
697
  });
@@ -752,30 +719,13 @@ describe("scheduler RRULE execution", () => {
752
719
  },
753
720
  });
754
721
 
755
- const notifyCalls: Array<{
756
- id: string;
757
- label: string;
758
- message: string;
759
- routingIntent: string;
760
- routingHints: Record<string, unknown>;
761
- }> = [];
762
- const notifyScheduleOneShot = (payload: {
763
- id: string;
764
- label: string;
765
- message: string;
766
- routingIntent: string;
767
- routingHints: Record<string, unknown>;
768
- }) => {
769
- notifyCalls.push(payload);
770
- };
771
-
772
- const scheduler = startScheduler(async () => {}, notifyScheduleOneShot);
722
+ const scheduler = startScheduler();
773
723
  await new Promise((resolve) => setTimeout(resolve, 500));
774
724
  scheduler.stop();
775
725
 
776
- expect(notifyCalls).toHaveLength(1);
777
- expect(notifyCalls[0].routingIntent).toBe("all_channels");
778
- expect(notifyCalls[0].routingHints).toEqual({
726
+ expect(notifySignalCalls).toHaveLength(1);
727
+ expect(notifySignalCalls[0].routingIntent).toBe("all_channels");
728
+ expect(notifySignalCalls[0].routingHints).toEqual({
779
729
  requestedByUser: true,
780
730
  channelMentions: ["telegram", "slack"],
781
731
  priority: "high",