



























@@ -20,6 +20,10 @@ type SessionListOptions = {
2020totalCount: number;
2121};
222223+const SESSION_PAGE_SIZE = 50;
24+const TOTAL_MOCK_SESSIONS = 650;
25+const TOTAL_TELEGRAM_SESSIONS = 180;
26+2327const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
2428const uiRoot = path.join(repoRoot, "ui");
2529@@ -45,16 +49,21 @@ function parsePort(value: string | undefined, fallback: number): number {
4549return Number.isInteger(parsed) && parsed > 0 && parsed < 65_536 ? parsed : fallback;
4650}
475148-function sessionRow(key: string, label: string, updatedAt: number) {
52+function sessionRow(
53+key: string,
54+label: string,
55+updatedAt: number,
56+options: { model?: string; modelProvider?: string } = {},
57+) {
4958return {
5059contextTokens: null,
5160displayName: label,
5261hasActiveRun: false,
5362 key,
5463kind: "direct",
5564 label,
56-model: "gpt-5.5",
57-modelProvider: "openai",
65+model: options.model ?? "gpt-5.5",
66+modelProvider: options.modelProvider ?? "openai",
5867status: "done",
5968totalTokens: 0,
6069 updatedAt,
@@ -80,8 +89,92 @@ function sessionsListResponse(sessions: unknown[], options: SessionListOptions)
8089};
8190}
829192+function pagedSessionsListResponse(sessions: unknown[], offset: number) {
93+const normalizedOffset = Math.max(0, Math.floor(offset));
94+const page = sessions.slice(normalizedOffset, normalizedOffset + SESSION_PAGE_SIZE);
95+const nextOffset = normalizedOffset + SESSION_PAGE_SIZE;
96+return sessionsListResponse(page, {
97+hasMore: nextOffset < sessions.length,
98+nextOffset: nextOffset < sessions.length ? nextOffset : null,
99+offset: normalizedOffset,
100+totalCount: sessions.length,
101+});
102+}
103+104+function buildSessionRows(params: {
105+baseTime: number;
106+count: number;
107+keyPrefix: string;
108+labelPrefix: string;
109+model?: string;
110+modelProvider?: string;
111+}) {
112+return Array.from({ length: params.count }, (_value, index) => {
113+const ordinal = index + 1;
114+const padded = String(ordinal).padStart(3, "0");
115+return sessionRow(
116+`agent:${params.keyPrefix}-${padded}`,
117+`${params.labelPrefix} ${padded}`,
118+params.baseTime - ordinal * 60_000,
119+{ model: params.model, modelProvider: params.modelProvider },
120+);
121+});
122+}
123+124+function buildSessionListCases(
125+sessions: unknown[],
126+matchBase: Record<string, unknown> = {},
127+): Array<{ match: Record<string, unknown>; response: unknown }> {
128+const cases: Array<{ match: Record<string, unknown>; response: unknown }> = [];
129+for (let offset = SESSION_PAGE_SIZE; offset < sessions.length; offset += SESSION_PAGE_SIZE) {
130+cases.push({
131+match: { ...matchBase, offset },
132+response: pagedSessionsListResponse(sessions, offset),
133+});
134+}
135+cases.push({
136+match: matchBase,
137+response: pagedSessionsListResponse(sessions, 0),
138+});
139+return cases;
140+}
141+142+function buildSearchSessionListCases(
143+sessions: unknown[],
144+searchTerms: string[],
145+): Array<{ match: Record<string, unknown>; response: unknown }> {
146+return searchTerms.flatMap((search) => buildSessionListCases(sessions, { search }));
147+}
148+149+function searchPrefixes(term: string): string[] {
150+return Array.from({ length: term.length }, (_value, index) => term.slice(0, index + 1));
151+}
152+83153function createChatPickerScenario(): ControlUiMockGatewayScenario {
84154const baseTime = Date.parse("2026-05-22T09:00:00.000Z");
155+const sessions = [
156+sessionRow("agent:alpha", "Alpha planning", baseTime - 1_000),
157+ ...buildSessionRows({
158+baseTime: baseTime - 60_000,
159+count: TOTAL_MOCK_SESSIONS - 1,
160+keyPrefix: "history",
161+labelPrefix: "Long running session",
162+}),
163+];
164+const telegramSessions = buildSessionRows({
165+baseTime: baseTime - 30_000,
166+count: TOTAL_TELEGRAM_SESSIONS,
167+keyPrefix: "telegram",
168+labelPrefix: "Telegram investigation",
169+});
170+const claudeSessions = buildSessionRows({
171+baseTime: baseTime - 45_000,
172+count: 75,
173+keyPrefix: "model-claude",
174+labelPrefix: "Model search result",
175+model: "claude-sonnet-4-6",
176+modelProvider: "anthropic",
177+});
85178return {
86179assistantAgentId: "openclaw-mock",
87180assistantName: "OpenClaw mock",
@@ -90,7 +183,7 @@ function createChatPickerScenario(): ControlUiMockGatewayScenario {
90183{
91184content: [
92185{
93-text: 'Mock Control UI is running. Open the chat picker, search for "telegram", then use Load more.',
186+text: `Mock Control UI is running with ${TOTAL_MOCK_SESSIONS} sessions. Open the chat picker, search for "telegram" or "claude", then use Load more repeatedly.`,
94187type: "text",
95188},
96189],
@@ -101,40 +194,20 @@ function createChatPickerScenario(): ControlUiMockGatewayScenario {
101194methodResponses: {
102195"sessions.list": {
103196cases: [
104-{
105-match: { offset: 50, search: "telegram" },
106-response: sessionsListResponse(
107-[
108-sessionRow("agent:telegram-51", "Telegram archive page 51", baseTime - 180_000),
109-sessionRow("agent:telegram-52", "Telegram archive page 52", baseTime - 240_000),
110-],
111-{ hasMore: false, nextOffset: null, offset: 50, totalCount: 4 },
112-),
113-},
114-{
115-match: { search: "telegram" },
116-response: sessionsListResponse(
117-[
118-sessionRow("agent:telegram", "Telegram follow-up", baseTime - 60_000),
119-sessionRow("agent:telegram-mobile", "Telegram mobile handoff", baseTime - 120_000),
120-],
121-{ hasMore: true, nextOffset: 50, totalCount: 4 },
122-),
123-},
124-{
125-match: {},
126-response: sessionsListResponse(
127-[
128-sessionRow("agent:alpha", "Alpha planning", baseTime - 1_000),
129-sessionRow("agent:design", "Design review", baseTime - 30_000),
130-],
131-{ hasMore: true, nextOffset: 50, totalCount: 125 },
132-),
133-},
197+ ...buildSearchSessionListCases(telegramSessions, searchPrefixes("telegram")),
198+ ...buildSearchSessionListCases(claudeSessions, [
199+ ...searchPrefixes("claude"),
200+ ...searchPrefixes("claude-sonnet-4-6"),
201+ ...searchPrefixes("anthropic"),
202+]),
203+ ...buildSessionListCases(sessions),
134204],
135205},
136206},
137-models: [{ id: "gpt-5.5", name: "gpt-5.5", provider: "openai" }],
207+models: [
208+{ id: "gpt-5.5", name: "gpt-5.5", provider: "openai" },
209+{ id: "claude-sonnet-4-6", name: "claude-sonnet-4-6", provider: "anthropic" },
210+],
138211sessionKey: "agent:alpha",
139212};
140213}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。