Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions lua/opencode/commands/handlers/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
# AGENTS.md (handlers)

This directory owns domain actions and command definitions.
This directory owns command-facing action adapters and command definitions.

One-line positioning:
- `handlers/` = **command-entry adapters**
- `services/` = **cross-entry reusable business primitives**

## Scope

- `M.actions`: domain operations
- `M.actions`: command-facing action adapters
- `M.command_defs`: command-facing definitions (desc/completions/execute)
- No command pipeline logic here

## Relation with services

- Handlers should call `services/*` when logic is shared by command/UI/quick_chat entries.
- Handlers should not become the only place for reusable business logic.
- If an action is needed outside command entry paths, move/keep it in `services/`.

## Hard Invariants

- Handlers do not call `dispatch.execute` directly
- Handlers do not parse command text
- Handlers do not decide hook routing
- Keep action behavior identical across entry points
- Handlers must not introduce any new bind/execute entry symbols (`*.run`, `bind_*`, or dispatch wrappers)
- Prefer `services/*` over direct new `opencode.session` / `opencode.api` requires in handlers

## Structure Guidance

- Keep `actions` and `command_defs` aligned by domain
- Keep keymap compatibility aliases explicit and grouped
- Avoid duplicating command validation already guaranteed by parse schema

## Current boundary debt (file-level TODO)

The following handler files still directly require `opencode.session` and should be routed via services APIs.

- [ ] `lua/opencode/commands/handlers/diff.lua` -> `opencode.session`
- [ ] `lua/opencode/commands/handlers/session.lua` -> `opencode.session`

Sync rule:
- keep this list aligned with `lua/opencode/services/AGENTS.md`
- remove an item only after code + tests pass

## Editing Rules

- Prefer consolidation over introducing new layers
Expand All @@ -46,12 +68,14 @@ This directory owns domain actions and command definitions.
- Is `command_defs` declarative and minimal?
- Are aliases grouped and obvious?
- Did we avoid reintroducing duplicate argument validation paths?
- Did we add any new direct `session/api` requires in handlers?

## Reject Conditions

- Any direct call to `dispatch.execute` from handlers
- Any parse/hook routing logic added to handlers
- Any new entry-style wrapper added in handlers
- Any new direct `require('opencode.session')` or `require('opencode.api')` in handlers without exception note

## Minimal Regression Commands

Expand All @@ -63,6 +87,6 @@ This directory owns domain actions and command definitions.
## Entry Notes For New Agents

- Start from the domain file you are touching (`window/session/diff/workflow/surface/agent/permission`), then verify invariants against `commands/dispatch.lua`.
- Treat handlers as **domain behavior + command definition only**.
- Treat handlers as **command adaptation + command definition only**.
- If you feel the need to touch parse/dispatch from handlers, stop and move that change to the command infrastructure layer.
- Keep compatibility aliases explicit, local, and justified inline.
18 changes: 9 additions & 9 deletions lua/opencode/commands/handlers/agent.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
local core = require('opencode.core')
local config_file = require('opencode.config_file')
---@type OpencodeState
local state = require('opencode.state')
local util = require('opencode.util')
local Promise = require('opencode.promise')
local agent_model = require('opencode.services.agent_model')

local M = {
actions = {},
Expand All @@ -18,23 +18,23 @@ local function invalid_arguments(message)
end

function M.actions.configure_provider()
core.configure_provider()
agent_model.configure_provider()
end

function M.actions.configure_variant()
core.configure_variant()
agent_model.configure_variant()
end

function M.actions.cycle_variant()
core.cycle_variant()
agent_model.cycle_variant()
end

function M.actions.agent_plan()
core.switch_to_mode('plan')
agent_model.switch_to_mode('plan')
end

function M.actions.agent_build()
core.switch_to_mode('build')
agent_model.switch_to_mode('build')
end

M.actions.select_agent = Promise.async(function()
Expand All @@ -47,7 +47,7 @@ M.actions.select_agent = Promise.async(function()
return
end

core.switch_to_mode(selection)
agent_model.switch_to_mode(selection)
end)
end)

Expand All @@ -60,11 +60,11 @@ M.actions.switch_mode = Promise.async(function()
end

local next_index = (current_index % #modes) + 1
core.switch_to_mode(modes[next_index])
agent_model.switch_to_mode(modes[next_index])
end)

M.actions.current_model = Promise.async(function()
return core.initialize_current_model()
return agent_model.initialize_current_model()
end)

local agent_subcommands = { 'plan', 'build', 'select' }
Expand Down
4 changes: 2 additions & 2 deletions lua/opencode/commands/handlers/diff.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
local core = require('opencode.core')
local git_review = require('opencode.git_review')
local session_store = require('opencode.session')
---@type OpencodeState
local state = require('opencode.state')
local session_runtime = require('opencode.services.session_runtime')

local M = {
actions = {},
Expand All @@ -11,7 +11,7 @@ local M = {
local diff_subcommands = { 'open', 'next', 'prev', 'close' }

local function with_output_open(callback, open_if_closed)
local open_fn = open_if_closed and core.open_if_closed or core.open
local open_fn = open_if_closed and session_runtime.open_if_closed or session_runtime.open
return function(...)
local args = { ... }
open_fn({ new_session = false, focus = 'output' }):and_then(function()
Expand Down
18 changes: 9 additions & 9 deletions lua/opencode/commands/handlers/session.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
local core = require('opencode.core')
---@type OpencodeState
local state = require('opencode.state')
local session_store = require('opencode.session')
local Promise = require('opencode.promise')
local window_actions = require('opencode.commands.handlers.window').actions
local session_runtime = require('opencode.services.session_runtime')
local agent_model = require('opencode.services.agent_model')

local M = {
actions = {},
Expand Down Expand Up @@ -77,13 +78,13 @@ local function run_api_action_with_checktime(request_promise, error_prefix)
end

function M.actions.open_input_new_session()
return core.open({ new_session = true, focus = 'input', start_insert = true })
return session_runtime.open({ new_session = true, focus = 'input', start_insert = true })
end

---@param title string
function M.actions.open_input_new_session_with_title(title)
return Promise.async(function(session_title)
local new_session = core.create_new_session(session_title):await()
local new_session = session_runtime.create_new_session(session_title):await()
if not new_session then
vim.notify('Failed to create new session', vim.log.levels.ERROR)
return
Expand All @@ -96,12 +97,12 @@ end

---@param parent_id? string
function M.actions.select_session(parent_id)
core.select_session(parent_id)
session_runtime.select_session(parent_id)
end

function M.actions.select_child_session()
local active = state.active_session
core.select_session(active and active.id or nil)
session_runtime.select_session(active and active.id or nil)
end

---@param current_session? Session
Expand Down Expand Up @@ -162,15 +163,14 @@ function M.actions.initialize()
return Promise.async(function()
local id = require('opencode.id')
local state_obj = state
local core_obj = core

local new_session = core_obj.create_new_session('AGENTS.md Initialization'):await()
local new_session = session_runtime.create_new_session('AGENTS.md Initialization'):await()
if not new_session then
vim.notify('Failed to create new session', vim.log.levels.ERROR)
return
end

if not core_obj.initialize_current_model():await() or not state_obj.current_model then
if not agent_model.initialize_current_model():await() or not state_obj.current_model then
vim.notify('No model selected', vim.log.levels.ERROR)
return
end
Expand Down Expand Up @@ -369,7 +369,7 @@ function M.actions.fork_session(message_id)
vim.schedule(function()
if response and response.id then
vim.notify('Session forked successfully. New session ID: ' .. response.id, vim.log.levels.INFO)
core.switch_session(response.id)
session_runtime.switch_session(response.id)
else
vim.notify('Session forked but no new session ID received', vim.log.levels.WARN)
end
Expand Down
14 changes: 7 additions & 7 deletions lua/opencode/commands/handlers/window.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
local core = require('opencode.core')
---@type OpencodeState
local state = require('opencode.state')
local ui = require('opencode.ui.ui')
local config = require('opencode.config')
local Promise = require('opencode.promise')
local input_window = require('opencode.ui.input_window')
local session_runtime = require('opencode.services.session_runtime')

local M = {
actions = {},
Expand All @@ -19,11 +19,11 @@ local function invalid_arguments(message)
end

function M.actions.open_input()
return core.open({ new_session = false, focus = 'input', start_insert = true })
return session_runtime.open({ new_session = false, focus = 'input', start_insert = true })
end

function M.actions.open_output()
return core.open({ new_session = false, focus = 'output' })
return session_runtime.open({ new_session = false, focus = 'output' })
end

function M.actions.close()
Expand All @@ -47,7 +47,7 @@ function M.actions.get_window_state()
end

function M.actions.cancel()
core.cancel()
session_runtime.cancel()
end

---@param hidden OpencodeHiddenBuffers|nil
Expand Down Expand Up @@ -90,8 +90,8 @@ M.actions.toggle = Promise.async(function(new_session)

local function open_windows(restore_hidden)
local ctx = build_toggle_open_context(restore_hidden == true)
return core
.open({
return session_runtime
.open({
new_session = is_new_session,
focus = ctx.focus,
start_insert = false,
Expand Down Expand Up @@ -132,7 +132,7 @@ end)
function M.actions.toggle_focus(new_session)
if not ui.is_opencode_focused() then
local focus = state.last_focused_opencode_window or 'input' ---@cast focus 'input' | 'output'
core.open({ new_session = new_session == true, focus = focus })
session_runtime.open({ new_session = new_session == true, focus = focus })
else
ui.return_to_last_code_win()
end
Expand Down
12 changes: 6 additions & 6 deletions lua/opencode/commands/handlers/workflow.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local core = require('opencode.core')
local util = require('opencode.util')
local config_file = require('opencode.config_file')
---@type OpencodeState
Expand All @@ -11,6 +10,7 @@ local Promise = require('opencode.promise')
local input_window = require('opencode.ui.input_window')
local ui = require('opencode.ui.ui')
local nvim = vim['api']
local session_runtime = require('opencode.services.session_runtime')

local M = {
actions = {},
Expand Down Expand Up @@ -49,8 +49,8 @@ end
---@param prompt string
---@param opts SendMessageOpts
local function run_with_opts(prompt, opts)
return core.open(opts):and_then(function()
return core.send_message(prompt, opts)
return session_runtime.open(opts):and_then(function()
return require('opencode.services.messaging').send_message(prompt, opts)
end)
end

Expand Down Expand Up @@ -189,7 +189,7 @@ for _, action_name in ipairs({ 'debug_output', 'debug_message', 'debug_session'
end

function M.actions.paste_image()
core.paste_image_from_clipboard()
session_runtime.paste_image_from_clipboard()
end

M.actions.submit_input_prompt = Promise.async(function()
Expand Down Expand Up @@ -301,12 +301,12 @@ function M.actions.toggle_max_messages()
end

M.actions.review = Promise.async(function(args)
local new_session = core.create_new_session('Code review checklist for diffs and PRs'):await()
local new_session = session_runtime.create_new_session('Code review checklist for diffs and PRs'):await()
if not new_session then
vim.notify('Failed to create new session', vim.log.levels.ERROR)
return
end
if not core.initialize_current_model():await() or not state.current_model then
if not require('opencode.services.agent_model').initialize_current_model():await() or not state.current_model then
vim.notify('No model selected', vim.log.levels.ERROR)
return
end
Expand Down
Loading
Loading