diff --git a/src/frontends/telegram/confirmations.test.ts b/src/frontends/telegram/confirmations.test.ts new file mode 100644 index 0000000..0ad216c --- /dev/null +++ b/src/frontends/telegram/confirmations.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect } from 'vitest'; +import { formatConfirmationMessage, parseConfirmationCallback } from './confirmations.js'; + +describe('formatConfirmationMessage', () => { + it('formats tool and args into readable message', () => { + const message = formatConfirmationMessage('shell.exec', { cmd: 'ls -la' }); + + expect(message).toContain('shell.exec'); + expect(message).toContain('ls -la'); + }); +}); + +describe('parseConfirmationCallback', () => { + it('parses approve callback data', () => { + const result = parseConfirmationCallback('confirm:abc123:approve'); + + expect(result).toEqual({ + id: 'abc123', + approved: true, + }); + }); + + it('parses deny callback data', () => { + const result = parseConfirmationCallback('confirm:abc123:deny'); + + expect(result).toEqual({ + id: 'abc123', + approved: false, + }); + }); + + it('returns null for invalid callback data', () => { + expect(parseConfirmationCallback('invalid')).toBeNull(); + expect(parseConfirmationCallback('other:data')).toBeNull(); + }); +}); diff --git a/src/frontends/telegram/confirmations.ts b/src/frontends/telegram/confirmations.ts new file mode 100644 index 0000000..4227917 --- /dev/null +++ b/src/frontends/telegram/confirmations.ts @@ -0,0 +1,43 @@ +import { InlineKeyboard } from 'grammy'; + +export function formatConfirmationMessage(tool: string, args: Record): string { + const argsStr = Object.entries(args) + .map(([key, value]) => ` ${key}: ${JSON.stringify(value)}`) + .join('\n'); + + return `🔐 **Confirmation Required** + +Tool: \`${tool}\` +Arguments: +${argsStr || ' (none)'} + +Approve this action?`; +} + +export function createConfirmationKeyboard(confirmationId: string): InlineKeyboard { + return new InlineKeyboard() + .text('✅ Approve', `confirm:${confirmationId}:approve`) + .text('❌ Deny', `confirm:${confirmationId}:deny`); +} + +export interface ConfirmationCallbackData { + id: string; + approved: boolean; +} + +export function parseConfirmationCallback(data: string): ConfirmationCallbackData | null { + const parts = data.split(':'); + if (parts.length !== 3 || parts[0] !== 'confirm') { + return null; + } + + const [, id, action] = parts; + if (action !== 'approve' && action !== 'deny') { + return null; + } + + return { + id, + approved: action === 'approve', + }; +} diff --git a/src/frontends/telegram/index.ts b/src/frontends/telegram/index.ts index fcec8bf..8981511 100644 --- a/src/frontends/telegram/index.ts +++ b/src/frontends/telegram/index.ts @@ -1,2 +1,8 @@ export { createTelegramBot, type TelegramBotConfig } from './bot.js'; export { isAllowedChat, createMessageHandler, createResetHandler } from './handlers.js'; +export { + formatConfirmationMessage, + createConfirmationKeyboard, + parseConfirmationCallback, + type ConfirmationCallbackData, +} from './confirmations.js';