forked from bai/curriculum-project-hub
feat: add interactive approval/confirmation cards
- sendApprovalCard: interactive card with configurable buttons - resolveCard: patch card to show resolution state (green/red) - ApprovalManager: register/resolve lifecycle with 5min timeout - request_approval MCP tool: agent can ask user for confirmation - Wire onCardAction in trigger handler and server startup - Add unit tests (5) for card JSON, manager, and routing
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
export interface ApprovalResult {
|
||||
readonly action: string;
|
||||
readonly resolvedBy: string;
|
||||
}
|
||||
|
||||
export interface PendingApproval {
|
||||
readonly messageId: string;
|
||||
readonly chatId: string;
|
||||
readonly resolve: (result: ApprovalResult) => void;
|
||||
}
|
||||
|
||||
export interface ApprovalManagerOptions {
|
||||
readonly timeoutMs?: number;
|
||||
}
|
||||
|
||||
export class ApprovalManager {
|
||||
private readonly timeoutMs: number;
|
||||
private pending = new Map<string, PendingApproval>();
|
||||
|
||||
constructor(options: ApprovalManagerOptions = {}) {
|
||||
this.timeoutMs = options.timeoutMs ?? 5 * 60 * 1000;
|
||||
}
|
||||
|
||||
register(messageId: string, chatId: string): Promise<ApprovalResult> {
|
||||
return new Promise<ApprovalResult>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
this.pending.delete(messageId);
|
||||
reject(new Error(`approval timed out after ${this.timeoutMs}ms`));
|
||||
}, this.timeoutMs);
|
||||
const unref = (timeout as { unref?: () => void }).unref;
|
||||
if (unref !== undefined) unref.call(timeout);
|
||||
|
||||
this.pending.set(messageId, {
|
||||
messageId,
|
||||
chatId,
|
||||
resolve: (result) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(result);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resolve(messageId: string, action: string, resolvedBy: string): void {
|
||||
const pending = this.pending.get(messageId);
|
||||
if (pending === undefined) return;
|
||||
this.pending.delete(messageId);
|
||||
pending.resolve({ action, resolvedBy });
|
||||
}
|
||||
|
||||
hasPending(messageId: string): boolean {
|
||||
return this.pending.has(messageId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user