diff --git a/lua/opencode/services/messaging.lua b/lua/opencode/services/messaging.lua index 4bd433b5..821f9180 100644 --- a/lua/opencode/services/messaging.lua +++ b/lua/opencode/services/messaging.lua @@ -17,6 +17,10 @@ M.send_message = Promise.async(function(prompt, opts) return false end + if state.active_session.parentID then + return false + end + local mentioned_files = context.get_context().mentioned_files or {} local allowed, err_msg = util.check_prompt_allowed(config.prompt_guard, mentioned_files) diff --git a/lua/opencode/services/session_runtime.lua b/lua/opencode/services/session_runtime.lua index dd0755cf..cc77a86a 100644 --- a/lua/opencode/services/session_runtime.lua +++ b/lua/opencode/services/session_runtime.lua @@ -49,7 +49,17 @@ M.switch_session = Promise.async(function(session_id) state.session.set_active(selected_session) if state.ui.is_visible() then - ui.focus_input() + if selected_session and selected_session.parentID then + if not input_window.is_hidden() then + input_window._hide() + end + ui.focus_output() + else + if input_window.is_hidden() then + input_window._show() + end + ui.focus_input() + end else M.open() end diff --git a/tests/unit/services_messaging_spec.lua b/tests/unit/services_messaging_spec.lua index 234398f7..d4472535 100644 --- a/tests/unit/services_messaging_spec.lua +++ b/tests/unit/services_messaging_spec.lua @@ -71,6 +71,23 @@ describe('opencode.services.messaging', function() state.api_client.create_message = orig end) + it('returns false when active session is a child session', function() + state.ui.set_windows({ mock = 'windows' }) + state.session.set_active({ id = 'child1', parentID = 'parent1' }) + + local create_called = false + local orig = state.api_client.create_message + state.api_client.create_message = function(_, sid, params) + create_called = true + return Promise.new():resolve({ id = 'm1' }) + end + + local sent = messaging.send_message('hello world'):wait() + assert.is_false(sent) + assert.is_false(create_called) + state.api_client.create_message = orig + end) + it('increments and decrements user_message_count correctly', function() state.ui.set_windows({ mock = 'windows' }) state.session.set_active({ id = 'sess1' }) diff --git a/tests/unit/services_session_runtime_spec.lua b/tests/unit/services_session_runtime_spec.lua index 1c211a6c..db67c295 100644 --- a/tests/unit/services_session_runtime_spec.lua +++ b/tests/unit/services_session_runtime_spec.lua @@ -333,6 +333,71 @@ describe('opencode.services.session_runtime', function() end) end) + describe('switch_session', function() + local input_window = require('opencode.ui.input_window') + + it('hides input window when switching to a child session', function() + state.ui.set_windows({ mock = 'windows', input_buf = 1, output_buf = 2, input_win = 3, output_win = 4 }) + local orig_is_visible = state.ui.is_visible + state.ui.is_visible = function() return true end + stub(input_window, 'is_hidden').returns(false) + stub(input_window, '_hide') + + session.get_by_id:revert() + stub(session, 'get_by_id').invokes(function(id) + return Promise.new():resolve({ id = id, title = id, modified = os.time(), parentID = 'parent1' }) + end) + + session_runtime.switch_session('child1'):wait() + + assert.stub(input_window._hide).was_called() + assert.stub(ui.focus_output).was_called() + + input_window.is_hidden:revert() + input_window._hide:revert() + state.ui.is_visible = orig_is_visible + end) + + it('shows input window when switching to a non-child session', function() + state.ui.set_windows({ mock = 'windows', input_buf = 1, output_buf = 2, input_win = 3, output_win = 4 }) + local orig_is_visible = state.ui.is_visible + state.ui.is_visible = function() return true end + stub(input_window, 'is_hidden').returns(true) + stub(input_window, '_show') + + session_runtime.switch_session('root1'):wait() + + assert.stub(input_window._show).was_called() + assert.stub(ui.focus_input).was_called() + + input_window.is_hidden:revert() + input_window._show:revert() + state.ui.is_visible = orig_is_visible + end) + + it('does not hide input when already hidden on child session switch', function() + state.ui.set_windows({ mock = 'windows', input_buf = 1, output_buf = 2, input_win = 3, output_win = 4 }) + local orig_is_visible = state.ui.is_visible + state.ui.is_visible = function() return true end + stub(input_window, 'is_hidden').returns(true) + stub(input_window, '_hide') + + session.get_by_id:revert() + stub(session, 'get_by_id').invokes(function(id) + return Promise.new():resolve({ id = id, title = id, modified = os.time(), parentID = 'parent1' }) + end) + + session_runtime.switch_session('child1'):wait() + + assert.stub(input_window._hide).was_not_called() + assert.stub(ui.focus_output).was_called() + + input_window.is_hidden:revert() + input_window._hide:revert() + state.ui.is_visible = orig_is_visible + end) + end) + describe('send_message', function() it('delegates message-sending coverage to services_messaging_spec', function() -- This spec focuses on session_runtime responsibilities.