mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
fix(tui): bound session list recency (#77752)
This commit is contained in:
@@ -69,6 +69,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- TUI/sessions: bound the session picker to recent rows and use exact lookup-style refreshes for the active session, so dusty stores no longer make TUI hydrate weeks-old transcripts before becoming responsive. Thanks @vincentkoc.
|
||||
- Doctor/gateway: report recent supervisor restart handoffs in `openclaw doctor --deep`, using the installed service environment when available so service-managed clean exits are visible in guided diagnostics. Thanks @shakkernerd.
|
||||
- Gateway/status: show recent supervisor restart handoffs in `openclaw gateway status --deep`, including JSON details, so clean service-managed restarts are reported as restart handoffs instead of opaque stopped-service diagnostics. Thanks @shakkernerd.
|
||||
- Providers/Fireworks: expose Kimi models as thinking-off-only and keep K2.5/K2.6 requests on `thinking: disabled`, so manual model switches do not send Fireworks-rejected `reasoning*` parameters. Refs #74289. Thanks @frankekn.
|
||||
|
||||
@@ -82,7 +82,7 @@ Notes:
|
||||
|
||||
- Model picker: list available models and set the session override.
|
||||
- Agent picker: choose a different agent.
|
||||
- Session picker: shows only sessions for the current agent.
|
||||
- Session picker: shows up to 50 sessions for the current agent updated in the last 7 days. Use `/session <key>` to jump to an older known session.
|
||||
- Settings: toggle deliver, tool output expansion, and thinking visibility.
|
||||
|
||||
## Keyboard shortcuts
|
||||
|
||||
@@ -220,15 +220,7 @@ export class GatewayChatClient implements TuiBackend {
|
||||
}
|
||||
|
||||
async listSessions(opts?: SessionsListParams) {
|
||||
return await this.client.request<GatewaySessionList>("sessions.list", {
|
||||
limit: opts?.limit,
|
||||
activeMinutes: opts?.activeMinutes,
|
||||
includeGlobal: opts?.includeGlobal,
|
||||
includeUnknown: opts?.includeUnknown,
|
||||
includeDerivedTitles: opts?.includeDerivedTitles,
|
||||
includeLastMessage: opts?.includeLastMessage,
|
||||
agentId: opts?.agentId,
|
||||
});
|
||||
return await this.client.request<GatewaySessionList>("sessions.list", opts ?? {});
|
||||
}
|
||||
|
||||
async listAgents() {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createCommandHandlers } from "./tui-command-handlers.js";
|
||||
import {
|
||||
TUI_RECENT_SESSIONS_ACTIVE_MINUTES,
|
||||
TUI_SESSION_PICKER_LIMIT,
|
||||
} from "./tui-session-list-policy.js";
|
||||
|
||||
type LoadHistoryMock = ReturnType<typeof vi.fn> & (() => Promise<void>);
|
||||
type RunAuthFlow = NonNullable<Parameters<typeof createCommandHandlers>[0]["runAuthFlow"]>;
|
||||
@@ -16,6 +20,7 @@ async function flushAsyncSelect() {
|
||||
function createHarness(params?: {
|
||||
sendChat?: ReturnType<typeof vi.fn>;
|
||||
getGatewayStatus?: ReturnType<typeof vi.fn>;
|
||||
listSessions?: ReturnType<typeof vi.fn>;
|
||||
patchSession?: ReturnType<typeof vi.fn>;
|
||||
resetSession?: ReturnType<typeof vi.fn>;
|
||||
runAuthFlow?: RunAuthFlow;
|
||||
@@ -32,6 +37,7 @@ function createHarness(params?: {
|
||||
}) {
|
||||
const sendChat = params?.sendChat ?? vi.fn().mockResolvedValue({ runId: "r1" });
|
||||
const getGatewayStatus = params?.getGatewayStatus ?? vi.fn().mockResolvedValue({});
|
||||
const listSessions = params?.listSessions ?? vi.fn().mockResolvedValue({ sessions: [] });
|
||||
const patchSession = params?.patchSession ?? vi.fn().mockResolvedValue({});
|
||||
const resetSession = params?.resetSession ?? vi.fn().mockResolvedValue({ ok: true });
|
||||
const setSession = params?.setSession ?? (vi.fn().mockResolvedValue(undefined) as SetSessionMock);
|
||||
@@ -64,8 +70,8 @@ function createHarness(params?: {
|
||||
sessionInfo: {},
|
||||
};
|
||||
|
||||
const { handleCommand } = createCommandHandlers({
|
||||
client: { sendChat, getGatewayStatus, patchSession, resetSession } as never,
|
||||
const { handleCommand, openSessionSelector } = createCommandHandlers({
|
||||
client: { sendChat, getGatewayStatus, listSessions, patchSession, resetSession } as never,
|
||||
chatLog: { addUser, addSystem } as never,
|
||||
tui: { requestRender } as never,
|
||||
opts: params?.opts ?? {},
|
||||
@@ -92,7 +98,9 @@ function createHarness(params?: {
|
||||
return {
|
||||
handleCommand,
|
||||
getGatewayStatus,
|
||||
listSessions,
|
||||
sendChat,
|
||||
openSessionSelector,
|
||||
openOverlay,
|
||||
closeOverlay,
|
||||
patchSession,
|
||||
@@ -114,6 +122,31 @@ function createHarness(params?: {
|
||||
}
|
||||
|
||||
describe("tui command handlers", () => {
|
||||
it("bounds session picker hydration to recent TUI sessions", async () => {
|
||||
const listSessions = vi.fn().mockResolvedValue({
|
||||
sessions: [
|
||||
{
|
||||
key: "agent:main:main",
|
||||
displayName: "main",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
],
|
||||
});
|
||||
const { openSessionSelector } = createHarness({ listSessions });
|
||||
|
||||
await openSessionSelector();
|
||||
|
||||
expect(listSessions).toHaveBeenCalledWith({
|
||||
limit: TUI_SESSION_PICKER_LIMIT,
|
||||
activeMinutes: TUI_RECENT_SESSIONS_ACTIVE_MINUTES,
|
||||
includeGlobal: false,
|
||||
includeUnknown: false,
|
||||
includeDerivedTitles: true,
|
||||
includeLastMessage: true,
|
||||
agentId: "main",
|
||||
});
|
||||
});
|
||||
|
||||
it("renders the sending indicator before chat.send resolves", async () => {
|
||||
let resolveSend: (value: { runId: string }) => void = () => {
|
||||
throw new Error("sendChat promise resolver was not initialized");
|
||||
|
||||
@@ -18,6 +18,10 @@ import {
|
||||
} from "./components/selectors.js";
|
||||
import type { TuiBackend } from "./tui-backend.js";
|
||||
import { sanitizeRenderableText } from "./tui-formatters.js";
|
||||
import {
|
||||
TUI_RECENT_SESSIONS_ACTIVE_MINUTES,
|
||||
TUI_SESSION_PICKER_LIMIT,
|
||||
} from "./tui-session-list-policy.js";
|
||||
import { formatStatusSummary } from "./tui-status-summary.js";
|
||||
import type {
|
||||
AgentSummary,
|
||||
@@ -190,6 +194,8 @@ export function createCommandHandlers(context: CommandHandlerContext) {
|
||||
const openSessionSelector = async () => {
|
||||
try {
|
||||
const result = await client.listSessions({
|
||||
limit: TUI_SESSION_PICKER_LIMIT,
|
||||
activeMinutes: TUI_RECENT_SESSIONS_ACTIVE_MINUTES,
|
||||
includeGlobal: false,
|
||||
includeUnknown: false,
|
||||
includeDerivedTitles: true,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { TuiBackend } from "./tui-backend.js";
|
||||
import { createSessionActions } from "./tui-session-actions.js";
|
||||
import { TUI_SESSION_LOOKUP_LIMIT } from "./tui-session-list-policy.js";
|
||||
import type { TuiStateAccess } from "./tui-types.js";
|
||||
|
||||
describe("tui session actions", () => {
|
||||
@@ -96,6 +97,13 @@ describe("tui session actions", () => {
|
||||
|
||||
await Promise.resolve();
|
||||
expect(listSessions).toHaveBeenCalledTimes(1);
|
||||
expect(listSessions).toHaveBeenNthCalledWith(1, {
|
||||
limit: TUI_SESSION_LOOKUP_LIMIT,
|
||||
search: "agent:main:main",
|
||||
includeGlobal: false,
|
||||
includeUnknown: false,
|
||||
agentId: "main",
|
||||
});
|
||||
|
||||
resolveFirst?.({
|
||||
ts: Date.now(),
|
||||
|
||||
@@ -10,6 +10,7 @@ import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import type { ChatLog } from "./components/chat-log.js";
|
||||
import type { TuiAgentsList, TuiBackend } from "./tui-backend.js";
|
||||
import { asString, extractTextFromMessage, isCommandMessage } from "./tui-formatters.js";
|
||||
import { TUI_SESSION_LOOKUP_LIMIT } from "./tui-session-list-policy.js";
|
||||
import type { SessionInfo, TuiOptions, TuiStateAccess } from "./tui-types.js";
|
||||
|
||||
type SessionActionBtwPresenter = {
|
||||
@@ -234,6 +235,8 @@ export function createSessionActions(context: SessionActionContext) {
|
||||
};
|
||||
const listAgentId = resolveListAgentId();
|
||||
const result = await client.listSessions({
|
||||
limit: TUI_SESSION_LOOKUP_LIMIT,
|
||||
search: state.currentSessionKey,
|
||||
includeGlobal: false,
|
||||
includeUnknown: false,
|
||||
agentId: listAgentId,
|
||||
|
||||
3
src/tui/tui-session-list-policy.ts
Normal file
3
src/tui/tui-session-list-policy.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const TUI_RECENT_SESSIONS_ACTIVE_MINUTES = 7 * 24 * 60;
|
||||
export const TUI_SESSION_PICKER_LIMIT = 50;
|
||||
export const TUI_SESSION_LOOKUP_LIMIT = 5;
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
import { createLocalShellRunner } from "./tui-local-shell.js";
|
||||
import { createOverlayHandlers } from "./tui-overlays.js";
|
||||
import { createSessionActions } from "./tui-session-actions.js";
|
||||
import { TUI_SESSION_LOOKUP_LIMIT } from "./tui-session-list-policy.js";
|
||||
import {
|
||||
createEditorSubmitHandler,
|
||||
createSubmitBurstCoalescer,
|
||||
@@ -635,6 +636,8 @@ export async function runTui(opts: RunTuiOptions): Promise<TuiResult> {
|
||||
}
|
||||
const sessions = await client
|
||||
.listSessions({
|
||||
limit: TUI_SESSION_LOOKUP_LIMIT,
|
||||
search: rememberedKey,
|
||||
includeGlobal: false,
|
||||
includeUnknown: false,
|
||||
agentId: currentAgentId,
|
||||
|
||||
Reference in New Issue
Block a user