Skip to content

feat(sessions): list and scope sessions per Portal instance#38

Open
Nowaker wants to merge 1 commit intohosenur:mainfrom
Nowaker:list-all-sessions-from-directory
Open

feat(sessions): list and scope sessions per Portal instance#38
Nowaker wants to merge 1 commit intohosenur:mainfrom
Nowaker:list-all-sessions-from-directory

Conversation

@Nowaker
Copy link
Copy Markdown

@Nowaker Nowaker commented Apr 25, 2026

Problem

OpenCode's stable GET /session endpoint filters its results to the OpenCode process's currently active project. As a consequence, Portal's session list shows sessions from only one project at a time, even though the underlying OpenCode database holds sessions for many.

The flat-but-single-project list is confusing for users who maintain many projects: when you point Portal at a directory, you see your global sessions plus that directory's sessions, but every other project's sessions are silently hidden.

After fixing the visibility, the opposite problem also matters: a Portal instance that was started against a sub-tree (e.g. --directory ~/projects/work) should not have to scroll through sessions from unrelated trees. Each instance should focus on the sub-tree it was started for.

What this PR does

Two changes, one commit each.

1. List all sessions across all projects (commit 1)

OpenCode's first-party web UI uses GET /experimental/session, which returns sessions across every project the OpenCode server knows about. Switch Portal's /api/opencode/[port]/sessions route to the same endpoint so Portal's session list mirrors what OpenCode itself shows.

A small getOpencodeBaseUrl(port) helper is added to opencode-client.ts so handlers that need a raw URL (rather than the SDK client) don't have to reimplement hostname resolution from ~/.portal.json.

The new code falls back to the SDK call (client.session.list()) when /experimental/session is not OK (older OpenCode versions, future renames), so users on stale OpenCode binaries continue to see something rather than an empty list.

2. Scope to the instance's --directory by default (commit 2)

After (1) the session list is global. To keep multi-instance setups useful, results are filtered to sessions whose directory is at or under the Portal instance's --directory.

Two query overrides are honored on /api/opencode/[port]/sessions:

  • ?scope=all — return everything (the unfiltered behavior from commit 1).
  • ?directory=<path> — scope to an arbitrary path. Useful for drilling into a specific project from a Portal instance whose --directory is broader.

Path matching is path-component-aware: /foo/bar matches /foo/bar and /foo/bar/... but NOT /foo/bar-baz. Implemented with path.resolve plus an explicit / boundary check.

Instances whose --directory is / are treated as unscoped (no filter applied).

A getInstanceDirectory(port) helper is added next to getOpencodeBaseUrl.

Compatibility

  • The /experimental/* namespace is officially marked experimental upstream. In practice it is reasonably stable since OpenCode's own UI depends on it.
  • Behavior change for existing Portal users: a Portal instance with --directory ~/projects/foo no longer shows sessions from other projects unless ?scope=all is passed. This is intentional and matches user expectation.
  • The SDK fallback ensures stale OpenCode binaries keep working.

Test plan

  1. Run two Portal instances, one at --directory ~/projects and one at --directory ~/projects/foo, against the same OpenCode server.
  2. Default behavior: instance at ~/projects shows all sessions under ~/projects/...; instance at ~/projects/foo shows only that sub-tree.
  3. ?scope=all on either: full firehose.
  4. ?directory=/some/path: results constrained to that path. Override works even when the path is outside the instance's --directory.
  5. Sibling exclusion: a session in ~/projects/foo-bar is NOT shown by an instance whose scope is ~/projects/foo.

All five verified manually with two Portal instances against a server holding 95 sessions across 16 projects: 89 (parent scope), 34 (sub-tree), 95 (?scope=all), 13 (?directory= exact), 0 (sibling).

Notes

  • No test files exist under apps/web/src/server/ today; happy to add coverage if there is a preferred harness.
  • bun.lock changes from bun install were deliberately not committed; this PR touches application code only.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 25, 2026

Someone is attempting to deploy a commit to the hosenur's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Nowaker Nowaker changed the title feat(sessions): list sessions across all projects via /experimental/session feat(sessions): list and scope sessions per Portal instance Apr 25, 2026
OpenCode's stable GET /session filters its results to the OpenCode
process's currently active project. As a result, Portal's session list
shows sessions from only one project at a time, even though the
underlying OpenCode database holds sessions for many. Users with many
projects experience a confusing flat-but-incomplete list.

After fixing the visibility, the opposite problem also matters: a
Portal instance started against a sub-tree (e.g.
`--directory ~/projects/work`) should not have to scroll through
sessions from unrelated trees. Each instance should focus on the
sub-tree it was started for.

This PR makes two coordinated changes to
`/api/opencode/[port]/sessions`:

1) List all sessions across all projects.

   Switch from `client.session.list()` (which hits GET /session) to
   GET /experimental/session, the same endpoint OpenCode's first-party
   web UI uses. Falls back to the SDK call when the experimental
   endpoint is not OK, so users on stale OpenCode binaries keep
   working.

2) Scope the result to the Portal instance's --directory by default.

   The instance's directory (from `~/.portal.json`) is read on each
   request and used as a prefix filter. Two query overrides are
   honored:

     - ?scope=all         — return everything (the unfiltered
                            behavior).
     - ?directory=<path>  — scope to an arbitrary path. Useful for
                            drilling into a specific project from a
                            Portal instance whose --directory is
                            broader.

   Path matching is path-component-aware: `/foo/bar` matches
   `/foo/bar` and `/foo/bar/...` but NOT `/foo/bar-baz`.
   Implemented with `path.resolve` plus an explicit `/` boundary
   check. Instances whose --directory is `/` are treated as unscoped.

Two small helpers are added next to the existing client cache in
`opencode-client.ts` (`getOpencodeBaseUrl`, `getInstanceDirectory`)
so handlers don't have to reimplement hostname resolution and
instance lookup from `~/.portal.json`.

Behavior change: a Portal instance with --directory ~/projects/foo
no longer shows sessions from other projects unless ?scope=all is
passed. This is intentional and matches user expectation when running
multiple Portal instances against different sub-trees.

The /experimental/* namespace is officially marked experimental
upstream. In practice it is reasonably stable since OpenCode's own UI
depends on it. The SDK fallback covers the rest.
@Nowaker Nowaker force-pushed the list-all-sessions-from-directory branch from 2160b74 to 108ab66 Compare April 25, 2026 07:04
Copy link
Copy Markdown
Owner

@hosenur hosenur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issue:

  • apps/web/src/server/opencode/[port]/sessions.ts:36-47 fetches /experimental/session without a limit/cursor, then applies the subtree filter locally. Upstream defaults this endpoint to 100 globally sorted rows, so once there are more than 100 newer sessions outside the selected instance directory, this route can return an empty or truncated list for the selected directory even though matching sessions exist. Please either page through x-next-cursor before filtering, or otherwise request enough data from upstream in a way that preserves the intended scoped list.

Verified: bunx turbo run build --filter=app passes on the PR worktree. I also checked the installed @opencode-ai/sdk v2; it exposes client.experimental.session.list(), but upstream directory filtering is exact, so the local subtree filter still needs pagination if it fetches globally.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants