import { html, nothing } from 'lit'; import { LightElement } from '../../lib/base.js'; export class InboxPage extends LightElement { static properties = { visible: { type: Boolean }, _data: { state: true }, _error: { state: true }, }; constructor() { super(); this.visible = false; this._data = null; this._error = null; this._pollTimer = null; this._expanded = new Set(); } updated(changed) { if (!changed.has('visible')) return; if (this.visible) { this._load(); this._startPolling(); } else { this._stopPolling(); } } disconnectedCallback() { super.disconnectedCallback(); this._stopPolling(); } _startPolling() { this._stopPolling(); this._pollTimer = setInterval(() => this._load(), 8000); } _stopPolling() { if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; } } async _load() { try { const res = await fetch('/api/inbox'); if (!res.ok) throw new Error(`HTTP ${res.status}`); this._data = await res.json(); this._error = null; } catch (e) { this._error = e.message; } } async _resolveApproval(requestId, action, note = '', toolCallId = null) { try { // Live items resolve by request_id; DB-persisted (post-restart) items carry // request_id 0 → resolve by the durable, source-agnostic tool_call_id. const url = requestId ? `/api/inbox/approvals/${requestId}/resolve` : `/api/tools/${toolCallId}/resolve`; const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action, note }), }); if (!res.ok) throw new Error(await res.text()); await this._load(); } catch (e) { this._error = e.message; } } _rejectWithNote(requestId, toolCallId) { const note = prompt('Rejection reason (optional):') ?? ''; this._resolveApproval(requestId, 'reject', note, toolCallId); } async _resolveClarification(requestId, inputEl) { const answer = inputEl.value.trim(); if (!answer) return; try { const res = await fetch(`/api/inbox/clarifications/${requestId}/resolve`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ answer }), }); if (!res.ok) throw new Error(await res.text()); await this._load(); } catch (e) { this._error = e.message; } } _toggleRaw(id) { if (this._expanded.has(id)) this._expanded.delete(id); else this._expanded.add(id); this.requestUpdate(); } _keyArgs(args) { const entries = []; for (const key of ['path', 'command', 'url', 'origin', 'destination', 'name', 'message', 'query']) { if (args[key] !== undefined) { let val = args[key]; if (typeof val === 'object') val = JSON.stringify(val); entries.push({ key, value: String(val) }); } } return entries; } _fmt(iso) { if (!iso) return ''; return new Date(iso).toLocaleString(undefined, { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit', }); } _renderApprovalCard(item) { const id = `raw-${item.request_id}`; const open = this._expanded.has(id); const label = item.context_label ?? item.source; const args = item.arguments ?? {}; const keyArgs = this._keyArgs(args); return html`
Approval ${label} ${this._fmt(item.created_at)}
${item.tool_name} ${item.agent_id}
${keyArgs.length > 0 ? html`
${keyArgs.map(kv => html`
${kv.key} ${kv.value}
`)}
` : nothing}
${JSON.stringify(args, null, 2)}
`; } _renderClarificationCard(item) { const label = item.context_label ?? item.source; return html`
Question ${label} ${this._fmt(item.created_at)}
${item.title}
${item.question}
${item.suggested_answers?.length ? html`
${item.suggested_answers.map(a => html` `)}
` : nothing}
`; } render() { if (!this.visible) return nothing; const approvals = this._data?.approvals ?? []; const clarifications = this._data?.clarifications ?? []; const total = approvals.length + clarifications.length; return html`
Inbox ${total > 0 ? html`${total}` : nothing}
${this._error ? html`
${this._error}
` : nothing} ${total === 0 ? html`

No pending requests

` : html`
${approvals.length > 0 ? html` ${approvals.map(item => this._renderApprovalCard(item))} ` : nothing} ${clarifications.length > 0 ? html` ${clarifications.map(item => this._renderClarificationCard(item))} ` : nothing}
`}
`; } } customElements.define('inbox-page', InboxPage);