Repository navigation
fix(sidebar): open pinned Agent instead of All Agents - #20327
Conversation
5eae3c2 to
b924cf2
Compare
b924cf2 to
32832c6
Compare
There was a problem hiding this comment.
Cherry Review · Blocked
1 blocking
Local validation (advisory)
These results do not determine the code-review verdict. Repository CI checks retain their own merge requirements.
targeted-code-check: passed. Vitest preflight succeeded; all 4 frozen changed-test targets were supplied, with 4 test files collected and all 136 tests passing.
See inline comments for individual findings.
32832c6 to
b5dd7d4
Compare
There was a problem hiding this comment.
Cherry Review · Blocked
1 blocking
Local validation (advisory)
These results do not determine the code-review verdict. Repository CI checks retain their own merge requirements.
targeted-code-check: passed. Vitest preflight succeeded; all 4 frozen changed-test targets were supplied, with 4 test files collected and all 136 tests passing.
See inline comments for individual findings.
A newer review conclusion is available, so this REQUEST_CHANGES review was dismissed
|
/review |
There was a problem hiding this comment.
Cherry Review · Passed
1 non_blocking
Local validation (advisory)
These results do not determine the code-review verdict. Repository CI checks retain their own merge requirements.
targeted-code-check: passed. Vitest preflight succeeded; all 4 frozen changed-test targets were supplied, with 4 test files collected and all 137 tests passing.
See inline comments for individual findings.
| await waitFor(() => expect(agentPageMocks.activeSessionOptions?.activeSessionId).toBe('session-pinned-agent')) | ||
| }) | ||
|
|
||
| it('does not close Manage Agents when a leftover agentId remains on a bound session', async () => { |
There was a problem hiding this comment.
Non-blocking risk: The regression test does not place the stale agentId in the tab URL that production inspects, so it passes even if the new close logic is removed.
inv_e603d9ae72bfe99d#c0
A newer review conclusion is available, so this REQUEST_CHANGES review was dismissed
2a049e3 to
9f04424
Compare
There was a problem hiding this comment.
Cherry Review · Blocked
1 blocking
Local validation (advisory)
These results do not determine the code-review verdict. Repository CI checks retain their own merge requirements.
targeted-code-check: passed. Vitest preflight succeeded; all 5 frozen changed-test targets were supplied, with 5 test files collected and all 153 tests passing.
Previously reported findings
src/renderer/components/app/sidebarShortcuts/runtime.tsx:294Must fix: Clicking a pinned Agent does not close Manage Agents when its matching conversation is already open in a non-active tab.inv_d291b15d6dd12851#c0— Existing discussion
Accepted explanations (responses from the previous round that were verified and are no longer counted):
src/renderer/routes/app/agents.tsx:17Accepted the author's explanation that binding a session drops agentId through a session-only redirect; the current beforeLoad implementation confirms that later loads cannot retarget the bound conversation to the agent's newest session.src/renderer/routes/app/agents.tsx:17Accepted the author's explanation that an already-active pinned Agent receives the ?agentId= tab marker and closes Manage Agents; the current runtime active-tab rewrite and AgentPage tabAgentId effect establish that behavior.
9f04424 to
e1e0164
Compare
There was a problem hiding this comment.
Cherry Review · Blocked
1 blocking · 1 non_blocking
Local validation (advisory)
These results do not determine the code-review verdict. Repository CI checks retain their own merge requirements.
targeted-code-check: passed. Vitest preflight succeeded; all 5 frozen changed-test targets were supplied, with 5 test files collected and all 166 tests passing.
See inline comments for individual findings.
Accepted explanations (responses from the previous round that were verified and are no longer counted):
src/renderer/routes/app/agents.tsx:17Accepted the author's explanation that binding a session drops agentId through a session-only redirect; the current beforeLoad implementation confirms that later loads cannot retarget the bound conversation to the agent's newest session.src/renderer/routes/app/agents.tsx:17Accepted the author's explanation that an already-active pinned Agent receives the ?agentId= tab marker and closes Manage Agents; the current runtime active-tab rewrite and AgentPage tabAgentId effect establish that behavior.
| // Manage Agents after this tab is focused, including background tabs. | ||
| if ( | ||
| existing.url !== destination.url && | ||
| new URLSearchParams(destination.url.split('?')[1] ?? '').has('agentId') |
There was a problem hiding this comment.
Non-blocking risk: Agent-specific activation policy has leaked into the shared navigation gateway.
inv_0be57c740632fced#c0
There was a problem hiding this comment.
Moved the ?agentId= rewrite onto destination.replaceExistingUrl so the gateway no longer sniffs agent search params. The agent provider sets that flag.
| if (activeTab && destination.matchesCurrent?.(activeTab.url)) return | ||
| if (activeTab && destination.conversation && findConversationTab([activeTab], destination.conversation)) return | ||
| const existing = destination.conversation | ||
| ? findConversationTab(tabs, destination.conversation) |
There was a problem hiding this comment.
Must fix: Conversation shortcuts can switch away from an already-active matching tab when an earlier duplicate exists.
inv_0be57c740632fced#c1
There was a problem hiding this comment.
Fixed: if the active tab already matches the destination conversation, we reuse it instead of findConversationTab's earliest duplicate. Regression covers an earlier topic/session duplicate, and pinned Agent still writes ?agentId= onto that later tab.
A newer review conclusion is available, so this REQUEST_CHANGES review was dismissed
There was a problem hiding this comment.
Cherry Review · Passed
1 non_blocking
Local validation (advisory)
These results do not determine the code-review verdict. Repository CI checks retain their own merge requirements.
targeted-code-check: passed. Vitest preflight succeeded; all 5 frozen changed-test targets were supplied, with 5 test files collected and all 169 tests passing.
See inline comments for individual findings.
A newer review conclusion is available, so this REQUEST_CHANGES review was dismissed
Honor agentId over a leftover All Agents sessionId so a pinned sidebar click lands on that Agent's conversation. Drop agentId once a session is bound so later loads cannot retarget the conversation or re-close Manage Agents. Signed-off-by: GokayAI <[email protected]>
Sidebar writes ?agentId= onto the tab before beforeLoad drops it. Close the directory from that tab URL so a pin still leaves All Agents when the agent's latest session is already displayed. Signed-off-by: GokayAI <[email protected]>
Pinned Agent activation keeps conversation identity for tab reuse but opens via ?agentId= so a click still retargets a tab already showing that agent's latest session, including a background tab. Signed-off-by: GokayAI <[email protected]>
The close regression inspects the tab URL production reads, so removing the tabAgentId close logic fails the test. Leftover agentId that exists only in route search still leaves Manage Agents open. Signed-off-by: GokayAI <[email protected]>
Prefer the already-active conversation tab over an earlier duplicate when activating a sidebar shortcut. Destinations that need a URL rewrite on reuse now declare replaceExistingUrl instead of the gateway sniffing ?agentId=. Signed-off-by: GokayAI <[email protected]>
d572662 to
ac3e951
Compare
|
Rebased onto latest |
There was a problem hiding this comment.
Cherry Review · Passed
1 non_blocking
Local validation (advisory)
These results do not determine the code-review verdict. Repository CI checks retain their own merge requirements.
targeted-code-check: passed. Vitest preflight succeeded; all 5 frozen changed-test targets were supplied, with 5 test files collected and all 171 tests passing.
Previously reported findings
src/renderer/components/app/sidebarShortcuts/providers.tsx:210Non-blocking risk: Pinned-Agent activation can reuse one session tab and then navigate that tab to a different, newer session.inv_d7889a37dd429319#c0— Existing discussion
Keeps the pinned-Agent entry behaviour from #20327 and moves each rule into the layer that owns it. - `/app/agents` declares its search precedence in `search.middlewares` (a conflicting `sessionId` is dropped when the destination is an Agent entry), and `beforeLoad` goes back to resolving the entry plus the canonical session-only redirect. - The shared `TabRouter` returns to its previous search handling. Omitted search already clears in the pinned `@tanstack/router-core`, so the previous object form and the PR's reducer form produce the same search: no route keeps a changed search semantic. - The "opened from a pinned Agent" event travels as typed destination metadata into tab state instead of a `?agentId=` URL the page re-parses. `AgentPage` closes Manage Agents from one explicit predicate (`enteredByPin` or `activationResolved`) and consumes the marker; reusing a matching tab no longer replaces unrelated tab metadata. Signed-off-by: cherry-coding[bot] <342372629+cherry-coding[bot]@users.noreply.github.com>
Restores the #20327 state so current main merges in without conflicts; the refactor is re-applied immediately after the merge. Net PR diff is unchanged. Signed-off-by: cherry-coding[bot] <342372629+cherry-coding[bot]@users.noreply.github.com>
Keeps the pinned-Agent entry behaviour from #20327 and moves each rule into the layer that owns it. - `/app/agents` declares its search precedence in `search.middlewares` (a conflicting `sessionId` is dropped when the destination is an Agent entry), and `beforeLoad` goes back to resolving the entry plus the canonical session-only redirect. - The shared `TabRouter` returns to its previous search handling. Omitted search already clears in the pinned `@tanstack/router-core`, so the previous object form and the PR's reducer form produce the same search: no route keeps a changed search semantic. - The "opened from a pinned Agent" event travels as typed destination metadata into tab state instead of a `?agentId=` URL the page re-parses. `AgentPage` closes Manage Agents from one explicit predicate (`enteredByPin` or `activationResolved`) and consumes the marker; reusing a matching tab no longer replaces unrelated tab metadata. Signed-off-by: cherry-coding[bot] <342372629+cherry-coding[bot]@users.noreply.github.com>
) ### What this PR does Before this PR: the pinned-Agent entry fix reached its goal through three workarounds. - The shared `TabRouter` navigated with a search **function** for every route, replacing any search of any destination. - "This tab was opened by clicking this pinned Agent" was carried as `?agentId=` in the tab URL, and `AgentPage` re-parsed that URL by hand (`split('?')` + `new URLSearchParams`) to decide whether to close Manage Agents. - Reusing a matching tab wrote `metadata: undefined`, wiping any other tab metadata on it. After this PR: identical behaviour, with each rule in the layer that owns it. 1. `/app/agents` declares its search precedence in `search.middlewares`: when the destination is an Agent entry (and is not a prepared feedback/skill intent), the conflicting `sessionId` is dropped before validation runs. `beforeLoad` is back to what it owns — entry resolution (scoped resolver for an Agent entry, global resolver otherwise) and the canonical `sessionId`-only redirect with `replace: true`. 2. `TabRouter` goes back to its previous search handling (`search: search ? Object.fromEntries(new URLSearchParams(search)) : undefined`). No route's search semantics change; the only route that declares retention/precedence middleware is `/app/agents`. This is a semantic revert: in the pinned `@tanstack/[email protected]`, `applyNext` returns `{}` for an omitted search (`router.ts`, `applyNext`), so the previous object form and the removed function form produce the same search for every current route. 3. The pin event travels as typed destination metadata into tab state: `agentSidebarShortcut.activate` emits `metadata: { agentPinPending: true }`, `openWorkspace` merges it into the reused tab (even when the URL is unchanged), and `AgentPage` closes Manage Agents from one explicit predicate — `enteredByPin || activationResolved` — then consumes only that field from a copied metadata bag. Reusing a matching tab no longer replaces unrelated metadata. Fixes #20251 ### Why we need it and why it was done in this way The behaviour is #20327's; this PR only moves each rule to the layer that owns it, because the previous shape had two problems beyond style: a change to a shared navigation component altered the search semantics of every route (including `/app/chat?assistantId=`, `/app/code?tool=`, `/app/knowledge?baseId=`, `/app/files?entryId=`), and the page re-implemented `validateSearch` by parsing a URL string. Tradeoffs made: - The pin event is a boolean pending marker consumed synchronously by the owning page — not a sticky "this session belongs to a pinned Agent" flag (which would re-close Manage Agents after the user reopens it) and not an activation token (no measurement showed an acknowledgement clearing a later, still-unhandled activation). - `beforeLoad` keeps resolution: the middleware can only express precedence while a destination is built, so the asynchronous "which session does this Agent resume" decision stays where the data lives. Alternatives considered: `ToOptions.state` (not carried by the URL-only tab bridge), `beforeLoad` context (cannot survive the normalising redirect), and the built-in `retainSearchParams` / `stripSearchParams` (they cannot express "drop this key only when another destination key is present"). Links to places where the discussion took place: #20327, #20251. ### Breaking changes None. Behaviour is intended to be identical to #20327; the visible fix and the flag semantics are unchanged. ### Special notes for your reviewer - **Relationship to #20327:** #20327 has landed on `main`, so this branch has now merged `main` in: the PR diff is exactly the normalisation (12 files), nothing else. Reviewing the top commit alone is enough; the `Merge branch 'main'` and the preceding revert commit exist only to re-base the normalisation without rewriting published history, and their net effect is zero. - **Test changes:** assertions that only recorded the previous implementation were replaced with outcomes — `TabRouter.test.tsx` now drives the real router instead of asserting a captured reducer call, `runtime.test.ts` asserts active tab / tab count / metadata instead of mock calls, `AgentPage.test.tsx` covers marker consumption (reopen and a second activation) and keeps a raw tab-URL residue as a negative case that must *not* close the directory, and `agent-entry-normalization.test.tsx` mounts a real `RouterProvider` over the production route options to check the dual-id case before load. ### Verification ``` pnpm exec vitest run --project renderer \ src/renderer/components/app/sidebarShortcuts/__tests__/providers.test.tsx \ src/renderer/components/app/sidebarShortcuts/__tests__/runtime.test.ts \ src/renderer/components/layout/__tests__/TabRouter.test.tsx \ src/renderer/pages/agents/__tests__/AgentPage.test.tsx \ src/renderer/routes/app/__tests__/conversation-entry.test.ts \ src/renderer/routes/app/__tests__/agent-entry-normalization.test.tsx \ src/renderer/pages/agents/__tests__/routeSearch.test.ts \ src/renderer/components/layout/__tests__/TabsProvider.test.tsx # 8 files, 217 tests passed — re-run on the merged head (90bd399) pnpm exec tsc --noEmit -p tsconfig.web.json --composite false # exit 0 pnpm exec oxlint --deny-warnings <changed files> # exit 0 pnpm exec oxfmt --check <changed files> # exit 0 (12 files) ``` Pre/post control (source reverted to the branch base, these tests kept): 15 failures across all six suites, including the dual-id initial load, the leftover tab-URL residue (must not close Manage Agents) and the metadata transport cases — i.e. the new tests fail without the normalisation and pass with it. ### Real-app evidence (Electron, built from this head) The three frames below come from the real Electron app (`pnpm build` on this head), driven through product channels only: one Agent and one session are created via `ai.agent.create` / `POST /agent-sessions`, the entry is pinned through the `ui.sidebar_shortcut` preference, and everything else is clicking the actual UI. Flow: click the pinned Agent once → switch the centre pane to All Agents (`Display mode` → `Manage Agents`) → click the same pinned sidebar entry again, i.e. the "latest session already on screen" case. **1. All Agents (Manage Agents) open, pinned Agent in the sidebar** — this frame is byte-identical on the pre-fix build.  **2. After clicking the pinned Agent: its session is shown and Manage Agents is closed.**  **3. Control — the same step on the pre-fix build (`main` before #20327): Manage Agents stays open, the reported bug.**  How it was run: an out-of-tree Playwright probe over the built app (`_electron.launch`), one run per build with a fresh `CS_DEV_PROFILE_ROOT`. On this head the assertion (`Manage Agents` gone after the click) passes; on the pre-fix build the same probe fails on exactly that assertion (`toHaveCount(0)` received 1). Frames are captured before the assertion, so #3 is the failing run's own screenshot. ### Not verified here - No committed smoke spec for this flow: the probe lives outside the repo, so CI still covers this path only at the renderer/router level. Say the word if you want it promoted into `tests/e2e/smoke/`. - macOS only. The `search` semantics claim above was measured, not assumed, with a mounted router: ``` omitted search -> {} search: true -> current search object form == fresh-object reducer explicit session kept middleware runs for the initial memory-history URL before beforeLoad ``` ### Release note ```release-note NONE ``` --------- Signed-off-by: GokayAI <[email protected]> Signed-off-by: cherry-coding[bot] <342372629+cherry-coding[bot]@users.noreply.github.com> Co-authored-by: GokayAI <[email protected]> Co-authored-by: cherry-coding[bot] <342372629+cherry-coding[bot]@users.noreply.github.com>
What this PR does
Before this PR:
Clicking a pinned Agent in the sidebar from All Agents stayed on the directory. Leftover
sessionIdon the Agents tab maskedagentId. Binding a session also keptagentIdin the URL, so a later route load could replace the bound conversation with the agent's newest session, and AgentPage could close Manage Agents again whenever its activation effect reran.After this PR:
That click opens the pinned Agent's conversation and closes Manage Agents, including when that Agent's latest session is already displayed. Once a session is bound, route
agentIdis dropped so later loads keep the bound conversation.Fixes #20251
Why we need it and why it was done in this way
The following tradeoffs were made:
agentIdwins over leftoversessionId. TabRouter replaces search instead of merging. After binding, the route URL is session-only (same pattern as chat'sassistantId→topicId). AgentPage closes Manage Agents from the tab URL, which still carries?agentId=while beforeLoad drops it from route search. Pinned Agent activation keeps conversation identity for tab reuse but opens via?agentId=so a same-session pin still retargets the current tab.The following alternatives were considered:
Changing only Sidebar URLs — leftover
sessionIdstill won inbeforeLoadand TabRouter merge. Leaving TabRoutersearchunset, which kept the previoussessionId. Keeping routeagentIdafter binding so AgentPage could re-close Manage Agents on the same-session pin — that left the entry hint active for later loads. Opening the boundsessionIddirectly from the shortcut —openWorkspacethen no-oped when that session was already displayed, so Manage Agents stayed open.Links to places where the discussion took place: #20251
Breaking changes
None
Special notes for your reviewer
Tests cover TabRouter search replace, agents
beforeLoaddroppingagentIdafter bind, shortcut activation writing?agentId=on a same-session pin, AgentPage closing Manage Agents from that tab URL, and not re-closing when a leftover routeagentIdremains on a bound session.First-time fork CI may need
/ok-to-test.Checklist
This checklist is not enforcing, but it's a reminder of items that could be relevant to every PR.
Approvers are expected to review this list.
main/gh-pr-review,gh pr diff, or GitHub UI) before requesting review from othersRelease note