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:
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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',
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user