Files
AgentHub/client/src/lib/stopSession.ts
T
nntrivi2001 6099841cef fix(sessions): archiving a live session stops its pane
Archive on a session a live pane held only hid its row, and the list
deliberately does not bring such a tab back, so the pane kept running
with nothing on screen showing it — a member archived five sessions
believing them closed and kept ~2.7 GB of claude/omp/postgres-mcp alive
for up to two days. Archive on a live session is now a Stop (endRuns,
the tab X's confirm, DELETE /api/run/:id?archive=1) in the sidebar row,
the workspace home's Recents and their right-click menu.
2026-10-29 14:26:25 +07:00

59 lines
2.1 KiB
TypeScript

/**
* @file stopSession — the one way the UI ends live panes: **Stop** kills the
* pane and ARCHIVES the session it held (`DELETE /api/run/:id?archive=1`), so
* a session the user closed leaves its workspace's lists; **Sleep** kills the
* pane and KEEPS the session listed, to be resumed later by sending it a
* message. Both ask first when any pane holds an AI agent (it may be
* mid-turn); a plain shell holds no session, so it neither asks nor sleeps.
* Used by the tab bar (`ConsoleTabs`), the split panes (`Workspace`) and a
* session's right-click menu (`SessionMenu`), and a live session's Archive
* (`WorkspaceSessionList`, `WorkspaceHome`) — archiving never leaves a pane running.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import type { TFunction } from "i18next";
import { api, type RunHandle } from "./api";
import { confirmDialog } from "./confirm";
import { toast } from "./toast";
/** Whether a pane can sleep: an AI pane (a shell has no session to keep). */
export const canSleep = (run: RunHandle | null | undefined): boolean =>
!!run && run.tool !== "shell";
/**
* Stop (archive) or sleep (keep) `runs` after a confirm, and return the ids
* that were ended. `name` titles a one-pane confirm; `t` is the `lanes`
* namespace.
*/
export async function endRuns(
runs: RunHandle[],
{ sleep = false, name, t }: { sleep?: boolean; name: string; t: TFunction }
): Promise<string[]> {
const list = sleep ? runs.filter(canSleep) : runs;
if (!list.length) return [];
const kind = sleep ? "sleep" : "stop";
if (
list.some((r) => r.tool !== "shell") &&
!(await confirmDialog({
title:
list.length > 1
? t(`tabs.${kind}GroupTitle`, { count: list.length })
: t(`tabs.${kind}Title`, { name }),
message: t(`tabs.${kind}Body`),
confirmLabel: t(`tabs.${kind}Confirm`),
destructive: !sleep,
}))
)
return [];
const ended: string[] = [];
for (const r of list) {
try {
await api.run.kill(r.id, { archive: !sleep });
ended.push(r.id);
} catch (err) {
toast.error(err);
}
}
return ended;
}