Skip to content

fix(agents): show global-assistant sessions in every drive's sidebar - #2325

Merged
2witstudios merged 6 commits into
masterfrom
pu/no-sessions-in-drive
Aug 4, 2026
Merged

2witstudios merged 6 commits into
masterfrom
pu/no-sessions-in-drive

Conversation

@2witstudios

@2witstudios 2witstudios commented Aug 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Clicking a past conversation from a drive's Agents page opened the pane correctly, but never populated that drive's left sidebar — because the conversation lived in a global-assistant session (agent_sessions.driveId IS NULL), and the sidebar's list() query filtered with a plain driveId = X equality, which never matches NULL in Postgres.
  • Global-assistant sessions belong to the user, not to any drive, so every drive's sidebar now unconditionally shows them in their own "Global Assistant" section (mirroring the dashboard sidebar's existing Assistant group), rather than inferring drive membership from a global session's conversation activity — which would create cross-drive ambiguity about what "belongs" to a drive.
  • agent-sessions-store.ts: list()'s driveId filter now ORs in driveId IS NULL, still gated by the ownerId condition so it can't leak another user's global sessions across drives.
  • AgentsSidebar.tsx: drive-mode now splits into the drive's own session group (unchanged, headerless) plus a distinct Global Assistant group, reusing the existing ASSISTANT_GROUP_KEY/spawn affordance.
  • Adds integration test coverage (inclusion for the same owner + exclusion for a different owner's global session).

Test plan

  • bun run typecheck passes for @pagespace/lib and web
  • bun run --filter '@pagespace/lib' test:db -- src/services/agent-sessions/__tests__/agent-sessions-store.integration.test.ts against a real Postgres instance (couldn't run in this worktree — no local DB role configured)
  • Manual: open/create a global-assistant session, chat with an agent from Drive D, navigate to Drive D's Agents page, click that past conversation — confirm it now appears under "Global Assistant" in Drive D's sidebar, and also in Drive E's sidebar (not just D's), while drive-scoped sessions still don't leak across drives

🤖 Generated with Claude Code

https://claude.ai/code/session_015WjrwseJCNQTQPNrQ6nZ2S

Summary by CodeRabbit

  • New Features

    • Drive views now separate drive-specific sessions from Global Assistant sessions.
    • Existing Global Assistant sessions appear in a dedicated group when viewing a drive.
    • New assistant sessions started from a drive are correctly associated with the Global Assistant.
  • Bug Fixes

    • Improved session filtering so relevant global sessions appear for the selected owner while unrelated sessions remain hidden.
  • Tests

    • Added coverage for session grouping, filtering, empty states, and assistant session creation.

Clicking a past conversation from a drive's Agents page opened the pane
correctly but never populated that drive's left sidebar, even after a
refresh — because the conversation lived in a global-assistant session
(agent_sessions.driveId IS NULL), and the sidebar's list() query filtered
drive-scoped requests with a plain `driveId = X` equality. NULL never
equals X in Postgres, so global sessions were unconditionally excluded.

Global-assistant sessions belong to the user, not to any drive, so every
drive's sidebar now shows them unconditionally in their own "Global
Assistant" section (mirroring the dashboard sidebar's existing Assistant
group) rather than trying to infer drive membership from a global
session's conversation activity, which would create cross-drive
ambiguity about what "belongs" to a drive.

- agent-sessions-store.ts: list()'s driveId filter now ORs in
  driveId IS NULL, still gated by the ownerId condition so it can't leak
  another user's global sessions.
- AgentsSidebar.tsx: drive-mode splits into the drive's own session group
  (unchanged) plus a distinct Global Assistant group.
- Adds integration test coverage for inclusion + owner scoping.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@2witstudios, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 16 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8340bf91-9677-44f8-8dec-361ae3b45da3

📥 Commits

Reviewing files that changed from the base of the PR and between a962db3 and 9992f9a.

📒 Files selected for processing (6)
  • apps/web/src/components/layout/left-sidebar/AgentsSidebar.tsx
  • apps/web/src/components/layout/left-sidebar/__tests__/AgentsSidebar.test.tsx
  • packages/lib/src/services/agent-sessions/__tests__/agent-sessions-store.integration.test.ts
  • packages/lib/src/services/agent-sessions/__tests__/agent-sessions.test.ts
  • packages/lib/src/services/agent-sessions/__tests__/fakes.ts
  • packages/lib/src/services/agent-sessions/agent-sessions-store.ts
📝 Walkthrough

Walkthrough

Drive-scoped session listings now include the current owner’s global sessions. The sidebar displays these sessions in a separate “Global Assistant” group and supports creating new global sessions with driveId: null.

Changes

Drive-scoped global assistant sessions

Layer / File(s) Summary
Session filter contract and database behavior
packages/lib/src/services/agent-sessions/agent-sessions-store.ts
AgentSessionListFilter now distinguishes owner-scoped and ownerless drive queries. Owner-scoped queries include matching global sessions, while ownerless queries remain drive-only.
Session filtering fakes and tests
packages/lib/src/services/agent-sessions/__tests__/*
The fake store and unit and integration tests now cover owner-matched global sessions and exclusion of unrelated or ownerless global sessions.
Sidebar grouping and assistant-session creation
apps/web/src/components/layout/left-sidebar/AgentsSidebar.tsx, apps/web/src/components/layout/left-sidebar/__tests__/AgentsSidebar.test.tsx
Drive mode groups global sessions under “Global Assistant”, omits empty assistant groups, renders differing-drive headers, and creates assistant sessions with driveId: null.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AgentsSidebar
  participant listAgentSessions
  participant AgentSessionStore
  participant Database
  AgentsSidebar->>listAgentSessions: Request sessions for the current drive
  listAgentSessions->>AgentSessionStore: Apply drive and owner filters
  AgentSessionStore->>Database: Query drive sessions and matching owner global sessions
  Database-->>AgentSessionStore: Return filtered sessions
  AgentSessionStore-->>listAgentSessions: Return session list
  listAgentSessions-->>AgentsSidebar: Render drive and Global Assistant groups
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: displaying global-assistant sessions in each drive's Agents sidebar.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/no-sessions-in-drive

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eaee6e6f89

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// ASSISTANT_GROUP_KEY). The ownerId condition above already scopes
// this to the caller when supplied, so this can't surface another
// user's global sessions.
conditions.push(or(eq(agentSessions.driveId, filter.driveId), isNull(agentSessions.driveId))!);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require an owner before unioning global sessions

When a caller uses the supported ownerless { driveId } filter, this OR returns every active global session because the preceding owner condition is absent. AgentSessionListFilter explicitly permits this shape, and the existing listAgentSessions tests exercise it as a drive-wide listing, so a caller authorized to enumerate one drive can now receive unrelated users' global sessions. Only include driveId IS NULL when ownerId is present, or introduce a separate owner-required filter for this sidebar behavior.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Confirmed and fixed in 3ffc6b3. Only OR in driveId IS NULL when the filter also names an ownerId; an ownerless { driveId } listing (the admin-facing "every session in this drive, any owner" shape) now keeps the original strict equality, so it can't return other users' global sessions. Added a unit test for both the owned and ownerless shapes in agent-sessions.test.ts, updated the in-memory fake to mirror the same owner-gated union, and added an integration-test regression case against real Postgres for this exact scenario. Leaving this thread open for verification.

… P1)

Codex correctly flagged that the previous fix unioned in driveId-IS-NULL
sessions unconditionally: AgentSessionListFilter allows an ownerless
{ driveId } shape (the admin "every session in this drive, any owner"
listing agent-sessions.test.ts's listAgentSessions already exercises),
and without an owner to scope the union, it would return every user's
global-assistant sessions across the whole deployment, not just ones
relevant to the drive.

- agent-sessions-store.ts: only OR in driveId IS NULL when the filter
  also names an ownerId; an ownerless driveId filter keeps the old
  strict equality.
- fakes.ts: the in-memory list() now mirrors that same owner-gated
  union so unit tests against the fake match production semantics.
- Adds unit coverage (owned vs ownerless driveId filter) and an
  integration-test regression case against real Postgres for the
  exact leak Codex described.
…group

An adversarial review pass surfaced three real problems with the previous
commit's client change:

1. It broke two pre-existing tests (AgentsSidebar.test.tsx: the new-session
   palette's "Global Assistant" agent pick, and the null-agentPageId
   conversation-label fallback) via a text collision — the new group header
   rendered unconditionally whenever canSpawn was true (mirroring global
   mode's always-show Assistant group), even with zero global sessions to
   show.
2. That same unconditional render sits right next to the drive palette's
   own, unrelated "Global Assistant" agent-picker entry (spawns a
   DRIVE-scoped session with the assistant as its first agent) — same label,
   different scope, easy to conflate.
3. Group order contradicted session-groups.ts's documented "Assistant
   first" rule (drive mode appended it last).

Fix: the drive-mode Assistant group now renders ONLY when the caller
actually has a global session to show (no canSpawn escape hatch), and is
ordered first, matching the dashboard's convention. Adds direct coverage
in AgentsSidebar.test.tsx for the null-driveId session's separate section,
the escape-hatch removal (regression pin for the collision), and spawning
from the group posting driveId: null.
…d filter type

A /simplify pass (reuse, simplification, efficiency, altitude angles, run
in parallel) on the last two commits' diff converged on:

- AgentsSidebar.tsx's drive-mode grouping hand-rolled exactly what
  session-groups.ts's buildSessionGroups already does (bucket by drive,
  Assistant group only when non-empty, Assistant first). Now calls it with
  a single-drive roster instead of duplicating the bucketing/ordering
  logic inline — also removes a double .filter() pass over the session
  list and an unused DriveGroup import.
- The header-suppression check's `!driveId ||` disjunct was dead:
  `group.driveId` is always a real string (never undefined), so it can
  never equal an undefined `driveId` regardless — `group.driveId !==
  driveId` alone already covers both modes.
- AgentSessionListFilter's drive-scoped shape had `ownerId` as merely
  optional, making "owned" vs "ownerless" (a real behavioral fork in
  list() — exactly what Codex's P1 was about) a runtime presence-check
  standing in for what should be a type-level distinction, leaving room
  for a future caller to pass `ownerId: possiblyUndefined` and land in
  either behavior. Split into three explicit variants; list() and the
  in-memory fake now narrow with `'ownerId' in filter` instead of
  `!== undefined` to match.

No behavior change intended — full lib unit suite (8700 tests) and the
web sidebar/session-groups suites still pass, typecheck clean.
@2witstudios

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/web/src/components/layout/left-sidebar/__tests__/AgentsSidebar.test.tsx`:
- Around line 1309-1317: Update the test around the nameInput interaction to
type a non-empty session name before submitting with Enter, then change the
mockPost expectation to assert that exact name in the request body instead of an
empty string.

In `@packages/lib/src/services/agent-sessions/agent-sessions-store.ts`:
- Around line 68-81: Normalize an undefined ownerId to the ownerless
drive-scoped path before selecting branches in agent-sessions-store.ts (lines
68-81 and 415-425), so only a defined ownerId adds global sessions and applies
the owner filter. Update the related fake in
packages/lib/src/services/agent-sessions/__tests__/fakes.ts (lines 130-143) as
needed and add a regression test in
packages/lib/src/services/agent-sessions/__tests__/agent-sessions.test.ts (lines
458-480) for { driveId, ownerId: undefined }, verifying it behaves as an
ownerless drive listing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c6b1bb0d-bac0-4b6b-9e32-20ff7df05d10

📥 Commits

Reviewing files that changed from the base of the PR and between 7a5ea17 and a962db3.

📒 Files selected for processing (6)
  • apps/web/src/components/layout/left-sidebar/AgentsSidebar.tsx
  • apps/web/src/components/layout/left-sidebar/__tests__/AgentsSidebar.test.tsx
  • packages/lib/src/services/agent-sessions/__tests__/agent-sessions-store.integration.test.ts
  • packages/lib/src/services/agent-sessions/__tests__/agent-sessions.test.ts
  • packages/lib/src/services/agent-sessions/__tests__/fakes.ts
  • packages/lib/src/services/agent-sessions/agent-sessions-store.ts

Comment thread packages/lib/src/services/agent-sessions/agent-sessions-store.ts
…odeRabbit)

CodeRabbit found a gap in the P1 fix from 3ffc6b3: 'ownerId' in filter is
true even for { driveId, ownerId: undefined } (the key exists, it's just
unset), so a caller constructing the filter that way would both take the
owner-scoped global-session union AND push a never-matching
eq(ownerId, undefined) condition — silently returning zero rows rather than
the intended ownerless drive-wide listing, for a shape the stricter
three-variant AgentSessionListFilter type no longer names but doesn't fully
guard against at the value level.

- agent-sessions-store.ts's list() and the in-memory fake now normalize to
  `'ownerId' in filter ? filter.ownerId : undefined` and branch on that
  VALUE (not just the key's presence), so an explicitly-undefined ownerId
  is treated identically to an absent one.
- Added regression coverage at both the unit (fake store) and integration
  (real Postgres) layers for { driveId, ownerId: undefined } — reached via
  a deliberate cast, since the type itself no longer permits constructing
  that shape directly, documented as pinning the runtime guard rather than
  describing a shape callers should use.
- Also addressed a minor test-quality nit CodeRabbit raised on the new
  "spawning from the Assistant group" test: it typed Enter on an empty
  name and only asserted name: '' — now types a real name and asserts
  that exact value, strictly widening what the test proves (the
  empty-name path stays covered by the pre-existing dashboard-mode test
  that exercises it on purpose).
@2witstudios

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3446607a10

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// encodes "no canSpawn escape hatch" above (`hasAssistantSessions`
// still shows the group once one exists).
const driveName = drives.find((d) => d.id === driveId)?.name ?? driveId;
return buildSessionGroups(filteredSessions, { assistant: false, drives: [{ driveId, driveName }] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve cross-drive agent names for global sessions

When a global session contains a conversation with an agent from another drive, this newly rendered group cannot label that agent correctly in drive mode. AgentsSidebar still calls usePageAgents(driveId), whose agentsByDrive result is filtered to the current drive, while global sessions explicitly support agents from every accessible drive; consequently conversationLabel falls back to the generic Agent for every out-of-drive agent. Use an unfiltered agent-name lookup for global-session rows while retaining the drive-filtered list for the drive spawn palette.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Confirmed and fixed in 9992f9a — usePageAgents now called unfiltered (undefined instead of driveId) in both modes, so agentNamesById covers every accessible agent, not just this drive's. The spawn palette's drive-scoping is unaffected since useSpawnSession already does its own per-drive .find() lookup on the full list. Added a regression test with a driveId-aware mock (closer to the real hook's filtering than the file's existing fixture) and verified it by temporarily reverting the fix and confirming the test fails as expected. Leaving this thread open for verification.

…ve (review: Codex P2)

Codex's re-review caught a real gap: AgentsSidebar still called
usePageAgents(driveId), whose agentsByDrive result is filtered to the
current drive. A global-assistant session can legitimately host a
conversation with an agent from ANY accessible drive (that's the whole
premise of surfacing them in every drive's sidebar), so once that
session's conversations render here, an out-of-drive agent's id was
never in agentNamesById and conversationLabel silently fell back to the
generic "Agent" instead of the real name.

Fix: call usePageAgents(undefined, ...) — unfiltered — in both modes now,
not just global mode. The spawn palette's drive-scoping is unaffected:
useSpawnSession already does its own
`agentsByDrive.find(entry => entry.driveId === spawnTarget.driveId)`
lookup, so handing it the full list changes nothing about which agents a
drive's "+" offers.

Added a regression test with a driveId-aware mock (a closer simulation of
the real hook's filtering than the file's existing driveId-agnostic
fixture) asserting a global session's out-of-drive agent renders its real
name. Verified end-to-end by temporarily reverting the fix and confirming
the new test fails exactly as expected before restoring it.
@2witstudios
2witstudios merged commit 8537322 into master Aug 4, 2026
3 checks passed
@2witstudios
2witstudios deleted the pu/no-sessions-in-drive branch August 6, 2026 00:47
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.

1 participant