feat: add Telegram confirmation UI components

Implements Phase 2 Task 7 - Telegram Confirmation UI:
- formatConfirmationMessage(): formats tool and args into readable message
- createConfirmationKeyboard(): creates approve/deny inline keyboard
- parseConfirmationCallback(): parses callback data from button clicks
- Full test coverage with vitest

All tests passing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-02-03 00:29:32 -08:00
parent 782163cda6
commit 298f87615d
3 changed files with 85 additions and 0 deletions
@@ -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();
});
});
+43
View File
@@ -0,0 +1,43 @@
import { InlineKeyboard } from 'grammy';
export function formatConfirmationMessage(tool: string, args: Record<string, unknown>): 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',
};
}
+6
View File
@@ -1,2 +1,8 @@
export { createTelegramBot, type TelegramBotConfig } from './bot.js'; export { createTelegramBot, type TelegramBotConfig } from './bot.js';
export { isAllowedChat, createMessageHandler, createResetHandler } from './handlers.js'; export { isAllowedChat, createMessageHandler, createResetHandler } from './handlers.js';
export {
formatConfirmationMessage,
createConfirmationKeyboard,
parseConfirmationCallback,
type ConfirmationCallbackData,
} from './confirmations.js';