Skip to content

Agent context features and collection mutation cleanup#455

Closed
larryro wants to merge 5 commits into
mainfrom
feat/agent-context-features
Closed

Agent context features and collection mutation cleanup#455
larryro wants to merge 5 commits into
mainfrom
feat/agent-context-features

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Feb 13, 2026

Summary

  • Add contextFeatures to agent config so builtin agents can declare which context features they need, allowing the beforeContext hook to skip unnecessary queries
  • Consolidate duplicate beforeGenerateHook implementations into the shared lib/agent_chat module and remove the redundant onError hook
  • Add message info dialog and tools summary to test chat panel for inspecting message metadata and token estimation
  • Fix thread listing to paginate through all pages instead of only the first 100
  • Replace optimistic collection mutations with direct Convex mutations across custom agents, customers, vendors, integrations, organization, and teams

Test plan

  • Verify builtin agents still load correct context (integrations only loaded when declared)
  • Test custom agent config tabs (general, instructions, tools, knowledge) save correctly without Collection API
  • Confirm test chat panel shows message info dialog and tools summary
  • Verify thread listing returns all threads, not just first page
  • Run existing mutation hook tests for vendors, teams, organization, and integrations

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added message metadata viewer with info icon for chat messages
    • Display tool summaries in context window for better visibility
    • Improved thread sorting and pagination
  • Bug Fixes

    • Fixed chat reset functionality in test panels
    • Corrected context window token count display with estimated prefix
  • UI/UX Improvements

    • Updated conversation clear icon for better visual clarity
    • Enhanced markdown styling for better readability
    • Added email field display in member edit form
    • Improved context statistics calculation

Allow each builtin agent to declare which context features it needs
(e.g., integrations) so the beforeContext hook can skip unnecessary
queries for agents that don't use them.
…nation

Merge duplicate beforeGenerateHook implementations into shared
lib/agent_chat module and remove the redundant onError hook. Add a tools
summary section to the context window with token estimation for system
prompt and tools. Fix thread listing to paginate through all pages
instead of only the first 100, and sort threads client-side by creation
time. Switch customer delete from collection API to direct mutation.
Add AssistantMessageInfo component to test chat panel so users can
inspect message metadata. Replace reset icon with Trash2 and implement
proper chat reset by unmounting/remounting the panel. Style h3 headings
in context window content for better readability.
…tations for custom agents

Simplify useUpdateCustomAgent and useUpdateCustomAgentMetadata hooks by
removing the Collection dependency and using useConvexMutation directly.
This removes the need for useCustomAgentCollection across all agent
config tabs (general, instructions, tools, knowledge).
@larryro larryro closed this Feb 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

This pull request implements a comprehensive refactor converting collection-based mutation hooks to Convex mutation-based hooks across multiple features. The changes remove Collection parameters from numerous hooks (e.g., useUpdateCustomAgent, useDeleteCustomer, useRemoveMember, useDeleteIntegration) and replace their implementations with direct calls to useConvexMutation, returning mutateAsync functions. Related components are updated to invoke these simplified hooks without collection arguments. The PR also adds UI enhancements to message metadata display (context window token prefix), introduces contextFeatures fields throughout agent configuration, reorganizes internal action hooks to a shared lib/agent_chat namespace, removes onError hook support from agent response generation, integrates tool summaries with token estimation in response context, refactors thread pagination to traverse all pages, and updates test suites to validate the new mutation hook patterns.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (40 files):

⚔️ package-lock.json (content)
⚔️ services/platform/app/features/chat/components/message-info-dialog.tsx (content)
⚔️ services/platform/app/features/chat/hooks/queries.ts (content)
⚔️ services/platform/app/features/custom-agents/components/custom-agent-knowledge.tsx (content)
⚔️ services/platform/app/features/custom-agents/components/test-chat-panel.tsx (content)
⚔️ services/platform/app/features/custom-agents/hooks/mutations.ts (content)
⚔️ services/platform/app/features/customers/components/customer-delete-dialog.tsx (content)
⚔️ services/platform/app/features/customers/hooks/mutations.ts (content)
⚔️ services/platform/app/features/settings/integrations/components/integration-manage-dialog.tsx (content)
⚔️ services/platform/app/features/settings/integrations/hooks/__tests__/mutation-hooks.test.ts (content)
⚔️ services/platform/app/features/settings/integrations/hooks/mutations.ts (content)
⚔️ services/platform/app/features/settings/organization/components/member-delete-dialog.tsx (content)
⚔️ services/platform/app/features/settings/organization/components/member-edit-dialog.tsx (content)
⚔️ services/platform/app/features/settings/organization/components/member-row-actions.tsx (content)
⚔️ services/platform/app/features/settings/organization/components/member-table.tsx (content)
⚔️ services/platform/app/features/settings/organization/components/organization-settings-client.tsx (content)
⚔️ services/platform/app/features/settings/organization/hooks/__tests__/mutation-hooks.test.ts (content)
⚔️ services/platform/app/features/settings/organization/hooks/mutations.ts (content)
⚔️ services/platform/app/features/settings/teams/components/team-members-dialog.tsx (content)
⚔️ services/platform/app/features/settings/teams/hooks/__tests__/mutation-hooks.test.ts (content)
⚔️ services/platform/app/features/settings/teams/hooks/mutations.ts (content)
⚔️ services/platform/app/features/vendors/components/vendor-delete-dialog.tsx (content)
⚔️ services/platform/app/features/vendors/components/vendor-edit-dialog.tsx (content)
⚔️ services/platform/app/features/vendors/hooks/mutations.ts (content)
⚔️ services/platform/app/routes/dashboard/$id/custom-agents/$agentId.tsx (content)
⚔️ services/platform/app/routes/dashboard/$id/custom-agents/$agentId/index.tsx (content)
⚔️ services/platform/app/routes/dashboard/$id/custom-agents/$agentId/instructions.tsx (content)
⚔️ services/platform/app/routes/dashboard/$id/custom-agents/$agentId/tools.tsx (content)
⚔️ services/platform/convex/_generated/api.d.ts (content)
⚔️ services/platform/convex/agents/builtin_agents.ts (content)
⚔️ services/platform/convex/agents/chat/config.ts (content)
⚔️ services/platform/convex/agents/chat/internal_actions.ts (content)
⚔️ services/platform/convex/custom_agents/config.ts (content)
⚔️ services/platform/convex/lib/agent_chat/internal_actions.ts (content)
⚔️ services/platform/convex/lib/agent_chat/types.ts (content)
⚔️ services/platform/convex/lib/agent_response/generate_response.ts (content)
⚔️ services/platform/convex/lib/agent_response/types.ts (content)
⚔️ services/platform/convex/threads/list_threads.ts (content)
⚔️ services/platform/messages/en.json (content)
⚔️ services/platform/package.json (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the two main changes: adding agent context features and removing Collection-based mutations in favor of direct Convex mutations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/agent-context-features
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feat/agent-context-features
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/platform/app/features/custom-agents/components/test-chat-panel.tsx (1)

27-40: 🧹 Nitpick | 🔵 Trivial

Avoid eager per‑message metadata fetch.

useMessageMetadata runs for every assistant message even when the dialog is closed. This can generate N+1 queries and slow the panel for long threads. Consider skipping the query until the dialog is opened.

⚡ Suggested lazy fetch
-  const { metadata } = useMessageMetadata(messageId);
+  const { metadata } = useMessageMetadata(isOpen ? messageId : null);

Also applies to: 114-142, 711-717

🤖 Fix all issues with AI agents
In `@services/platform/app/features/chat/hooks/queries.ts`:
- Around line 22-29: The useThreads hook currently uses React's useMemo (sorted
= useMemo(...)) which forces tests to call hooks via a dispatcher; remove
useMemo to allow calling useThreads directly in tests: replace the useMemo-based
computed value with a plain inline computation (e.g., compute sorted by copying
and sorting data with the same comparator) inside the useThreads implementation
and return that value as threads; ensure you remove the useMemo import/usage and
keep the sorting logic (b._creationTime - a._creationTime) associated with the
sorted variable so the hook remains pure and testable without renderHook.

In `@services/platform/app/features/custom-agents/components/test-chat-panel.tsx`:
- Around line 144-156: The onReset prop is optional but handleClearChat
currently relies on it, leaving threadId and messages stale if omitted; update
TestChatPanelContent to either make onReset required in TestChatPanelProps or
(preferred) add a local fallback reset inside handleClearChat: after the delete
thread call, if onReset is provided call it, otherwise locally reset the panel
state (clear threadId via setThreadId(null or ''), clear messages via
setMessages([]), reset input/state flags like setIsStreaming(false)/setInput('')
as needed) so the UI fully resets; reference TestChatPanelProps,
TestChatPanelContent and the handleClearChat function when making this change.

In `@services/platform/app/features/vendors/hooks/mutations.ts`:
- Around line 4-16: The three hooks return inconsistent shapes:
useBulkCreateVendors returns the full mutation object while useDeleteVendor and
useUpdateVendor return only mutateAsync; to fix, make the return shapes
consistent—either return the full mutation object from useDeleteVendor and
useUpdateVendor (by returning the result of useConvexMutation in useDeleteVendor
and useUpdateVendor) or return only mutateAsync from useBulkCreateVendors;
update useDeleteVendor and useUpdateVendor (or useBulkCreateVendors) so all
hooks (useBulkCreateVendors, useDeleteVendor, useUpdateVendor) expose the same
API and add a short comment if a special-case is intentional.

Comment on lines +22 to 29
const sorted = useMemo(
() => data?.slice().sort((a, b) => b._creationTime - a._creationTime),
[data],
);

return {
threads: data,
threads: sorted,
isLoading,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the file mentioned in the review
find . -path "*services/platform/app/features/chat/hooks/queries.ts" -o -path "*features/chat/hooks/queries.ts" | head -20

Repository: tale-project/tale

Length of output: 115


🏁 Script executed:

# Also check if the file exists with different path patterns
fd "queries.ts" -t f | grep -i chat | head -20

Repository: tale-project/tale

Length of output: 113


🏁 Script executed:

# List all test files related to chat hooks
fd -t f "\.test\.|\.spec\." | grep -i "chat.*hook" | head -20

Repository: tale-project/tale

Length of output: 209


🏁 Script executed:

# Search for useThreads hook usage patterns
rg "useThreads" -t ts -t tsx --max-count 50

Repository: tale-project/tale

Length of output: 88


🏁 Script executed:

# Read the queries.ts file to see the actual code
cat -n services/platform/app/features/chat/hooks/queries.ts | head -50

Repository: tale-project/tale

Length of output: 2203


🏁 Script executed:

# Search for useThreads in test files with proper context
rg "useThreads" -t ts -C 5 services/platform/app/features/chat/hooks/__tests__/

Repository: tale-project/tale

Length of output: 3728


🏁 Script executed:

# Check if renderHook is being used in the test files
rg "renderHook|render" -t ts services/platform/app/features/chat/hooks/__tests__/ | head -20

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Check the full test file to see test setup and imports
cat -n services/platform/app/features/chat/hooks/__tests__/collection-hooks.test.ts | head -80

Repository: tale-project/tale

Length of output: 1621


🏁 Script executed:

# Search for renderHook usage in the codebase
rg "renderHook" -t ts --max-count 20

Repository: tale-project/tale

Length of output: 1134


🏁 Script executed:

# Check what testing libraries are available
rg "from.*testing|import.*testing" services/platform/app/features/chat/hooks/__tests__/ -t ts

Repository: tale-project/tale

Length of output: 43


Update test to use renderHook for useThreads hook or remove useMemo to avoid dispatcher error.

The test calls useThreads directly (lines 29, 41 in collection-hooks.test.ts) but useThreads is a hook containing useMemo. When hooks are invoked outside React's dispatcher context, useMemo throws Cannot read properties of null (reading 'useMemo').

Either wrap the hook call with renderHook from @testing-library/react (which is available in the project), or remove useMemo and compute the sorted array inline. The latter approach is simpler and avoids unnecessary hook overhead in this case.

🔧 Suggested code change (remove `useMemo` to keep tests hook‑free)
-  const sorted = useMemo(
-    () => data?.slice().sort((a, b) => b._creationTime - a._creationTime),
-    [data],
-  );
-
   return {
-    threads: sorted,
+    threads: data?.slice().sort((a, b) => b._creationTime - a._creationTime),
     isLoading,
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const sorted = useMemo(
() => data?.slice().sort((a, b) => b._creationTime - a._creationTime),
[data],
);
return {
threads: data,
threads: sorted,
isLoading,
return {
threads: data?.slice().sort((a, b) => b._creationTime - a._creationTime),
isLoading,
🧰 Tools
🪛 GitHub Actions: Test

[error] 22-22: Cannot read properties of null (reading 'useMemo') in useMemo call while processing useThreads data.

🪛 GitHub Check: Test

[failure] 22-22: app/features/chat/hooks/tests/collection-hooks.test.ts > useThreads > returns data even while loading
TypeError: Cannot read properties of null (reading 'useMemo')
❯ process.env.NODE_ENV.exports.useMemo ../../node_modules/react/cjs/react.development.js:1251:33
❯ Module.useThreads app/features/chat/hooks/queries.ts:22:18
❯ app/features/chat/hooks/tests/collection-hooks.test.ts:41:20


[failure] 22-22: app/features/chat/hooks/tests/collection-hooks.test.ts > useThreads > returns data when loaded
TypeError: Cannot read properties of null (reading 'useMemo')
❯ process.env.NODE_ENV.exports.useMemo ../../node_modules/react/cjs/react.development.js:1251:33
❯ Module.useThreads app/features/chat/hooks/queries.ts:22:18
❯ app/features/chat/hooks/tests/collection-hooks.test.ts:29:20

🤖 Prompt for AI Agents
In `@services/platform/app/features/chat/hooks/queries.ts` around lines 22 - 29,
The useThreads hook currently uses React's useMemo (sorted = useMemo(...)) which
forces tests to call hooks via a dispatcher; remove useMemo to allow calling
useThreads directly in tests: replace the useMemo-based computed value with a
plain inline computation (e.g., compute sorted by copying and sorting data with
the same comparator) inside the useThreads implementation and return that value
as threads; ensure you remove the useMemo import/usage and keep the sorting
logic (b._creationTime - a._creationTime) associated with the sorted variable so
the hook remains pure and testable without renderHook.

Comment on lines 144 to 156
interface TestChatPanelProps {
organizationId: string;
agentId: string;
onClose: () => void;
onReset?: () => void;
}

function TestChatPanelContent({
organizationId,
agentId,
onClose,
onReset,
}: TestChatPanelProps) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clear‑chat now relies on onReset and can leave stale state.

If a caller omits onReset, handleClearChat deletes the thread but leaves threadId and local messages intact, so the UI won’t actually reset. Either make onReset required or add a local fallback reset path.

🧹 Suggested fallback reset when onReset is undefined
-  const handleClearChat = useCallback(() => {
+  const handleClearChat = useCallback(() => {
     if (threadId) {
       void deleteChatThread({ threadId }).catch((error) => {
         console.error('Error deleting test chat thread:', error);
       });
     }
-    onReset?.();
-  }, [threadId, deleteChatThread, onReset]);
+    if (onReset) {
+      onReset();
+      return;
+    }
+    setThreadId(null);
+    setMessages([]);
+    setPendingUserMessage(null);
+    setInputValue('');
+    clearAttachments();
+  }, [threadId, deleteChatThread, onReset, clearAttachments]);

Also applies to: 525-532

🤖 Prompt for AI Agents
In `@services/platform/app/features/custom-agents/components/test-chat-panel.tsx`
around lines 144 - 156, The onReset prop is optional but handleClearChat
currently relies on it, leaving threadId and messages stale if omitted; update
TestChatPanelContent to either make onReset required in TestChatPanelProps or
(preferred) add a local fallback reset inside handleClearChat: after the delete
thread call, if onReset is provided call it, otherwise locally reset the panel
state (clear threadId via setThreadId(null or ''), clear messages via
setMessages([]), reset input/state flags like setIsStreaming(false)/setInput('')
as needed) so the UI fully resets; reference TestChatPanelProps,
TestChatPanelContent and the handleClearChat function when making this change.

Comment on lines 4 to 16
export function useBulkCreateVendors() {
return useConvexMutation(api.vendors.mutations.bulkCreateVendors);
}

export function useDeleteVendor(collection: Collection<Vendor, string>) {
return useCallback(
async (args: { vendorId: string }) => {
const tx = collection.delete(args.vendorId);
await tx.isPersisted.promise;
},
[collection],
);
export function useDeleteVendor() {
const { mutateAsync } = useConvexMutation(api.vendors.mutations.deleteVendor);
return mutateAsync;
}

export function useUpdateVendor(collection: Collection<Vendor, string>) {
return useCallback(
async (args: {
vendorId: string;
name?: string;
email?: string;
phone?: string;
externalId?: string;
source?: 'manual_import' | 'file_upload' | 'circuly';
locale?: string;
address?: {
street?: string;
city?: string;
state?: string;
zip?: string;
country?: string;
};
tags?: string[];
metadata?: Record<string, unknown>;
notes?: string;
}) => {
const tx = collection.update(args.vendorId, (draft) => {
if (args.name !== undefined) draft.name = args.name;
if (args.email !== undefined) draft.email = args.email;
if (args.phone !== undefined) draft.phone = args.phone;
if (args.externalId !== undefined) draft.externalId = args.externalId;
if (args.source !== undefined) draft.source = args.source;
if (args.locale !== undefined) draft.locale = args.locale;
if (args.address !== undefined) draft.address = args.address;
if (args.tags !== undefined) draft.tags = args.tags;
if (args.metadata !== undefined) draft.metadata = args.metadata;
if (args.notes !== undefined) draft.notes = args.notes;
});
await tx.isPersisted.promise;
},
[collection],
);
export function useUpdateVendor() {
const { mutateAsync } = useConvexMutation(api.vendors.mutations.updateVendor);
return mutateAsync;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Inconsistent return patterns across hooks.

useBulkCreateVendors returns the full mutation object (including mutateAsync, isPending, etc.), while useDeleteVendor and useUpdateVendor return only mutateAsync. This inconsistency means consumers get different APIs from the same module.

If intentional (e.g., bulk operations need isPending for progress UI), consider adding a brief comment explaining the design choice. Otherwise, consider aligning all hooks to return the same shape for consistency.

🤖 Prompt for AI Agents
In `@services/platform/app/features/vendors/hooks/mutations.ts` around lines 4 -
16, The three hooks return inconsistent shapes: useBulkCreateVendors returns the
full mutation object while useDeleteVendor and useUpdateVendor return only
mutateAsync; to fix, make the return shapes consistent—either return the full
mutation object from useDeleteVendor and useUpdateVendor (by returning the
result of useConvexMutation in useDeleteVendor and useUpdateVendor) or return
only mutateAsync from useBulkCreateVendors; update useDeleteVendor and
useUpdateVendor (or useBulkCreateVendors) so all hooks (useBulkCreateVendors,
useDeleteVendor, useUpdateVendor) expose the same API and add a short comment if
a special-case is intentional.

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