// ═══════════════════════════════════════════════════════════════ // FRONTEND STATE & WIRING CHANGES // These are modifications to existing files (not full replacements). // The full controller and view files are provided separately. // ═══════════════════════════════════════════════════════════════ // ── app-view-state.ts ──────────────────────────────────────── // Add these properties to the AppViewState interface: recentArchivedSessions: Array<{ sessionId: string; sessionKey: string; displayName?: string; firstMessage?: string; updatedAt: number; }> | null; archivedSessionsLoading: boolean; archivedSessionsResult: import("./controllers/sessions.ts").ArchivedSessionsResult | null; archivedSessionsError: string | null; archivedSessionsSearch: string; liveSessionsPage: number; liveSessionsPageSize: number; archivedSessionsPage: number; archivedSessionsPageSize: number; // ── app.ts ─────────────────────────────────────────────────── // Add these @state() decorated properties to the OpenClawApp class: @state() recentArchivedSessions: Array<{ sessionId: string; sessionKey: string; displayName?: string; firstMessage?: string; updatedAt: number }> | null = null; @state() archivedSessionsLoading = false; @state() archivedSessionsResult: import("./controllers/sessions.ts").ArchivedSessionsResult | null = null; @state() archivedSessionsError: string | null = null; @state() archivedSessionsSearch = ""; @state() liveSessionsPage = 1; @state() liveSessionsPageSize = 10; @state() archivedSessionsPage = 1; @state() archivedSessionsPageSize = 10; // ── app-chat.ts ────────────────────────────────────────────── // 1. Add import: import { loadSessions, loadRecentArchivedSessions } from "./controllers/sessions.ts"; // 2. In refreshChat(), add loadRecentArchivedSessions call: export async function refreshChat(host: ChatHost, opts?: { scheduleScroll?: boolean }) { await Promise.all([ loadChatHistory(host as unknown as OpenClawApp), loadSessions(host as unknown as OpenClawApp, { activeMinutes: CHAT_SESSIONS_ACTIVE_MINUTES, }), loadRecentArchivedSessions(host as unknown as OpenClawApp), // <── ADD THIS refreshChatAvatar(host), ]); // ... } // ── app-settings.ts ────────────────────────────────────────── // 1. Add import: import { loadSessions, loadArchivedSessions } from "./controllers/sessions.ts"; // 2. In the sessions tab loader, add archived sessions load with pagination: if (host.tab === "sessions") { await loadSessions(host as unknown as OpenClawApp); const app = host as unknown as OpenClawApp; await loadArchivedSessions(app, undefined, undefined, app.archivedSessionsPageSize, 0); } // ── app-render.ts ──────────────────────────────────────────── // 1. Add imports: import { deleteSessionAndRefresh, loadSessions, patchSession, loadArchivedSessions, resumeSession, renameSession, deleteArchivedSession } from "./controllers/sessions.ts"; // 2. In the renderSessions() call (tab === "sessions"), add all these props: // Live sessions pagination livePageSize: state.liveSessionsPageSize, livePage: state.liveSessionsPage, onLivePageSizeChange: (size: number) => { state.liveSessionsPageSize = size; state.liveSessionsPage = 1; }, onLivePageChange: (page: number) => { state.liveSessionsPage = page; }, // Archive active session onArchive: async (key) => { const confirmed = window.confirm(`Archive session "${key}"?\n\nThis will deactivate the session and move it to session history.`); if (!confirmed) return; try { await state.client?.request("sessions.archive", { key }); await loadSessions(state); await loadArchivedSessions(state, undefined, state.archivedSessionsSearch || undefined, state.archivedSessionsPageSize, (state.archivedSessionsPage - 1) * state.archivedSessionsPageSize); } catch (err) { state.sessionsError = String(err); } }, // Archived sessions archivedLoading: state.archivedSessionsLoading, archivedResult: state.archivedSessionsResult, archivedError: state.archivedSessionsError, archivedSearch: state.archivedSessionsSearch, archivedPageSize: state.archivedSessionsPageSize, archivedPage: state.archivedSessionsPage, onArchivedSearchChange: (search: string) => { state.archivedSessionsSearch = search; state.archivedSessionsPage = 1; void loadArchivedSessions(state, undefined, search, state.archivedSessionsPageSize, 0); }, onArchivedRefresh: () => { void loadArchivedSessions(state, undefined, state.archivedSessionsSearch || undefined, state.archivedSessionsPageSize, (state.archivedSessionsPage - 1) * state.archivedSessionsPageSize); }, onArchivedPageSizeChange: (size: number) => { state.archivedSessionsPageSize = size; state.archivedSessionsPage = 1; void loadArchivedSessions(state, undefined, state.archivedSessionsSearch || undefined, size, 0); }, onArchivedPageChange: (page: number) => { state.archivedSessionsPage = page; void loadArchivedSessions(state, undefined, state.archivedSessionsSearch || undefined, state.archivedSessionsPageSize, (page - 1) * state.archivedSessionsPageSize); }, onResumeSession: async (sessionId: string) => { await resumeSession(state, sessionId); await loadSessions(state); await loadArchivedSessions(state, undefined, state.archivedSessionsSearch || undefined, state.archivedSessionsPageSize, (state.archivedSessionsPage - 1) * state.archivedSessionsPageSize); }, onRenameSession: (sessionId: string, name: string) => { void renameSession(state, sessionId, name); }, onDeleteArchivedSession: (sessionId: string) => { void deleteArchivedSession(state, sessionId); }, // ── app-render.helpers.ts ──────────────────────────────────── // 1. In resolveSessionOptions(), add isUserSession filter to exclude cron/subagent/openai: const isUserSession = (key: string) => { return !key.includes(":cron:") && !key.includes(":subagent:") && !key.includes(":openai:"); }; // Then in the sessions loop, add isUserSession check: if (sessions?.sessions) { const filtered = sessions.sessions .filter((s) => matchesAgent(s.key) && isUserSession(s.key)) // <── ADD isUserSession .toSorted((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)); // ... } // 2. Add renderRecentArchivedOptions function (uses sessionId, not sessionKey): function renderRecentArchivedOptions( state: AppViewState, _activeOptions: Array<{ key: string }>, ) { const archived = state.recentArchivedSessions; if (!archived || archived.length === 0) { return html` `; } // Use __archived__:{sessionId} as value since all share the same sessionKey return html` ${archived.slice(0, 10).map((s) => { const name = s.displayName || (s.firstMessage ? s.firstMessage.slice(0, 40) + (s.firstMessage.length > 40 ? "…" : "") : s.sessionId); return html``; })} `; } // 3. In the session , add: ${renderRecentArchivedOptions(state, sessionOptions)}