From be503ba5ec4dcbcfc7e5f2733672aee4422b82ee Mon Sep 17 00:00:00 2001 From: Guillaume BOEHM Date: Mon, 27 Oct 2025 23:28:06 +0100 Subject: [PATCH 1/3] fix: Respect user set default_mode and improve agent management - The default_mode option was not being respected when setting the state current_mode. The current_mode is now set when first opening the opencode window. - The mode switching is now wrapped in a "switch_to_mode" function to ensure the mode exists in the available agents for more control. - The build and plan agents were added to the available agents even if explicitly disabled in the opencode config -> leading to a hang when sending messages to opencode. They are now prepended if they are not disabled. - The agent list was always randomly returned, it is now sorted before using it. --- lua/opencode/api.lua | 8 +++--- lua/opencode/config_file.lua | 16 +++++++++-- lua/opencode/core.lua | 54 ++++++++++++++++++++++++++++++++++++ lua/opencode/state.lua | 2 +- 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/lua/opencode/api.lua b/lua/opencode/api.lua index 7b89a771..0ffa3ba9 100644 --- a/lua/opencode/api.lua +++ b/lua/opencode/api.lua @@ -357,11 +357,11 @@ function M.initialize() end function M.agent_plan() - state.current_mode = 'plan' + require('opencode.core').switch_to_mode('plan') end function M.agent_build() - state.current_mode = 'build' + require('opencode.core').switch_to_mode('build') end function M.select_agent() @@ -373,7 +373,7 @@ function M.select_agent() return end - state.current_mode = selection + require('opencode.core').switch_to_mode(selection) end) end @@ -389,7 +389,7 @@ function M.switch_mode() -- Calculate next index, wrapping around if necessary local next_index = (current_index % #modes) + 1 - state.current_mode = modes[next_index] + require('opencode.core').switch_to_mode(modes[next_index]) end function M.with_header(lines, show_welcome) diff --git a/lua/opencode/config_file.lua b/lua/opencode/config_file.lua index b58632d4..74896aff 100644 --- a/lua/opencode/config_file.lua +++ b/lua/opencode/config_file.lua @@ -77,13 +77,23 @@ function M.get_opencode_agents() end local agents = {} for agent, opts in pairs(cfg.agent or {}) do - if opts.mode == 'primary' or opts.mode == 'all' then + -- Only include agents that are enabled and have the right mode + if opts.disable ~= true and (opts.mode == 'primary' or opts.mode == 'all') then table.insert(agents, agent) end end - for _, mode in ipairs({ 'build', 'plan' }) do + + -- Sort the agents before prepending the default agents + table.sort(agents) + + -- Only add build/plan as fallbacks if they're not explicitly disabled in config + for _, mode in ipairs({ 'plan', 'build' }) do if not vim.tbl_contains(agents, mode) then - table.insert(agents, mode) + local mode_config = cfg.agent and cfg.agent[mode] + -- Only add if not explicitly disabled or if no config exists (default behavior) + if mode_config == nil or (mode_config.disable ~= true) then + table.insert(agents, 1, mode) + end end end return agents diff --git a/lua/opencode/core.lua b/lua/opencode/core.lua index 6fa4f7d2..c39d9a26 100644 --- a/lua/opencode/core.lua +++ b/lua/opencode/core.lua @@ -45,6 +45,8 @@ function M.open(opts) state.opencode_server = server_job.ensure_server() --[[@as OpencodeServer]] end + M.ensure_current_mode() + local are_windows_closed = state.windows == nil if are_windows_closed then @@ -254,6 +256,58 @@ local function on_opencode_server() state.current_permission = nil end +--- Switches the current mode to the specified agent. +--- @param mode string The agent/mode to switch to +--- @return boolean success Returns true if the mode was switched successfully, false otherwise +function M.switch_to_mode(mode) + if not mode or mode == '' then + vim.notify('Mode cannot be empty', vim.log.levels.ERROR) + return false + end + + local config_file = require('opencode.config_file') + local available_agents = config_file.get_opencode_agents() + + if not vim.tbl_contains(available_agents, mode) then + vim.notify( + string.format('Invalid mode "%s". Available modes: %s', mode, table.concat(available_agents, ', ')), + vim.log.levels.ERROR + ) + return false + end + + state.current_mode = mode + ui.render_output() + return true +end + +--- Ensure the current_mode is set using the config.default_mode or falling back to the first available agent. +--- @return boolean success Returns true if current_mode is set +function M.ensure_current_mode() + if state.current_mode == nil then + local config_file = require('opencode.config_file') + local available_agents = config_file.get_opencode_agents() + + if not available_agents or #available_agents == 0 then + vim.notify('No available agents found', vim.log.levels.ERROR) + return false + end + + local default_mode = config.default_mode + + -- Try to use the configured default mode if it's available + if default_mode and vim.tbl_contains(available_agents, default_mode) then + state.current_mode = default_mode + else + -- Fallback to first available agent + state.current_mode = available_agents[1] + end + + ui.render_output() + end + return true +end + function M.setup() state.subscribe('opencode_server', on_opencode_server) diff --git a/lua/opencode/state.lua b/lua/opencode/state.lua index 35dbc004..9b934a83 100644 --- a/lua/opencode/state.lua +++ b/lua/opencode/state.lua @@ -53,7 +53,7 @@ local _state = { last_output_window_position = nil, last_code_win_before_opencode = nil, display_route = nil, - current_mode = config.default_mode, + current_mode = nil, last_output = 0, -- context last_sent_context = nil, From 8c84c2a3b63099b27b0796bb49f1e715bba5081e Mon Sep 17 00:00:00 2001 From: Francis Belanger Date: Thu, 30 Oct 2025 07:28:35 -0400 Subject: [PATCH 2/3] fix(tests): fix tests for pr 80 --- lua/opencode/core.lua | 2 -- lua/opencode/ui/topbar.lua | 4 ++-- tests/helpers.lua | 1 + tests/unit/core_spec.lua | 3 +++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lua/opencode/core.lua b/lua/opencode/core.lua index c39d9a26..320404c4 100644 --- a/lua/opencode/core.lua +++ b/lua/opencode/core.lua @@ -302,8 +302,6 @@ function M.ensure_current_mode() -- Fallback to first available agent state.current_mode = available_agents[1] end - - ui.render_output() end return true end diff --git a/lua/opencode/ui/topbar.lua b/lua/opencode/ui/topbar.lua index f01bbf6b..c0c669a0 100644 --- a/lua/opencode/ui/topbar.lua +++ b/lua/opencode/ui/topbar.lua @@ -26,11 +26,11 @@ local function format_model_info() end local function format_mode_info() - return ' ' .. state.current_mode:upper() .. ' ' + return ' ' .. (state.current_mode or ''):upper() .. ' ' end local function get_mode_highlight() - local mode = state.current_mode:lower() + local mode = (state.current_mode or ''):lower() if mode == 'build' then return '%#OpencodeAgentBuild#' elseif mode == 'plan' then diff --git a/tests/helpers.lua b/tests/helpers.lua index f5cb111c..7eee68ae 100644 --- a/tests/helpers.lua +++ b/tests/helpers.lua @@ -20,6 +20,7 @@ function M.replay_setup() return nil end + state.current_mode = 'build' -- default mode for tests state.windows = ui.create_windows() -- we use the event manager to dispatch events diff --git a/tests/unit/core_spec.lua b/tests/unit/core_spec.lua index 3f95a864..4633baba 100644 --- a/tests/unit/core_spec.lua +++ b/tests/unit/core_spec.lua @@ -21,6 +21,9 @@ local function mock_api_client() get_current_project = function() return Promise.new():resolve({ id = 'test-project-id' }) end, + get_config = function() + return Promise.new():resolve({ model = 'gpt-4' }) + end, } end From 666797da64fe080daaf4a5daf669daf66ba3a9b3 Mon Sep 17 00:00:00 2001 From: Guillaume BOEHM Date: Thu, 30 Oct 2025 08:08:55 +0100 Subject: [PATCH 3/3] tests(config_file): Add disabled default agents config test --- tests/unit/config_file_spec.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit/config_file_spec.lua b/tests/unit/config_file_spec.lua index 09a7d22f..19eb831c 100644 --- a/tests/unit/config_file_spec.lua +++ b/tests/unit/config_file_spec.lua @@ -64,6 +64,21 @@ describe('config_file.setup', function() assert.True(vim.tbl_contains(agents, 'plan')) end) + it('get_opencode_agents respects disabled defaults', function() + state.api_client = { + get_config = function() + return Promise.new():resolve({ agent = { ['custom'] = { mode = 'primary' }, ['build'] = { disable = true }, ['plan'] = { disable = false } } }) + end, + get_current_project = function() + return Promise.new():resolve({ id = 'p1' }) + end, + } + local agents = config_file.get_opencode_agents() + assert.True(vim.tbl_contains(agents, 'custom')) + assert.False(vim.tbl_contains(agents, 'build')) + assert.True(vim.tbl_contains(agents, 'plan')) + end) + it('get_opencode_project returns project', function() local project = { id = 'p1', name = 'X' } state.api_client = {