// ═══════════════════════════════════════════════════════════════
// 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`
`;
}
// 3. In the session