Skip to content

feat: agent context features and chat improvements#454

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

feat: agent context features and chat improvements#454
larryro wants to merge 4 commits into
mainfrom
feat/agent-context-features

Conversation

@larryro

@larryro larryro commented Feb 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add contextFeatures to agent config for selective context loading, allowing each builtin agent to declare which context features it needs so unnecessary queries are skipped
  • Consolidate duplicate beforeGenerateHook implementations into shared lib/agent_chat module and remove redundant onError hook
  • Add tools summary section to context window with token estimation for system prompt and tools
  • Fix thread listing to paginate through all pages instead of only the first 100, with client-side sorting by creation time
  • Add message info dialog to test chat panel and improve clear chat UX (Trash2 icon, proper reset via remount)
  • Switch customer delete from collection API to direct mutation

Test plan

  • Verify agents with different contextFeatures configs only load relevant context
  • Confirm thread listing shows all threads, not just the first page
  • Test message info dialog in test chat panel
  • Test clear chat properly resets the conversation
  • Verify tools summary displays correct token estimates

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added message metadata dialog for assistant messages in test chat.
    • Added tool summary display in context window for agent generation.
  • Bug Fixes

    • Fixed thread pagination to retrieve all threads instead of single page.
  • Improvements

    • Token counts now display approximate values with tilde prefix.
    • Threads now sorted by creation time (newest first).
    • Test chat panel UI updated with reset functionality and improved controls.
    • Context window label updated to show estimation.

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.
@coderabbitai

coderabbitai Bot commented Feb 13, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR refactors the agent chat and custom agents systems while improving UI components. Key changes include: (1) introducing a contextFeatures system that enables selective feature loading in agent hooks, (2) removing onError hooks from the agent response pipeline, (3) adding a toolsSummary to the context window display without sending it to the LLM, (4) reorganizing internal action hooks and their references across agent variants, (5) converting customer deletion from collection-based to Convex mutation-based operations, (6) enhancing the test chat panel with message metadata display via a new AssistantMessageInfo component, (7) fixing thread pagination to fetch all pages, and (8) updating message dialog styling with CSS adjustments.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: agent context features and chat improvements' accurately captures the main objectives of the changeset, which include context features for agents, chat UI improvements, and various supporting changes across multiple modules.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ 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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
services/platform/convex/agents/chat/internal_actions.ts (1)

132-132: 🧹 Nitpick | 🔵 Trivial

Redundant nullish coalescing operator.

integrationsList is already typed as Record<string, unknown>[] | undefined, so ?? undefined is a no-op.

♻️ Suggested simplification
     return {
       integrationsInfo,
-      integrationsList: integrationsList ?? undefined,
+      integrationsList,
     };
🤖 Fix all issues with AI agents
In `@services/platform/app/features/chat/hooks/queries.ts`:
- Around line 22-28: Tests are calling the useThreads hook directly instead of
via a React hook wrapper; update collection-hooks.test.ts to import renderHook
from '@testing-library/react' and replace direct calls like
useThreads(mockCollection as never) with const { result } = renderHook(() =>
useThreads(mockCollection as never)); then use result.current to access threads
in assertions (update all occurrences and any destructuring that assumed a
direct return).

In `@services/platform/app/features/custom-agents/components/test-chat-panel.tsx`:
- Around line 525-532: The handler handleClearChat currently calls onReset
unconditionally after attempting deleteChatThread, so onReset should be invoked
only when the deletion succeeds; update handleClearChat to await or chain the
deleteChatThread call (using async/await or .then) and move the onReset?.()
invocation into the success branch, while logging/handling errors in the catch
branch and not calling onReset there; reference the handleClearChat function and
the deleteChatThread and onReset symbols when making the change.
- Around line 114-142: The IconButton in AssistantMessageInfo currently uses a
hardcoded aria-label "Message info"; replace this with a localized string by
using the project's translation hook/function (e.g., call useTranslation()/t or
useIntl().formatMessage) inside AssistantMessageInfo and pass the translated
label to the IconButton aria-label prop; ensure you import the appropriate i18n
hook at the top of the component file and use a clear key like
"assistant.messageInfo" when calling the translation function.

In `@services/platform/convex/threads/list_threads.ts`:
- Around line 32-61: Add a defensive iteration cap to the pagination loop in
list_threads.ts to prevent an infinite loop if result.isDone is never true:
track an iteration counter inside the for(;;) loop (near the
paginationOpts/result handling) and break with a logged warning or thrown error
once a reasonable max (e.g., 1000) is reached; reference the pagination
variables cursor, paginationOpts, result.isDone, result.continueCursor and the
target query components.agent.threads.listThreadsByUserId when making the
change.

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

return {
threads: data,
threads: sorted,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the test file structure and how useThreads is called
echo "=== Locating test file ==="
find . -name "collection-hooks.test.ts" -o -name "*useThreads*test*" 2>/dev/null | head -20

echo -e "\n=== Checking for test file at mentioned path ==="
if [ -f "services/platform/app/features/chat/hooks/__tests__/collection-hooks.test.ts" ]; then
  echo "File found, examining content..."
  wc -l "services/platform/app/features/chat/hooks/__tests__/collection-hooks.test.ts"
else
  echo "File not found at exact path, searching..."
  find . -path "*/chat/hooks/*test*" -type f 2>/dev/null
fi

Repository: tale-project/tale

Length of output: 1467


🏁 Script executed:

#!/bin/bash
# Search for useThreads usage in test files
echo "=== Searching for useThreads in test files ==="
rg -n "useThreads" --glob "**/*.test.ts" --glob "**/*.test.tsx" -A 5 -B 2

Repository: tale-project/tale

Length of output: 2975


🏁 Script executed:

#!/bin/bash
# Check if renderHook is imported/used anywhere in tests
echo "=== Checking for renderHook usage ==="
rg "renderHook|@testing-library/react" --glob "**/*.test.ts" --glob "**/*.test.tsx" -n

Repository: tale-project/tale

Length of output: 1676


🏁 Script executed:

#!/bin/bash
# Read the complete test file
echo "=== Complete test file content ==="
cat -n services/platform/app/features/chat/hooks/__tests__/collection-hooks.test.ts

Repository: tale-project/tale

Length of output: 1656


🏁 Script executed:

#!/bin/bash
# Read the queries.ts file to see useThreads implementation
echo "=== Checking queries.ts file size ==="
wc -l services/platform/app/features/chat/hooks/queries.ts

echo -e "\n=== Full queries.ts content ==="
cat -n services/platform/app/features/chat/hooks/queries.ts

Repository: tale-project/tale

Length of output: 12628


🏁 Script executed:

#!/bin/bash
# Check for test run output or CI configuration
echo "=== Looking for vitest configuration ==="
find . -name "vitest.config.*" -o -name "vite.config.*" 2>/dev/null | head -5

echo -e "\n=== Checking if tests directory has any setup files ==="
ls -la services/platform/app/features/chat/hooks/__tests__/ 2>/dev/null || echo "Dir not accessible in this context"

Repository: tale-project/tale

Length of output: 485


🏁 Script executed:

#!/bin/bash
# Check vitest configuration
echo "=== vitest.config.mjs content ==="
cat services/platform/vitest.config.mjs

echo -e "\n=== vite.config.ts content ==="
cat services/platform/vite.config.ts

Repository: tale-project/tale

Length of output: 4584


🏁 Script executed:

#!/bin/bash
# Check if there are any CI/test runner configurations
echo "=== Checking for CI configuration ==="
find . -name "*.yml" -o -name "*.yaml" 2>/dev/null | grep -i -E "(ci|github|gitlab|test)" | head -10

echo -e "\n=== Checking if tests are run elsewhere ==="
grep -r "collection-hooks.test" . --include="*.yml" --include="*.yaml" --include="*.json" 2>/dev/null | head -5

echo -e "\n=== Checking package.json test scripts ==="
if [ -f "services/platform/package.json" ]; then
  jq '.scripts' services/platform/package.json 2>/dev/null | grep -i test
fi

Repository: tale-project/tale

Length of output: 1163


🏁 Script executed:

#!/bin/bash
# Check the UI test configuration which may have different exclusions
echo "=== vitest.ui.config.mjs content ==="
cat services/platform/vitest.ui.config.mjs 2>/dev/null || echo "File not found"

echo -e "\n=== Check test.yml CI workflow ==="
cat .github/workflows/test.yml 2>/dev/null | head -100

Repository: tale-project/tale

Length of output: 2635


🏁 Script executed:

#!/bin/bash
# Check the setup-ui.ts file to see what React context is provided
echo "=== Checking test setup file ==="
if [ -f "services/platform/test/setup-ui.ts" ]; then
  cat services/platform/test/setup-ui.ts
else
  echo "File not found at services/platform/test/setup-ui.ts"
  find . -name "setup-ui.ts" -type f 2>/dev/null
fi

Repository: tale-project/tale

Length of output: 2764


Update tests to use renderHook for hook testing.

The sorting implementation is correct, but the tests call useThreads directly without the proper React hook wrapper. Update services/platform/app/features/chat/hooks/__tests__/collection-hooks.test.ts to wrap hook calls with renderHook from @testing-library/react:

import { renderHook } from '@testing-library/react';

// Change direct calls like:
// const result = useThreads(mockCollection as never);

// To:
// const { result } = renderHook(() => useThreads(mockCollection as never));
🧰 Tools
🪛 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 - 28,
Tests are calling the useThreads hook directly instead of via a React hook
wrapper; update collection-hooks.test.ts to import renderHook from
'@testing-library/react' and replace direct calls like useThreads(mockCollection
as never) with const { result } = renderHook(() => useThreads(mockCollection as
never)); then use result.current to access threads in assertions (update all
occurrences and any destructuring that assumed a direct return).

Comment on lines +114 to +142
function AssistantMessageInfo({
messageId,
timestamp,
}: {
messageId: string;
timestamp: Date;
}) {
const [isOpen, setIsOpen] = useState(false);
const { metadata } = useMessageMetadata(messageId);

return (
<div className="flex items-center">
<IconButton
icon={Info}
aria-label="Message info"
onClick={() => setIsOpen(true)}
iconSize={3}
className="text-muted-foreground hover:text-foreground p-1"
/>
<MessageInfoDialog
isOpen={isOpen}
onOpenChange={setIsOpen}
messageId={messageId}
timestamp={timestamp}
metadata={metadata}
/>
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Internationalize the aria-label for accessibility.

The hardcoded "Message info" string should use the translation function for proper i18n support.

🌐 Proposed fix
 function AssistantMessageInfo({
   messageId,
   timestamp,
 }: {
   messageId: string;
   timestamp: Date;
 }) {
   const [isOpen, setIsOpen] = useState(false);
   const { metadata } = useMessageMetadata(messageId);
+  const { t: tCommon } = useT('common');

   return (
     <div className="flex items-center">
       <IconButton
         icon={Info}
-        aria-label="Message info"
+        aria-label={tCommon('actions.showInfo')}
         onClick={() => setIsOpen(true)}
         iconSize={3}
         className="text-muted-foreground hover:text-foreground p-1"
       />
🤖 Prompt for AI Agents
In `@services/platform/app/features/custom-agents/components/test-chat-panel.tsx`
around lines 114 - 142, The IconButton in AssistantMessageInfo currently uses a
hardcoded aria-label "Message info"; replace this with a localized string by
using the project's translation hook/function (e.g., call useTranslation()/t or
useIntl().formatMessage) inside AssistantMessageInfo and pass the translated
label to the IconButton aria-label prop; ensure you import the appropriate i18n
hook at the top of the component file and use a clear key like
"assistant.messageInfo" when calling the translation function.

Comment on lines +525 to +532
const handleClearChat = useCallback(() => {
if (threadId) {
void deleteChatThread({ threadId }).catch((error) => {
console.error('Error deleting test chat thread:', error);
});
}
}, [threadId, deleteChatThread]);
onReset?.();
}, [threadId, deleteChatThread, onReset]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

onReset is called even if thread deletion fails.

The onReset?.() callback fires unconditionally after the delete attempt, regardless of whether deleteChatThread succeeded or failed. If the deletion fails, this will still remount the component, potentially causing a confusing UX where the user expects a fresh state but the old thread may persist.

Consider calling onReset only on successful deletion:

🛠️ Proposed fix
   const handleClearChat = useCallback(() => {
     if (threadId) {
-      void deleteChatThread({ threadId }).catch((error) => {
-        console.error('Error deleting test chat thread:', error);
-      });
+      void deleteChatThread({ threadId })
+        .then(() => {
+          onReset?.();
+        })
+        .catch((error) => {
+          console.error('Error deleting test chat thread:', error);
+        });
+    } else {
+      onReset?.();
     }
-    onReset?.();
   }, [threadId, deleteChatThread, onReset]);
📝 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 handleClearChat = useCallback(() => {
if (threadId) {
void deleteChatThread({ threadId }).catch((error) => {
console.error('Error deleting test chat thread:', error);
});
}
}, [threadId, deleteChatThread]);
onReset?.();
}, [threadId, deleteChatThread, onReset]);
const handleClearChat = useCallback(() => {
if (threadId) {
void deleteChatThread({ threadId })
.then(() => {
onReset?.();
})
.catch((error) => {
console.error('Error deleting test chat thread:', error);
});
} else {
onReset?.();
}
}, [threadId, deleteChatThread, onReset]);
🤖 Prompt for AI Agents
In `@services/platform/app/features/custom-agents/components/test-chat-panel.tsx`
around lines 525 - 532, The handler handleClearChat currently calls onReset
unconditionally after attempting deleteChatThread, so onReset should be invoked
only when the deletion succeeds; update handleClearChat to await or chain the
deleteChatThread call (using async/await or .then) and move the onReset?.()
invocation into the success branch, while logging/handling errors in the catch
branch and not calling onReset there; reference the handleClearChat function and
the deleteChatThread and onReset symbols when making the change.

Comment on lines +32 to +61
for (;;) {
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Convex pagination cursor type
const paginationOpts = { cursor, numItems: 100 } as {
cursor: string | null;
numItems: number;
};
const result = await ctx.runQuery(
components.agent.threads.listThreadsByUserId,
{
userId: args.userId,
order: 'desc',
paginationOpts,
},
);

for (const thread of result.page) {
if (thread.status !== 'active') continue;
if (!isGeneralThread(thread.summary)) continue;

threads.push({
_id: thread._id,
_creationTime: thread._creationTime,
title: thread.title,
status: thread.status,
userId: thread.userId,
});
}

if (result.isDone) break;
cursor = result.continueCursor;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Pagination implementation looks correct.

The loop correctly paginates through all pages, filtering for active general threads. The comment on lines 29-31 clearly explains the rationale for full pagination.

One minor consideration: there's no upper bound on iterations. If result.isDone were never true due to an API bug, this would loop indefinitely. For defensive coding, consider adding a safety limit:

🛡️ Optional: Add iteration safety limit
 export async function listThreads(
   ctx: QueryCtx,
   args: Pick<ListThreadsArgs, 'userId'>,
 ): Promise<Thread[]> {
   const threads: Thread[] = [];
   let cursor: string | null = null;
+  const MAX_PAGES = 100; // Safety limit: 10,000 threads max
+  let pageCount = 0;

   // Paginate through all pages to collect every general thread.
   // Sub-threads consume pagination slots but are filtered out, so a
   // single page of 100 can miss general threads for active users.
   for (;;) {
+    if (++pageCount > MAX_PAGES) {
+      console.warn('listThreads: reached max page limit');
+      break;
+    }
     // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Convex pagination cursor type
🤖 Prompt for AI Agents
In `@services/platform/convex/threads/list_threads.ts` around lines 32 - 61, Add a
defensive iteration cap to the pagination loop in list_threads.ts to prevent an
infinite loop if result.isDone is never true: track an iteration counter inside
the for(;;) loop (near the paginationOpts/result handling) and break with a
logged warning or thrown error once a reasonable max (e.g., 1000) is reached;
reference the pagination variables cursor, paginationOpts, result.isDone,
result.continueCursor and the target query
components.agent.threads.listThreadsByUserId when making the change.

…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
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