Spaces:
Paused
Paused
| import { randomUUID } from "node:crypto"; | |
| import type { ExecApprovalDecision } from "../infra/exec-approvals.js"; | |
| export type ExecApprovalRequestPayload = { | |
| command: string; | |
| cwd?: string | null; | |
| host?: string | null; | |
| security?: string | null; | |
| ask?: string | null; | |
| agentId?: string | null; | |
| resolvedPath?: string | null; | |
| sessionKey?: string | null; | |
| }; | |
| export type ExecApprovalRecord = { | |
| id: string; | |
| request: ExecApprovalRequestPayload; | |
| createdAtMs: number; | |
| expiresAtMs: number; | |
| resolvedAtMs?: number; | |
| decision?: ExecApprovalDecision; | |
| resolvedBy?: string | null; | |
| }; | |
| type PendingEntry = { | |
| record: ExecApprovalRecord; | |
| resolve: (decision: ExecApprovalDecision | null) => void; | |
| reject: (err: Error) => void; | |
| timer: ReturnType<typeof setTimeout>; | |
| }; | |
| export class ExecApprovalManager { | |
| private pending = new Map<string, PendingEntry>(); | |
| create( | |
| request: ExecApprovalRequestPayload, | |
| timeoutMs: number, | |
| id?: string | null, | |
| ): ExecApprovalRecord { | |
| const now = Date.now(); | |
| const resolvedId = id && id.trim().length > 0 ? id.trim() : randomUUID(); | |
| const record: ExecApprovalRecord = { | |
| id: resolvedId, | |
| request, | |
| createdAtMs: now, | |
| expiresAtMs: now + timeoutMs, | |
| }; | |
| return record; | |
| } | |
| async waitForDecision( | |
| record: ExecApprovalRecord, | |
| timeoutMs: number, | |
| ): Promise<ExecApprovalDecision | null> { | |
| return await new Promise<ExecApprovalDecision | null>((resolve, reject) => { | |
| const timer = setTimeout(() => { | |
| this.pending.delete(record.id); | |
| resolve(null); | |
| }, timeoutMs); | |
| this.pending.set(record.id, { record, resolve, reject, timer }); | |
| }); | |
| } | |
| resolve(recordId: string, decision: ExecApprovalDecision, resolvedBy?: string | null): boolean { | |
| const pending = this.pending.get(recordId); | |
| if (!pending) { | |
| return false; | |
| } | |
| clearTimeout(pending.timer); | |
| pending.record.resolvedAtMs = Date.now(); | |
| pending.record.decision = decision; | |
| pending.record.resolvedBy = resolvedBy ?? null; | |
| this.pending.delete(recordId); | |
| pending.resolve(decision); | |
| return true; | |
| } | |
| getSnapshot(recordId: string): ExecApprovalRecord | null { | |
| const entry = this.pending.get(recordId); | |
| return entry?.record ?? null; | |
| } | |
| } | |