@vellumai/assistant 0.11.5 → 0.11.6-staging.1
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/AGENTS.md +5 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +70 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +16 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +6 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +121 -61
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/openapi.yaml +421 -15
- package/package.json +1 -1
- package/scripts/sync-web-search-catalog.ts +6 -0
- package/src/__tests__/app-pin-store.test.ts +149 -0
- package/src/__tests__/channel-availability-routes.test.ts +23 -1
- package/src/__tests__/channel-readiness-discord.test.ts +231 -0
- package/src/__tests__/channel-readiness-service.test.ts +126 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +141 -0
- package/src/__tests__/channel-reply-delivery.test.ts +4 -4
- package/src/__tests__/client-os-metadata-persistence.test.ts +23 -10
- package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-seed-composer.test.ts +8 -0
- package/src/__tests__/conversation-slash-commands.test.ts +8 -0
- package/src/__tests__/disk-pressure-policy.test.ts +6 -0
- package/src/__tests__/gemini-provider.test.ts +138 -0
- package/src/__tests__/history-repair.test.ts +105 -3
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/llm-catalog-parity.test.ts +45 -0
- package/src/__tests__/migration-import-from-path.test.ts +349 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +102 -0
- package/src/__tests__/oauth-commands-routes.test.ts +89 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +7 -6
- package/src/__tests__/openai-provider.test.ts +18 -0
- package/src/__tests__/openai-responses-provider.test.ts +18 -0
- package/src/__tests__/platform-callback-registration.test.ts +184 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +71 -3
- package/src/__tests__/pricing.test.ts +2 -2
- package/src/__tests__/public-ingress-urls.test.ts +36 -0
- package/src/__tests__/resolve-trust-class.test.ts +0 -48
- package/src/__tests__/sanitize-config-for-transfer.test.ts +28 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +85 -3
- package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
- package/src/agent/history-repair/history-repair.ts +45 -14
- package/src/agent/loop.ts +4 -1
- package/src/api/constants/profile-config-validation.ts +60 -0
- package/src/api/events/tool-result.ts +6 -1
- package/src/api/events/watch-retro-completed.ts +52 -0
- package/src/api/index.ts +11 -0
- package/src/apps/app-pin-reconciler.ts +92 -0
- package/src/apps/app-pin-store.ts +125 -0
- package/src/channels/gateway-channel-socket-health.ts +32 -0
- package/src/channels/gateway-discord-admission.ts +32 -0
- package/src/channels/types.ts +20 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
- package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
- package/src/cli/commands/__tests__/inference-providers.test.ts +67 -2
- package/src/cli/commands/channels/__tests__/channels.test.ts +85 -0
- package/src/cli/commands/channels/index.ts +45 -31
- package/src/cli/commands/inference-profiles.ts +56 -3
- package/src/cli/commands/inference-providers.ts +28 -2
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/request.test.ts +290 -0
- package/src/cli/commands/oauth/request.ts +57 -41
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/open-browser.test.ts +67 -0
- package/src/cli/lib/open-browser.ts +24 -5
- package/src/config/__tests__/profile-materialization.test.ts +26 -0
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +6 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/profile-materialization.ts +29 -0
- package/src/config/sanitize-for-transfer.ts +16 -0
- package/src/config/schemas/llm.ts +7 -0
- package/src/config/schemas/services.ts +6 -0
- package/src/context/outbound-sanitize.ts +6 -0
- package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
- package/src/daemon/conversation-error.ts +24 -2
- package/src/daemon/conversation-slash.ts +6 -15
- package/src/daemon/daemon-control.ts +1 -0
- package/src/daemon/disk-pressure-policy.ts +7 -1
- package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
- package/src/daemon/handlers/config-ingress.ts +115 -5
- package/src/daemon/lifecycle.ts +26 -0
- package/src/daemon/message-types/web-activity.ts +3 -2
- package/src/daemon/trust-context.ts +0 -37
- package/src/inbound/__tests__/tunnel-probe.test.ts +448 -0
- package/src/inbound/platform-callback-registration.ts +28 -2
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/inbound/tunnel-probe.ts +261 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +25 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +8 -2
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +212 -10
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +5 -2
- package/src/live-voice/live-voice-connection.ts +46 -8
- package/src/live-voice/live-voice-manager.ts +25 -0
- package/src/live-voice/live-voice-session-manager.ts +318 -2
- package/src/live-voice/live-voice-session.ts +52 -2
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +126 -68
- package/src/messaging/providers/channel-transport.ts +64 -47
- package/src/messaging/providers/discord/send.test.ts +46 -1
- package/src/messaging/providers/discord/send.ts +51 -0
- package/src/messaging/providers/discord/transport.ts +26 -3
- package/src/messaging/providers/index.ts +22 -47
- package/src/messaging/providers/slack/send.test.ts +83 -26
- package/src/messaging/providers/slack/send.ts +120 -51
- package/src/messaging/providers/slack/stream-tasks.test.ts +26 -0
- package/src/messaging/providers/slack/stream-tasks.ts +39 -0
- package/src/messaging/providers/slack/transport.ts +24 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +109 -12
- package/src/messaging/providers/telegram-bot/send.ts +43 -0
- package/src/messaging/providers/telegram-bot/transport.ts +25 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +30 -7
- package/src/notifications/adapters/telegram.ts +48 -1
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/conversation-seed-composer.ts +7 -2
- package/src/oauth/byo-connection.test.ts +63 -0
- package/src/oauth/byo-connection.ts +16 -15
- package/src/oauth/connection.test.ts +111 -0
- package/src/oauth/connection.ts +142 -1
- package/src/oauth/platform-connection.test.ts +34 -0
- package/src/oauth/platform-connection.ts +28 -5
- package/src/oauth/seed-providers.ts +15 -1
- package/src/permissions/types.ts +3 -1
- package/src/persistence/conversation-crud.ts +46 -0
- package/src/persistence/conversation-types.ts +11 -9
- package/src/persistence/db-async-query.ts +2 -1
- package/src/persistence/db-maintenance.ts +15 -0
- package/src/persistence/embeddings/qdrant-manager.ts +1 -0
- package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
- package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
- package/src/persistence/migrations/369-create-app-pins.ts +37 -0
- package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
- package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/infrastructure.ts +17 -0
- package/src/persistence/schema/watch.ts +29 -0
- package/src/persistence/steps.ts +6 -0
- package/src/plugins/mtime-cache.ts +11 -0
- package/src/providers/__tests__/retry-network-error.test.ts +84 -0
- package/src/providers/connection-resolution.ts +23 -1
- package/src/providers/content-blocks.ts +9 -0
- package/src/providers/fetch-provider-catalog.ts +19 -0
- package/src/providers/gemini/client.ts +13 -5
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +92 -0
- package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
- package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -0
- package/src/providers/inference/adapter-factory.ts +0 -9
- package/src/providers/inference/credential-rotation.ts +61 -0
- package/src/providers/inference/endpoint-probe.ts +115 -0
- package/src/providers/inference/profile-probe.ts +256 -0
- package/src/providers/model-catalog.ts +170 -125
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +42 -60
- package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
- package/src/providers/openai/__tests__/orphan-tool-result-guard.test.ts +34 -2
- package/src/providers/openai/api-error-normalization.ts +16 -2
- package/src/providers/openai/chat-completions-provider.ts +75 -29
- package/src/providers/openai/responses-provider.ts +5 -2
- package/src/providers/openrouter/client.ts +0 -1
- package/src/providers/provider-send-message.ts +11 -0
- package/src/providers/retry.ts +6 -0
- package/src/providers/search-provider-catalog.ts +20 -0
- package/src/providers/vercel-ai-gateway/client.ts +0 -1
- package/src/runtime/AGENTS.md +1 -0
- package/src/runtime/__tests__/desktop-presence.test.ts +27 -4
- package/src/runtime/__tests__/host-observe.test.ts +302 -0
- package/src/runtime/channel-readiness-service.ts +214 -14
- package/src/runtime/channel-readiness-types.ts +49 -2
- package/src/runtime/channel-reply-delivery.ts +2 -2
- package/src/runtime/desktop-presence.ts +24 -21
- package/src/runtime/host-observe.ts +246 -0
- package/src/runtime/http-server.ts +181 -1
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +104 -0
- package/src/runtime/migrations/staged-import-path.ts +116 -0
- package/src/runtime/routes/__tests__/app-pin-routes.test.ts +383 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +80 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -56
- package/src/runtime/routes/__tests__/watch-routes-guardian-cache.test.ts +139 -0
- package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
- package/src/runtime/routes/app-management-routes.ts +140 -29
- package/src/runtime/routes/channel-availability-routes.ts +1 -0
- package/src/runtime/routes/channel-readiness-routes.ts +14 -2
- package/src/runtime/routes/conversation-query-routes.ts +10 -0
- package/src/runtime/routes/guardian-approval-interception.ts +24 -33
- package/src/runtime/routes/host-cu-routes.ts +18 -0
- package/src/runtime/routes/identity-routes.ts +2 -0
- package/src/runtime/routes/inbound-message-handler.ts +10 -7
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +166 -308
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +158 -335
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profiles-routes.ts +232 -31
- package/src/runtime/routes/inference-provider-connection-routes.ts +24 -4
- package/src/runtime/routes/ingress-status-routes.ts +180 -0
- package/src/runtime/routes/live-voice-routes.test.ts +40 -1
- package/src/runtime/routes/live-voice-routes.ts +34 -0
- package/src/runtime/routes/migration-routes.ts +218 -10
- package/src/runtime/routes/oauth-commands-routes.ts +23 -16
- package/src/runtime/routes/plugins-routes.ts +12 -28
- package/src/runtime/routes/question-routes.ts +6 -0
- package/src/runtime/routes/secret-routes.ts +7 -27
- package/src/runtime/routes/settings-routes.ts +9 -6
- package/src/runtime/routes/watch-routes.ts +807 -0
- package/src/runtime/slack-reply-session.test.ts +230 -121
- package/src/runtime/slack-reply-session.ts +113 -81
- package/src/runtime/{slack-task-progress.test.ts → task-progress.test.ts} +1 -28
- package/src/runtime/{slack-task-progress.ts → task-progress.ts} +30 -51
- package/src/security/__tests__/untrusted-content.test.ts +42 -0
- package/src/security/untrusted-content.ts +28 -9
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +108 -0
- package/src/telemetry/live-voice-funnel.ts +75 -8
- package/src/tools/credentials/store.ts +18 -6
- package/src/tools/network/__tests__/firecrawl-compat.test.ts +77 -0
- package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
- package/src/tools/network/__tests__/web-search.test.ts +97 -2
- package/src/tools/network/firecrawl-compat.ts +90 -0
- package/src/tools/network/web-fetch.ts +142 -62
- package/src/tools/network/web-search.ts +141 -55
- package/src/tools/types.ts +2 -1
- package/src/util/oauth-request-body.test.ts +74 -0
- package/src/util/oauth-request-body.ts +60 -0
- package/src/util/worker-process.ts +1 -0
- package/src/watch/__tests__/watch-retro.test.ts +665 -0
- package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
- package/src/watch/__tests__/watch-timeline.test.ts +670 -0
- package/src/watch/watch-retro.ts +480 -0
- package/src/watch/watch-session-manager.ts +575 -0
- package/src/watch/watch-timeline.ts +848 -0
|
@@ -319,9 +319,9 @@ mock.module("../../../cli/lib/toggle-plugin.js", () => ({
|
|
|
319
319
|
enablePlugin: enablePluginSpy,
|
|
320
320
|
}));
|
|
321
321
|
|
|
322
|
-
// Spy on the plugin-schedule reconcile
|
|
323
|
-
//
|
|
324
|
-
//
|
|
322
|
+
// Spy on the plugin-schedule reconcile so a toggle cannot silently go back
|
|
323
|
+
// to poking it from the route. Enable and disable both await the source
|
|
324
|
+
// reconcile, which converges schedules itself.
|
|
325
325
|
const reconcilePluginSchedulesSpy = mock(() => Promise.resolve());
|
|
326
326
|
|
|
327
327
|
mock.module("../../../schedule/plugin-schedule-reconciler.js", () => ({
|
|
@@ -2597,27 +2597,10 @@ async function invokeEnable(
|
|
|
2597
2597
|
return (await enableHandler(args)) as { ok: boolean };
|
|
2598
2598
|
}
|
|
2599
2599
|
|
|
2600
|
-
function invokeDisable(
|
|
2601
|
-
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
|
-
/**
|
|
2605
|
-
* Wait for a toggle handler's fire-and-forget reconcile poke to land. The poke
|
|
2606
|
-
* runs off the microtask queue (the disable path also resolves a lazy
|
|
2607
|
-
* `import()` first), so the assertion cannot run on the handler's synchronous
|
|
2608
|
-
* return.
|
|
2609
|
-
*/
|
|
2610
|
-
async function waitForReconcile(
|
|
2611
|
-
spy: { mock: { calls: unknown[] } },
|
|
2612
|
-
label: string,
|
|
2613
|
-
): Promise<void> {
|
|
2614
|
-
for (let i = 0; i < 50; i++) {
|
|
2615
|
-
if (spy.mock.calls.length > 0) {
|
|
2616
|
-
return;
|
|
2617
|
-
}
|
|
2618
|
-
await new Promise((resolve) => setTimeout(resolve, 2));
|
|
2619
|
-
}
|
|
2620
|
-
throw new Error(`${label} was never triggered`);
|
|
2600
|
+
async function invokeDisable(
|
|
2601
|
+
args: RouteHandlerArgs = {},
|
|
2602
|
+
): Promise<{ ok: boolean }> {
|
|
2603
|
+
return (await disableHandler(args)) as { ok: boolean };
|
|
2621
2604
|
}
|
|
2622
2605
|
|
|
2623
2606
|
/** Assert the spy received exactly one sync_changed carrying `plugins:list`. */
|
|
@@ -2745,42 +2728,38 @@ describe("POST /v1/plugins/:name/disable", () => {
|
|
|
2745
2728
|
reconcilePluginSourcesNowSpy.mockClear();
|
|
2746
2729
|
});
|
|
2747
2730
|
|
|
2748
|
-
test("
|
|
2731
|
+
test("awaits the source reconcile so shutdown runs before the route returns", async () => {
|
|
2749
2732
|
disablePluginSpy.mockImplementation((name) =>
|
|
2750
2733
|
toggleResult(name, "disable"),
|
|
2751
2734
|
);
|
|
2752
2735
|
|
|
2753
|
-
invokeDisable({ pathParams: { name: "simple-memory" } });
|
|
2736
|
+
await invokeDisable({ pathParams: { name: "simple-memory" } });
|
|
2754
2737
|
|
|
2755
|
-
//
|
|
2756
|
-
//
|
|
2757
|
-
//
|
|
2758
|
-
//
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
"plugin schedule reconcile",
|
|
2762
|
-
);
|
|
2763
|
-
expect(reconcilePluginSourcesNowSpy).not.toHaveBeenCalled();
|
|
2738
|
+
// The sentinel hides tools at read time, but `init` already opened
|
|
2739
|
+
// in-process work (a live subscribe, a poll worker). The source
|
|
2740
|
+
// reconcile is what runs `shutdown`. It converges schedules itself, so
|
|
2741
|
+
// the route does not poke them separately.
|
|
2742
|
+
expect(reconcilePluginSourcesNowSpy).toHaveBeenCalledTimes(1);
|
|
2743
|
+
expect(reconcilePluginSchedulesSpy).not.toHaveBeenCalled();
|
|
2764
2744
|
});
|
|
2765
2745
|
|
|
2766
|
-
test("a failed toggle does not
|
|
2746
|
+
test("a failed toggle does not run the source reconcile", async () => {
|
|
2767
2747
|
disablePluginSpy.mockImplementation((name) => {
|
|
2768
2748
|
throw new PluginDirectoryNotFoundError(name);
|
|
2769
2749
|
});
|
|
2770
2750
|
|
|
2771
|
-
expect(
|
|
2772
|
-
|
|
2773
|
-
);
|
|
2774
|
-
|
|
2775
|
-
expect(reconcilePluginSchedulesSpy).not.toHaveBeenCalled();
|
|
2751
|
+
await expect(
|
|
2752
|
+
invokeDisable({ pathParams: { name: "ghost" } }),
|
|
2753
|
+
).rejects.toThrow(NotFoundError);
|
|
2754
|
+
expect(reconcilePluginSourcesNowSpy).not.toHaveBeenCalled();
|
|
2776
2755
|
});
|
|
2777
2756
|
|
|
2778
|
-
test("disables the plugin and broadcasts sync_changed(plugins:list)", () => {
|
|
2757
|
+
test("disables the plugin and broadcasts sync_changed(plugins:list)", async () => {
|
|
2779
2758
|
disablePluginSpy.mockImplementation((name) =>
|
|
2780
2759
|
toggleResult(name, "disable"),
|
|
2781
2760
|
);
|
|
2782
2761
|
|
|
2783
|
-
const result = invokeDisable({ pathParams: { name: "simple-memory" } });
|
|
2762
|
+
const result = await invokeDisable({ pathParams: { name: "simple-memory" } });
|
|
2784
2763
|
|
|
2785
2764
|
expect(result).toEqual({ ok: true });
|
|
2786
2765
|
expect(disablePluginSpy.mock.calls[0]?.[0]).toBe("simple-memory");
|
|
@@ -2789,12 +2768,12 @@ describe("POST /v1/plugins/:name/disable", () => {
|
|
|
2789
2768
|
expectPluginsListBroadcast();
|
|
2790
2769
|
});
|
|
2791
2770
|
|
|
2792
|
-
test("threads x-vellum-client-id into the published event's originClientId", () => {
|
|
2771
|
+
test("threads x-vellum-client-id into the published event's originClientId", async () => {
|
|
2793
2772
|
disablePluginSpy.mockImplementation((name) =>
|
|
2794
2773
|
toggleResult(name, "disable"),
|
|
2795
2774
|
);
|
|
2796
2775
|
|
|
2797
|
-
invokeDisable({
|
|
2776
|
+
await invokeDisable({
|
|
2798
2777
|
pathParams: { name: "simple-memory" },
|
|
2799
2778
|
headers: { "x-vellum-client-id": "client-xyz" },
|
|
2800
2779
|
});
|
|
@@ -2806,35 +2785,35 @@ describe("POST /v1/plugins/:name/disable", () => {
|
|
|
2806
2785
|
});
|
|
2807
2786
|
});
|
|
2808
2787
|
|
|
2809
|
-
test("PluginAlreadyInStateException → ConflictError (409), no broadcast", () => {
|
|
2788
|
+
test("PluginAlreadyInStateException → ConflictError (409), no broadcast", async () => {
|
|
2810
2789
|
disablePluginSpy.mockImplementation((name) => {
|
|
2811
2790
|
throw new PluginAlreadyInStateException(name, "disable");
|
|
2812
2791
|
});
|
|
2813
2792
|
|
|
2814
|
-
expect(
|
|
2793
|
+
await expect(
|
|
2815
2794
|
invokeDisable({ pathParams: { name: "simple-memory" } }),
|
|
2816
|
-
).toThrow(ConflictError);
|
|
2795
|
+
).rejects.toThrow(ConflictError);
|
|
2817
2796
|
expect(broadcastMessageSpy).not.toHaveBeenCalled();
|
|
2818
2797
|
});
|
|
2819
2798
|
|
|
2820
|
-
test("PluginDirectoryNotFoundError → NotFoundError (404)", () => {
|
|
2799
|
+
test("PluginDirectoryNotFoundError → NotFoundError (404)", async () => {
|
|
2821
2800
|
disablePluginSpy.mockImplementation((name) => {
|
|
2822
2801
|
throw new PluginDirectoryNotFoundError(name);
|
|
2823
2802
|
});
|
|
2824
2803
|
|
|
2825
|
-
expect(
|
|
2826
|
-
|
|
2827
|
-
);
|
|
2804
|
+
await expect(
|
|
2805
|
+
invokeDisable({ pathParams: { name: "ghost" } }),
|
|
2806
|
+
).rejects.toThrow(NotFoundError);
|
|
2828
2807
|
});
|
|
2829
2808
|
|
|
2830
|
-
test("InvalidPluginNameError → BadRequestError (400)", () => {
|
|
2809
|
+
test("InvalidPluginNameError → BadRequestError (400)", async () => {
|
|
2831
2810
|
disablePluginSpy.mockImplementation(() => {
|
|
2832
2811
|
throw new ToggleInvalidPluginNameError("../escape");
|
|
2833
2812
|
});
|
|
2834
2813
|
|
|
2835
|
-
expect(
|
|
2836
|
-
|
|
2837
|
-
);
|
|
2814
|
+
await expect(
|
|
2815
|
+
invokeDisable({ pathParams: { name: "../escape" } }),
|
|
2816
|
+
).rejects.toThrow(BadRequestError);
|
|
2838
2817
|
});
|
|
2839
2818
|
});
|
|
2840
2819
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A watch session's principal lookup against a stale guardian-delivery cache.
|
|
3
|
+
*
|
|
4
|
+
* The cache keeps a successful read that found no binding, and a gateway-side
|
|
5
|
+
* binding write does not invalidate it, so the read a session start depends on
|
|
6
|
+
* has to force a refresh or a first run fails for the cache's whole TTL. The
|
|
7
|
+
* guardian reader is mocked here rather than in the main watch-routes suite so
|
|
8
|
+
* the substitution stays inside this file.
|
|
9
|
+
*/
|
|
10
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import type { GuardianDelivery } from "@vellumai/gateway-client";
|
|
13
|
+
|
|
14
|
+
let cachedResult: GuardianDelivery[] | null = null;
|
|
15
|
+
let freshResult: GuardianDelivery[] | null = null;
|
|
16
|
+
let cachedCalls = 0;
|
|
17
|
+
let freshCalls = 0;
|
|
18
|
+
|
|
19
|
+
// Only the two read entry points are substituted; the rest of the reader's
|
|
20
|
+
// surface stays real, because other modules in this graph import from it.
|
|
21
|
+
const realReader =
|
|
22
|
+
await import("../../../contacts/guardian-delivery-reader.js");
|
|
23
|
+
mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
|
|
24
|
+
...realReader,
|
|
25
|
+
getGuardianDelivery: () => {
|
|
26
|
+
cachedCalls++;
|
|
27
|
+
return Promise.resolve(cachedResult);
|
|
28
|
+
},
|
|
29
|
+
getGuardianDeliveryFresh: () => {
|
|
30
|
+
freshCalls++;
|
|
31
|
+
return Promise.resolve(freshResult);
|
|
32
|
+
},
|
|
33
|
+
peekCachedGuardianDelivery: () => undefined,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const { initializeDb } = await import("../../../persistence/db-init.js");
|
|
37
|
+
const { WatchSessionManager } =
|
|
38
|
+
await import("../../../watch/watch-session-manager.js");
|
|
39
|
+
const { WatchStreamSession, resolveWatchActorPrincipalId } =
|
|
40
|
+
await import("../watch-routes.js");
|
|
41
|
+
|
|
42
|
+
await initializeDb();
|
|
43
|
+
|
|
44
|
+
afterAll(() => {
|
|
45
|
+
mock.restore();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/** The shape `guardianForChannel` matches on for the vellum binding. */
|
|
49
|
+
function guardian(principalId: string): GuardianDelivery {
|
|
50
|
+
return {
|
|
51
|
+
principalId,
|
|
52
|
+
channelType: "vellum",
|
|
53
|
+
status: "active",
|
|
54
|
+
} as unknown as GuardianDelivery;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A socket that keeps the frame types the session sent it. */
|
|
58
|
+
function newSocket() {
|
|
59
|
+
const types: string[] = [];
|
|
60
|
+
return {
|
|
61
|
+
types,
|
|
62
|
+
send: (data: string) => {
|
|
63
|
+
types.push((JSON.parse(data) as { type: string }).type);
|
|
64
|
+
},
|
|
65
|
+
close: () => {},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A transcriber that opens without reaching any provider. */
|
|
70
|
+
function newTranscriber() {
|
|
71
|
+
return {
|
|
72
|
+
providerId: "deepgram" as const,
|
|
73
|
+
boundaryId: "daemon-streaming" as const,
|
|
74
|
+
start: async () => {},
|
|
75
|
+
sendAudio: () => {},
|
|
76
|
+
stop: () => {},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
describe("watch principal lookup against a stale guardian cache", () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
cachedCalls = 0;
|
|
83
|
+
freshCalls = 0;
|
|
84
|
+
cachedResult = [];
|
|
85
|
+
freshResult = [];
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("reads past an empty cached answer to the binding that now exists", async () => {
|
|
89
|
+
cachedResult = [];
|
|
90
|
+
freshResult = [guardian("guardian-just-bound")];
|
|
91
|
+
|
|
92
|
+
expect(await resolveWatchActorPrincipalId()).toBe("guardian-just-bound");
|
|
93
|
+
expect(freshCalls).toBe(1);
|
|
94
|
+
expect(cachedCalls).toBe(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("a session started after the binding appears reaches ready", async () => {
|
|
98
|
+
// The cache still holds the empty answer from before the guardian was
|
|
99
|
+
// bound, which is the state a first run starts in.
|
|
100
|
+
cachedResult = [];
|
|
101
|
+
freshResult = [guardian("guardian-just-bound")];
|
|
102
|
+
|
|
103
|
+
const manager = new WatchSessionManager({
|
|
104
|
+
observe: async () => ({ ok: true, axTree: "Window: Editor" }),
|
|
105
|
+
});
|
|
106
|
+
const ws = newSocket();
|
|
107
|
+
const session = new WatchStreamSession(ws, {
|
|
108
|
+
mimeType: "audio/webm",
|
|
109
|
+
manager,
|
|
110
|
+
resolveTranscriber: async () => newTranscriber(),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await session.start();
|
|
114
|
+
|
|
115
|
+
// The handshake rather than the exact sequence: the session reads the
|
|
116
|
+
// screen as it opens, so its capture acknowledgement lands in here too.
|
|
117
|
+
expect(ws.types[0]).toBe("ready");
|
|
118
|
+
expect(ws.types).not.toContain("error");
|
|
119
|
+
expect(manager.isActive()).toBe(true);
|
|
120
|
+
|
|
121
|
+
session.destroy();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("no binding at all still fails closed", async () => {
|
|
125
|
+
cachedResult = [];
|
|
126
|
+
freshResult = [];
|
|
127
|
+
|
|
128
|
+
const ws = newSocket();
|
|
129
|
+
const session = new WatchStreamSession(ws, {
|
|
130
|
+
mimeType: "audio/webm",
|
|
131
|
+
manager: new WatchSessionManager(),
|
|
132
|
+
resolveTranscriber: async () => newTranscriber(),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await session.start();
|
|
136
|
+
|
|
137
|
+
expect(ws.types).toEqual(["error", "closed"]);
|
|
138
|
+
});
|
|
139
|
+
});
|