feat(v2/telegram): send pairing-success confirmation to paired chat
After a Telegram pair-code is successfully consumed, send a one-shot "Pairing success! I'm spinning up the agent now, you'll get a message from them shortly." reply to the same chat so the user knows the code was accepted before the agent's own welcome DM arrives. Best-effort: any sendMessage failure is logged but not rethrown, so a Telegram outage can't undo a successful pairing or trigger the interceptor's fail-open path. Also includes a no-op prettier reformat in chat-sdk-bridge.ts that the husky hook missed in the previous commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -124,9 +124,7 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
|||||||
// Project chat-sdk's nested author into the flat sender fields the router
|
// Project chat-sdk's nested author into the flat sender fields the router
|
||||||
// expects (see src/router.ts extractAndUpsertUser). Native adapters already
|
// expects (see src/router.ts extractAndUpsertUser). Native adapters already
|
||||||
// populate these directly; this brings chat-sdk adapters in line.
|
// populate these directly; this brings chat-sdk adapters in line.
|
||||||
const author = serialized.author as
|
const author = serialized.author as { userId?: string; fullName?: string; userName?: string } | undefined;
|
||||||
| { userId?: string; fullName?: string; userName?: string }
|
|
||||||
| undefined;
|
|
||||||
if (author) {
|
if (author) {
|
||||||
const name = author.fullName ?? author.userName;
|
const name = author.fullName ?? author.userName;
|
||||||
serialized.senderId = author.userId;
|
serialized.senderId = author.userId;
|
||||||
|
|||||||
@@ -85,9 +85,35 @@ function readInboundFields(message: InboundMessage): InboundFields {
|
|||||||
* the user to owner if the instance has no owner yet, and short-circuits.
|
* the user to owner if the instance has no owner yet, and short-circuits.
|
||||||
* On miss: forwards to the host.
|
* On miss: forwards to the host.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Send a one-shot confirmation back to the paired chat. Best-effort — failures
|
||||||
|
* are logged but never propagated, so a Telegram outage can't undo a successful
|
||||||
|
* pairing or trigger the interceptor's fail-open path.
|
||||||
|
*/
|
||||||
|
async function sendPairingConfirmation(token: string, platformId: string): Promise<void> {
|
||||||
|
const chatId = platformId.split(':').slice(1).join(':');
|
||||||
|
if (!chatId) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
chat_id: chatId,
|
||||||
|
text: "Pairing success! I'm spinning up the agent now, you'll get a message from them shortly.",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
log.warn('Telegram pairing confirmation non-OK', { status: res.status });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log.warn('Telegram pairing confirmation failed', { err });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createPairingInterceptor(
|
function createPairingInterceptor(
|
||||||
botUsernamePromise: Promise<string | null>,
|
botUsernamePromise: Promise<string | null>,
|
||||||
hostOnInbound: ChannelSetup['onInbound'],
|
hostOnInbound: ChannelSetup['onInbound'],
|
||||||
|
token: string,
|
||||||
): ChannelSetup['onInbound'] {
|
): ChannelSetup['onInbound'] {
|
||||||
return (platformId, threadId, message) => {
|
return (platformId, threadId, message) => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
@@ -159,6 +185,8 @@ function createPairingInterceptor(
|
|||||||
promotedToOwner,
|
promotedToOwner,
|
||||||
intent: consumed.intent,
|
intent: consumed.intent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await sendPairingConfirmation(token, platformId);
|
||||||
})().catch((err) => {
|
})().catch((err) => {
|
||||||
log.error('Telegram pairing interceptor error', { err });
|
log.error('Telegram pairing interceptor error', { err });
|
||||||
// Fail open: pass through so a pairing bug doesn't break normal traffic.
|
// Fail open: pass through so a pairing bug doesn't break normal traffic.
|
||||||
@@ -191,7 +219,7 @@ registerChannelAdapter('telegram', {
|
|||||||
async setup(hostConfig: ChannelSetup) {
|
async setup(hostConfig: ChannelSetup) {
|
||||||
const intercepted: ChannelSetup = {
|
const intercepted: ChannelSetup = {
|
||||||
...hostConfig,
|
...hostConfig,
|
||||||
onInbound: createPairingInterceptor(botUsernamePromise, hostConfig.onInbound),
|
onInbound: createPairingInterceptor(botUsernamePromise, hostConfig.onInbound, token),
|
||||||
};
|
};
|
||||||
return withRetry(() => bridge.setup(intercepted), 'bridge.setup');
|
return withRetry(() => bridge.setup(intercepted), 'bridge.setup');
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user