forked from bai/curriculum-project-hub
4736610cf3
- 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
55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
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);
|
|
}
|
|
}
|