mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(tui): centralize gateway chat history limits (#117773)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
This commit is contained in:
committed by
GitHub
parent
2bbc2f5bc4
commit
d669e9eddd
@@ -0,0 +1,2 @@
|
||||
/** Largest history page accepted by the Gateway wire contract. */
|
||||
export const CHAT_HISTORY_MAX_ENTRIES = 1000;
|
||||
@@ -1,7 +1,12 @@
|
||||
// Gateway Protocol tests cover typed chat stream events.
|
||||
import { Value } from "typebox/value";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ChatEventSchema, ChatSendParamsSchema, ChatStatusEventSchema } from "./logs-chat.js";
|
||||
import {
|
||||
ChatEventSchema,
|
||||
ChatHistoryParamsSchema,
|
||||
ChatSendParamsSchema,
|
||||
ChatStatusEventSchema,
|
||||
} from "./logs-chat.js";
|
||||
|
||||
const statusEvent = {
|
||||
runId: "run-1",
|
||||
@@ -11,6 +16,15 @@ const statusEvent = {
|
||||
phase: "preparing_context",
|
||||
} as const;
|
||||
|
||||
describe("ChatHistoryParamsSchema", () => {
|
||||
it("accepts the history boundary and rejects larger requests", () => {
|
||||
const request = { sessionKey: "agent:main:main" };
|
||||
|
||||
expect(Value.Check(ChatHistoryParamsSchema, { ...request, limit: 1000 })).toBe(true);
|
||||
expect(Value.Check(ChatHistoryParamsSchema, { ...request, limit: 1001 })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ChatStatusEventSchema", () => {
|
||||
it("accepts closed startup phases through the chat event union", () => {
|
||||
expect(Value.Check(ChatStatusEventSchema, statusEvent)).toBe(true);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Gateway Protocol schema module defines protocol validation shapes.
|
||||
import type { Static } from "typebox";
|
||||
import { Type } from "typebox";
|
||||
import { CHAT_HISTORY_MAX_ENTRIES } from "./chat-history-constants.js";
|
||||
import { closedObject } from "./closed-object.js";
|
||||
import { ChatSendSessionKeyString, InputProvenanceSchema, NonEmptyString } from "./primitives.js";
|
||||
|
||||
@@ -25,7 +26,7 @@ export const LogsTailResultSchema = closedObject({
|
||||
export const ChatHistoryParamsSchema = closedObject({
|
||||
sessionKey: NonEmptyString,
|
||||
agentId: Type.Optional(NonEmptyString),
|
||||
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 1000 })),
|
||||
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: CHAT_HISTORY_MAX_ENTRIES })),
|
||||
offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
||||
messageId: Type.Optional(NonEmptyString),
|
||||
sessionId: Type.Optional(NonEmptyString),
|
||||
|
||||
@@ -58,9 +58,11 @@ describe("cli program (smoke)", () => {
|
||||
await runProgram(["tui", "--timeout-ms", "45000"]);
|
||||
const options = firstMockArg(runTui) as {
|
||||
timeoutMs?: number;
|
||||
historyLimit?: number;
|
||||
forceProcessExitOnReturn?: boolean;
|
||||
};
|
||||
expect(options?.timeoutMs).toBe(45000);
|
||||
expect(options?.historyLimit).toBe(200);
|
||||
expect(options?.forceProcessExitOnReturn).toBe(true);
|
||||
});
|
||||
|
||||
@@ -92,6 +94,30 @@ describe("cli program (smoke)", () => {
|
||||
expect(runTui).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("accepts the maximum Gateway tui history limit", async () => {
|
||||
await runProgram(["tui", "--history-limit", "1000"]);
|
||||
|
||||
expect(firstMockArg(runTui)).toMatchObject({ local: false, historyLimit: 1000 });
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ entryPoint: "tui --local", args: ["tui", "--local"] },
|
||||
{ entryPoint: "terminal", args: ["terminal"] },
|
||||
{ entryPoint: "chat", args: ["chat"] },
|
||||
])("preserves oversized history limits for local $entryPoint", async ({ args }) => {
|
||||
await runProgram([...args, "--history-limit", "1001"]);
|
||||
|
||||
expect(firstMockArg(runTui)).toMatchObject({ local: true, historyLimit: 1001 });
|
||||
expect(runtime.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects tui history limits above the Gateway maximum", async () => {
|
||||
await expect(runProgram(["tui", "--history-limit", "1001"])).rejects.toThrow("exit");
|
||||
|
||||
expect(runtime.error).toHaveBeenCalledWith("Error: --history-limit must be at most 1000.");
|
||||
expect(runTui).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runs setup wizard when wizard flags are present", async () => {
|
||||
await runProgram(["setup", "--remote-url", "ws://example"]);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Registers the terminal UI subcommand and normalizes its local-vs-gateway options.
|
||||
import type { Command } from "commander";
|
||||
import { CHAT_HISTORY_MAX_ENTRIES } from "../../packages/gateway-protocol/src/schema/chat-history-constants.js";
|
||||
import { formatDocsLink } from "../../packages/terminal-core/src/links.js";
|
||||
import { theme } from "../../packages/terminal-core/src/theme.js";
|
||||
import { parseStrictPositiveInteger } from "../infra/parse-finite-number.js";
|
||||
@@ -51,6 +52,9 @@ export function registerTuiCli(program: Command) {
|
||||
if (historyLimit === undefined) {
|
||||
throw new Error("--history-limit must be a positive integer.");
|
||||
}
|
||||
if (!isLocal && historyLimit > CHAT_HISTORY_MAX_ENTRIES) {
|
||||
throw new Error(`--history-limit must be at most ${CHAT_HISTORY_MAX_ENTRIES}.`);
|
||||
}
|
||||
const { runTui } = await import("../tui/tui.js");
|
||||
await runTui({
|
||||
local: isLocal,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
validateChatHistoryParams,
|
||||
validateChatMetadataParams,
|
||||
} from "../../../packages/gateway-protocol/src/index.js";
|
||||
import { CHAT_HISTORY_MAX_ENTRIES } from "../../../packages/gateway-protocol/src/schema/chat-history-constants.js";
|
||||
import {
|
||||
listAgentIds,
|
||||
resolveDefaultAgentId,
|
||||
@@ -385,7 +386,7 @@ async function handleChatHistoryRequest({
|
||||
requestedSessionId && requestedSessionId !== entry?.sessionId ? undefined : entry;
|
||||
const resolvedSessionModel = resolveSessionModelRef(cfg, entry, sessionAgentId);
|
||||
const requested = typeof limit === "number" ? limit : 200;
|
||||
const max = Math.min(1000, requested);
|
||||
const max = Math.min(CHAT_HISTORY_MAX_ENTRIES, requested);
|
||||
const maxHistoryBytes = getMaxChatHistoryMessagesBytes();
|
||||
const effectiveMaxChars = resolveEffectiveChatHistoryMaxChars(cfg, maxChars);
|
||||
let historyPage: Awaited<ReturnType<typeof readChatHistoryPage>>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Implements the embedded backend used by local TUI sessions.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { SessionsPatchResult } from "../../packages/gateway-protocol/src/index.js";
|
||||
import { CHAT_HISTORY_MAX_ENTRIES } from "../../packages/gateway-protocol/src/schema/chat-history-constants.js";
|
||||
import { agentCommandFromIngress } from "../agents/agent-command.js";
|
||||
import { isAgentLifecycleYieldedWaiting } from "../agents/agent-lifecycle-parent-state.js";
|
||||
import {
|
||||
@@ -621,7 +622,10 @@ export class EmbeddedTuiBackend implements TuiBackend {
|
||||
this.runtimePluginRegistry =
|
||||
runtimePluginsPrewarm.status === "warmed" ? runtimePluginsPrewarm.registry : undefined;
|
||||
const resolvedSessionModel = resolveSessionModelRef(cfg, entry, sessionAgentId);
|
||||
const max = Math.min(1000, typeof opts.limit === "number" ? opts.limit : 200);
|
||||
const max = Math.min(
|
||||
CHAT_HISTORY_MAX_ENTRIES,
|
||||
typeof opts.limit === "number" ? opts.limit : 200,
|
||||
);
|
||||
const maxHistoryBytes = getMaxChatHistoryMessagesBytes();
|
||||
const effectiveMaxChars = resolveEffectiveChatHistoryMaxChars(cfg);
|
||||
const historyPage = await readChatHistoryPage({
|
||||
|
||||
Reference in New Issue
Block a user