From 72af5831d8cf066677814e9187c6eff572a3eea9 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Sun, 30 Nov 2025 13:48:29 -0500 Subject: [PATCH 1/5] feat: add messages_limit and sessions_list_limit configuration with proper defaults - Add messages_limit (default: 100) and sessions_list_limit (default: 150) to TUI config - Maintain backward compatibility with existing behavior - Regenerate SDK to include new fields --- .../cmd/tui/component/dialog-session-list.tsx | 5 +- .../opencode/src/cli/cmd/tui/context/sync.tsx | 4 +- packages/opencode/src/config/config.ts | 11 ++ packages/opencode/test/config/config.test.ts | 120 ++++++++++++++++++ packages/sdk/js/src/gen/types.gen.ts | 8 ++ 5 files changed, 146 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 5e0095a8dfef..24558b154a40 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -24,6 +24,9 @@ export function DialogSessionList() { const options = createMemo(() => { const today = new Date().toDateString() + const sessionsListLimit = (sync.data.config.tui as any)?.sessions_list_limit + const limit = sessionsListLimit === "none" ? undefined : sessionsListLimit || 150 + return sync.data.session .filter((x) => x.parentID === undefined) .map((x) => { @@ -41,7 +44,7 @@ export function DialogSessionList() { footer: Locale.time(x.time.updated), } }) - .slice(0, 150) + .slice(0, limit) }) createEffect(() => { diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index b7ef8a2214b4..ca04e9d561e8 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -332,9 +332,11 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ }, async sync(sessionID: string) { if (fullSyncedSessions.has(sessionID)) return + const messagesLimit = (store.config.tui as any)?.messages_limit + const limit = messagesLimit === "none" ? undefined : messagesLimit || 100 const [session, messages, todo, diff] = await Promise.all([ sdk.client.session.get({ path: { id: sessionID }, throwOnError: true }), - sdk.client.session.messages({ path: { id: sessionID }, query: { limit: 100 } }), + sdk.client.session.messages({ path: { id: sessionID }, query: { limit } }), sdk.client.session.todo({ path: { id: sessionID } }), sdk.client.session.diff({ path: { id: sessionID } }), ]) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index a49612090553..f7de8b82ca79 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -460,7 +460,18 @@ export namespace Config { .enum(["auto", "stacked"]) .optional() .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"), + sessions_list_limit: z + .union([z.number().min(1), z.literal("none")]) + .optional() + .default(150) + .describe("Maximum number of sessions to display in session list, or 'none' to show all sessions"), + messages_limit: z + .union([z.number().min(1), z.literal("none")]) + .optional() + .default(100) + .describe("Maximum number of messages to load per session when syncing, or 'none' to load all messages"), }) + export type TUI = z.infer export const Layout = z.enum(["auto", "stretch"]).meta({ ref: "LayoutConfig", diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 2ff8c01cdb07..a996a53c6c4e 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -501,3 +501,123 @@ test("deduplicates duplicate plugins from global and local configs", async () => }, }) }) + +test("handles TUI configuration with sessions_list_limit and messages_limit", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + tui: { + sessions_list_limit: 200, + messages_limit: 50, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.tui?.sessions_list_limit).toBe(200) + expect(config.tui?.messages_limit).toBe(50) + }, + }) +}) + +test("handles TUI configuration with sessions_list_limit set to 'none'", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + tui: { + sessions_list_limit: "none", + messages_limit: 75, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.tui?.sessions_list_limit).toBe("none") + expect(config.tui?.messages_limit).toBe(75) + }, + }) +}) + +test("validates TUI sessions_list_limit schema - rejects invalid values", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + tui: { + sessions_list_limit: -5, // Invalid: negative number + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await expect(Config.get()).rejects.toThrow() + }, + }) +}) + +test("validates TUI messages_limit schema - rejects invalid values", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + tui: { + messages_limit: 0, // Invalid: must be >= 1 + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await expect(Config.get()).rejects.toThrow() + }, + }) +}) + +test("handles partial TUI configuration with backward compatibility", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + tui: { + scroll_speed: 2.5, + // sessions_list_limit and messages_limit not specified - should inherit from global config + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.tui?.scroll_speed).toBe(2.5) + // Note: sessions_list_limit and messages_limit may be inherited from global config + // The important thing is that the config loads successfully and scroll_speed is set correctly + }, + }) +}) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 70fceedbcb2c..e93ecfbdb49d 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1008,6 +1008,14 @@ export type Config = { * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column */ diff_style?: "auto" | "stacked" + /** + * Maximum number of sessions to display in session list, or 'none' to show all sessions + */ + sessions_list_limit?: number | "none" + /** + * Maximum number of messages to load per session when syncing, or 'none' to load all messages + */ + messages_limit?: number | "none" } /** * Command configuration, see https://opencode.ai/docs/commands From e03641d846677c3542fd0ac5d72676aa6cc1eea0 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Sun, 30 Nov 2025 15:18:37 -0500 Subject: [PATCH 2/5] tidy: for consistency sessions_list_limit->session_list_limit. --- .../cmd/tui/component/dialog-session-list.tsx | 2 +- packages/opencode/src/config/config.ts | 2 +- packages/opencode/test/config/config.test.ts | 20 +++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 24558b154a40..bbb8a91e8b7d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -24,7 +24,7 @@ export function DialogSessionList() { const options = createMemo(() => { const today = new Date().toDateString() - const sessionsListLimit = (sync.data.config.tui as any)?.sessions_list_limit + const sessionsListLimit = (sync.data.config.tui as any)?.session_list_limit const limit = sessionsListLimit === "none" ? undefined : sessionsListLimit || 150 return sync.data.session diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index f7de8b82ca79..847c35abacb7 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -460,7 +460,7 @@ export namespace Config { .enum(["auto", "stacked"]) .optional() .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"), - sessions_list_limit: z + session_list_limit: z .union([z.number().min(1), z.literal("none")]) .optional() .default(150) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index a996a53c6c4e..d6f0500eaa80 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -502,7 +502,7 @@ test("deduplicates duplicate plugins from global and local configs", async () => }) }) -test("handles TUI configuration with sessions_list_limit and messages_limit", async () => { +test("handles TUI configuration with session_list_limit and messages_limit", async () => { await using tmp = await tmpdir({ init: async (dir) => { await Bun.write( @@ -510,7 +510,7 @@ test("handles TUI configuration with sessions_list_limit and messages_limit", as JSON.stringify({ $schema: "https://opencode.ai/config.json", tui: { - sessions_list_limit: 200, + session_list_limit: 200, messages_limit: 50, }, }), @@ -521,13 +521,13 @@ test("handles TUI configuration with sessions_list_limit and messages_limit", as directory: tmp.path, fn: async () => { const config = await Config.get() - expect(config.tui?.sessions_list_limit).toBe(200) + expect(config.tui?.session_list_limit).toBe(200) expect(config.tui?.messages_limit).toBe(50) }, }) }) -test("handles TUI configuration with sessions_list_limit set to 'none'", async () => { +test("handles TUI configuration with session_list_limit set to 'none'", async () => { await using tmp = await tmpdir({ init: async (dir) => { await Bun.write( @@ -535,7 +535,7 @@ test("handles TUI configuration with sessions_list_limit set to 'none'", async ( JSON.stringify({ $schema: "https://opencode.ai/config.json", tui: { - sessions_list_limit: "none", + session_list_limit: "none", messages_limit: 75, }, }), @@ -546,13 +546,13 @@ test("handles TUI configuration with sessions_list_limit set to 'none'", async ( directory: tmp.path, fn: async () => { const config = await Config.get() - expect(config.tui?.sessions_list_limit).toBe("none") + expect(config.tui?.session_list_limit).toBe("none") expect(config.tui?.messages_limit).toBe(75) }, }) }) -test("validates TUI sessions_list_limit schema - rejects invalid values", async () => { +test("validates TUI session_list_limit schema - rejects invalid values", async () => { await using tmp = await tmpdir({ init: async (dir) => { await Bun.write( @@ -560,7 +560,7 @@ test("validates TUI sessions_list_limit schema - rejects invalid values", async JSON.stringify({ $schema: "https://opencode.ai/config.json", tui: { - sessions_list_limit: -5, // Invalid: negative number + session_list_limit: -5, // Invalid: negative number }, }), ) @@ -605,7 +605,7 @@ test("handles partial TUI configuration with backward compatibility", async () = $schema: "https://opencode.ai/config.json", tui: { scroll_speed: 2.5, - // sessions_list_limit and messages_limit not specified - should inherit from global config + // session_list_limit and messages_limit not specified - should inherit from global config }, }), ) @@ -616,7 +616,7 @@ test("handles partial TUI configuration with backward compatibility", async () = fn: async () => { const config = await Config.get() expect(config.tui?.scroll_speed).toBe(2.5) - // Note: sessions_list_limit and messages_limit may be inherited from global config + // Note: session_list_limit and messages_limit may be inherited from global config // The important thing is that the config loads successfully and scroll_speed is set correctly }, }) From 8254075b0a9a68d90b596d6a5bd53d82bbf08266 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Sun, 30 Nov 2025 15:28:07 -0500 Subject: [PATCH 3/5] fix: same as last, missed one. --- packages/sdk/js/src/gen/types.gen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index e93ecfbdb49d..ab947702f804 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1011,7 +1011,7 @@ export type Config = { /** * Maximum number of sessions to display in session list, or 'none' to show all sessions */ - sessions_list_limit?: number | "none" + session_list_limit?: number | "none" /** * Maximum number of messages to load per session when syncing, or 'none' to load all messages */ From 77dc72fb7fdff858fc705df62a00443ca1ccd328 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Sun, 30 Nov 2025 18:51:24 -0500 Subject: [PATCH 4/5] wip: hammer on messages_limit a bit --- packages/opencode/src/cli/cmd/tui/context/sync.tsx | 5 ++++- packages/opencode/src/config/config.ts | 2 +- packages/opencode/src/session/index.ts | 14 +++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index ca04e9d561e8..67fd53b71ea1 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -184,7 +184,9 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ event.properties.info.sessionID, produce((draft) => { draft.splice(result.index, 0, event.properties.info) - if (draft.length > 100) draft.shift() + const maxMessages = (store.config.tui as any)?.messages_limit + const maxMessagesCount = maxMessages === "none" ? Infinity : maxMessages || 100 + if (draft.length > maxMessagesCount) draft.shift() }), ) break @@ -350,6 +352,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ for (const message of messages.data!) { draft.part[message.info.id] = message.parts } + draft.session_diff[sessionID] = diff.data ?? [] }), ) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 847c35abacb7..b5e91bbfaab4 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -469,7 +469,7 @@ export namespace Config { .union([z.number().min(1), z.literal("none")]) .optional() .default(100) - .describe("Maximum number of messages to load per session when syncing, or 'none' to load all messages"), + .describe("Maximum number of message parts to load per session when syncing, or 'none' to load all messages"), }) export type TUI = z.infer diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index f09818caa2e6..257606af27ee 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -291,11 +291,23 @@ export namespace Session { }), async (input) => { const result = [] as MessageV2.WithParts[] + let totalParts = 0 + for await (const msg of MessageV2.stream(input.sessionID)) { - if (input.limit && result.length >= input.limit) break + if (input.limit && totalParts + msg.parts.length > input.limit) { + // If adding this message would exceed the limit, check if we can fit a partial message + if (totalParts < input.limit) { + // We have room for some parts of this message, but this would be complex to implement + // For now, just break to stay within the limit + break + } + break + } result.push(msg) + totalParts += msg.parts.length } result.reverse() + return result }, ) From 5f73f7f90d5854bd25ecbd51f1d93759827837c2 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Mon, 1 Dec 2025 00:27:50 -0500 Subject: [PATCH 5/5] wip: fiddle with messages_limit.. --- packages/opencode/src/cli/cmd/tui/context/sync.tsx | 9 ++++++++- packages/opencode/src/session/index.ts | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 67fd53b71ea1..f3ab41aa50b2 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -186,7 +186,14 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ draft.splice(result.index, 0, event.properties.info) const maxMessages = (store.config.tui as any)?.messages_limit const maxMessagesCount = maxMessages === "none" ? Infinity : maxMessages || 100 - if (draft.length > maxMessagesCount) draft.shift() + // DEBUG: Log message limit behavior + console.log( + `[SYNC] Session ${event.properties.info.sessionID}: messages_limit=${maxMessages}, maxMessagesCount=${maxMessagesCount}`, + ) + if (draft.length > maxMessagesCount) { + console.log(`[SYNC] Session ${event.properties.info.sessionID}: LIMIT EXCEEDED - shifting messages`) + draft.shift() + } }), ) break diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 257606af27ee..ecdd0e580ea2 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -308,6 +308,10 @@ export namespace Session { } result.reverse() + // DEBUG: Log what we're returning + console.log( + `[Session.messages] Returning ${result.length} messages for session ${input.sessionID} with limit ${input.limit}`, + ) return result }, )