const MAX_STEPS = 100; export class ComputerUseSession { constructor({ stepsMax = 20, grantMinutes = 3, mode = 'act', clock = () => Date.now(), audit } = {}) { this.clock = clock; this.mode = mode; this.grantMinutes = grantMinutes; this.stepsMax = Math.min(MAX_STEPS, Math.max(1, stepsMax)); this.active = false; this.stepsUsed = 0; this.sessionId = null; this.backend = 'none'; this.expiresAt = null; this.audit = audit; } grant({ persist = false, monitors = 'focused' } = {}) { if (this.mode === 'off') throw new Error('Desktop access is disabled in Settings'); this.active = true; this.stepsUsed = 0; this.sessionId = `cu_${this.clock().toString(36)}`; this.expiresAt = this.clock() + this.grantMinutes * 60 * 1000; return { session_id: this.sessionId, restore_token_present: Boolean(persist), monitors, backend: this.backend }; } setBackend(backend) { this.backend = ['portal-ei', 'portal-notify', 'ydotool', 'none'].includes(backend) ? backend : 'none'; return this.backend; } revoke() { this.active = false; this.sessionId = null; this.expiresAt = null; this.backend = 'none'; Promise.resolve(this.audit?.wipeTemp?.()).catch(() => {}); } status() { if (this.active && this.expiresAt != null && this.clock() >= this.expiresAt) this.revoke(); return { active: this.active, mode: this.mode, steps_used: this.stepsUsed, steps_max: this.stepsMax, grant_expires_at: this.expiresAt, backend: this.backend, }; } assertActive() { if (!this.active) throw new Error('computer-use grant is inactive'); if (this.expiresAt && this.clock() >= this.expiresAt) { this.revoke(); throw new Error('computer-use grant expired'); } } beginStep() { this.assertActive(); if (this.mode !== 'act') throw new Error('Desktop access is observe-only'); if (this.stepsUsed >= this.stepsMax) throw new Error('computer-use step budget exhausted'); this.stepsUsed += 1; } }