@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isUserTerminalHistoryError,
|
|
2
3
|
ORDERING_ERROR_PATTERNS,
|
|
3
4
|
WEB_SEARCH_ORDERING_PATTERNS,
|
|
4
5
|
} from "../agent/history-repair/history-repair.js";
|
|
@@ -480,6 +481,14 @@ function classifyCore(
|
|
|
480
481
|
errorCategory: "stale_web_search_content",
|
|
481
482
|
};
|
|
482
483
|
}
|
|
484
|
+
if (isUserTerminalHistoryError(message)) {
|
|
485
|
+
return {
|
|
486
|
+
code: "PROVIDER_ORDERING",
|
|
487
|
+
userMessage: "An internal error occurred. Please try again.",
|
|
488
|
+
retryable: true,
|
|
489
|
+
errorCategory: "history_user_terminal",
|
|
490
|
+
};
|
|
491
|
+
}
|
|
483
492
|
if (isOrderingError(message)) {
|
|
484
493
|
return {
|
|
485
494
|
code: "PROVIDER_ORDERING",
|
|
@@ -645,13 +654,17 @@ function reasonToClassification(
|
|
|
645
654
|
return contextTooLargeClassification();
|
|
646
655
|
case "vision_unsupported":
|
|
647
656
|
return visionNotSupportedClassification();
|
|
657
|
+
// Two producers share this reason: SDK transport failures that never got
|
|
658
|
+
// a response (OpenAI APIConnectionError), and Gemini responses whose empty
|
|
659
|
+
// body reveals a proxy/egress filter intercepting the request. The copy
|
|
660
|
+
// must stay broad enough for both.
|
|
648
661
|
case "network_error":
|
|
649
662
|
return {
|
|
650
663
|
code: "PROVIDER_NETWORK",
|
|
651
664
|
userMessage:
|
|
652
|
-
"The provider
|
|
665
|
+
"The request could not reach the provider: the connection failed before a real response arrived. Check that the endpoint is reachable and that no proxy or egress filter is intercepting the request, then try again.",
|
|
653
666
|
retryable: true,
|
|
654
|
-
errorCategory: "
|
|
667
|
+
errorCategory: "provider_network_error",
|
|
655
668
|
};
|
|
656
669
|
case "model_not_found":
|
|
657
670
|
return {
|
|
@@ -1099,6 +1112,15 @@ function classifyByMessage(
|
|
|
1099
1112
|
};
|
|
1100
1113
|
}
|
|
1101
1114
|
|
|
1115
|
+
if (isUserTerminalHistoryError(message)) {
|
|
1116
|
+
return {
|
|
1117
|
+
code: "PROVIDER_ORDERING",
|
|
1118
|
+
userMessage: "An internal error occurred. Please try again.",
|
|
1119
|
+
retryable: true,
|
|
1120
|
+
errorCategory: "history_user_terminal",
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1102
1124
|
// General tool_use/tool_result ordering errors
|
|
1103
1125
|
if (isOrderingError(message)) {
|
|
1104
1126
|
return {
|
|
@@ -295,6 +295,11 @@ function resolveStatusCommand(context: SlashContext): SlashResolution {
|
|
|
295
295
|
|
|
296
296
|
const CLEAN_HELP_LINE =
|
|
297
297
|
"/clean — Strip injected runtime context and reset memory injection state (no summarization)";
|
|
298
|
+
const FORK_CAPABLE_INTERFACES = new Set<InterfaceId>([
|
|
299
|
+
"macos",
|
|
300
|
+
"windows",
|
|
301
|
+
"ios",
|
|
302
|
+
]);
|
|
298
303
|
|
|
299
304
|
function resolveCommandsList(context?: SlashContext): string[] {
|
|
300
305
|
const fallbackLines = [
|
|
@@ -315,21 +320,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
|
|
|
315
320
|
return fallbackLines;
|
|
316
321
|
}
|
|
317
322
|
|
|
318
|
-
if (context.userMessageInterface
|
|
319
|
-
return [
|
|
320
|
-
"/commands — List all available commands",
|
|
321
|
-
"/compact — Force context compaction immediately",
|
|
322
|
-
CLEAN_HELP_LINE,
|
|
323
|
-
"/context — Show conversation context usage",
|
|
324
|
-
"/model — List or switch inference profile",
|
|
325
|
-
"/models — List all available models",
|
|
326
|
-
"/status — Show conversation status and context usage",
|
|
327
|
-
"/btw — Ask a side question while the assistant is working",
|
|
328
|
-
"/fork — Fork the current conversation into a new branch",
|
|
329
|
-
];
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (context.userMessageInterface === "ios") {
|
|
323
|
+
if (FORK_CAPABLE_INTERFACES.has(context.userMessageInterface)) {
|
|
333
324
|
return [
|
|
334
325
|
"/commands — List all available commands",
|
|
335
326
|
"/compact — Force context compaction immediately",
|
|
@@ -57,7 +57,13 @@ const BACKGROUND_SOURCES = new Set([
|
|
|
57
57
|
"schedule",
|
|
58
58
|
"task",
|
|
59
59
|
]);
|
|
60
|
-
const LOCAL_OWNER_INTERFACES = new Set([
|
|
60
|
+
const LOCAL_OWNER_INTERFACES = new Set([
|
|
61
|
+
"macos",
|
|
62
|
+
"windows",
|
|
63
|
+
"web",
|
|
64
|
+
"vellum",
|
|
65
|
+
"cli",
|
|
66
|
+
]);
|
|
61
67
|
|
|
62
68
|
export function classifyDiskPressureTurnPolicy(
|
|
63
69
|
status: DiskPressureStatus,
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the ingress section of the workspace config: the records
|
|
3
|
+
* `vellum tunnel` leaves behind, who owns the public base URL, and when the
|
|
4
|
+
* records are dropped. The record truth table lives on the shared parser
|
|
5
|
+
* (`packages/service-contracts/src/__tests__/ingress.test.ts`); what is
|
|
6
|
+
* checked here is that the daemon reads the keys the CLI and the gateway
|
|
7
|
+
* write, and inherits that validation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
let rawConfig: Record<string, unknown> = {};
|
|
13
|
+
|
|
14
|
+
mock.module("../../../config/loader.js", () => ({
|
|
15
|
+
loadRawConfig: () => rawConfig,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
dropTunnelRecordsForNewUrl,
|
|
20
|
+
getIngressConfigResult,
|
|
21
|
+
isVelayManagedIngress,
|
|
22
|
+
loadLastTunnelRecord,
|
|
23
|
+
loadPairingTunnelRecord,
|
|
24
|
+
loadRecordedAssistantId,
|
|
25
|
+
} from "../config-ingress.js";
|
|
26
|
+
|
|
27
|
+
const TUNNEL_URL = "https://assistant-1.example.ts.net";
|
|
28
|
+
const PAIRING_URL = "https://assistant-1.pairing.example.ts.net";
|
|
29
|
+
|
|
30
|
+
describe("workspace tunnel records", () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
rawConfig = {};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("reads a well-formed record", () => {
|
|
36
|
+
rawConfig = {
|
|
37
|
+
ingress: {
|
|
38
|
+
assistantId: "assistant-1",
|
|
39
|
+
lastTunnel: { provider: "tailscale", publicBaseUrl: TUNNEL_URL },
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
expect(loadLastTunnelRecord()).toEqual({
|
|
44
|
+
provider: "tailscale",
|
|
45
|
+
publicBaseUrl: TUNNEL_URL,
|
|
46
|
+
});
|
|
47
|
+
expect(loadRecordedAssistantId()).toBe("assistant-1");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("reads a pairing record beside the tunnel that owns the URL", () => {
|
|
51
|
+
rawConfig = {
|
|
52
|
+
ingress: {
|
|
53
|
+
publicBaseUrl: "https://webhooks.ngrok.app",
|
|
54
|
+
lastTunnel: {
|
|
55
|
+
provider: "ngrok",
|
|
56
|
+
publicBaseUrl: "https://webhooks.ngrok.app",
|
|
57
|
+
},
|
|
58
|
+
pairingTunnel: { provider: "tailscale", publicBaseUrl: PAIRING_URL },
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
expect(loadPairingTunnelRecord()).toEqual({
|
|
63
|
+
provider: "tailscale",
|
|
64
|
+
publicBaseUrl: PAIRING_URL,
|
|
65
|
+
});
|
|
66
|
+
expect(loadLastTunnelRecord()).toEqual({
|
|
67
|
+
provider: "ngrok",
|
|
68
|
+
publicBaseUrl: "https://webhooks.ngrok.app",
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("inherits the shared validation for the pairing record", () => {
|
|
73
|
+
rawConfig = {
|
|
74
|
+
ingress: {
|
|
75
|
+
pairingTunnel: { provider: "wireguard", publicBaseUrl: PAIRING_URL },
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
expect(loadPairingTunnelRecord()).toBeNull();
|
|
79
|
+
|
|
80
|
+
rawConfig = { ingress: { pairingTunnel: { provider: "tailscale" } } };
|
|
81
|
+
expect(loadPairingTunnelRecord()).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("returns null when the ingress section is absent", () => {
|
|
85
|
+
expect(loadLastTunnelRecord()).toBeNull();
|
|
86
|
+
expect(loadPairingTunnelRecord()).toBeNull();
|
|
87
|
+
expect(loadRecordedAssistantId()).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("returns null when the ingress section is not an object", () => {
|
|
91
|
+
rawConfig = { ingress: "nonsense" };
|
|
92
|
+
|
|
93
|
+
expect(loadLastTunnelRecord()).toBeNull();
|
|
94
|
+
expect(loadRecordedAssistantId()).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("returns null for a provider outside the shared registry", () => {
|
|
98
|
+
// Readers render the provider into a shell command they tell the user to
|
|
99
|
+
// run, so an unknown one makes the whole record unusable.
|
|
100
|
+
rawConfig = {
|
|
101
|
+
ingress: {
|
|
102
|
+
lastTunnel: { provider: "wireguard", publicBaseUrl: TUNNEL_URL },
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
expect(loadLastTunnelRecord()).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("returns null for a malformed record", () => {
|
|
110
|
+
rawConfig = { ingress: { lastTunnel: { provider: "ngrok" } } };
|
|
111
|
+
expect(loadLastTunnelRecord()).toBeNull();
|
|
112
|
+
|
|
113
|
+
rawConfig = {
|
|
114
|
+
ingress: {
|
|
115
|
+
lastTunnel: { provider: "ngrok", publicBaseUrl: "example.ts.net" },
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
expect(loadLastTunnelRecord()).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("returns null for a blank recorded assistant id", () => {
|
|
122
|
+
rawConfig = { ingress: { assistantId: " " } };
|
|
123
|
+
|
|
124
|
+
expect(loadRecordedAssistantId()).toBeNull();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("falls back to typed defaults for a mistyped ingress section", () => {
|
|
128
|
+
rawConfig = { ingress: { publicBaseUrl: 42, enabled: "yes" } };
|
|
129
|
+
|
|
130
|
+
const config = getIngressConfigResult();
|
|
131
|
+
|
|
132
|
+
expect(config.publicBaseUrl).toBe("");
|
|
133
|
+
expect(config.enabled).toBe(false);
|
|
134
|
+
expect(config.explicitlyDisabled).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("reports Velay ownership of the public base URL", () => {
|
|
138
|
+
rawConfig = {
|
|
139
|
+
ingress: { publicBaseUrl: TUNNEL_URL, publicBaseUrlManagedBy: "velay" },
|
|
140
|
+
};
|
|
141
|
+
expect(isVelayManagedIngress()).toBe(true);
|
|
142
|
+
|
|
143
|
+
rawConfig = { ingress: { publicBaseUrl: TUNNEL_URL } };
|
|
144
|
+
expect(isVelayManagedIngress()).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("explicitlyDisabled", () => {
|
|
149
|
+
beforeEach(() => {
|
|
150
|
+
rawConfig = {};
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("only an explicit false is an opt-out", () => {
|
|
154
|
+
// `ingress.enabled` is opt-out across the assistant: a URL set through
|
|
155
|
+
// `config set ingress.publicBaseUrl` never writes the flag and is live.
|
|
156
|
+
rawConfig = { ingress: { publicBaseUrl: TUNNEL_URL } };
|
|
157
|
+
expect(getIngressConfigResult().explicitlyDisabled).toBe(false);
|
|
158
|
+
|
|
159
|
+
rawConfig = { ingress: { publicBaseUrl: TUNNEL_URL, enabled: true } };
|
|
160
|
+
expect(getIngressConfigResult().explicitlyDisabled).toBe(false);
|
|
161
|
+
|
|
162
|
+
rawConfig = { ingress: { publicBaseUrl: TUNNEL_URL, enabled: false } };
|
|
163
|
+
expect(getIngressConfigResult().explicitlyDisabled).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("dropTunnelRecordsForNewUrl", () => {
|
|
168
|
+
const records = () => ({
|
|
169
|
+
publicBaseUrl: TUNNEL_URL,
|
|
170
|
+
assistantId: "assistant-1",
|
|
171
|
+
lastTunnel: { provider: "tailscale", publicBaseUrl: TUNNEL_URL },
|
|
172
|
+
pairingTunnel: { provider: "tailscale", publicBaseUrl: PAIRING_URL },
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("keeps the records when the URL is unchanged", () => {
|
|
176
|
+
const ingress: Record<string, unknown> = records();
|
|
177
|
+
|
|
178
|
+
dropTunnelRecordsForNewUrl(ingress, ` ${TUNNEL_URL}/ `);
|
|
179
|
+
|
|
180
|
+
expect(ingress).toEqual(records());
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("drops the pairing record too, so a new URL is the one reported", () => {
|
|
184
|
+
// The pairing record outranks `publicBaseUrl` in the status route, so a
|
|
185
|
+
// leftover one would hide the address the user just set.
|
|
186
|
+
const retargeted: Record<string, unknown> = records();
|
|
187
|
+
|
|
188
|
+
dropTunnelRecordsForNewUrl(retargeted, "https://other.example.ts.net");
|
|
189
|
+
|
|
190
|
+
expect(retargeted.pairingTunnel).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("drops the records when the URL is retargeted or cleared", () => {
|
|
194
|
+
const retargeted: Record<string, unknown> = records();
|
|
195
|
+
dropTunnelRecordsForNewUrl(retargeted, "https://other.example.ts.net");
|
|
196
|
+
expect(retargeted).toEqual({ publicBaseUrl: TUNNEL_URL });
|
|
197
|
+
|
|
198
|
+
const cleared: Record<string, unknown> = records();
|
|
199
|
+
dropTunnelRecordsForNewUrl(cleared, undefined);
|
|
200
|
+
expect(cleared).toEqual({ publicBaseUrl: TUNNEL_URL });
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("is a no-op without an ingress section", () => {
|
|
204
|
+
expect(() =>
|
|
205
|
+
dropTunnelRecordsForNewUrl(undefined, TUNNEL_URL),
|
|
206
|
+
).not.toThrow();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INGRESS_ASSISTANT_ID_KEY,
|
|
3
|
+
INGRESS_LAST_TUNNEL_KEY,
|
|
4
|
+
INGRESS_PAIRING_TUNNEL_KEY,
|
|
5
|
+
normalizePublicBaseUrl,
|
|
6
|
+
parseRecordedAssistantId,
|
|
7
|
+
parseTunnelRecord,
|
|
8
|
+
type TunnelRecord,
|
|
9
|
+
} from "@vellumai/service-contracts/ingress";
|
|
10
|
+
|
|
1
11
|
import { updatePhoneNumberWebhooks } from "../../calls/twilio-rest.js";
|
|
2
12
|
import {
|
|
3
13
|
getGatewayInternalBaseUrl,
|
|
@@ -11,13 +21,105 @@ import {
|
|
|
11
21
|
getTwilioStatusCallbackUrl,
|
|
12
22
|
getTwilioVoiceWebhookUrl,
|
|
13
23
|
type IngressConfig,
|
|
24
|
+
isPublicIngressDisabled,
|
|
14
25
|
} from "../../inbound/public-ingress-urls.js";
|
|
26
|
+
import { isPlainObject } from "../../util/object.js";
|
|
15
27
|
import { log } from "./shared.js";
|
|
16
28
|
|
|
17
29
|
export function computeGatewayTarget(): string {
|
|
18
30
|
return getGatewayInternalBaseUrl();
|
|
19
31
|
}
|
|
20
32
|
|
|
33
|
+
/**
|
|
34
|
+
* The `ingress` section of the raw workspace config.
|
|
35
|
+
*
|
|
36
|
+
* `vellum tunnel` writes this section (`cli/src/lib/ingress-config.ts`). The
|
|
37
|
+
* CLI is a separate build unit the assistant does not depend on, so the key
|
|
38
|
+
* names and the record validation are shared through
|
|
39
|
+
* `@vellumai/service-contracts/ingress` instead.
|
|
40
|
+
*/
|
|
41
|
+
function readIngressSection(): Record<string, unknown> {
|
|
42
|
+
const ingress = loadRawConfig().ingress;
|
|
43
|
+
return isPlainObject(ingress) ? ingress : {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Read the last tunnel that ran, or null when it is absent or malformed. */
|
|
47
|
+
export function loadLastTunnelRecord(): TunnelRecord | null {
|
|
48
|
+
return parseTunnelRecord(readIngressSection()[INGRESS_LAST_TUNNEL_KEY]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read the tunnel published for device pairing alone, or null when it is
|
|
53
|
+
* absent or malformed.
|
|
54
|
+
*
|
|
55
|
+
* A tailnet-only tunnel started beside configured webhook integrations records
|
|
56
|
+
* itself here instead of replacing `publicBaseUrl`, which those callbacks need
|
|
57
|
+
* to keep resolving to an address their callers can reach.
|
|
58
|
+
*/
|
|
59
|
+
export function loadPairingTunnelRecord(): TunnelRecord | null {
|
|
60
|
+
return parseTunnelRecord(readIngressSection()[INGRESS_PAIRING_TUNNEL_KEY]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Read the assistant id the last tunnel fronted, or null when absent. The
|
|
65
|
+
* daemon cannot derive it: it scopes itself as `self` internally.
|
|
66
|
+
*/
|
|
67
|
+
export function loadRecordedAssistantId(): string | null {
|
|
68
|
+
return parseRecordedAssistantId(
|
|
69
|
+
readIngressSection()[INGRESS_ASSISTANT_ID_KEY],
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Key under `ingress` naming the component that owns `publicBaseUrl`. */
|
|
74
|
+
const INGRESS_MANAGED_BY_KEY = "publicBaseUrlManagedBy";
|
|
75
|
+
|
|
76
|
+
/** Value that key carries when a self-hosted gateway's Velay tunnel owns it. */
|
|
77
|
+
const VELAY_MANAGED_BY = "velay";
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* True when Velay owns `ingress.publicBaseUrl`.
|
|
81
|
+
*
|
|
82
|
+
* A gateway started with `VELAY_BASE_URL` writes the URL when its tunnel comes
|
|
83
|
+
* up, clears it when the tunnel drops, and reconnects on its own
|
|
84
|
+
* (`gateway/src/velay/client.ts`). Readers must not offer the user a tunnel
|
|
85
|
+
* command for an ingress they do not own.
|
|
86
|
+
*/
|
|
87
|
+
export function isVelayManagedIngress(): boolean {
|
|
88
|
+
return readIngressSection()[INGRESS_MANAGED_BY_KEY] === VELAY_MANAGED_BY;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Drop `ingress.assistantId`, `ingress.lastTunnel` and `ingress.pairingTunnel`
|
|
93
|
+
* from `ingress` when `nextPublicBaseUrl` replaces a different address,
|
|
94
|
+
* mutating it in place.
|
|
95
|
+
*
|
|
96
|
+
* The first two describe the address being replaced, so keeping them makes the
|
|
97
|
+
* status route call a legitimate retarget `foreign` and name a tunnel that
|
|
98
|
+
* never fronted the new address. The pairing record goes with them because it
|
|
99
|
+
* outranks `publicBaseUrl` where the status route reports an address: a
|
|
100
|
+
* leftover one would hide the address the user just set behind a tunnel they
|
|
101
|
+
* are no longer running. Every writer of `ingress.publicBaseUrl` owes this
|
|
102
|
+
* cleanup, so it lives here rather than in each of them. An absent `ingress`
|
|
103
|
+
* section carries no records to drop.
|
|
104
|
+
*/
|
|
105
|
+
export function dropTunnelRecordsForNewUrl(
|
|
106
|
+
ingress: Record<string, unknown> | undefined,
|
|
107
|
+
nextPublicBaseUrl: unknown,
|
|
108
|
+
): void {
|
|
109
|
+
if (!ingress) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
normalizePublicBaseUrl(nextPublicBaseUrl) ===
|
|
114
|
+
normalizePublicBaseUrl(ingress.publicBaseUrl)
|
|
115
|
+
) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
delete ingress[INGRESS_ASSISTANT_ID_KEY];
|
|
119
|
+
delete ingress[INGRESS_LAST_TUNNEL_KEY];
|
|
120
|
+
delete ingress[INGRESS_PAIRING_TUNNEL_KEY];
|
|
121
|
+
}
|
|
122
|
+
|
|
21
123
|
/**
|
|
22
124
|
* Read the current ingress config from the raw workspace config file.
|
|
23
125
|
* Extracted so it can be called from both the daemon message handler
|
|
@@ -25,6 +127,7 @@ export function computeGatewayTarget(): string {
|
|
|
25
127
|
*/
|
|
26
128
|
export function getIngressConfigResult(): {
|
|
27
129
|
enabled: boolean;
|
|
130
|
+
explicitlyDisabled: boolean;
|
|
28
131
|
publicBaseUrl: string;
|
|
29
132
|
localGatewayTarget: string;
|
|
30
133
|
managedCallbacks: boolean;
|
|
@@ -40,6 +143,7 @@ export function getIngressConfigResult(): {
|
|
|
40
143
|
if (assistantId) {
|
|
41
144
|
return {
|
|
42
145
|
enabled: true,
|
|
146
|
+
explicitlyDisabled: false,
|
|
43
147
|
publicBaseUrl: `${platformBase}/gateway/callbacks/${assistantId}`,
|
|
44
148
|
localGatewayTarget: computeGatewayTarget(),
|
|
45
149
|
managedCallbacks: true,
|
|
@@ -48,12 +152,18 @@ export function getIngressConfigResult(): {
|
|
|
48
152
|
}
|
|
49
153
|
}
|
|
50
154
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
155
|
+
const ingress = readIngressSection();
|
|
156
|
+
// Typed reads, not casts: a hand-edited config must not hand callers a
|
|
157
|
+
// value whose type contradicts this function's return type.
|
|
158
|
+
const publicBaseUrl =
|
|
159
|
+
typeof ingress.publicBaseUrl === "string" ? ingress.publicBaseUrl : "";
|
|
160
|
+
const enabled =
|
|
161
|
+
typeof ingress.enabled === "boolean" ? ingress.enabled : undefined;
|
|
55
162
|
return {
|
|
56
|
-
enabled,
|
|
163
|
+
enabled: enabled === true,
|
|
164
|
+
// `enabled` is opt-out across the assistant, so an unset flag is not off:
|
|
165
|
+
// callers asking whether ingress is live read this, not `!enabled`.
|
|
166
|
+
explicitlyDisabled: isPublicIngressDisabled({ ingress: { enabled } }),
|
|
57
167
|
publicBaseUrl,
|
|
58
168
|
localGatewayTarget: computeGatewayTarget(),
|
|
59
169
|
managedCallbacks: false,
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { config as dotenvConfig } from "dotenv";
|
|
2
2
|
|
|
3
|
+
import { reconcileAppPins } from "../apps/app-pin-reconciler.js";
|
|
3
4
|
import { reconcileCallsOnStartup } from "../calls/call-recovery.js";
|
|
4
5
|
import { TwilioVoiceProvider } from "../calls/twilio-provider.js";
|
|
5
6
|
import { expireInteractionBoundGuardianRequests } from "../channels/gateway-guardian-requests.js";
|
|
@@ -62,6 +63,7 @@ import {
|
|
|
62
63
|
getWorkspaceDir,
|
|
63
64
|
} from "../util/platform.js";
|
|
64
65
|
import { APP_VERSION } from "../version.js";
|
|
66
|
+
import { drainOrphanedWatchTimelineEntries } from "../watch/watch-timeline.js";
|
|
65
67
|
import { getWorkflowRunManager } from "../workflows/run-manager.js";
|
|
66
68
|
import { repairAdaptiveThinkingOnManagedProfiles } from "../workspace/adaptive-thinking-repair.js";
|
|
67
69
|
import { ensureByokDefaultProfiles } from "../workspace/byok-default-profile-ensure.js";
|
|
@@ -385,6 +387,29 @@ export async function runDaemon(): Promise<void> {
|
|
|
385
387
|
log.warn({ err }, "Profiler retention sweep failed — continuing startup");
|
|
386
388
|
}
|
|
387
389
|
|
|
390
|
+
// Reclaim watch-timeline entries whose conversation is gone. Their purge
|
|
391
|
+
// runs after the conversation row is already committed as deleted and
|
|
392
|
+
// cannot be retried by the caller, and nothing cascades into the table, so
|
|
393
|
+
// a failed purge or a crash between the two writes would otherwise keep
|
|
394
|
+
// narration, AX trees, and screenshots of the user's screen for as long as
|
|
395
|
+
// the database lives. Startup is the pass every install gets: the periodic
|
|
396
|
+
// pass runs from database maintenance on the memory plugin's jobs worker,
|
|
397
|
+
// which an install with that plugin disabled or `memory.enabled: false`
|
|
398
|
+
// never starts, so this is the only sweep that install's residue sees and
|
|
399
|
+
// it drains rather than taking one page. Best-effort inside the sweep,
|
|
400
|
+
// which reports zero rather than throwing.
|
|
401
|
+
try {
|
|
402
|
+
const sweptWatchEntries = await drainOrphanedWatchTimelineEntries();
|
|
403
|
+
if (sweptWatchEntries > 0) {
|
|
404
|
+
log.info(
|
|
405
|
+
{ sweptWatchEntries },
|
|
406
|
+
"Swept watch timeline entries for deleted conversations on startup",
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
} catch (err) {
|
|
410
|
+
log.warn({ err }, "Watch timeline sweep failed, continuing startup");
|
|
411
|
+
}
|
|
412
|
+
|
|
388
413
|
// Backfill oauth_connection rows for manual-token providers (Telegram,
|
|
389
414
|
// Slack channel) that already have stored credentials from before the
|
|
390
415
|
// oauth_connection migration. Safe to call on every startup.
|
|
@@ -722,6 +747,7 @@ export async function runDaemon(): Promise<void> {
|
|
|
722
747
|
// the same shape as schedule recovery above.
|
|
723
748
|
try {
|
|
724
749
|
await reconcilePluginSchedules();
|
|
750
|
+
reconcileAppPins();
|
|
725
751
|
} catch (err) {
|
|
726
752
|
log.error({ err }, "Plugin schedule reconcile failed, continuing startup");
|
|
727
753
|
}
|
|
@@ -11,10 +11,11 @@ export type WebSearchProviderId =
|
|
|
11
11
|
| "perplexity"
|
|
12
12
|
| "tavily"
|
|
13
13
|
| "keenable"
|
|
14
|
-
| "firecrawl"
|
|
14
|
+
| "firecrawl"
|
|
15
|
+
| "fastcrw";
|
|
15
16
|
|
|
16
17
|
/** Provider that backed a `web_fetch` call. `default` is the built-in fetcher. */
|
|
17
|
-
export type WebFetchProviderId = "default" | "firecrawl";
|
|
18
|
+
export type WebFetchProviderId = "default" | "firecrawl" | "fastcrw";
|
|
18
19
|
|
|
19
20
|
export interface WebSearchResultItem {
|
|
20
21
|
rank: number; // 1-indexed
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Extracted from conversation-runtime-assembly.ts to break circular
|
|
5
5
|
* imports (memory/conversation-crud → daemon/conversation-runtime-assembly).
|
|
6
6
|
*/
|
|
7
|
-
import type { ChannelConversationType } from "@vellumai/gateway-client";
|
|
8
7
|
|
|
9
8
|
import { isHttpAuthDisabled } from "../config/env.js";
|
|
10
9
|
import { canSeePersonalMemory } from "../runtime/effective-capabilities.js";
|
|
@@ -88,39 +87,3 @@ export function isPersonalMemoryAllowed(
|
|
|
88
87
|
trustClass: resolveTrustClass(trustContext),
|
|
89
88
|
});
|
|
90
89
|
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Map a channel-native chat type (Telegram `private`/`group`/`supergroup`,
|
|
94
|
-
* Slack `im`/`mpim`/`channel`, Discord `dm`/`channel`) onto the
|
|
95
|
-
* permission-matrix conversation-type axis. Slack and Discord both forward
|
|
96
|
-
* every non-DM as `"channel"` without distinguishing public from private, so
|
|
97
|
-
* `"channel"` maps to undefined: a permissive public-channel cell must not
|
|
98
|
-
* silently govern private channels. The channel-type tier starts matching for
|
|
99
|
-
* their non-DMs once the gateway forwards the distinct type.
|
|
100
|
-
*
|
|
101
|
-
* Every DM-capable channel must have an arm here. A missing one is silent:
|
|
102
|
-
* the cell stays settable, because the adapter validates, and simply never
|
|
103
|
-
* matches, so the guardian sets a rule for DMs and it does nothing.
|
|
104
|
-
*/
|
|
105
|
-
export function mapChatTypeToConversationType(
|
|
106
|
-
chatType?: string,
|
|
107
|
-
): ChannelConversationType | undefined {
|
|
108
|
-
switch (chatType) {
|
|
109
|
-
case "im": // Slack DM
|
|
110
|
-
case "private": // Telegram DM
|
|
111
|
-
// Discord has no string vocabulary for this: its API distinguishes a DM by
|
|
112
|
-
// a numeric channel type, and the absence of a guild. The normalizer names
|
|
113
|
-
// it for the axis rather than inventing a native-looking word.
|
|
114
|
-
case "dm":
|
|
115
|
-
return "dm";
|
|
116
|
-
// "mpim" is Slack's multi-party DM, forwarded under its own name rather
|
|
117
|
-
// than reported as "im": a room with several people in it takes the
|
|
118
|
-
// tighter `private` cell and the group-chat etiquette, not the `dm` one.
|
|
119
|
-
case "mpim":
|
|
120
|
-
case "group": // Telegram group
|
|
121
|
-
case "supergroup": // Telegram supergroup
|
|
122
|
-
return "private";
|
|
123
|
-
default:
|
|
124
|
-
return undefined;
|
|
125
|
-
}
|
|
126
|
-
}
|