feat(ui): delete sessions from Control UI

This commit is contained in:
Peter Steinberger
2026-01-16 22:33:38 +00:00
parent 76d3d58b5c
commit 929b86e302
5 changed files with 43 additions and 8 deletions

View File

@@ -27,6 +27,7 @@
- TUI: show provider/model labels for the active session and default model.
- Heartbeat: add per-agent heartbeat configuration and multi-agent docs example.
- UI: show gateway auth guidance + doc link on unauthorized Control UI connections.
- UI: add session deletion action in Control UI sessions list. (#1017) — thanks @Szpadel.
- Security: warn on weak model tiers (Haiku, below GPT-5, below Claude 4.5) in `clawdbot security audit`.
- Apps: store node auth tokens encrypted (Keychain/SecurePrefs).
- Cron: isolated cron jobs now start a fresh session id on every run to prevent context buildup.

View File

@@ -537,7 +537,7 @@
.table-head,
.table-row {
display: grid;
grid-template-columns: 1.4fr 0.8fr 0.8fr 0.7fr 0.8fr 0.8fr;
grid-template-columns: 1.4fr 1fr 0.8fr 0.7fr 0.8fr 0.8fr 0.8fr 0.8fr 0.6fr;
gap: 12px;
align-items: center;
}

View File

@@ -57,7 +57,7 @@ import {
updateTelegramForm,
} from "./controllers/connections";
import { loadPresence } from "./controllers/presence";
import { loadSessions, patchSession } from "./controllers/sessions";
import { deleteSession, loadSessions, patchSession } from "./controllers/sessions";
import {
installSkill,
loadSkills,
@@ -277,11 +277,12 @@ export function renderApp(state: AppViewState) {
state.sessionsFilterLimit = next.limit;
state.sessionsIncludeGlobal = next.includeGlobal;
state.sessionsIncludeUnknown = next.includeUnknown;
},
onRefresh: () => loadSessions(state),
onPatch: (key, patch) => patchSession(state, key, patch),
})
: nothing}
},
onRefresh: () => loadSessions(state),
onPatch: (key, patch) => patchSession(state, key, patch),
onDelete: (key) => deleteSession(state, key),
})
: nothing}
${state.tab === "cron"
? renderCron({

View File

@@ -60,3 +60,22 @@ export async function patchSession(
state.sessionsError = String(err);
}
}
export async function deleteSession(state: SessionsState, key: string) {
if (!state.client || !state.connected) return;
if (state.sessionsLoading) return;
const confirmed = window.confirm(
`Delete session "${key}"?\n\nDeletes the session entry and archives its transcript.`,
);
if (!confirmed) return;
state.sessionsLoading = true;
state.sessionsError = null;
try {
await state.client.request("sessions.delete", { key, deleteTranscript: true });
await loadSessions(state);
} catch (err) {
state.sessionsError = String(err);
} finally {
state.sessionsLoading = false;
}
}

View File

@@ -29,6 +29,7 @@ export type SessionsProps = {
reasoningLevel?: string | null;
},
) => void;
onDelete: (key: string) => void;
};
const THINK_LEVELS = ["", "off", "minimal", "low", "medium", "high"] as const;
@@ -157,10 +158,13 @@ export function renderSessions(props: SessionsProps) {
<div>Thinking</div>
<div>Verbose</div>
<div>Reasoning</div>
<div>Actions</div>
</div>
${rows.length === 0
? html`<div class="muted">No sessions found.</div>`
: rows.map((row) => renderRow(row, props.basePath, props.onPatch))}
: rows.map((row) =>
renderRow(row, props.basePath, props.onPatch, props.onDelete, props.loading),
)}
</div>
</section>
`;
@@ -170,6 +174,8 @@ function renderRow(
row: GatewaySessionRow,
basePath: string,
onPatch: SessionsProps["onPatch"],
onDelete: SessionsProps["onDelete"],
disabled: boolean,
) {
const updated = row.updatedAt ? formatAgo(row.updatedAt) : "n/a";
const rawThinking = row.thinkingLevel ?? "";
@@ -196,6 +202,7 @@ function renderRow(
<div>
<select
.value=${thinking}
?disabled=${disabled}
@change=${(e: Event) => {
const value = (e.target as HTMLSelectElement).value;
onPatch(row.key, {
@@ -211,6 +218,7 @@ function renderRow(
<div>
<select
.value=${verbose}
?disabled=${disabled}
@change=${(e: Event) => {
const value = (e.target as HTMLSelectElement).value;
onPatch(row.key, { verboseLevel: value || null });
@@ -224,6 +232,7 @@ function renderRow(
<div>
<select
.value=${reasoning}
?disabled=${disabled}
@change=${(e: Event) => {
const value = (e.target as HTMLSelectElement).value;
onPatch(row.key, { reasoningLevel: value || null });
@@ -234,6 +243,11 @@ function renderRow(
)}
</select>
</div>
<div>
<button class="btn danger" ?disabled=${disabled} @click=${() => onDelete(row.key)}>
Delete
</button>
</div>
</div>
`;
}