feat: add Regent orchestrator as persistent right sidebar#1
feat: add Regent orchestrator as persistent right sidebar#1soyboyscout wants to merge 2 commits intomainfrom
Conversation
Replace the full-page Regent route with a persistent right sidebar panel that includes vertical thread tabs and an AI chat interface powered by Tambo. Users can now interact with the Regent without leaving their current session. - Add RegentSidebar component with vertical tab bar for thread switching - Add Regent toggle button to TopBar (diamond icon, matches sidebar pattern) - Add store state for regentPanelOpen and rightPanelActiveTab - Split RegentPanel into RegentProvider + RegentChat exports - Add session-aware Tambo tools and custom components (SessionCard, TaskOverview) - Remove Regent from sidebar navigation and routing - Remove inline collapsed Context button (TopBar toggle handles it) - Add VITE_TAMBO_API_KEY to .env.example
|
Caution PR Summary Skipped - Monthly Quota ExceededPR summary skipped as you have reached the free tier limit of 50 PR summaries per month. Please upgrade to a paid plan for MatterAI. Current Plan: Free Tier Upgrade your plan on the console here: https://app.matterai.so/ai-code-reviews?tab=Billing |
📝 WalkthroughWalkthroughAdds Tambo AI integration: new Regent chat UI (RegentPanel, RegentSidebar), session-focused Tambo tools, task/session display components, store state for panel control, package deps, env var, and related tests and mocks. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as App/TopBar/RightPanel
participant Store as useStore
participant Regent as RegentProvider/RegentChat
participant Tambo as Tambo API / Threads DB
User->>UI: Click "Toggle Regent panel" / open sidebar
UI->>Store: setRegentPanelOpen(true)
Store-->>UI: regentPanelOpen=true
UI->>Regent: render RegentProvider (apiKey via env)
Regent->>Tambo: fetch thread list (useTamboThreadList)
Tambo-->>Regent: thread list
Regent-->>UI: render VerticalTabBar with threads
User->>UI: Click thread tab (id)
UI->>Store: setRightPanelActiveTab(id)
UI->>Regent: useTambo.switchThread(id)
Regent->>Tambo: request thread messages / stream
Tambo-->>Regent: stream messages
Regent-->>UI: update RegentChat feed (auto-scroll)
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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. Comment |
Review Summary by QodoAdd Regent orchestrator as persistent right sidebar with Tambo AI integration
WalkthroughsDescription• Add Regent orchestrator as persistent right sidebar with vertical thread tabs • Implement session-aware Tambo tools for AI-powered agent orchestration • Add Regent toggle button to TopBar matching existing sidebar pattern • Create custom Tambo components (SessionCard, TaskOverview) for session management • Add store state for regentPanelOpen and rightPanelActiveTab Diagramflowchart LR
A["Store State<br/>regentPanelOpen<br/>rightPanelActiveTab"] -->|manages| B["RegentSidebar<br/>Vertical Tabs"]
B -->|renders| C["RegentChat<br/>Tambo Provider"]
C -->|uses| D["Session Tools<br/>listSessions<br/>getSessionMessages<br/>getSessionTasks"]
D -->|powers| E["Custom Components<br/>SessionCard<br/>TaskOverview"]
F["TopBar<br/>Regent Toggle"] -->|controls| A
G["Tambo API<br/>Threads & Messages"] -->|feeds| C
File Changes1. web/src/regent/tools/session-tools.ts
|
Code Review by Qodo
1. RegentPanel missing .test.tsx
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the user experience by transforming the Regent orchestrator into a persistent, interactive right sidebar. This change integrates AI-powered chat capabilities, allowing users to manage and query agent sessions directly from a convenient panel. The update streamlines workflow by providing quick access to session details and tasks, moving away from a full-page view to a more integrated and responsive design. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant new feature: the Regent orchestrator as a persistent right sidebar. The implementation is comprehensive, adding new components, AI tools via Tambo, and extensive tests. The code is generally well-structured. I've identified one high-severity issue concerning a hardcoded user key that could lead to data leakage, and a couple of medium-severity issues related to a potential bug from an incomplete useEffect dependency array and a schema inconsistency. Addressing these points will improve the robustness and security of the new feature.
| return ( | ||
| <TamboProvider | ||
| apiKey={TAMBO_API_KEY} | ||
| userKey="companion-user" |
There was a problem hiding this comment.
The userKey for TamboProvider is hardcoded to "companion-user". If this application is used by multiple users, they will all share the same Tambo user profile, including chat threads and history. This is a potential data privacy and security issue.
The userKey should be a unique identifier for each authenticated user. You could use a user ID from your authentication system, for example.
<TamboProvider
apiKey={TAMBO_API_KEY}
userKey={getUniqueUserId()} // e.g., from auth context or store
tools={sessionTools}
components={[sessionCardTamboComponent, taskOverviewTamboComponent]}
>| useEffect(() => { | ||
| if (currentThreadId && currentThreadId !== activeTab) { | ||
| setActiveTab(currentThreadId); | ||
| } | ||
| }, [currentThreadId]); |
There was a problem hiding this comment.
The useEffect hook has an incomplete dependency array. It is missing activeTab. This could lead to using a stale value of activeTab inside the effect's closure, potentially causing bugs where the UI does not update as expected when the current thread changes.
| useEffect(() => { | |
| if (currentThreadId && currentThreadId !== activeTab) { | |
| setActiveTab(currentThreadId); | |
| } | |
| }, [currentThreadId]); | |
| useEffect(() => { | |
| if (currentThreadId && currentThreadId !== activeTab) { | |
| setActiveTab(currentThreadId); | |
| } | |
| }, [currentThreadId, activeTab, setActiveTab]); |
| items: { | ||
| type: "object" as const, | ||
| properties: { | ||
| sessionName: { type: "string" as const }, | ||
| subject: { type: "string" as const }, | ||
| status: { type: "string" as const }, | ||
| activeForm: { type: "string" as const }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
The JSON schema for the tasks array items is missing the required field. This makes all properties of a task item optional from the schema's perspective, which is inconsistent with the TaskEntry TypeScript interface where sessionName, subject, and status are required. This could lead to unexpected behavior if the AI provides incomplete task objects.
items: {
type: "object" as const,
properties: {
sessionName: { type: "string" as const },
subject: { type: "string" as const },
status: { type: "string" as const },
activeForm: { type: "string" as const },
},
required: ["sessionName", "subject", "status"],
},
There was a problem hiding this comment.
4 issues found across 16 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="web/src/regent/components/TaskOverview.tsx">
<violation number="1" location="web/src/regent/components/TaskOverview.tsx:113">
P2: `status` is too loosely validated in `propsSchema`; unsupported values are accepted but then omitted from all rendered groups.</violation>
</file>
<file name="web/src/components/RegentPanel.tsx">
<violation number="1" location="web/src/components/RegentPanel.tsx:142">
P1: Hardcoded `userKey` makes all users share the same Tambo identity, which can leak/mix thread data across users.</violation>
</file>
<file name="web/src/regent/tools/session-tools.ts">
<violation number="1" location="web/src/regent/tools/session-tools.ts:17">
P2: Tool implementations return stringified JSON while `outputSchema` declares structured object output, creating a schema/output type mismatch.</violation>
</file>
<file name="web/src/regent/components/SessionCard.tsx">
<violation number="1" location="web/src/regent/components/SessionCard.tsx:129">
P2: `propsSchema` incorrectly rejects nullable values for `currentTask` and `gitBranch` even though the component contract allows `null`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| return ( | ||
| <TamboProvider | ||
| apiKey={TAMBO_API_KEY} | ||
| userKey="companion-user" |
There was a problem hiding this comment.
P1: Hardcoded userKey makes all users share the same Tambo identity, which can leak/mix thread data across users.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/src/components/RegentPanel.tsx, line 142:
<comment>Hardcoded `userKey` makes all users share the same Tambo identity, which can leak/mix thread data across users.</comment>
<file context>
@@ -0,0 +1,159 @@
+ return (
+ <TamboProvider
+ apiKey={TAMBO_API_KEY}
+ userKey="companion-user"
+ tools={sessionTools}
+ components={[sessionCardTamboComponent, taskOverviewTamboComponent]}
</file context>
| properties: { | ||
| sessionName: { type: "string" as const }, | ||
| subject: { type: "string" as const }, | ||
| status: { type: "string" as const }, |
There was a problem hiding this comment.
P2: status is too loosely validated in propsSchema; unsupported values are accepted but then omitted from all rendered groups.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/src/regent/components/TaskOverview.tsx, line 113:
<comment>`status` is too loosely validated in `propsSchema`; unsupported values are accepted but then omitted from all rendered groups.</comment>
<file context>
@@ -0,0 +1,125 @@
+ properties: {
+ sessionName: { type: "string" as const },
+ subject: { type: "string" as const },
+ status: { type: "string" as const },
+ activeForm: { type: "string" as const },
+ },
</file context>
| tool: async () => { | ||
| const store = getStore(); | ||
| const sessions = store.sdkSessions.filter((s) => !s.archived); | ||
| return JSON.stringify( |
There was a problem hiding this comment.
P2: Tool implementations return stringified JSON while outputSchema declares structured object output, creating a schema/output type mismatch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/src/regent/tools/session-tools.ts, line 17:
<comment>Tool implementations return stringified JSON while `outputSchema` declares structured object output, creating a schema/output type mismatch.</comment>
<file context>
@@ -0,0 +1,261 @@
+ tool: async () => {
+ const store = getStore();
+ const sessions = store.sdkSessions.filter((s) => !s.archived);
+ return JSON.stringify(
+ sessions.map((s) => ({
+ sessionId: s.sessionId,
</file context>
| type: "number" as const, | ||
| description: "Number of pending permission requests", | ||
| }, | ||
| currentTask: { |
There was a problem hiding this comment.
P2: propsSchema incorrectly rejects nullable values for currentTask and gitBranch even though the component contract allows null.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/src/regent/components/SessionCard.tsx, line 129:
<comment>`propsSchema` incorrectly rejects nullable values for `currentTask` and `gitBranch` even though the component contract allows `null`.</comment>
<file context>
@@ -0,0 +1,149 @@
+ type: "number" as const,
+ description: "Number of pending permission requests",
+ },
+ currentTask: {
+ type: "string" as const,
+ description: "Currently active task description, or null",
</file context>
There was a problem hiding this comment.
Walkthrough
This PR integrates Tambo AI chat functionality into the web application through a new collapsible Regent sidebar panel. The implementation adds the Tambo AI SDK and React Query dependencies, creates a chat interface with thread management, and implements six session management tools for AI agent interactions. The sidebar features responsive design with mobile/desktop layouts, thread navigation tabs, and conditional rendering based on API key presence. State management is extended to control panel visibility and active tabs. Comprehensive test coverage is added for all new components, and the TopBar receives a toggle button for panel control.
Changes
| File(s) | Summary |
|---|---|
web/.env.example |
Added VITE_TAMBO_API_KEY environment variable for Tambo service integration. |
web/package.json |
Added four new dependencies: @tambo-ai/client, @tambo-ai/react, @tambo-ai/typescript-sdk, and @tanstack/react-query. |
web/src/components/RegentPanel.tsx |
Introduced new Regent AI chat panel with three components: RegentChat (message display and streaming), RegentProvider (Tambo SDK wrapper with session tools), and RegentPanel (main export). Includes hasTamboApiKey() utility function. |
web/src/components/RightPanel.tsx |
Implemented collapsible sidebar with VerticalTabBar (thread navigation with up to 20 tabs) and RegentSidebar (responsive container with mobile/desktop layouts). Integrates Tambo hooks and Zustand store for state management. |
web/src/regent/components/SessionCard.tsx |
Created SessionCard component displaying session metadata with real-time status indicators, helper functions for status visualization, and Tambo component registry integration. |
web/src/regent/components/TaskOverview.tsx |
Added TaskOverview component with status-based task grouping (in-progress, pending, completed), truncation logic, and Tambo component configuration export. |
web/src/regent/tools/session-tools.ts |
Implemented six AI agent tools: listSessions, getSessionMessages, getSessionTasks, getSessionStatus, getSessionDiff, and getAllSessionsSummary with JSON schemas for type safety. |
web/src/App.tsx |
Replaced conditional task panel toggle button with persistent RegentSidebar component, removing desktop-only floating button with 'Context' label. |
web/src/components/TopBar.tsx |
Added Regent panel toggle button with conditional rendering based on API key presence, using diamond-shaped SVG icon and active/inactive styling. |
web/src/store.ts |
Added regentPanelOpen (defaults to true for viewports >= 1280px) and rightPanelActiveTab state properties with corresponding setters and reset logic. |
web/src/App.test.tsx |
|
web/src/components/TopBar.test.tsx |
Added mocks for RegentSidebar and hasTamboApiKey function to isolate component testing. |
web/src/components/RightPanel.test.tsx |
|
web/src/regent/components/SessionCard.test.tsx |
|
web/src/regent/components/TaskOverview.test.tsx |
|
web/src/regent/tools/session-tools.test.ts |
Added comprehensive test suites covering rendering, user interactions, state management, conditional logic, and edge cases using Vitest and React Testing Library. |
🔗 Cross-Repository Impact Analysis
Enable automatic detection of breaking changes across your dependent repositories. → Set up now
Learn more about Cross-Repository Analysis
What It Does
- Automatically identifies repositories that depend on this code
- Analyzes potential breaking changes across your entire codebase
- Provides risk assessment before merging to prevent cross-repo issues
How to Enable
- Visit Settings → Code Management
- Configure repository dependencies
- Future PRs will automatically include cross-repo impact analysis!
Benefits
- 🛡️ Prevent breaking changes across repositories
- 🔍 Catch integration issues before they reach production
- 📊 Better visibility into your multi-repo architecture
| type: "number" as const, | ||
| description: "Number of pending permission requests", | ||
| }, | ||
| currentTask: { | ||
| type: "string" as const, | ||
| description: "Currently active task description, or null", | ||
| }, |
There was a problem hiding this comment.
Correctness: The propsSchema for currentTask and gitBranch defines the type as string, but the SessionCardProps interface and the schema's own descriptions allow null. This mismatch causes valid null values to be rejected by validation or tooling consuming this schema. Update the schema to accept null.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In `web/src/regent/components/SessionCard.tsx` around lines 126-132, update the `propsSchema` definitions for `currentTask` and `gitBranch` to allow `null` (e.g., `type: ["string", "null"] as const`). This keeps the schema consistent with the component props that allow `null`.
| outputSchema: { | ||
| type: "object" as const, | ||
| properties: {}, |
There was a problem hiding this comment.
Correctness: outputSchema declares an object, but the tool returns a JSON string. This type mismatch violates the tool's contract and leads to runtime failures during output validation. Align the schema with the actual return type (or return a real object/array). Apply the same fix to the other new tools returning JSON.stringify(...).
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In `web/src/regent/tools/session-tools.ts` around lines 35-37, the tool returns `JSON.stringify(...)` but `outputSchema` is declared as an object. Update `outputSchema` to `type: "string"` (or return a real object) to avoid output validation errors. Apply the same alignment to all newly added tools that return JSON strings.
| import { useRef, useEffect, type ReactNode } from "react"; | ||
| import { | ||
| TamboProvider, | ||
| useTambo, | ||
| useTamboThreadInput, | ||
| ComponentRenderer, | ||
| } from "@tambo-ai/react"; | ||
| import { sessionTools } from "../regent/tools/session-tools.js"; | ||
| import { sessionCardTamboComponent } from "../regent/components/SessionCard.js"; | ||
| import { taskOverviewTamboComponent } from "../regent/components/TaskOverview.js"; | ||
|
|
||
| const TAMBO_API_KEY = (import.meta as unknown as Record<string, Record<string, string>>).env?.VITE_TAMBO_API_KEY; | ||
|
|
||
| export function hasTamboApiKey(): boolean { | ||
| return !!TAMBO_API_KEY; | ||
| } |
There was a problem hiding this comment.
1. regentpanel missing .test.tsx 📘 Rule violation ⛯ Reliability
web/src/components/RegentPanel.tsx is a new component but there is no corresponding RegentPanel.test.tsx providing render, axe, and interaction coverage. This violates the baseline frontend component test requirement and risks regressions in the Regent chat/provider UI.
Agent Prompt
## Issue description
`web/src/components/RegentPanel.tsx` is a new frontend component but it does not have a corresponding `RegentPanel.test.tsx` that includes (1) a render test, (2) an axe accessibility scan assertion using `toHaveNoViolations()`, and (3) interaction/state behavior tests.
## Issue Context
Compliance requires baseline test coverage for each new/modified component under `web/src/components/`. This PR introduces the Regent chat/provider UI, including API-key gating and a composer.
## Fix Focus Areas
- web/src/components/RegentPanel.tsx[1-159]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| // @vitest-environment jsdom | ||
| import { render, screen, fireEvent } from "@testing-library/react"; | ||
| import "@testing-library/jest-dom"; | ||
| import { describe, it, expect, vi, beforeEach } from "vitest"; | ||
|
|
||
| const mockSwitchThread = vi.fn(); | ||
| const mockStartNewThread = vi.fn(); | ||
|
|
||
| vi.mock("@tambo-ai/react", () => ({ | ||
| TamboProvider: ({ children }: { children: React.ReactNode }) => <div data-testid="tambo-provider">{children}</div>, | ||
| useTambo: () => ({ | ||
| messages: [], | ||
| isStreaming: false, | ||
| currentThreadId: "thread-1", | ||
| switchThread: mockSwitchThread, | ||
| startNewThread: mockStartNewThread, | ||
| client: {}, | ||
| thread: undefined, | ||
| streamingState: { status: "idle" }, | ||
| isWaiting: false, | ||
| isIdle: true, | ||
| registerComponent: vi.fn(), | ||
| registerTool: vi.fn(), | ||
| registerTools: vi.fn(), | ||
| componentList: new Map(), | ||
| toolRegistry: new Map(), | ||
| initThread: vi.fn(), | ||
| dispatch: vi.fn(), | ||
| cancelRun: vi.fn(), | ||
| authState: { status: "identified" }, | ||
| isIdentified: true, | ||
| updateThreadName: vi.fn(), | ||
| }), | ||
| useTamboThreadInput: () => ({ | ||
| value: "", | ||
| setValue: vi.fn(), | ||
| submit: vi.fn(), | ||
| isPending: false, | ||
| }), | ||
| useTamboThreadList: () => ({ | ||
| data: { | ||
| threads: [ | ||
| { id: "thread-1", name: "Thread One", runStatus: "idle", createdAt: "2025-01-01", updatedAt: "2025-01-01" }, | ||
| { id: "thread-2", name: "Thread Two", runStatus: "idle", createdAt: "2025-01-01", updatedAt: "2025-01-01" }, | ||
| ], | ||
| hasMore: false, | ||
| }, | ||
| isLoading: false, | ||
| isError: false, | ||
| }), | ||
| ComponentRenderer: () => null, | ||
| })); | ||
|
|
||
| const workerStub = vi.hoisted(() => { | ||
| return vi.fn().mockImplementation(() => ({ | ||
| postMessage: vi.fn(), | ||
| terminate: vi.fn(), | ||
| addEventListener: vi.fn(), | ||
| removeEventListener: vi.fn(), | ||
| onmessage: null, | ||
| onerror: null, | ||
| })); | ||
| }); | ||
|
|
||
| vi.stubGlobal("Worker", workerStub); | ||
|
|
||
| vi.mock("../regent/tools/session-tools.js", () => ({ | ||
| sessionTools: [], | ||
| })); | ||
|
|
||
| vi.mock("../regent/components/SessionCard.js", () => ({ | ||
| sessionCardTamboComponent: { | ||
| name: "SessionCard", | ||
| description: "test", | ||
| component: () => null, | ||
| propsSchema: { type: "object" as const, properties: {}, required: [] }, | ||
| }, | ||
| SessionCard: () => null, | ||
| })); | ||
|
|
||
| vi.mock("../regent/components/TaskOverview.js", () => ({ | ||
| taskOverviewTamboComponent: { | ||
| name: "TaskOverview", | ||
| description: "test", | ||
| component: () => null, | ||
| propsSchema: { type: "object" as const, properties: {}, required: [] }, | ||
| }, | ||
| TaskOverview: () => null, | ||
| })); | ||
|
|
||
| import { RegentSidebar } from "./RightPanel.js"; | ||
| import { useStore } from "../store.js"; | ||
|
|
||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| useStore.setState({ | ||
| regentPanelOpen: true, | ||
| rightPanelActiveTab: "", | ||
| }); | ||
| }); | ||
|
|
||
| describe("RegentSidebar", () => { | ||
| it("renders Regent thread tabs from Tambo thread list", () => { | ||
| render(<RegentSidebar />); | ||
| expect(screen.getByTitle("Thread One")).toBeInTheDocument(); | ||
| expect(screen.getByTitle("Thread Two")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("shows new thread button", () => { | ||
| render(<RegentSidebar />); | ||
| expect(screen.getByTitle("New Regent thread")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("renders RegentChat when panel is open", () => { | ||
| render(<RegentSidebar />); | ||
| expect(screen.getByPlaceholderText("Ask the Regent...")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("switching to a thread tab calls switchThread", () => { | ||
| render(<RegentSidebar />); | ||
| fireEvent.click(screen.getByTitle("Thread Two")); | ||
| expect(mockSwitchThread).toHaveBeenCalledWith("thread-2"); | ||
| }); | ||
|
|
||
| it("collapses panel when closed", () => { | ||
| useStore.setState({ regentPanelOpen: false }); | ||
| const { container } = render(<RegentSidebar />); | ||
| const panelWrapper = container.querySelector(".translate-x-full"); | ||
| expect(panelWrapper).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("clicking new thread button calls startNewThread", () => { | ||
| render(<RegentSidebar />); | ||
| fireEvent.click(screen.getByTitle("New Regent thread")); | ||
| expect(mockStartNewThread).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("shows diamond header icon in vertical tab bar", () => { | ||
| render(<RegentSidebar />); | ||
| const tabBar = screen.getByTitle("New Regent thread").parentElement; | ||
| expect(tabBar).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("shows numbered tabs for each thread", () => { | ||
| render(<RegentSidebar />); | ||
| expect(screen.getByText("1")).toBeInTheDocument(); | ||
| expect(screen.getByText("2")).toBeInTheDocument(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
2. rightpanel.test.tsx missing axe 📘 Rule violation ⛯ Reliability
web/src/components/RightPanel.test.tsx includes render and interaction checks but does not run an axe accessibility scan with toHaveNoViolations(). This violates the required accessibility baseline for new/modified components.
Agent Prompt
## Issue description
`web/src/components/RightPanel.test.tsx` lacks the required axe accessibility test (`expect(results).toHaveNoViolations()`).
## Issue Context
Other component tests in the repo use `vitest-axe` (dynamic import) to enforce a baseline accessibility check. Rule 2 requires this for each new/modified component test.
## Fix Focus Areas
- web/src/components/RightPanel.test.tsx[1-149]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const TAMBO_API_KEY = (import.meta as unknown as Record<string, Record<string, string>>).env?.VITE_TAMBO_API_KEY; | ||
|
|
||
| export function hasTamboApiKey(): boolean { | ||
| return !!TAMBO_API_KEY; | ||
| } |
There was a problem hiding this comment.
3. Tambo key exposed client 🐞 Bug ⛨ Security
Regent reads VITE_TAMBO_API_KEY from the Vite client environment and passes it to TamboProvider, which means the key is shipped to every browser. If the key is privileged, it can be extracted and abused (billing/tenant access).
Agent Prompt
### Issue description
`VITE_TAMBO_API_KEY` is consumed in the browser and passed to `TamboProvider`, which ships the key to all clients. If the key is privileged, it can be extracted from the JS bundle/devtools and reused.
### Issue Context
This app already has an authenticated backend API layer (`/api` with Bearer auth). Prefer using that backend to hold third-party secrets and/or mint scoped, short-lived tokens.
### Fix Focus Areas
- web/src/components/RegentPanel.tsx[12-16]
- web/src/components/RegentPanel.tsx[139-145]
- web/.env.example[1-3]
- web/src/api.ts[5-13]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| <TamboProvider | ||
| apiKey={TAMBO_API_KEY} | ||
| userKey="companion-user" | ||
| tools={sessionTools} | ||
| components={[sessionCardTamboComponent, taskOverviewTamboComponent]} |
There was a problem hiding this comment.
4. Hard-coded tambo userkey 🐞 Bug ⛨ Security
TamboProvider is initialized with a constant userKey ("companion-user"), so different authenticated
users/devices can share the same Tambo identity and potentially see each other’s threads/history.
This is a privacy/security risk for an app with auth tokens.
Agent Prompt
### Issue description
`userKey` is hard-coded to a constant value, which collapses all users into one shared Tambo identity and can mix threads/history across users.
### Issue Context
The UI uses an auth token (`companion_auth_token`) and tracks authentication state in the store, so multiple distinct principals are expected.
### Fix Focus Areas
- web/src/components/RegentPanel.tsx[139-145]
- web/src/store.ts[33-44]
- web/src/api.ts[5-13]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc9552a24b
ℹ️ 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".
web/src/components/RightPanel.tsx
Outdated
| fixed inset-y-0 right-0 lg:relative lg:inset-auto z-40 lg:z-auto | ||
| h-full shrink-0 transition-all duration-200 pt-safe lg:pt-0 | ||
| ${regentPanelOpen ? "w-full lg:w-[340px] translate-x-0" : "w-0 translate-x-full lg:w-0 lg:translate-x-full"} |
There was a problem hiding this comment.
Add a mobile close affordance for the Regent sidebar
On small screens this panel expands to w-full (lg:w-[340px] only applies on desktop), so it covers the entire viewport; because the only close path is the backdrop click behind it and there is no in-panel close button, users who open Regent on mobile can get stuck with no visible way to dismiss it except reloading. This is reproducible whenever VITE_TAMBO_API_KEY is set and the Regent toggle is used on a phone-sized viewport.
Useful? React with 👍 / 👎.
| it("renders Regent thread tabs from Tambo thread list", () => { | ||
| render(<RegentSidebar />); | ||
| expect(screen.getByTitle("Thread One")).toBeInTheDocument(); | ||
| expect(screen.getByTitle("Thread Two")).toBeInTheDocument(); |
There was a problem hiding this comment.
Decouple RightPanel tests from Tambo env configuration
These tests assume RegentSidebar renders, but the component short-circuits to null when hasTamboApiKey() is false; since this file does not mock that guard (unlike TopBar.test.tsx), the assertions become environment-dependent and will fail in CI configurations without VITE_TAMBO_API_KEY (I checked .github/workflows/ci.yml, which only injects PostHog vars for test/build jobs).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (8)
web/src/components/TopBar.test.tsx (1)
15-17: Add test coverage for Regent panel toggle.The mock sets
hasTamboApiKeyto always returnfalse, which prevents testing the Regent toggle functionality. Consider adding a test case that mockshasTamboApiKeyto returntrueand verifies:
- The Regent toggle button is rendered
- Clicking the button calls
setRegentPanelOpen- Active/inactive styling is applied correctly
Also, the mock store state (lines 19-39) should include
regentPanelOpenandsetRegentPanelOpento prevent potential runtime errors ifhasTamboApiKeyreturnstrue:regentPanelOpen: false, setRegentPanelOpen: vi.fn(),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/TopBar.test.tsx` around lines 15 - 17, Update the test to cover the Regent panel toggle by adding a case where the mocked hasTamboApiKey returns true and asserting the toggle behavior: adjust the mock of hasTamboApiKey to return true for that test, ensure the mocked store includes regentPanelOpen (e.g., false) and setRegentPanelOpen (a vi.fn()) so the component can render, then verify the Regent toggle button is rendered, simulate a click and assert setRegentPanelOpen was called, and finally assert the button receives the correct active/inactive styling after toggling; reference the mocked function hasTamboApiKey and the store props regentPanelOpen and setRegentPanelOpen when making these changes.web/src/components/RegentPanel.tsx (2)
12-12: Simplify the Vite environment variable access.The type assertion chain is verbose. Vite provides built-in types for
import.meta.env. Consider using the standard pattern:-const TAMBO_API_KEY = (import.meta as unknown as Record<string, Record<string, string>>).env?.VITE_TAMBO_API_KEY; +const TAMBO_API_KEY = import.meta.env.VITE_TAMBO_API_KEY as string | undefined;If the project lacks Vite client types, add them in
tsconfig.jsonvia"types": ["vite/client"].🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/RegentPanel.tsx` at line 12, Replace the verbose type assertion used to read the env var for TAMBO_API_KEY by using Vite's built-in import.meta.env access: reference import.meta.env.VITE_TAMBO_API_KEY when defining TAMBO_API_KEY (symbol: TAMBO_API_KEY in RegentPanel.tsx) and, if TS errors occur, enable Vite client types by adding "types": ["vite/client"] to the tsconfig.json compilerOptions.types so the standard typed import.meta.env is available.
141-142: Consider dynamic user identification for Tambo.The
userKeyis hardcoded as"companion-user", which means all users of the application will share the same Tambo identity. This could lead to:
- Thread history mixing between different users
- Incorrect context in AI responses
- Privacy concerns in multi-user deployments
If this is intentional for a single-user scenario, consider adding a comment explaining the design decision. Otherwise, derive the userKey from user authentication state or a unique identifier.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/RegentPanel.tsx` around lines 141 - 142, The userKey prop on the RegentPanel/Tambo integration is hardcoded as "companion-user" (userKey="companion-user"), which will merge identities across users; change userKey to a per-user unique value (e.g., derive from your auth/session user id, currentUser.id, or a generated per-session UUID passed into the component) so each user gets a distinct Tambo identity and thread history, or if a single-shared identity is intentional add a clear inline comment on the RegentPanel/Tambo call explaining that design choice and the reason for using a static userKey.web/.env.example (1)
3-3: Consider adding a comment for obtaining the Tambo API key.For better developer experience, consider adding a comment indicating where developers can obtain a Tambo API key:
VITE_POSTHOG_KEY=phc_your_project_key VITE_POSTHOG_HOST=https://us.i.posthog.com +# Get your Tambo API key from https://tambo.ai (or relevant URL) VITE_TAMBO_API_KEY=your_tambo_api_key🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/.env.example` at line 3, Add a one-line comment above the VITE_TAMBO_API_KEY entry in web/.env.example that tells developers where to obtain the Tambo API key (e.g., link to Tambo signup or docs) and any required scope/format, referencing the VITE_TAMBO_API_KEY variable so it's clear which env value the comment applies to.web/src/regent/components/SessionCard.tsx (1)
129-136: Schema doesn't indicate nullable fields.The
currentTaskandgitBranchprops acceptnullper the TypeScript interface (lines 12-13), but the JSON schema doesn't indicate this. Consider addingnullable: trueor using a union type if the schema consumer needs to know these can be null.💡 Optional schema improvement
currentTask: { type: "string" as const, description: "Currently active task description, or null", + nullable: true, }, gitBranch: { type: "string" as const, description: "Git branch name, or null", + nullable: true, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/regent/components/SessionCard.tsx` around lines 129 - 136, The JSON schema for the SessionCard props declares currentTask and gitBranch as type "string" but the TypeScript interface allows null; update the schema for the properties currentTask and gitBranch in SessionCard.tsx so consumers know they can be null — either add "nullable: true" to each property or change the type to a union that accepts null (e.g., type ["string", "null"]) so the schema matches the TypeScript interface.web/src/components/RightPanel.tsx (1)
33-37: Missing dependency in useEffect.The
setActiveTabfunction is used inside the effect but not included in the dependency array. While Zustand selectors are typically stable, including it follows React best practices and satisfies the exhaustive-deps lint rule.♻️ Suggested fix
useEffect(() => { if (currentThreadId && currentThreadId !== activeTab) { setActiveTab(currentThreadId); } - }, [currentThreadId]); + }, [currentThreadId, activeTab, setActiveTab]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/RightPanel.tsx` around lines 33 - 37, The useEffect that synchronizes currentThreadId to activeTab is missing setActiveTab in its dependency array; update the effect watching [currentThreadId] to include setActiveTab as well (i.e., [currentThreadId, setActiveTab]) so the effect references are stable and satisfy exhaustive-deps while keeping the logic inside useEffect (currentThreadId, activeTab, setActiveTab).web/src/components/RightPanel.test.tsx (1)
138-142: Consider strengthening the diamond icon assertion.This test only verifies that the "New Regent thread" button has a parent element, which doesn't actually confirm the diamond icon is rendered. Consider querying for the SVG element or using a more specific assertion.
💡 Suggested improvement
it("shows diamond header icon in vertical tab bar", () => { render(<RegentSidebar />); - const tabBar = screen.getByTitle("New Regent thread").parentElement; - expect(tabBar).toBeInTheDocument(); + // Query the header icon SVG in the vertical tab bar + const headerSection = screen.getByTitle("New Regent thread").closest('[class*="flex-col"]'); + const diamondIcon = headerSection?.querySelector('svg'); + expect(diamondIcon).toBeInTheDocument(); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/RightPanel.test.tsx` around lines 138 - 142, The test "shows diamond header icon in vertical tab bar" currently only checks parentElement existence; update it to assert the actual diamond icon is rendered by querying the SVG/icon node inside the tab bar. In RightPanel.test.tsx (the test with render(<RegentSidebar />) and const tabBar = screen.getByTitle("New Regent thread").parentElement), replace or extend the expect to look for the SVG element (e.g., querySelector('svg') on tabBar or use screen.getByRole/getByTitle/getByTestId for the diamond icon) and assert it is in the document and visible, or assert a specific icon CSS class or aria-label that the RegentSidebar renders for the diamond icon.web/src/regent/tools/session-tools.ts (1)
30-39: Consider defining meaningful output schemas.All tools use empty output schemas (
properties: {}). While this works, defining actual schemas would improve documentation and enable better tooling/validation for consumers of the Tambo API.Also applies to: 73-77, 105-109, 147-151, 193-197, 248-252
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/regent/tools/session-tools.ts` around lines 30 - 39, Several tool definitions in session-tools.ts currently use empty outputSchema objects (outputSchema: { properties: {} }) which provide no validation or documentation; for each tool definition where you see outputSchema and inputSchema declared (the blocks with type: "object" as const and properties: {}), replace the empty properties with meaningful JSON Schema properties and required arrays that reflect the tool's actual return shape (e.g., string/number/boolean fields, nested objects, arrays), update inputSchema similarly where inputs are non-empty, and ensure any shared types are imported or exported as TypeScript types/interfaces; update all occurrences (the repeated schema blocks in this file) so consumers of the Tambo API get concrete schemas and validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/package.json`:
- Around line 56-59: The package.json lists pre-1.0 Tambo packages (notably
"@tambo-ai/client" at ^0.0.1 and "@tambo-ai/typescript-sdk" at ^0.93.1) which
may be unstable; confirm with the Tambo AI team that these exact releases are
production-ready, then pin those dependencies to exact versions (remove ^ and
use exact version strings) for "@tambo-ai/client", "@tambo-ai/react" and
"@tambo-ai/typescript-sdk" to avoid automatic minor/patch upgrades and reduce
risk of unexpected breaking changes during installs.
In `@web/src/components/RegentPanel.tsx`:
- Around line 75-79: The ComponentRenderer is being passed an empty-string
fallback for threadId which violates its requirement for a valid thread ID; in
RegentPanel replace the current rendering logic so that ComponentRenderer (used
in the JSX block with props content, threadId, messageId) is only rendered when
currentThreadId is non-null/defined (i.e., guard the render with a check for
currentThreadId) or ensure currentThreadId is resolved earlier; update the JSX
around ComponentRenderer to skip rendering when currentThreadId is falsy rather
than passing "".
---
Nitpick comments:
In `@web/.env.example`:
- Line 3: Add a one-line comment above the VITE_TAMBO_API_KEY entry in
web/.env.example that tells developers where to obtain the Tambo API key (e.g.,
link to Tambo signup or docs) and any required scope/format, referencing the
VITE_TAMBO_API_KEY variable so it's clear which env value the comment applies
to.
In `@web/src/components/RegentPanel.tsx`:
- Line 12: Replace the verbose type assertion used to read the env var for
TAMBO_API_KEY by using Vite's built-in import.meta.env access: reference
import.meta.env.VITE_TAMBO_API_KEY when defining TAMBO_API_KEY (symbol:
TAMBO_API_KEY in RegentPanel.tsx) and, if TS errors occur, enable Vite client
types by adding "types": ["vite/client"] to the tsconfig.json
compilerOptions.types so the standard typed import.meta.env is available.
- Around line 141-142: The userKey prop on the RegentPanel/Tambo integration is
hardcoded as "companion-user" (userKey="companion-user"), which will merge
identities across users; change userKey to a per-user unique value (e.g., derive
from your auth/session user id, currentUser.id, or a generated per-session UUID
passed into the component) so each user gets a distinct Tambo identity and
thread history, or if a single-shared identity is intentional add a clear inline
comment on the RegentPanel/Tambo call explaining that design choice and the
reason for using a static userKey.
In `@web/src/components/RightPanel.test.tsx`:
- Around line 138-142: The test "shows diamond header icon in vertical tab bar"
currently only checks parentElement existence; update it to assert the actual
diamond icon is rendered by querying the SVG/icon node inside the tab bar. In
RightPanel.test.tsx (the test with render(<RegentSidebar />) and const tabBar =
screen.getByTitle("New Regent thread").parentElement), replace or extend the
expect to look for the SVG element (e.g., querySelector('svg') on tabBar or use
screen.getByRole/getByTitle/getByTestId for the diamond icon) and assert it is
in the document and visible, or assert a specific icon CSS class or aria-label
that the RegentSidebar renders for the diamond icon.
In `@web/src/components/RightPanel.tsx`:
- Around line 33-37: The useEffect that synchronizes currentThreadId to
activeTab is missing setActiveTab in its dependency array; update the effect
watching [currentThreadId] to include setActiveTab as well (i.e.,
[currentThreadId, setActiveTab]) so the effect references are stable and satisfy
exhaustive-deps while keeping the logic inside useEffect (currentThreadId,
activeTab, setActiveTab).
In `@web/src/components/TopBar.test.tsx`:
- Around line 15-17: Update the test to cover the Regent panel toggle by adding
a case where the mocked hasTamboApiKey returns true and asserting the toggle
behavior: adjust the mock of hasTamboApiKey to return true for that test, ensure
the mocked store includes regentPanelOpen (e.g., false) and setRegentPanelOpen
(a vi.fn()) so the component can render, then verify the Regent toggle button is
rendered, simulate a click and assert setRegentPanelOpen was called, and finally
assert the button receives the correct active/inactive styling after toggling;
reference the mocked function hasTamboApiKey and the store props regentPanelOpen
and setRegentPanelOpen when making these changes.
In `@web/src/regent/components/SessionCard.tsx`:
- Around line 129-136: The JSON schema for the SessionCard props declares
currentTask and gitBranch as type "string" but the TypeScript interface allows
null; update the schema for the properties currentTask and gitBranch in
SessionCard.tsx so consumers know they can be null — either add "nullable: true"
to each property or change the type to a union that accepts null (e.g., type
["string", "null"]) so the schema matches the TypeScript interface.
In `@web/src/regent/tools/session-tools.ts`:
- Around line 30-39: Several tool definitions in session-tools.ts currently use
empty outputSchema objects (outputSchema: { properties: {} }) which provide no
validation or documentation; for each tool definition where you see outputSchema
and inputSchema declared (the blocks with type: "object" as const and
properties: {}), replace the empty properties with meaningful JSON Schema
properties and required arrays that reflect the tool's actual return shape
(e.g., string/number/boolean fields, nested objects, arrays), update inputSchema
similarly where inputs are non-empty, and ensure any shared types are imported
or exported as TypeScript types/interfaces; update all occurrences (the repeated
schema blocks in this file) so consumers of the Tambo API get concrete schemas
and validation.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
web/.env.exampleweb/package.jsonweb/src/App.test.tsxweb/src/App.tsxweb/src/components/RegentPanel.tsxweb/src/components/RightPanel.test.tsxweb/src/components/RightPanel.tsxweb/src/components/TopBar.test.tsxweb/src/components/TopBar.tsxweb/src/regent/components/SessionCard.test.tsxweb/src/regent/components/SessionCard.tsxweb/src/regent/components/TaskOverview.test.tsxweb/src/regent/components/TaskOverview.tsxweb/src/regent/tools/session-tools.test.tsweb/src/regent/tools/session-tools.tsweb/src/store.ts
| "@tambo-ai/client": "^0.0.1", | ||
| "@tambo-ai/react": "^1.1.0", | ||
| "@tambo-ai/typescript-sdk": "^0.93.1", | ||
| "@tanstack/react-query": "^5.90.21", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if Tambo packages exist on npm and their versions
echo "=== Checking `@tambo-ai/client` ==="
curl -s https://registry.npmjs.org/@tambo-ai/client 2>/dev/null | jq -r '.name, .["dist-tags"], (.versions | keys | reverse | .[0:5])' 2>/dev/null || echo "Not found or private"
echo -e "\n=== Checking `@tambo-ai/react` ==="
curl -s https://registry.npmjs.org/@tambo-ai/react 2>/dev/null | jq -r '.name, .["dist-tags"], (.versions | keys | reverse | .[0:5])' 2>/dev/null || echo "Not found or private"
echo -e "\n=== Checking `@tambo-ai/typescript-sdk` ==="
curl -s https://registry.npmjs.org/@tambo-ai/typescript-sdk 2>/dev/null | jq -r '.name, .["dist-tags"], (.versions | keys | reverse | .[0:5])' 2>/dev/null || echo "Not found or private"
echo -e "\n=== Checking `@tanstack/react-query` ==="
curl -s https://registry.npmjs.org/@tanstack/react-query 2>/dev/null | jq -r '.name, .["dist-tags"].latest' 2>/dev/null || echo "Not found"Repository: CodeCraftersLLC/companion
Length of output: 561
Verify stability of @tambo-ai/client before production use.
The @tambo-ai/client package is at version 0.0.1—the only available release on npm. This extremely early version indicates the package is in initial development stages and may be unstable. Additionally, the @tambo-ai/typescript-sdk is at version 0.93.1, which, while more mature, is still pre-1.0 and subject to breaking changes.
Consider:
- Verifying with the Tambo AI team that these versions are production-ready.
- Pinning to exact versions for all Tambo packages to prevent unexpected breaking changes during dependency updates.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/package.json` around lines 56 - 59, The package.json lists pre-1.0 Tambo
packages (notably "@tambo-ai/client" at ^0.0.1 and "@tambo-ai/typescript-sdk" at
^0.93.1) which may be unstable; confirm with the Tambo AI team that these exact
releases are production-ready, then pin those dependencies to exact versions
(remove ^ and use exact version strings) for "@tambo-ai/client",
"@tambo-ai/react" and "@tambo-ai/typescript-sdk" to avoid automatic minor/patch
upgrades and reduce risk of unexpected breaking changes during installs.
| <ComponentRenderer | ||
| content={content} | ||
| threadId={currentThreadId ?? ""} | ||
| messageId={msg.id} | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
@tambo-ai/react ComponentRenderer threadId prop documentation
💡 Result:
In @tambo-ai/react (React SDK 1.0+), ComponentRenderer expects a threadId prop that is the ID of the thread that the message (and its component content block) belongs to—you pass it alongside the content block and the messageId when rendering a "component" content block. [1]
You can typically source that ID from useTambo()’s currentThreadId (or from your thread object, e.g. thread.id). [2]
Example (from the migration docs):
<ComponentRenderer
content={block}
threadId={threadId}
messageId={message.id}
fallback={<div>Unknown component: {block.name}</div>}
/>
``` [1]
**Sources:** [1] [2]
---
🏁 Script executed:
```shell
# Search for ComponentRenderer imports and usage in the codebase
rg "ComponentRenderer" --type tsx --type ts -B 2 -A 5Repository: CodeCraftersLLC/companion
Length of output: 96
🏁 Script executed:
# Find the file and examine the context around the flagged lines
cat -n web/src/components/RegentPanel.tsx | sed -n '60,90p'Repository: CodeCraftersLLC/companion
Length of output: 1336
🏁 Script executed:
# Check for type definitions or documentation for ComponentRenderer
fd -e d.ts -e ts | xargs rg "ComponentRenderer" -lRepository: CodeCraftersLLC/companion
Length of output: 53
Remove empty string fallback for threadId—ComponentRenderer requires a valid thread ID.
The @tambo-ai/react ComponentRenderer expects threadId to be "the ID of the thread that the message belongs to." Passing an empty string when currentThreadId is null does not meet this requirement and may cause runtime errors or unexpected behavior. Either ensure currentThreadId is always available at this point, or skip rendering the component entirely when the thread ID is unavailable.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/RegentPanel.tsx` around lines 75 - 79, The
ComponentRenderer is being passed an empty-string fallback for threadId which
violates its requirement for a valid thread ID; in RegentPanel replace the
current rendering logic so that ComponentRenderer (used in the JSX block with
props content, threadId, messageId) is only rendered when currentThreadId is
non-null/defined (i.e., guard the render with a check for currentThreadId) or
ensure currentThreadId is resolved earlier; update the JSX around
ComponentRenderer to skip rendering when currentThreadId is falsy rather than
passing "".
There was a problem hiding this comment.
Pull request overview
This PR introduces “Regent” as a persistent right-side panel (instead of a full-page route) and wires it into the existing UI/store to support multi-thread Tambo chat plus session-orchestration tools/components.
Changes:
- Added Regent right sidebar with vertical thread tabs and a TopBar toggle (gated by Tambo API key).
- Introduced Tambo session-aware tools plus Regent UI components (SessionCard, TaskOverview).
- Extended global store state for Regent panel open/active-thread tracking and updated tests/docs/env.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/store.ts | Adds Regent panel UI state + actions to the global store. |
| web/src/regent/tools/session-tools.ts | Implements Tambo tools for listing sessions/messages/tasks/status/diff. |
| web/src/regent/tools/session-tools.test.ts | Adds unit tests for the new session tools. |
| web/src/regent/components/TaskOverview.tsx | Adds aggregated task overview component + Tambo component registration. |
| web/src/regent/components/TaskOverview.test.tsx | Adds TaskOverview rendering tests. |
| web/src/regent/components/SessionCard.tsx | Adds session summary card + Tambo component registration. |
| web/src/regent/components/SessionCard.test.tsx | Adds SessionCard interaction/render tests. |
| web/src/components/TopBar.tsx | Adds Regent toggle button to TopBar (gated by API key). |
| web/src/components/TopBar.test.tsx | Mocks Regent gating for TopBar tests. |
| web/src/components/RightPanel.tsx | Adds RegentSidebar + vertical thread tab bar and panel layout. |
| web/src/components/RightPanel.test.tsx | Adds RegentSidebar behavior tests (thread tabs/new thread/panel collapse). |
| web/src/components/RegentPanel.tsx | Adds Tambo provider + chat UI (RegentChat) + hasTamboApiKey gating. |
| web/src/App.tsx | Renders RegentSidebar persistently at app root and adjusts task panel comment. |
| web/src/App.test.tsx | Mocks RegentSidebar for App tests. |
| web/package.json | Adds Tambo + React Query dependencies. |
| web/.env.example | Documents VITE_TAMBO_API_KEY. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| tool: async () => { | ||
| const store = getStore(); | ||
| const sessions = store.sdkSessions.filter((s) => !s.archived); | ||
| return JSON.stringify( | ||
| sessions.map((s) => ({ |
There was a problem hiding this comment.
The tools are returning JSON-serialized strings (JSON.stringify(...)), but outputSchema is declared as an object. If Tambo validates or uses the schema for function-calling, this mismatch can break tool execution or degrade model behavior. Either return a real JS object/array that matches outputSchema (preferred), or change outputSchema to type "string" and document that the payload is JSON-encoded (apply consistently across all tools in this file).
| return JSON.stringify({ | ||
| sessionId: args.sessionId, | ||
| cwd: sdkSession.cwd, | ||
| changedFileCount: files.length, | ||
| files: files.slice(0, 30).map((f) => ({ |
There was a problem hiding this comment.
api.getChangedFiles(cwd, ...) returns repo-wide paths (not scoped to cwd). Here changedFileCount/files are computed from the full repo diff, which can report changes outside the session working directory. Filter the returned files to those within sdkSession.cwd (same prefix filtering used in App.tsx/DiffPanel.tsx) before counting and slicing.
| return JSON.stringify({ | |
| sessionId: args.sessionId, | |
| cwd: sdkSession.cwd, | |
| changedFileCount: files.length, | |
| files: files.slice(0, 30).map((f) => ({ | |
| const cwdPrefix = | |
| sdkSession.cwd.endsWith("/") || sdkSession.cwd.endsWith("\\") | |
| ? sdkSession.cwd | |
| : sdkSession.cwd + "/"; | |
| const filteredFiles = files.filter((f) => { | |
| return f.path === sdkSession.cwd || f.path.startsWith(cwdPrefix); | |
| }); | |
| return JSON.stringify({ | |
| sessionId: args.sessionId, | |
| cwd: sdkSession.cwd, | |
| changedFileCount: filteredFiles.length, | |
| files: filteredFiles.slice(0, 30).map((f) => ({ |
| const TAMBO_API_KEY = (import.meta as unknown as Record<string, Record<string, string>>).env?.VITE_TAMBO_API_KEY; | ||
|
|
||
| export function hasTamboApiKey(): boolean { | ||
| return !!TAMBO_API_KEY; | ||
| } |
There was a problem hiding this comment.
TAMBO_API_KEY is read via a broad cast on import.meta and cached at module load. This bypasses the repo’s ImportMetaEnv typing and makes tests harder to control with vi.stubEnv(). Prefer using import.meta.env.VITE_TAMBO_API_KEY directly, and add VITE_TAMBO_API_KEY to src/vite-env.d.ts (like analytics.ts does for PostHog).
| <TamboProvider | ||
| apiKey={TAMBO_API_KEY} | ||
| userKey="companion-user" | ||
| tools={sessionTools} | ||
| components={[sessionCardTamboComponent, taskOverviewTamboComponent]} | ||
| > |
There was a problem hiding this comment.
userKey is hard-coded to "companion-user". If Tambo uses userKey to scope threads/history, this will mix thread identity across all users/browsers sharing the same API key (and can lead to unexpected data cross-talk). Generate a stable per-browser/user key (e.g., persisted UUID in localStorage, or derived from an authenticated user id) instead of a constant.
| {inProgress.map((t, i) => ( | ||
| <TaskRow key={`ip-${i}`} task={t} /> | ||
| ))} | ||
| </div> |
There was a problem hiding this comment.
Task rows use array indices as React keys. When tasks change order or move between sections (pending → in_progress → completed), React can reuse DOM nodes incorrectly and show stale content. Prefer a stable unique key (e.g., task.id) and include it in TaskEntry, or derive a stable composite key (sessionName + subject + status) if no id is available.
| import { RegentSidebar } from "./RightPanel.js"; | ||
| import { useStore } from "../store.js"; | ||
|
|
||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| useStore.setState({ | ||
| regentPanelOpen: true, | ||
| rightPanelActiveTab: "", | ||
| }); |
There was a problem hiding this comment.
This test relies on hasTamboApiKey() being true, but RegentPanel reads VITE_TAMBO_API_KEY at module import time and there’s no stub/mock here. On machines/CI without that env var, RegentSidebar will return null and these assertions will fail. Consider mocking ./RegentPanel.js in this test (like TopBar.test does), or refactor RegentPanel to read import.meta.env at runtime so you can vi.stubEnv() + dynamic import reliably.
| import { RegentSidebar } from "./components/RightPanel.js"; | ||
| import { UpdateBanner } from "./components/UpdateBanner.js"; |
There was a problem hiding this comment.
RegentSidebar (and its Tambo dependencies) is imported and rendered unconditionally at the app root. This means everyone pays the bundle/runtime cost for @tambo-ai/* even when Regent is disabled (no API key). Consider code-splitting RegentSidebar behind a lightweight API-key check (or behind the toggle) using React.lazy/dynamic import so the optional feature doesn’t impact initial load for all users.
Add bg-cc-bg for solid background and use top-11 positioning on mobile so the panel renders below the TopBar instead of covering it.
|
Caution PR Summary Skipped - Monthly Quota ExceededPR summary skipped as you have reached the free tier limit of 50 PR summaries per month. Please upgrade to a paid plan for MatterAI. Current Plan: Free Tier Upgrade your plan on the console here: https://app.matterai.so/ai-code-reviews?tab=Billing |
|
|
||
| <div |
There was a problem hiding this comment.
Correctness: Combining top-11, bottom-0, and h-full on the fixed sidebar causes it to overflow the viewport by 44px (the top-11 offset), which will hide content at the bottom of the panel. To fix this while preserving the desktop layout, remove the base h-full and use lg:h-full instead. This allows the top and bottom properties to correctly constrain the height on mobile.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In `web/src/components/RightPanel.tsx`, remove the `h-full` class from the Regent panel wrapper that now uses `top-11 bottom-0`. Keeping `h-full` with top/bottom causes the panel to overflow the viewport. Let top/bottom define the height or replace with a calc height.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
web/src/components/RightPanel.tsx (2)
33-37: Complete the effect dependencies for state sync.On Line 37, the effect reads
activeTabandsetActiveTabbut only depends oncurrentThreadId. Adding all referenced values keeps sync behavior deterministic.Suggested change
useEffect(() => { if (currentThreadId && currentThreadId !== activeTab) { setActiveTab(currentThreadId); } - }, [currentThreadId]); + }, [currentThreadId, activeTab, setActiveTab]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/RightPanel.tsx` around lines 33 - 37, The useEffect in RightPanel (the effect that checks currentThreadId and sets activeTab) references activeTab and setActiveTab but only lists currentThreadId in its dependency array; update the effect’s dependencies to include activeTab and setActiveTab so the effect runs deterministically whenever any referenced value changes (i.e., change the dependency array on the useEffect that contains currentThreadId, activeTab, setActiveTab).
53-79: Add explicit accessible names for thread/action buttons.On Lines 53-79, the controls are number-only/icon-only visually. Add explicit ARIA labels (and selected state) so screen readers can identify the active thread and “new thread” action reliably.
Suggested change
<button key={thread.id} type="button" onClick={() => handleTabClick(thread.id)} + aria-label={thread.name || `Regent thread ${i + 1}`} + aria-current={isActive ? "true" : undefined} title={thread.name || `Regent thread ${i + 1}`} className={`flex items-center justify-center w-full h-10 transition-colors cursor-pointer ${ isActive ? "bg-cc-active text-cc-fg border-l-2 border-cc-accent" : "text-cc-muted hover:text-cc-fg hover:bg-cc-hover" }`} > @@ <button type="button" onClick={handleNewThread} + aria-label="Start new Regent thread" title="New Regent thread" className="flex items-center justify-center w-full h-10 text-cc-muted hover:text-cc-fg hover:bg-cc-hover transition-colors cursor-pointer border-t border-cc-border" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/RightPanel.tsx` around lines 53 - 79, The thread buttons (rendered in the loop using thread.id, thread.name and onClick -> handleTabClick) and the "new thread" button (handleNewThread) lack explicit accessible names and state; add an aria-label to each thread button (e.g., use thread.name if present or `Regent thread ${i+1}`) and expose the active state via aria-pressed or aria-current/aria-selected when isActive is true, and add an explicit aria-label like "New Regent thread" to the new-thread button so screen readers can identify the action and the selected thread reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@web/src/components/RightPanel.tsx`:
- Around line 33-37: The useEffect in RightPanel (the effect that checks
currentThreadId and sets activeTab) references activeTab and setActiveTab but
only lists currentThreadId in its dependency array; update the effect’s
dependencies to include activeTab and setActiveTab so the effect runs
deterministically whenever any referenced value changes (i.e., change the
dependency array on the useEffect that contains currentThreadId, activeTab,
setActiveTab).
- Around line 53-79: The thread buttons (rendered in the loop using thread.id,
thread.name and onClick -> handleTabClick) and the "new thread" button
(handleNewThread) lack explicit accessible names and state; add an aria-label to
each thread button (e.g., use thread.name if present or `Regent thread ${i+1}`)
and expose the active state via aria-pressed or aria-current/aria-selected when
isActive is true, and add an explicit aria-label like "New Regent thread" to the
new-thread button so screen readers can identify the action and the selected
thread reliably.
Summary
Test plan
Summary by cubic
Adds Regent as a persistent right sidebar with Tambo-powered chat and vertical thread tabs, replacing the old full-page route. A diamond button in the TopBar toggles it independently of the Context panel, with a solid background and proper placement below the TopBar on mobile.
New Features
Migration
Written for commit 775a6eb. Summary will update on new commits.
Summary by CodeRabbit
New Features
Chores
Tests
EntelligenceAI PR Summary
This PR integrates Tambo AI chat functionality with a new collapsible Regent sidebar panel featuring thread management and session monitoring tools.
Confidence Score: 2/5 - Changes Needed
Files requiring special attention
web/src/regent/tools/session-tools.tsweb/src/regent/components/SessionCard.tsxweb/src/components/RightPanel.tsx