Skip to content

API requests: create session in a given directory, repo-scoped session lock, custom slash command registry #511

Closed

Description

@wzj-zz

Context

I built a git-worktree workflow on top of opencode.nvim: each session tab is bound to a worktree directory (<repo>/.worktrees/<branch>), so multiple agents can work in parallel on the same repo with full file isolation. It works well, but it currently depends on internal modules and monkey-patching, so any upstream refactor can silently break it. I'm requesting a few public APIs that would make this (and similar) workflows first-class.

Requests

1. Public "create/open a session in a given directory" API

Something like require('opencode.api').open_session({ directory = ..., new = true }).

Today this requires calling internals:

  • connection.operations.create_session(connection, { directory = ... }, ...) via opencode.server_job
  • opencode.services.session_runtime.open_session_in_tab(session)
  • plus manually fixing up opencode.state.context.set_current_cwd(dir) so @ completion and new sessions (open_input_new_session) resolve in that directory

Sessions bind their directory at creation time and the server already supports per-directory sessions (list_sessions_project), so this is mostly about exposing what's already there.

2. Repo-scoped session lock

lock_session_to_directory is currently boolean: when locked, :cd never switches the session. For worktree workflows the natural semantics are:

  • :cd anywhere inside the same repo (main checkout or any linked worktree) → stay on the current session
  • :cd to a different repo → follow as usual (load that project's last session)

Suggest e.g. lock_session_to_directory = "repo" or a user predicate/hook deciding whether a directory change should swap the session. Today I wrap session_runtime.is_session_locked and state.context.set_current_cwd to get this behavior.

3. Registry for custom slash commands

Something like require('opencode').register_slash_command({ name = 'worktree', args = true, desc = ..., fn = function(args) ... end }).

Today a custom slash command must be written into two internal tables: opencode.commands.slash builtin definitions (execution) and opencode.slash_commands definitions (completion menu).


Happy to test any of these or contribute a PR if you can point me at the shape you'd prefer.

Activity

  1. wzj-zz commented on Oct 10, 2026

    @wzj-zz
    ContributorAuthor

    Resolved by #517

  2. jensenojs commented on Oct 13, 2026

    @jensenojs
    Collaborator

    I'm not sure if you use Enter in the opencode output-window to jump to files and symbols. When I was developing that feature, I didn't take worktrees into account at all, so I'm not sure if the related behavior is reasonable ?

  3. wzj-zz commented on Oct 13, 2026

    @wzj-zz
    ContributorAuthor

    @jensenojs I don't use the <CR> jump feature and am not familiar with it. I tried to test it but couldn't get it to work — the jump doesn't trigger at all on Windows, even in a regular project without worktrees.

    I'm not sure if I'm using it wrong or if it's a bug. Happy to help debug if you can clarify the expected behavior.

  4. jensenojs commented on Oct 13, 2026

    @jensenojs
    Collaborator

    sth like #437

  5. wzj-zz commented on Oct 13, 2026

    @wzj-zz
    ContributorAuthor

    @jensenojs I dug into why <CR> never worked for me, and found two independent breakages under v2. Your #437 symbol jump is affected by both.

    1. V2 normalization drops id from assistant text parts

    v2/normalize.lua maps text parts to just { kind = 'text', text = part.text } — no id. But both reference_facts.collect_part_refs and formatter.current_part_text_references require part.id. Verified live in a v2 session: assistant text/reasoning parts have no id, and reference_facts.current_refs() stays empty. Tool parts keep their id (mapped_tool), so files the assistant edited still appear in the gr picker — which may have masked the bug. V1 keeps id on text parts, so this is v2-only. User-side ~ file attachments lose their refs too (mapped_file doesn't set id either).

    2. Symbol targets never resolve (at least on Windows / Neovim 0.12.5)

    Even with a file in current_files (via an edit tool call), symbol_snapshot.targets_for_token returns {}. I traced it: tree-sitter parses the file fine, query.get('lua', 'locals') succeeds, but query:iter_captures(root, content) yields a single local.scope capture for the whole file and nothing else. One thing that looks suspicious: symbol_snapshot's loop uses for id, node, metadata in query:iter_captures(...) and reads query.captures[id] as the capture name — but the real iter_captures returns (capture_id, node, metadata, match). Your tests mock iter_captures with a 3rd return value equal to the capture name string (tests/unit/symbol_snapshot_spec.lua), which doesn't match the real API signature. So the name-based filtering (@definition.function etc.) may never match against real query results.

    Net effect under v2: no file/symbol targets from assistant text at all, and symbol jump is dead even for tool-referenced files.

  6. jensenojs commented on Oct 13, 2026

    @jensenojs
    Collaborator

    I use this feature on a daily basis, and I'll take some time to look into what's going on with this issue...

    --

    ps. If you are interested in trying out this feature, you can take a deeper look. The owner previously did a lot of work on the v2 refactoring. Based on the symptoms, it seems related to the platform (I don't have a Windows machine either), but the opencode API doesn't feel like something that would be platform-dependent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions