@vellumai/assistant 0.10.3-staging.2 → 0.10.4-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/openapi.yaml +73 -56
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/assistant-stream-state.test.ts +3 -76
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +82 -32
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +6 -2
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/config-loader-backfill.test.ts +148 -0
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +6 -9
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
- package/src/__tests__/conversation-agent-loop.test.ts +98 -7
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-error.test.ts +18 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-title-service.test.ts +222 -201
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
- package/src/__tests__/guardian-dispatch.test.ts +50 -5
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
- package/src/__tests__/invite-redemption-service.test.ts +273 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/list-messages-page-latest.test.ts +173 -4
- package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
- package/src/__tests__/non-member-access-request.test.ts +15 -13
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +13 -5
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/relay-server.test.ts +88 -31
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/slack-block-formatting.test.ts +1 -38
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
- package/src/__tests__/stt-hints.test.ts +6 -3
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-role-registry.test.ts +17 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-tools.test.ts +398 -3
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
- package/src/__tests__/tool-start-timestamp.test.ts +4 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +183 -20
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +19 -15
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +108 -1
- package/src/api/responses/conversation-message.ts +9 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -4
- package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
- package/src/calls/guardian-dispatch.ts +14 -11
- package/src/calls/inbound-trust-reader.ts +7 -1
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +22 -2
- package/src/calls/relay-setup-router.ts +10 -10
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
- package/src/cli/commands/memory/worker.ts +97 -30
- package/src/cli/commands/plugins.ts +3 -146
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
- package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
- package/src/cli/lib/publish-plugin.ts +231 -1
- package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
- package/src/config/bundled-skills/subagent/SKILL.md +16 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/seed-inference-profiles.ts +76 -35
- package/src/config/sync-gated-profiles.ts +0 -3
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
- package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -58
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/member-write-relay.ts +25 -31
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
- package/src/daemon/conversation-agent-loop.ts +68 -61
- package/src/daemon/conversation-error.ts +7 -10
- package/src/daemon/conversation-tool-setup.ts +0 -10
- package/src/daemon/conversation.ts +10 -0
- package/src/daemon/external-plugins-bootstrap.ts +8 -2
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
- package/src/daemon/handlers/config-channels.ts +14 -29
- package/src/daemon/lifecycle.ts +16 -4
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/memory/__tests__/embedding-cache.test.ts +136 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +136 -61
- package/src/memory/conversation-title-service.ts +173 -24
- package/src/memory/embedding-backend.ts +8 -1
- package/src/memory/embedding-cache.ts +139 -0
- package/src/memory/jobs-worker.ts +75 -29
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +39 -0
- package/src/memory/steps.ts +1090 -367
- package/src/memory/worker-control.ts +104 -18
- package/src/memory/worker-process.ts +17 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/channel-binding-schema.ts +51 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
- package/src/messaging/providers/a2a/deliver.ts +5 -1
- package/src/messaging/providers/a2a/transport.ts +10 -0
- package/src/messaging/providers/callback-routing.ts +48 -0
- package/src/messaging/providers/channel-transport.ts +55 -0
- package/src/messaging/providers/index.ts +65 -241
- package/src/messaging/providers/slack/binding-metadata.ts +62 -0
- package/src/messaging/providers/slack/transport.ts +92 -0
- package/src/messaging/providers/telegram-bot/transport.ts +51 -0
- package/src/messaging/providers/whatsapp/transport.ts +38 -0
- package/src/notifications/__tests__/broadcaster.test.ts +0 -8
- package/src/notifications/__tests__/connected-channels.test.ts +8 -36
- package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
- package/src/notifications/destination-resolver.ts +7 -23
- package/src/notifications/emit-signal.ts +5 -11
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/registry.ts +55 -12
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/call-site-routing.ts +41 -0
- package/src/providers/provider-send-message.ts +6 -0
- package/src/providers/ratelimit.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +6 -0
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +6 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
- package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
- package/src/runtime/access-request-helper.ts +1 -2
- package/src/runtime/actor-trust-resolver.ts +44 -17
- package/src/runtime/anchored-guardian.test.ts +7 -54
- package/src/runtime/anchored-guardian.ts +4 -53
- package/src/runtime/assistant-stream-state.ts +12 -74
- package/src/runtime/channel-reply-delivery.ts +3 -8
- package/src/runtime/guardian-vellum-migration.ts +18 -16
- package/src/runtime/invite-redemption-service.ts +25 -10
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +27 -20
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +40 -25
- package/src/runtime/routes/conversation-list-routes.ts +1 -29
- package/src/runtime/routes/conversation-routes.ts +27 -7
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/services/conversation-serializer.ts +6 -49
- package/src/runtime/slack-block-formatting.ts +0 -15
- package/src/runtime/trust-verdict-consumer.ts +36 -41
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +245 -33
- package/src/subagent/types.ts +8 -1
- package/src/tools/registry.ts +10 -3
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +14 -0
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -197
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
|
@@ -1,277 +1,101 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Direct channel delivery — bypasses the gateway HTTP proxy.
|
|
3
3
|
*
|
|
4
|
-
* Each channel
|
|
5
|
-
*
|
|
4
|
+
* Each channel exposes a `ChannelTransport`; the callback-URL → channel mapping
|
|
5
|
+
* lives in `callback-routing.ts`. The gateway-client consults
|
|
6
6
|
* `isDirectDelivery()` before falling back to the HTTP proxy path.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Supported: Slack, Telegram, WhatsApp, A2A.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type {
|
|
12
12
|
ChannelDeliveryResult,
|
|
13
13
|
ChannelReplyPayload,
|
|
14
14
|
} from "@vellumai/gateway-client";
|
|
15
|
-
import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
|
|
16
15
|
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
import { a2aTransport } from "./a2a/transport.js";
|
|
17
|
+
import type { DirectDeliveryChannel } from "./callback-routing.js";
|
|
18
|
+
import { channelForCallback } from "./callback-routing.js";
|
|
19
|
+
import type { CallbackContext, ChannelTransport } from "./channel-transport.js";
|
|
20
|
+
import { slackTransport } from "./slack/transport.js";
|
|
21
|
+
import { telegramTransport } from "./telegram-bot/transport.js";
|
|
22
|
+
import { whatsappTransport } from "./whatsapp/transport.js";
|
|
23
|
+
|
|
24
|
+
// Keyed by `DirectDeliveryChannel` so the type checker enforces that the
|
|
25
|
+
// registered transports cover exactly the channels `callback-routing` resolves:
|
|
26
|
+
// add a channel to that set and this object fails to compile until its transport
|
|
27
|
+
// is registered here (and vice versa). No second list to drift against.
|
|
28
|
+
const TRANSPORTS: Record<DirectDeliveryChannel, ChannelTransport> = {
|
|
29
|
+
slack: slackTransport,
|
|
30
|
+
telegram: telegramTransport,
|
|
31
|
+
whatsapp: whatsappTransport,
|
|
32
|
+
a2a: a2aTransport,
|
|
33
|
+
};
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} catch {
|
|
43
|
-
return callbackUrl.endsWith(pathname);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function isWhatsAppCallback(callbackUrl: string): boolean {
|
|
48
|
-
return matchesPathname(callbackUrl, "/deliver/whatsapp");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function isTelegramCallback(callbackUrl: string): boolean {
|
|
52
|
-
return matchesPathname(callbackUrl, "/deliver/telegram");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function isSlackCallback(callbackUrl: string): boolean {
|
|
56
|
-
try {
|
|
57
|
-
return new URL(callbackUrl).pathname === "/deliver/slack";
|
|
58
|
-
} catch {
|
|
59
|
-
return callbackUrl.endsWith("/deliver/slack");
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function isA2ACallback(callbackUrl: string): boolean {
|
|
64
|
-
return matchesPathname(callbackUrl, "/deliver/a2a");
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the transport that owns a gateway callback URL, or `undefined` when
|
|
37
|
+
* no channel delivers it directly.
|
|
38
|
+
*/
|
|
39
|
+
export function getTransportForCallback(
|
|
40
|
+
callbackUrl: string,
|
|
41
|
+
): ChannelTransport | undefined {
|
|
42
|
+
const channel = channelForCallback(callbackUrl);
|
|
43
|
+
return channel ? TRANSPORTS[channel] : undefined;
|
|
65
44
|
}
|
|
66
45
|
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
threadTs?: string;
|
|
70
|
-
messageTs?: string;
|
|
71
|
-
} {
|
|
46
|
+
function callbackContext(callbackUrl: string): CallbackContext {
|
|
47
|
+
const params: Record<string, string> = {};
|
|
72
48
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return {};
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// Per-channel direct delivery
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
|
|
88
|
-
async function deliverWhatsApp(
|
|
89
|
-
payload: ChannelReplyPayload,
|
|
90
|
-
): Promise<ChannelDeliveryResult> {
|
|
91
|
-
const { chatId, text, attachments, approval } = payload;
|
|
92
|
-
|
|
93
|
-
if (text) {
|
|
94
|
-
await sendWhatsAppReply(chatId, text, approval);
|
|
95
|
-
} else if (approval) {
|
|
96
|
-
await sendWhatsAppReply(
|
|
97
|
-
chatId,
|
|
98
|
-
approval.plainTextFallback || "Approval required",
|
|
99
|
-
approval,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (attachments && attachments.length > 0) {
|
|
104
|
-
const result = await sendWhatsAppAttachments(chatId, attachments);
|
|
105
|
-
if (result.allFailed && !text) {
|
|
106
|
-
throw new ChannelDeliveryError(
|
|
107
|
-
502,
|
|
108
|
-
`All ${result.failureCount} attachments failed to deliver`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
log.info({ chatId, hasText: !!text }, "WhatsApp reply delivered (direct)");
|
|
114
|
-
return { ok: true };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function deliverTelegram(
|
|
118
|
-
payload: ChannelReplyPayload,
|
|
119
|
-
): Promise<ChannelDeliveryResult> {
|
|
120
|
-
const { chatId, text, attachments, approval, chatAction } = payload;
|
|
121
|
-
|
|
122
|
-
if (chatAction === "typing") {
|
|
123
|
-
await sendTelegramTypingIndicator(chatId);
|
|
124
|
-
log.debug({ chatId }, "Telegram typing indicator delivered (direct)");
|
|
125
|
-
return { ok: true };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (text) {
|
|
129
|
-
await sendTelegramReply(chatId, text, approval);
|
|
130
|
-
} else if (approval) {
|
|
131
|
-
await sendTelegramReply(
|
|
132
|
-
chatId,
|
|
133
|
-
approval.plainTextFallback || "Approval required",
|
|
134
|
-
approval,
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (attachments && attachments.length > 0) {
|
|
139
|
-
const result = await sendTelegramAttachments(chatId, attachments);
|
|
140
|
-
if (result.allFailed && !text) {
|
|
141
|
-
throw new ChannelDeliveryError(
|
|
142
|
-
502,
|
|
143
|
-
`All ${result.failureCount} attachments failed to deliver`,
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
log.info({ chatId, hasText: !!text }, "Telegram reply delivered (direct)");
|
|
149
|
-
return { ok: true };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async function deliverSlack(
|
|
153
|
-
callbackUrl: string,
|
|
154
|
-
payload: ChannelReplyPayload,
|
|
155
|
-
): Promise<ChannelDeliveryResult> {
|
|
156
|
-
const { chatId, text, attachments, chatAction, blocks } = payload;
|
|
157
|
-
const params = parseSlackCallbackParams(callbackUrl);
|
|
158
|
-
const threadTs = params.threadTs;
|
|
159
|
-
|
|
160
|
-
// Emoji reaction
|
|
161
|
-
if (payload.reaction) {
|
|
162
|
-
await sendSlackReaction(
|
|
163
|
-
chatId,
|
|
164
|
-
payload.reaction.name,
|
|
165
|
-
payload.reaction.messageTs,
|
|
166
|
-
payload.reaction.action,
|
|
167
|
-
);
|
|
168
|
-
return { ok: true };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Assistants API thread status
|
|
172
|
-
if (payload.assistantThreadStatus) {
|
|
173
|
-
const {
|
|
174
|
-
channel,
|
|
175
|
-
threadTs: statusThreadTs,
|
|
176
|
-
status,
|
|
177
|
-
loadingMessages,
|
|
178
|
-
} = payload.assistantThreadStatus;
|
|
179
|
-
await sendSlackAssistantThreadStatus(
|
|
180
|
-
channel,
|
|
181
|
-
statusThreadTs,
|
|
182
|
-
status,
|
|
183
|
-
loadingMessages,
|
|
184
|
-
);
|
|
185
|
-
return { ok: true };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Typing indicator
|
|
189
|
-
if (chatAction === "typing") {
|
|
190
|
-
const placeholderTs = await sendSlackTypingIndicator(chatId, threadTs);
|
|
191
|
-
log.debug({ chatId }, "Slack typing indicator delivered (direct)");
|
|
192
|
-
return { ok: true, ts: placeholderTs };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Text + blocks delivery
|
|
196
|
-
let sentTs: string | undefined;
|
|
197
|
-
if (text) {
|
|
198
|
-
const result = await sendSlackReply(chatId, text, {
|
|
199
|
-
threadTs,
|
|
200
|
-
blocks,
|
|
201
|
-
approval: payload.approval,
|
|
202
|
-
useBlocks: payload.useBlocks,
|
|
203
|
-
ephemeral: payload.ephemeral,
|
|
204
|
-
user: payload.user,
|
|
205
|
-
messageTs: payload.messageTs,
|
|
206
|
-
});
|
|
207
|
-
sentTs = result.ts;
|
|
208
|
-
} else if (payload.approval) {
|
|
209
|
-
const result = await sendSlackReply(
|
|
210
|
-
chatId,
|
|
211
|
-
payload.approval.plainTextFallback || "Approval required",
|
|
212
|
-
{
|
|
213
|
-
threadTs,
|
|
214
|
-
approval: payload.approval,
|
|
215
|
-
},
|
|
216
|
-
);
|
|
217
|
-
sentTs = result.ts;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Attachments
|
|
221
|
-
if (attachments && attachments.length > 0) {
|
|
222
|
-
const result = await sendSlackAttachments(chatId, attachments, threadTs);
|
|
223
|
-
if (result.allFailed && !text) {
|
|
224
|
-
throw new ChannelDeliveryError(
|
|
225
|
-
502,
|
|
226
|
-
`All ${result.failureCount} attachments failed to deliver`,
|
|
227
|
-
);
|
|
49
|
+
// Resolve against a dummy base so base-less callbacks (e.g.
|
|
50
|
+
// `/deliver/slack?threadTs=…`) still expose their params. `channelForCallback`
|
|
51
|
+
// already routes those as direct delivery, so dispatch must not drop
|
|
52
|
+
// threadTs/taskId for them.
|
|
53
|
+
const url = new URL(callbackUrl, "http://callback.invalid");
|
|
54
|
+
for (const [key, value] of url.searchParams) {
|
|
55
|
+
params[key] = value;
|
|
228
56
|
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Unparseable callback URL — deliver with no params.
|
|
229
59
|
}
|
|
230
|
-
|
|
231
|
-
log.info({ chatId, hasText: !!text }, "Slack reply delivered (direct)");
|
|
232
|
-
return { ok: true, ts: sentTs };
|
|
60
|
+
return { callbackUrl, params };
|
|
233
61
|
}
|
|
234
62
|
|
|
235
|
-
// ---------------------------------------------------------------------------
|
|
236
|
-
// Public API
|
|
237
|
-
// ---------------------------------------------------------------------------
|
|
238
|
-
|
|
239
63
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
64
|
+
* True when the callback URL targets a channel whose outbound delivery the
|
|
65
|
+
* assistant handles directly (no gateway hop).
|
|
242
66
|
*/
|
|
243
67
|
export function isDirectDelivery(callbackUrl: string): boolean {
|
|
244
|
-
return (
|
|
245
|
-
isWhatsAppCallback(callbackUrl) ||
|
|
246
|
-
isTelegramCallback(callbackUrl) ||
|
|
247
|
-
isSlackCallback(callbackUrl) ||
|
|
248
|
-
isA2ACallback(callbackUrl)
|
|
249
|
-
);
|
|
68
|
+
return getTransportForCallback(callbackUrl) !== undefined;
|
|
250
69
|
}
|
|
251
70
|
|
|
252
71
|
/**
|
|
253
|
-
* Deliver a channel reply directly to the provider API, bypassing the
|
|
254
|
-
*
|
|
72
|
+
* Deliver a channel reply directly to the provider API, bypassing the gateway
|
|
73
|
+
* HTTP proxy. Callers MUST check `isDirectDelivery()` first.
|
|
74
|
+
*
|
|
75
|
+
* Sub-operations (reaction, thread status, typing) route to the transport's
|
|
76
|
+
* optional method when both the payload field and the method are present;
|
|
77
|
+
* otherwise the reply is delivered as text / approval / attachments.
|
|
255
78
|
*/
|
|
256
79
|
export async function deliverDirect(
|
|
257
80
|
callbackUrl: string,
|
|
258
81
|
payload: ChannelReplyPayload,
|
|
259
82
|
): Promise<ChannelDeliveryResult> {
|
|
260
|
-
|
|
261
|
-
|
|
83
|
+
const transport = getTransportForCallback(callbackUrl);
|
|
84
|
+
if (!transport) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`deliverDirect called for unsupported callback: ${callbackUrl}`,
|
|
87
|
+
);
|
|
262
88
|
}
|
|
263
|
-
|
|
264
|
-
|
|
89
|
+
|
|
90
|
+
const ctx = callbackContext(callbackUrl);
|
|
91
|
+
if (payload.reaction && transport.sendReaction) {
|
|
92
|
+
return transport.sendReaction(ctx, payload);
|
|
265
93
|
}
|
|
266
|
-
if (
|
|
267
|
-
return
|
|
94
|
+
if (payload.assistantThreadStatus && transport.setThreadStatus) {
|
|
95
|
+
return transport.setThreadStatus(ctx, payload);
|
|
268
96
|
}
|
|
269
|
-
if (
|
|
270
|
-
return
|
|
97
|
+
if (payload.chatAction === "typing" && transport.sendTyping) {
|
|
98
|
+
return transport.sendTyping(ctx, payload);
|
|
271
99
|
}
|
|
272
|
-
|
|
273
|
-
// Defensive — isDirectDelivery should have returned false.
|
|
274
|
-
throw new Error(
|
|
275
|
-
`deliverDirect called for unsupported callback: ${callbackUrl}`,
|
|
276
|
-
);
|
|
100
|
+
return transport.deliver(ctx, payload);
|
|
277
101
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getConfig } from "../../../config/loader.js";
|
|
2
|
+
import type { ExternalConversationBinding } from "../../../memory/external-conversation-store.js";
|
|
3
|
+
import type { ChannelBindingMetadata } from "../../channel-binding-schema.js";
|
|
4
|
+
import {
|
|
5
|
+
buildSlackMessageDeepLinks,
|
|
6
|
+
buildSlackWebChannelUrl,
|
|
7
|
+
} from "./deep-link.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Slack's contribution to a serialized conversation channel binding: a
|
|
11
|
+
* human-readable channel name (falling back to the channel id) plus deep
|
|
12
|
+
* links that jump back to the source thread and channel in the Slack app or
|
|
13
|
+
* web client.
|
|
14
|
+
*
|
|
15
|
+
* The return type is derived from the channel-binding Zod schema
|
|
16
|
+
* (`ChannelBindingMetadata`) — the single source of truth that also drives
|
|
17
|
+
* `openapi.yaml` and the web client's generated types — so this builder cannot
|
|
18
|
+
* drift from the wire contract. Slack is the only channel that can currently
|
|
19
|
+
* produce message-level deep links, because the link inputs (workspace team
|
|
20
|
+
* id/url + a stable per-message timestamp) only exist for Slack.
|
|
21
|
+
*/
|
|
22
|
+
export function buildSlackBindingMetadata(
|
|
23
|
+
binding: ExternalConversationBinding,
|
|
24
|
+
): ChannelBindingMetadata {
|
|
25
|
+
const externalChatName =
|
|
26
|
+
binding.externalChatName?.trim() || binding.externalChatId;
|
|
27
|
+
const slackConfig = getConfig().slack;
|
|
28
|
+
|
|
29
|
+
const threadLink =
|
|
30
|
+
slackConfig && binding.externalThreadId
|
|
31
|
+
? buildSlackMessageDeepLinks({
|
|
32
|
+
teamId: slackConfig.teamId,
|
|
33
|
+
teamUrl: slackConfig.teamUrl,
|
|
34
|
+
channelId: binding.externalChatId,
|
|
35
|
+
messageTs: binding.externalThreadId,
|
|
36
|
+
})
|
|
37
|
+
: undefined;
|
|
38
|
+
const slackThread = binding.externalThreadId
|
|
39
|
+
? {
|
|
40
|
+
channelId: binding.externalChatId,
|
|
41
|
+
threadTs: binding.externalThreadId,
|
|
42
|
+
...(threadLink ? { link: threadLink } : {}),
|
|
43
|
+
}
|
|
44
|
+
: undefined;
|
|
45
|
+
|
|
46
|
+
const channelWebUrl = slackConfig
|
|
47
|
+
? buildSlackWebChannelUrl({
|
|
48
|
+
teamUrl: slackConfig.teamUrl,
|
|
49
|
+
channelId: binding.externalChatId,
|
|
50
|
+
})
|
|
51
|
+
: undefined;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
externalChatName,
|
|
55
|
+
...(slackThread ? { slackThread } : {}),
|
|
56
|
+
slackChannel: {
|
|
57
|
+
channelId: binding.externalChatId,
|
|
58
|
+
name: externalChatName,
|
|
59
|
+
...(channelWebUrl ? { link: { webUrl: channelWebUrl } } : {}),
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
|
|
2
|
+
|
|
3
|
+
import { getLogger } from "../../../util/logger.js";
|
|
4
|
+
import type { ChannelTransport } from "../channel-transport.js";
|
|
5
|
+
import {
|
|
6
|
+
sendSlackAssistantThreadStatus,
|
|
7
|
+
sendSlackAttachments,
|
|
8
|
+
sendSlackReaction,
|
|
9
|
+
sendSlackReply,
|
|
10
|
+
sendSlackTypingIndicator,
|
|
11
|
+
} from "./send.js";
|
|
12
|
+
|
|
13
|
+
const log = getLogger("slack-transport");
|
|
14
|
+
|
|
15
|
+
export const slackTransport: ChannelTransport = {
|
|
16
|
+
channel: "slack",
|
|
17
|
+
|
|
18
|
+
async deliver(ctx, payload) {
|
|
19
|
+
const { chatId, text, attachments, blocks } = payload;
|
|
20
|
+
const threadTs = ctx.params.threadTs;
|
|
21
|
+
|
|
22
|
+
let sentTs: string | undefined;
|
|
23
|
+
if (text) {
|
|
24
|
+
const result = await sendSlackReply(chatId, text, {
|
|
25
|
+
threadTs,
|
|
26
|
+
blocks,
|
|
27
|
+
approval: payload.approval,
|
|
28
|
+
useBlocks: payload.useBlocks,
|
|
29
|
+
ephemeral: payload.ephemeral,
|
|
30
|
+
user: payload.user,
|
|
31
|
+
messageTs: payload.messageTs,
|
|
32
|
+
});
|
|
33
|
+
sentTs = result.ts;
|
|
34
|
+
} else if (payload.approval) {
|
|
35
|
+
const result = await sendSlackReply(
|
|
36
|
+
chatId,
|
|
37
|
+
payload.approval.plainTextFallback || "Approval required",
|
|
38
|
+
{ threadTs, approval: payload.approval },
|
|
39
|
+
);
|
|
40
|
+
sentTs = result.ts;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (attachments && attachments.length > 0) {
|
|
44
|
+
const result = await sendSlackAttachments(chatId, attachments, threadTs);
|
|
45
|
+
if (result.allFailed && !text) {
|
|
46
|
+
throw new ChannelDeliveryError(
|
|
47
|
+
502,
|
|
48
|
+
`All ${result.failureCount} attachments failed to deliver`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
log.info({ chatId, hasText: !!text }, "Slack reply delivered (direct)");
|
|
54
|
+
return { ok: true, ts: sentTs };
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
async sendTyping(ctx, payload) {
|
|
58
|
+
const placeholderTs = await sendSlackTypingIndicator(
|
|
59
|
+
payload.chatId,
|
|
60
|
+
ctx.params.threadTs,
|
|
61
|
+
);
|
|
62
|
+
log.debug(
|
|
63
|
+
{ chatId: payload.chatId },
|
|
64
|
+
"Slack typing indicator delivered (direct)",
|
|
65
|
+
);
|
|
66
|
+
return { ok: true, ts: placeholderTs };
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async sendReaction(_ctx, payload) {
|
|
70
|
+
const reaction = payload.reaction;
|
|
71
|
+
if (!reaction) return { ok: true };
|
|
72
|
+
await sendSlackReaction(
|
|
73
|
+
payload.chatId,
|
|
74
|
+
reaction.name,
|
|
75
|
+
reaction.messageTs,
|
|
76
|
+
reaction.action,
|
|
77
|
+
);
|
|
78
|
+
return { ok: true };
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async setThreadStatus(_ctx, payload) {
|
|
82
|
+
const status = payload.assistantThreadStatus;
|
|
83
|
+
if (!status) return { ok: true };
|
|
84
|
+
await sendSlackAssistantThreadStatus(
|
|
85
|
+
status.channel,
|
|
86
|
+
status.threadTs,
|
|
87
|
+
status.status,
|
|
88
|
+
status.loadingMessages,
|
|
89
|
+
);
|
|
90
|
+
return { ok: true };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
|
|
2
|
+
|
|
3
|
+
import { getLogger } from "../../../util/logger.js";
|
|
4
|
+
import type { ChannelTransport } from "../channel-transport.js";
|
|
5
|
+
import {
|
|
6
|
+
sendTelegramAttachments,
|
|
7
|
+
sendTelegramReply,
|
|
8
|
+
sendTelegramTypingIndicator,
|
|
9
|
+
} from "./send.js";
|
|
10
|
+
|
|
11
|
+
const log = getLogger("telegram-transport");
|
|
12
|
+
|
|
13
|
+
export const telegramTransport: ChannelTransport = {
|
|
14
|
+
channel: "telegram",
|
|
15
|
+
|
|
16
|
+
async deliver(_ctx, payload) {
|
|
17
|
+
const { chatId, text, attachments, approval } = payload;
|
|
18
|
+
|
|
19
|
+
if (text) {
|
|
20
|
+
await sendTelegramReply(chatId, text, approval);
|
|
21
|
+
} else if (approval) {
|
|
22
|
+
await sendTelegramReply(
|
|
23
|
+
chatId,
|
|
24
|
+
approval.plainTextFallback || "Approval required",
|
|
25
|
+
approval,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (attachments && attachments.length > 0) {
|
|
30
|
+
const result = await sendTelegramAttachments(chatId, attachments);
|
|
31
|
+
if (result.allFailed && !text) {
|
|
32
|
+
throw new ChannelDeliveryError(
|
|
33
|
+
502,
|
|
34
|
+
`All ${result.failureCount} attachments failed to deliver`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
log.info({ chatId, hasText: !!text }, "Telegram reply delivered (direct)");
|
|
40
|
+
return { ok: true };
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async sendTyping(_ctx, payload) {
|
|
44
|
+
await sendTelegramTypingIndicator(payload.chatId);
|
|
45
|
+
log.debug(
|
|
46
|
+
{ chatId: payload.chatId },
|
|
47
|
+
"Telegram typing indicator delivered (direct)",
|
|
48
|
+
);
|
|
49
|
+
return { ok: true };
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
|
|
2
|
+
|
|
3
|
+
import { getLogger } from "../../../util/logger.js";
|
|
4
|
+
import type { ChannelTransport } from "../channel-transport.js";
|
|
5
|
+
import { sendWhatsAppAttachments, sendWhatsAppReply } from "./send.js";
|
|
6
|
+
|
|
7
|
+
const log = getLogger("whatsapp-transport");
|
|
8
|
+
|
|
9
|
+
export const whatsappTransport: ChannelTransport = {
|
|
10
|
+
channel: "whatsapp",
|
|
11
|
+
|
|
12
|
+
async deliver(_ctx, payload) {
|
|
13
|
+
const { chatId, text, attachments, approval } = payload;
|
|
14
|
+
|
|
15
|
+
if (text) {
|
|
16
|
+
await sendWhatsAppReply(chatId, text, approval);
|
|
17
|
+
} else if (approval) {
|
|
18
|
+
await sendWhatsAppReply(
|
|
19
|
+
chatId,
|
|
20
|
+
approval.plainTextFallback || "Approval required",
|
|
21
|
+
approval,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (attachments && attachments.length > 0) {
|
|
26
|
+
const result = await sendWhatsAppAttachments(chatId, attachments);
|
|
27
|
+
if (result.allFailed && !text) {
|
|
28
|
+
throw new ChannelDeliveryError(
|
|
29
|
+
502,
|
|
30
|
+
`All ${result.failureCount} attachments failed to deliver`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
log.info({ chatId, hasText: !!text }, "WhatsApp reply delivered (direct)");
|
|
36
|
+
return { ok: true };
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -37,14 +37,6 @@ mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
|
37
37
|
getGuardianDelivery: async () => null,
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
|
-
// Use the real destination-resolver (DB-free via the local-read stub below)
|
|
41
|
-
// so this mock does not leak into destination-resolver.test.ts under a shared
|
|
42
|
-
// bun-test invocation. With no guardian, the resolver still yields a vellum
|
|
43
|
-
// destination, which is all these tests exercise.
|
|
44
|
-
mock.module("../../contacts/contact-store.js", () => ({
|
|
45
|
-
findGuardianForChannel: () => null,
|
|
46
|
-
}));
|
|
47
|
-
|
|
48
40
|
mock.module("../conversation-pairing.js", () => ({
|
|
49
41
|
pairDeliveryWithConversation: async () => ({
|
|
50
42
|
conversationId: undefined,
|