feat(nuxi): use eve framework#2280
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
The Vercel build fails when crawlLinks discovers thousands of routes concurrently and a payload request returns 500 (e.g. directory-structure/pages). Matches perf/prerender-no-crawl (#2286).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe PR adds a Nuxi Eve layer with new environment and deployment configuration, chat state schema and migrations, public and internal chat APIs, Eve runtime and tool wiring, and client-side chat/session UI updates. It also updates admin MCP prompts and tool outputs to focus on saved web chats, votes, and Vercel observability. Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 11
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (12)
server/mcp/prompts/admin/review-agent-chats.ts-22-22 (1)
22-22: 🎯 Functional Correctness | 🟡 MinorChat link path is inconsistent with the actual application routes.
The prompt in
server/mcp/prompts/admin/review-agent-chats.tsinstructs reports to link tohttps://nuxt.com/admin/agent/<chat-id>. However, the application only defines the chat route atlayers/nuxi/app/pages/dashboard/chat/[id].vue, which corresponds tohttps://nuxt.com/dashboard/chat/<id>. The/admin/agent/path does not exist in the codebase, causing generated links to point to non-existent pages and creating a mismatch with the URLs emitted by admin tools.Update the template to use
https://nuxt.com/dashboard/chat/${chat-id}.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/mcp/prompts/admin/review-agent-chats.ts` at line 22, The chat link in the admin review prompt uses a non-existent `/admin/agent/` route, so update the template in review-agent-chats.ts to match the real chat page route used by the app. Change the generated link text for “Worst sessions” to point to the dashboard chat path, using the same chat id placeholder, so reports align with the route handled by the chat page component.nuxt.config.ts-127-128 (1)
127-128: 🔒 Security & Privacy | 🟡 MinorRemove unused
runtimeConfig.internalApiSecretor align checks to use it.The
internalApiSecretkey defined innuxt.config.ts(line 128) is never consumed viauseRuntimeConfig().internalApiSecretanywhere in the codebase. The validation logic inlayers/nuxi/agent/lib/internal-api.tsandlayers/nuxi/server/utils/internal-api.tsreadsprocess.env.INTERNAL_API_SECRETdirectly.This creates a redundant (dead) config entry. If future refactoring assumes this value is available via
runtimeConfig, it will default to''(per line 128), bypassing the strict env check.Action:
- Option A (Cleanup): Remove
internalApiSecretfromnuxt.config.tsentirely, as the checks correctly useprocess.env.- Option B (Refactor): Update
internal-api.tsfiles to useconst secret = useRuntimeConfig().internalApiSecret(and remove the|| ''fallback innuxt.config.tsto force failure on missing env), centralizing config access.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nuxt.config.ts` around lines 127 - 128, The runtimeConfig entry for internalApiSecret is redundant because the internal API checks already read process.env.INTERNAL_API_SECRET directly. Either remove internalApiSecret from nuxt.config.ts if you want to keep the current env-based validation in layers/nuxi/agent/lib/internal-api.ts and layers/nuxi/server/utils/internal-api.ts, or refactor those internal-api helpers to read useRuntimeConfig().internalApiSecret and drop the fallback so missing secrets fail consistently.layers/nuxi/server/api/chats/[id]/state.patch.ts-54-63 (1)
54-63: 🗄️ Data Integrity & Integration | 🟡 MinorConfirm
messagesrepresents the complete history or enforce a full-snapshot contract.The PATCH handler deletes all existing messages for the chat before inserting
body.messages. If the caller passes a partial list, the unlisted messages are permanently lost.Verification of
thread-state.tsconfirms the primary caller (createEveFinishHandler) passes the fullagentMessagesarray. However,persistChatStateis an exported utility accepting an optionalmessagesarray; if invoked elsewhere with a truncated list, data loss will occur.Additionally, the insert lacks
onConflictDoNothing. A payload containing duplicateids will abort the transaction with a 500 error.Code context
diff --git a/layers/nuxi/server/api/chats/[id]/state.patch.ts --- a/layers/nuxi/server/api/chats/[id]/state.patch.ts +++ b/layers/nuxi/server/api/chats/[id]/state.patch.ts @@ -54,6 +54,7 @@ if (body.messages?.length) { await tx.delete(schema.messages).where(eq(schema.messages.chatId, id)) await tx.insert(schema.messages).values(body.messages.map(m => ({ + // Warning: this deletes all previous messages if body.messages is not a full snapshot id: m.id, chatId: id, role: m.role,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/server/api/chats/`[id]/state.patch.ts around lines 54 - 63, The PATCH handler in state.patch.ts currently treats body.messages as a full replacement but does not enforce that contract, so partial payloads can delete existing chat history; either validate that the caller always provides the complete snapshot in persistChatState/createEveFinishHandler or change the update flow to merge instead of replacing. Also update the schema.messages insert path to tolerate duplicate message ids by using the existing tx.insert(schema.messages) flow with conflict handling such as onConflictDoNothing, so a repeated id does not fail the transaction.layers/nuxi/server/api/internal/github/search-issues.post.ts-6-10 (1)
6-10: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winConstrain
queryto a non-empty string.
query: z.string()accepts an empty string, which forwards a meaningless search tosearchGitHubIssues. The sibling web-search endpoint already usesz.string().min(1); align here for consistent input validation.♻️ Proposed change
const body = await readValidatedBody(event, z.object({ - query: z.string(), + query: z.string().min(1), repo: z.string().optional(), state: z.enum(['open', 'closed', 'all']).optional() }).parse)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/server/api/internal/github/search-issues.post.ts` around lines 6 - 10, The request validation in readValidatedBody for search-issues.post.ts allows an empty query, which can lead to meaningless searches in searchGitHubIssues. Update the z.object schema used in this handler so the query field requires a non-empty string, matching the validation pattern used by the sibling web-search endpoint and keeping input handling consistent.layers/nuxi/server/api/internal/agent/web-search.post.ts-19-23 (1)
19-23: 🔒 Security & Privacy | 🟡 MinorAvoid surfacing raw provider error messages.
Returning
(error as Error).messagedirectly exposes potential internal provider details and assumes the thrown value is always anErrorinstance. Instead, log the full error (including thecausechain) server-side and return a generic message to the client.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/server/api/internal/agent/web-search.post.ts` around lines 19 - 23, The web-search error handling in webSearch.post currently returns the raw provider message to the client and assumes the thrown value is always an Error. Update the catch block in the web-search handler to log the full error server-side, including any cause chain, and return a generic client-facing message instead of using error.message directly.layers/nuxi/server/utils/internal-api.ts-13-19 (1)
13-19: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winUse a constant-time comparison for the bearer secret.
requireInternalRequestis the central auth gate for every internal endpoint, but the secret is compared with!==, which short-circuits on the first differing byte and is susceptible to timing analysis. Prefer a constant-time comparison.🔒 Proposed hardening
+import { timingSafeEqual } from 'node:crypto' import type { H3Event } from 'h3' export function requireInternalRequest(event: H3Event) { const secret = process.env.INTERNAL_API_SECRET?.trim() if (!secret) { throw createError({ statusCode: 503, statusMessage: 'Internal API is not configured' }) } const authorization = getRequestHeader(event, 'authorization') - if (authorization !== `Bearer ${secret}`) { + const expected = `Bearer ${secret}` + const provided = authorization ?? '' + const a = Buffer.from(provided) + const b = Buffer.from(expected) + if (a.length !== b.length || !timingSafeEqual(a, b)) { throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }) } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/server/utils/internal-api.ts` around lines 13 - 19, The bearer secret check in requireInternalRequest currently uses a normal string comparison, which can leak timing information. Update the authorization validation in this function to use a constant-time equality check when comparing the incoming Authorization header against the expected Bearer value, while preserving the existing 401 Unauthorized behavior for mismatches.layers/nuxi/agent/tools/show_blog_post.ts-6-10 (1)
6-10: 🎯 Functional Correctness | 🟡 MinorReject whitespace-only blog titles.
The
titlefield ininputSchemausesz.string()without trim or minimum-length validation. This allows empty or whitespace-only strings to be sent to/api/internal/content, resulting in invalid or unhelpful API queries. Update the schema to trim input and enforce at least one character.Snippet
inputSchema: z.object({ title: z.string().describe('The blog post title or search keyword (e.g., "v4", "Nuxt 3.15", "TypeScript")') }),Use
.trim().min(1)to ensure valid input:title: z.string().trim().min(1).describe('...')🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/agent/tools/show_blog_post.ts` around lines 6 - 10, The title validation in showBlogPost is too permissive because inputSchema accepts empty or whitespace-only strings. Update the zod schema for title to trim the value and require at least one character before it is sent to /api/internal/content, using the existing inputSchema in show_blog_post.ts so the body builder only receives valid blog search terms.layers/nuxi/agent/tools/web_search.ts-6-8 (1)
6-8: 🚀 Performance & Scalability | 🟡 MinorRequire a non-empty, trimmed web search query.
The current
inputSchemaaccepts empty or whitespace-only strings viaz.string(). This risks triggering meaningless remote search requests and consuming external search budget.Update the schema to enforce validation:
Proposed change for
layers/nuxi/agent/tools/web_search.tsinputSchema: z.object({ query: z.string().min(1, 'Search query cannot be empty').trim().describe('Search query — match the user\'s wording; do not add calendar years unless they asked for one') }),Add
.min(1)to reject empty strings and.trim()to reject whitespace-only inputs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/agent/tools/web_search.ts` around lines 6 - 8, The web search tool schema currently accepts empty or whitespace-only queries because the `inputSchema` in `web_search.ts` only uses `z.string()`. Update the `query` field validation in `inputSchema` to require a non-empty trimmed string by adding trimming and a minimum-length check so invalid searches are rejected before reaching the remote search call.layers/nuxi/agent/tools/search_github_issues.ts-6-10 (1)
6-10: 🎯 Functional Correctness | 🟡 MinorReject blank issue searches.
The
queryfield currently accepts empty strings and whitespace-only input becausez.string()has no length constraints. This allows the agent to trigger broad or invalid GitHub searches. Update the schema to enforce a non-empty, trimmed string:- query: z.string().describe('Error message, keyword, or search term'), + query: z.string().trim().min(1, 'Search query cannot be empty').describe('Error message, keyword, or search term'),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/agent/tools/search_github_issues.ts` around lines 6 - 10, The search_github_issues tool currently allows blank or whitespace-only queries because inputSchema uses a plain z.string() for query. Update the query validator in search_github_issues.ts to require a trimmed, non-empty string so the tool rejects empty searches before reaching GitHub. Use the inputSchema definition and its query field as the place to apply the constraint.layers/nuxi/agent/hooks/chat-title.ts-14-29 (1)
14-29: 🩺 Stability & Availability | 🟡 MinorWrap the title fetch to prevent network errors from disrupting message flow.
According to the Eve framework documentation, errors thrown from
message.receivedhooks propagate through the event loop and can escalate toturn.failedorsession.failedevents. The current implementation only handles non-OK HTTP responses but lacks atry/catchwrapper around thefetchcall, meaning transient network failures (DNS issues, connection timeouts, etc.) will disrupt the normal message flow instead of degrading gracefully.Wrap the entire request block in
try/catchto ensure title generation remains best-effort:Proposed fix
- const response = await fetch(`${appOrigin()}/api/internal/chats/${encodeURIComponent(chatId)}/title`, { - method: 'POST', - headers: internalHeaders(), - body: JSON.stringify({ - userId: auth.principalId, - message: { - role: 'user', - parts: [{ type: 'text', text: message }] - } - }) - }) - - if (!response.ok) { - const text = await response.text().catch(() => '') - console.warn(`[chat-title] title generation failed (${response.status}): ${text}`) - } + try { + const response = await fetch(`${appOrigin()}/api/internal/chats/${encodeURIComponent(chatId)}/title`, { + method: 'POST', + headers: internalHeaders(), + body: JSON.stringify({ + userId: auth.principalId, + message: { + role: 'user', + parts: [{ type: 'text', text: message }] + } + }) + }) + + if (!response.ok) { + const text = await response.text().catch(() => '') + console.warn(`[chat-title] title generation failed (${response.status}): ${text}`) + } + } catch (error) { + console.warn('[chat-title] title generation request errored', error) + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/agent/hooks/chat-title.ts` around lines 14 - 29, The chat title request in the `message.received` hook only handles non-OK responses, so network exceptions from `fetch` can still bubble up and break message flow. Wrap the entire title-generation block in a `try/catch` in `chat-title.ts`, keeping the existing `response.ok` handling inside the `try` and logging any thrown error as a best-effort failure. Use the existing `appOrigin()`, `internalHeaders()`, and `console.warn` path so the hook degrades gracefully without interrupting the main message lifecycle.layers/nuxi/app/composables/useAgentChat.ts-197-212 (1)
197-212: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winRe-entrancy can double-persist the first user message.
initialDbPersistDone.value = trueis set only after the awaitedappendUserMessageToChat/createChatWithMessagecall. UnlikeuseStartChat, the chat-modesend/onSubmitpath has noloadingguard, so two quick submits (or programmaticaskQuestion) can both pass theinitialDbPersistDonecheck before either resolves, issuing duplicate persistence calls (and a duplicatePOST /api/chatsthat can error). Set the flag optimistically before awaiting and roll back on failure.🛡️ Proposed guard-before-await
async function persistFirstUserMessage(parts: UIMessage['parts']) { if (initialDbPersistDone.value) return + initialDbPersistDone.value = true const metadata = { createdAt: new Date().toISOString(), ...(useContext.value && agent.currentPage.value ? { pagePath: agent.currentPage.value } : {}) } - if (chatOptions.persistedInDb) { - await appendUserMessageToChat(chatOptions.chatId, parts, metadata) - } else { - await createChatWithMessage(chatOptions.chatId, parts, metadata) - } - - initialDbPersistDone.value = true + try { + if (chatOptions.persistedInDb) { + await appendUserMessageToChat(chatOptions.chatId, parts, metadata) + } else { + await createChatWithMessage(chatOptions.chatId, parts, metadata) + } + } catch (error) { + initialDbPersistDone.value = false + throw error + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/app/composables/useAgentChat.ts` around lines 197 - 212, The first-user-message persistence flow in persistFirstUserMessage can run twice because initialDbPersistDone.value is only set after awaiting appendUserMessageToChat/createChatWithMessage. Set the flag optimistically before the await so concurrent send/onSubmit or askQuestion calls in useAgentChat cannot pass the guard twice, and if the persistence call fails, roll the flag back so the message can be retried safely.layers/nuxi/app/composables/useAgentChat.ts-14-19 (1)
14-19: 📐 Maintainability & Code Quality | 🟡 MinorAdd explicit import for
ChatEveStatetype.
ChatEveStateis referenced on line 18 but has no explicit import in this file. The type is defined inlayers/nuxi/shared/types/chat.ts, which is not in Nuxt's default auto-import paths (composables/,components/,utils/,types/).Recommended fix:
import type { ChatEveState } from '../../shared/types/chat'This ensures the type resolves correctly during compilation, regardless of whether custom modules configure auto-imports for the
shared/directory.Current imports in useAgentChat.ts
import type { UIMessage } from 'ai' import { buildMessageParts, getMessageTextLength } from '../../shared/utils/paste-attachment' import { createEveChatSession } from './eve/session' import { getOrCreateEveAgent } from './eve/init' import { toUIMessages } from './eve/adapter' import { createEveFinishHandler, readAnonymousTitle, resumeOptionsFromChat } from './eve/thread-state' import { useChatVotes } from './useChatVotes' import { usePasteAttachment } from './usePasteAttachment'Note that
eve/thread-state.tsproperly importsChatEveState:import type { ChatEveState, ChatDetail } from '../../../shared/types/chat'🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/app/composables/useAgentChat.ts` around lines 14 - 19, `useAgentChat.ts` references `ChatEveState` in `ChatModeOptions` without importing it explicitly, so add a type-only import for `ChatEveState` from the shared chat types alongside the existing imports. Keep the change localized to the `useAgentChat` composable and ensure the `ChatModeOptions` definition resolves the type directly rather than relying on auto-imports.
🧹 Nitpick comments (2)
layers/nuxi/README.md (1)
5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSuppress false-positive markdownlint warning.
The fenced code block at line 5 contains a directory tree diagram, not executable code. Add a language tag (e.g.,
text) to suppress theMD040warning.-``` +```text🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/README.md` at line 5, The README’s fenced directory tree block is triggering a markdownlint MD040 false positive because it has no language tag. Update the fenced block in the README to use a non-code label such as text so the directory tree remains rendered as plain text while suppressing the warning.layers/nuxi/app/composables/useChatTools.ts (1)
85-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant double normalization (non-blocking).
getToolText(Line 210) passes a normalized name intoparseToolName, which normalizes again internally (Line 86). It's idempotent so behavior is unaffected; consider normalizing in one place to avoid confusion.Also applies to: 208-210
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/app/composables/useChatTools.ts` around lines 85 - 95, `getToolText` is normalizing the tool name before calling `parseToolName`, but `parseToolName` also normalizes internally, causing redundant double normalization. Update the flow so normalization happens in only one place: either pass the raw tool name into `parseToolName` and keep its current behavior, or remove the internal normalization and let `getToolText` own it. Use the `getToolText` and `parseToolName` symbols to keep the change localized and consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@layers/nuxi/agent/channels/eve.ts`:
- Around line 62-78: The prior-chat context lookup in eve.ts should be tied to
the authenticated user instead of trusting x-nuxi-chat-id alone. Update the
logic around the chatId/session check and the fetch to
/api/internal/chats/.../context so the current user session is forwarded (or
otherwise available) and the internal endpoint verifies chat ownership before
returning a summary. Keep the context-push flow in place, but only load data
when ownership is confirmed by the context endpoint.
In `@layers/nuxi/agent/lib/internal-api.ts`:
- Around line 30-37: The internalFetch helper currently calls fetch without any
AbortSignal, so stalled internal API requests can hang indefinitely. Update
internalFetch in internal-api.ts to always apply a mandatory global timeout (for
example, using AbortSignal.timeout(10000)) on the fetch call, and make sure the
signal is passed alongside the existing init options and headers so every
internal request fails fast instead of blocking the agent turn.
In `@layers/nuxi/agent/tools/admin-mcp.ts`:
- Around line 18-22: The Slack branch in canAccessAdminMcp is too broad because
any Slack issuer or presence of team_id currently grants admin MCP access.
Update canAccessAdminMcp to only allow Slack sessions when the team_id matches
an explicit allowlist, while keeping the existing authAttr(..., 'role') ===
'admin' path for non-Slack web/admin sessions. Use the existing
canAccessAdminMcp and authAttr helpers to locate and tighten the Slack access
check.
In `@layers/nuxi/agent/tools/show_module.ts`:
- Around line 28-31: The module lookup in show_module.ts currently uses a bare
fetch call without any application-level timeout, which can leave the tool
hanging on slow api.nuxt.com responses. Update the request logic around the
module API call in the show_module function to use the project’s existing
timeout wrapper or an AbortController-based timeout, and make sure timeout
failures are handled consistently with the existing 404 and non-ok response
handling.
In `@layers/nuxi/app/composables/eve/init.ts`:
- Around line 4-36: The module-scoped agentsByChatId cache in
getOrCreateEveAgent can leak across SSR requests and grow indefinitely on the
client. Update the caching in layers/nuxi/app/composables/eve/init.ts so it does
not persist globally on the server (use import.meta.server/client gating or
request-scoped state) and ensure entries are cleaned up automatically when the
consumer unmounts, ideally by wiring removeEveAgent into an onUnmounted cleanup
near getOrCreateEveAgent/useEveAgent.
In `@layers/nuxi/app/composables/eve/session.ts`:
- Around line 10-23: regenerate() is replaying only the text from
lastUserMessageText, so any file parts attached to the last user prompt are
lost. Update the session flow in eve/session.ts so regeneration reuses the full
last user message contents, preserving both text and non-text parts instead of
filtering to text only. Use lastUserMessageText and the regenerate/send path
that rebuilds the prompt to locate the change, and make sure file-only and
text+file prompts are forwarded unchanged.
In `@layers/nuxi/app/pages/dashboard/chat/`[id].vue:
- Around line 83-86: The first-user-message branch in the chat page is
re-sending an already persisted message, which causes a duplicate user turn in
the database. Update the logic in the dashboard chat page to use
`chat.regenerate()` instead of `send({ parts })` when `isOwner.value` and the
single existing message is a user message, so the assistant response is
generated from existing history without calling `persistFirstUserMessage` again.
Refer to `chat.regenerate`, `send`, and `persistFirstUserMessage` in
`useAgentChat` to keep the fix aligned with the existing chat flow.
In `@layers/nuxi/server/api/internal/agent/rate-limit/consume.post.ts`:
- Around line 6-11: The consume.post handler is trusting a caller-supplied
userId, which lets an internal caller choose whose rate limit is decremented.
Update this route to derive the principal from the request context and use the
same server-side identity binding already used in
layers/nuxi/server/utils/rate-limit.ts, or verify any forwarded identity is
cryptographically bound before passing it to consumeAgentRateLimitForUser. Keep
the change localized to the consume.post handler and the related
identity-binding helper usage.
In `@layers/nuxi/server/api/internal/chats/`[id]/messages.post.ts:
- Around line 12-18: The `readValidatedBody` schema in `messages.post.ts` is too
permissive for `parts`, so tighten it to match the `UIMessage` contract instead
of using a loose object with only `type`. Update the `messages` validation to
use a strict discriminated union on `parts` (for example via
`z.discriminatedUnion('type', ...)`) with the valid part shapes, then remove the
unsafe `as UIMessage['parts']` cast where the validated payload is consumed.
Keep the fix localized to the `readValidatedBody` schema and the message
persistence path so downstream consumers like `generate-chat-title` and
`ChatContent` receive only well-formed parts.
In `@layers/nuxi/server/utils/search-github-issues.ts`:
- Around line 27-32: The default org query builder in searchGithubIssues is
generating an impossible GitHub search because the NUXT_ORGS qualifiers are
space-joined and therefore ANDed together. Update the query construction in
search-github-issues so the non-repo branch combines the org qualifiers with an
explicit OR and wraps them in parentheses, keeping the existing repo branch
unchanged.
In `@server/db/migrations/sqlite/meta/0006_snapshot.json`:
- Around line 267-340: The 0006 Drizzle snapshot metadata is stale and no longer
matches the real schema. Regenerate the snapshot for the SQLite migration so the
messages table reflects the composite primary key on chat_id and id, the
obsolete messages_chat_id_idx is removed, and the votes table foreign key uses
the composite chat_id/message_id to chat_id/id relationship. Update the snapshot
JSON in the 0006 snapshot file rather than hand-editing individual fields so the
schema state stays consistent for Drizzle migration diffing.
---
Minor comments:
In `@layers/nuxi/agent/hooks/chat-title.ts`:
- Around line 14-29: The chat title request in the `message.received` hook only
handles non-OK responses, so network exceptions from `fetch` can still bubble up
and break message flow. Wrap the entire title-generation block in a `try/catch`
in `chat-title.ts`, keeping the existing `response.ok` handling inside the `try`
and logging any thrown error as a best-effort failure. Use the existing
`appOrigin()`, `internalHeaders()`, and `console.warn` path so the hook degrades
gracefully without interrupting the main message lifecycle.
In `@layers/nuxi/agent/tools/search_github_issues.ts`:
- Around line 6-10: The search_github_issues tool currently allows blank or
whitespace-only queries because inputSchema uses a plain z.string() for query.
Update the query validator in search_github_issues.ts to require a trimmed,
non-empty string so the tool rejects empty searches before reaching GitHub. Use
the inputSchema definition and its query field as the place to apply the
constraint.
In `@layers/nuxi/agent/tools/show_blog_post.ts`:
- Around line 6-10: The title validation in showBlogPost is too permissive
because inputSchema accepts empty or whitespace-only strings. Update the zod
schema for title to trim the value and require at least one character before it
is sent to /api/internal/content, using the existing inputSchema in
show_blog_post.ts so the body builder only receives valid blog search terms.
In `@layers/nuxi/agent/tools/web_search.ts`:
- Around line 6-8: The web search tool schema currently accepts empty or
whitespace-only queries because the `inputSchema` in `web_search.ts` only uses
`z.string()`. Update the `query` field validation in `inputSchema` to require a
non-empty trimmed string by adding trimming and a minimum-length check so
invalid searches are rejected before reaching the remote search call.
In `@layers/nuxi/app/composables/useAgentChat.ts`:
- Around line 197-212: The first-user-message persistence flow in
persistFirstUserMessage can run twice because initialDbPersistDone.value is only
set after awaiting appendUserMessageToChat/createChatWithMessage. Set the flag
optimistically before the await so concurrent send/onSubmit or askQuestion calls
in useAgentChat cannot pass the guard twice, and if the persistence call fails,
roll the flag back so the message can be retried safely.
- Around line 14-19: `useAgentChat.ts` references `ChatEveState` in
`ChatModeOptions` without importing it explicitly, so add a type-only import for
`ChatEveState` from the shared chat types alongside the existing imports. Keep
the change localized to the `useAgentChat` composable and ensure the
`ChatModeOptions` definition resolves the type directly rather than relying on
auto-imports.
In `@layers/nuxi/server/api/chats/`[id]/state.patch.ts:
- Around line 54-63: The PATCH handler in state.patch.ts currently treats
body.messages as a full replacement but does not enforce that contract, so
partial payloads can delete existing chat history; either validate that the
caller always provides the complete snapshot in
persistChatState/createEveFinishHandler or change the update flow to merge
instead of replacing. Also update the schema.messages insert path to tolerate
duplicate message ids by using the existing tx.insert(schema.messages) flow with
conflict handling such as onConflictDoNothing, so a repeated id does not fail
the transaction.
In `@layers/nuxi/server/api/internal/agent/web-search.post.ts`:
- Around line 19-23: The web-search error handling in webSearch.post currently
returns the raw provider message to the client and assumes the thrown value is
always an Error. Update the catch block in the web-search handler to log the
full error server-side, including any cause chain, and return a generic
client-facing message instead of using error.message directly.
In `@layers/nuxi/server/api/internal/github/search-issues.post.ts`:
- Around line 6-10: The request validation in readValidatedBody for
search-issues.post.ts allows an empty query, which can lead to meaningless
searches in searchGitHubIssues. Update the z.object schema used in this handler
so the query field requires a non-empty string, matching the validation pattern
used by the sibling web-search endpoint and keeping input handling consistent.
In `@layers/nuxi/server/utils/internal-api.ts`:
- Around line 13-19: The bearer secret check in requireInternalRequest currently
uses a normal string comparison, which can leak timing information. Update the
authorization validation in this function to use a constant-time equality check
when comparing the incoming Authorization header against the expected Bearer
value, while preserving the existing 401 Unauthorized behavior for mismatches.
In `@nuxt.config.ts`:
- Around line 127-128: The runtimeConfig entry for internalApiSecret is
redundant because the internal API checks already read
process.env.INTERNAL_API_SECRET directly. Either remove internalApiSecret from
nuxt.config.ts if you want to keep the current env-based validation in
layers/nuxi/agent/lib/internal-api.ts and
layers/nuxi/server/utils/internal-api.ts, or refactor those internal-api helpers
to read useRuntimeConfig().internalApiSecret and drop the fallback so missing
secrets fail consistently.
In `@server/mcp/prompts/admin/review-agent-chats.ts`:
- Line 22: The chat link in the admin review prompt uses a non-existent
`/admin/agent/` route, so update the template in review-agent-chats.ts to match
the real chat page route used by the app. Change the generated link text for
“Worst sessions” to point to the dashboard chat path, using the same chat id
placeholder, so reports align with the route handled by the chat page component.
---
Nitpick comments:
In `@layers/nuxi/app/composables/useChatTools.ts`:
- Around line 85-95: `getToolText` is normalizing the tool name before calling
`parseToolName`, but `parseToolName` also normalizes internally, causing
redundant double normalization. Update the flow so normalization happens in only
one place: either pass the raw tool name into `parseToolName` and keep its
current behavior, or remove the internal normalization and let `getToolText` own
it. Use the `getToolText` and `parseToolName` symbols to keep the change
localized and consistent.
In `@layers/nuxi/README.md`:
- Line 5: The README’s fenced directory tree block is triggering a markdownlint
MD040 false positive because it has no language tag. Update the fenced block in
the README to use a non-code label such as text so the directory tree remains
rendered as plain text while suppressing the warning.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5af94e76-a600-45a3-a7ea-8ece60c880b3
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (84)
.env.example.github/workflows/ci.yml.gitignoreREADME.mdapp/composables/useNavigation.tslayers/nuxi/README.mdlayers/nuxi/agent/agent.tslayers/nuxi/agent/channels/eve.tslayers/nuxi/agent/channels/slack.tslayers/nuxi/agent/connections/nuxt-mcp.tslayers/nuxi/agent/hooks/chat-title.tslayers/nuxi/agent/hooks/rate-limit.tslayers/nuxi/agent/instructions.tslayers/nuxi/agent/lib/base-instructions.tslayers/nuxi/agent/lib/define-nuxt-tool.tslayers/nuxi/agent/lib/internal-api.tslayers/nuxi/agent/tools/admin-mcp.tslayers/nuxi/agent/tools/open_playground.tslayers/nuxi/agent/tools/report_issue.tslayers/nuxi/agent/tools/search_github_issues.tslayers/nuxi/agent/tools/show_blog_post.tslayers/nuxi/agent/tools/show_hosting.tslayers/nuxi/agent/tools/show_module.tslayers/nuxi/agent/tools/show_template.tslayers/nuxi/agent/tools/web_search.tslayers/nuxi/app/components/agent/AgentChatMessages.vuelayers/nuxi/app/components/agent/AgentChatPrompt.vuelayers/nuxi/app/components/agent/AgentPanelChat.vuelayers/nuxi/app/components/chat/ChatContent.vuelayers/nuxi/app/composables/eve/adapter.tslayers/nuxi/app/composables/eve/init.tslayers/nuxi/app/composables/eve/session.tslayers/nuxi/app/composables/eve/thread-state.tslayers/nuxi/app/composables/eve/types.tslayers/nuxi/app/composables/useAgentChat.tslayers/nuxi/app/composables/useChatTools.tslayers/nuxi/app/composables/useChatVotes.tslayers/nuxi/app/composables/usePasteAttachment.tslayers/nuxi/app/pages/dashboard/chat/[id].vuelayers/nuxi/nuxt.config.tslayers/nuxi/package.jsonlayers/nuxi/server/api/chats/[id].post.tslayers/nuxi/server/api/chats/[id]/messages.delete.tslayers/nuxi/server/api/chats/[id]/messages.post.tslayers/nuxi/server/api/chats/[id]/state.patch.tslayers/nuxi/server/api/chats/index.post.tslayers/nuxi/server/api/internal/agent/rate-limit/consume.post.tslayers/nuxi/server/api/internal/agent/web-search.post.tslayers/nuxi/server/api/internal/chats/[id]/context.get.tslayers/nuxi/server/api/internal/chats/[id]/messages.post.tslayers/nuxi/server/api/internal/chats/[id]/title.post.tslayers/nuxi/server/api/internal/content.post.tslayers/nuxi/server/api/internal/github/search-issues.post.tslayers/nuxi/server/api/internal/session.get.tslayers/nuxi/server/db/schema.tslayers/nuxi/server/utils/chat-messages.tslayers/nuxi/server/utils/generate-chat-title.tslayers/nuxi/server/utils/internal-api.tslayers/nuxi/server/utils/rate-limit.tslayers/nuxi/server/utils/search-github-issues.tslayers/nuxi/server/utils/stats.tslayers/nuxi/server/utils/tools/search-github-issues.tslayers/nuxi/server/utils/tools/show-blog-post.tslayers/nuxi/server/utils/tools/show-hosting.tslayers/nuxi/server/utils/tools/show-template.tslayers/nuxi/shared/types/chat.tslayers/nuxi/shared/utils/chat.tsnuxt.config.tspackage.jsonserver/db/migrations/sqlite/0005_chat_eve_state.sqlserver/db/migrations/sqlite/0006_drop_agent_usage_metrics.sqlserver/db/migrations/sqlite/meta/0006_snapshot.jsonserver/db/migrations/sqlite/meta/_journal.jsonserver/mcp/admin.tsserver/mcp/prompts/admin/review-agent-chats.tsserver/mcp/prompts/admin/weekly-digest.tsserver/mcp/tools/admin/agent-usage-stats.tsserver/mcp/tools/admin/get-agent-chat.tsserver/mcp/tools/admin/list-agent-chats.tsserver/mcp/tools/admin/list-agent-votes.tsserver/mcp/tools/modules/list-modules.tstest/nuxt/setup.tsvercel.jsonvitest.config.ts
💤 Files with no reviewable changes (6)
- layers/nuxi/server/utils/tools/show-blog-post.ts
- layers/nuxi/server/api/chats/[id].post.ts
- layers/nuxi/server/utils/tools/show-hosting.ts
- layers/nuxi/server/utils/tools/search-github-issues.ts
- layers/nuxi/server/utils/stats.ts
- layers/nuxi/server/utils/tools/show-template.ts
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@layers/nuxi/server/utils/internal-api.ts`:
- Around line 30-33: `resolveInternalPrincipalId` currently falls back to
`session.id`, which is not a stable principal identifier and can break anonymous
ownership when the session rotates. Add a persistent `anonymousUserId` to the
session payload when the session is first created, preserve it through
regeneration/update flows, and change `resolveInternalPrincipalId` to return
`session.user?.id` or this stable `anonymousUserId` instead of the token ID.
Then update the ownership/rate-limit checks in handlers like
`agent/feedback.post.ts` and `chats/[id].get.ts` to compare against the same
stable anonymous identifier.
In `@layers/nuxi/server/utils/message-parts.ts`:
- Around line 30-38: The uiMessagePartSchema in message-parts.ts is missing
valid part types that the app already handles, so message persistence fails for
tool and source entries. Update uiMessagePartSchema to accept tool-prefixed
parts (matching type values starting with tool-) and add explicit support for
source-url and source-document parts, keeping uiMessagePartsSchema aligned with
the persisted message shapes. Use the existing textPartSchema, filePartSchema,
reasoningPartSchema, dynamicToolPartSchema, and stepStartPartSchema as the
reference point and extend the union with new schemas or custom validators so
these parts round-trip through messages.post and state.patch without Zod errors.
In `@package.json`:
- Line 54: The ai dependency range is broader than the minimumReleaseAge
exclusion, so updates like 7.0.3 can slip in and fail fresh installs. In
package.json, either pin the ai dependency to 7.0.2 or, if patch updates are
intended, update the minimumReleaseAgeExclude entry in pnpm-workspace.yaml to
cover the allowed ai 7.0.x versions. Use the ai dependency entry and the
minimumReleaseAgeExclude configuration as the matching symbols to keep both
policies aligned.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6208f5a4-bd02-4124-893d-e8fe076907ca
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (30)
.env.examplelayers/nuxi/README.mdlayers/nuxi/agent/channels/eve.tslayers/nuxi/agent/hooks/chat-title.tslayers/nuxi/agent/hooks/rate-limit.tslayers/nuxi/agent/lib/internal-api.tslayers/nuxi/agent/tools/admin-mcp.tslayers/nuxi/agent/tools/search_github_issues.tslayers/nuxi/agent/tools/show_blog_post.tslayers/nuxi/agent/tools/show_module.tslayers/nuxi/agent/tools/web_search.tslayers/nuxi/app/composables/eve/init.tslayers/nuxi/app/composables/eve/session.tslayers/nuxi/app/composables/useAgentChat.tslayers/nuxi/app/composables/useChatTools.tslayers/nuxi/app/pages/dashboard/chat/[id].vuelayers/nuxi/server/api/chats/[id]/state.patch.tslayers/nuxi/server/api/internal/agent/rate-limit/consume.post.tslayers/nuxi/server/api/internal/agent/web-search.post.tslayers/nuxi/server/api/internal/chats/[id]/context.get.tslayers/nuxi/server/api/internal/chats/[id]/messages.post.tslayers/nuxi/server/api/internal/github/search-issues.post.tslayers/nuxi/server/utils/internal-api.tslayers/nuxi/server/utils/message-parts.tslayers/nuxi/server/utils/search-github-issues.tsnuxt.config.tspackage.jsonpnpm-workspace.yamlserver/db/migrations/sqlite/meta/0006_snapshot.jsonserver/mcp/prompts/admin/review-agent-chats.ts
✅ Files skipped from review due to trivial changes (2)
- layers/nuxi/README.md
- server/db/migrations/sqlite/meta/0006_snapshot.json
🚧 Files skipped from review as they are similar to previous changes (18)
- layers/nuxi/agent/tools/search_github_issues.ts
- layers/nuxi/agent/tools/web_search.ts
- layers/nuxi/agent/tools/show_blog_post.ts
- .env.example
- layers/nuxi/agent/hooks/chat-title.ts
- server/mcp/prompts/admin/review-agent-chats.ts
- layers/nuxi/server/utils/search-github-issues.ts
- layers/nuxi/app/composables/eve/init.ts
- layers/nuxi/app/composables/eve/session.ts
- layers/nuxi/server/api/internal/github/search-issues.post.ts
- layers/nuxi/agent/tools/show_module.ts
- layers/nuxi/server/api/chats/[id]/state.patch.ts
- layers/nuxi/agent/lib/internal-api.ts
- layers/nuxi/app/composables/useChatTools.ts
- layers/nuxi/agent/hooks/rate-limit.ts
- layers/nuxi/agent/channels/eve.ts
- layers/nuxi/server/api/internal/chats/[id]/context.get.ts
- layers/nuxi/agent/tools/admin-mcp.ts
Use persistent anonymousUserId instead of rotating session.id for chat ownership and rate limits, and accept tool-*, source-url, and source-document parts in persistence validation.
Chats require login via requireUserSession; remove unused internal messages route and session-principal indirection. Derive DB persist mode from initialMessages in useAgentChat.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
layers/nuxi/app/composables/useAgentChat.ts (1)
197-215: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winSerialize the first-message persistence.
Line 199 marks the DB bootstrap as done before
appendUserMessageToChat/createChatWithMessagehas resolved. A secondsend()in that window skips persistence and can reach Eve before the chat row exists, which risks dropping or mis-ordering the opening messages.Suggested fix
+ let initialDbPersistPromise: Promise<void> | null = null + async function persistFirstUserMessage(parts: UIMessage['parts']) { if (initialDbPersistDone.value) return - initialDbPersistDone.value = true + if (initialDbPersistPromise) { + await initialDbPersistPromise + return + } const metadata = { createdAt: new Date().toISOString(), ...(useContext.value && agent.currentPage.value ? { pagePath: agent.currentPage.value } : {}) } - try { - if (chatOptions.initialMessages?.length) { - await appendUserMessageToChat(chatOptions.chatId, parts, metadata) - } else { - await createChatWithMessage(chatOptions.chatId, parts, metadata) - } - } catch (error) { - initialDbPersistDone.value = false - throw error - } + initialDbPersistPromise = (async () => { + if (chatOptions.initialMessages?.length) { + await appendUserMessageToChat(chatOptions.chatId, parts, metadata) + } else { + await createChatWithMessage(chatOptions.chatId, parts, metadata) + } + initialDbPersistDone.value = true + })() + + try { + await initialDbPersistPromise + } finally { + initialDbPersistPromise = null + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@layers/nuxi/app/composables/useAgentChat.ts` around lines 197 - 215, Serialize the first-message persistence in persistFirstUserMessage so initialDbPersistDone.value is not set to true until appendUserMessageToChat or createChatWithMessage has successfully completed. If a second send() can arrive while the first call is still awaiting the DB write, make it wait or queue behind the same in-flight promise so the chat row is created before any later message is sent to Eve, and keep the rollback path in the catch block for failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@layers/nuxi/app/composables/useNuxtAgent.ts`:
- Around line 106-108: The shared usage state in useNuxtAgent is not refreshed
when the auth principal changes, so stale counters can remain after
sign-in/sign-out. Update the existing watch(loggedIn) logic in useNuxtAgent to
also call refreshUsage alongside the chat refresh whenever the principal flips,
keeping rateLimitReached and usage in sync with the current user.
In `@layers/nuxi/server/utils/message-parts.ts`:
- Around line 30-33: The source-url schema in sourceUrlPartSchema is currently
too permissive because url accepts any string, so tighten it to only allow safe
web links with explicit http: or https: protocols. Update the validation in
uiMessagePartsSchema’s source-url branch to enforce the protocol check at the
schema level, keeping the existing type shape intact while rejecting
javascript:, data:, and other non-web schemes.
---
Outside diff comments:
In `@layers/nuxi/app/composables/useAgentChat.ts`:
- Around line 197-215: Serialize the first-message persistence in
persistFirstUserMessage so initialDbPersistDone.value is not set to true until
appendUserMessageToChat or createChatWithMessage has successfully completed. If
a second send() can arrive while the first call is still awaiting the DB write,
make it wait or queue behind the same in-flight promise so the chat row is
created before any later message is sent to Eve, and keep the rollback path in
the catch block for failures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a20391d7-7c34-4618-99f0-479e0ce4bea8
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (24)
layers/nuxi/app/components/agent/AgentChatMessages.vuelayers/nuxi/app/composables/eve/adapter.tslayers/nuxi/app/composables/useAgentChat.tslayers/nuxi/app/composables/useNuxtAgent.tslayers/nuxi/app/pages/dashboard/chat/[id].vuelayers/nuxi/package.jsonlayers/nuxi/server/api/agent/feedback.post.tslayers/nuxi/server/api/chats/[id].delete.tslayers/nuxi/server/api/chats/[id].get.tslayers/nuxi/server/api/chats/[id]/branch.post.tslayers/nuxi/server/api/chats/[id]/messages.delete.tslayers/nuxi/server/api/chats/[id]/title.patch.tslayers/nuxi/server/api/chats/[id]/visibility.patch.tslayers/nuxi/server/api/chats/[id]/votes.get.tslayers/nuxi/server/api/chats/[id]/votes.post.tslayers/nuxi/server/api/chats/index.get.tslayers/nuxi/server/api/internal/agent/rate-limit/consume.post.tslayers/nuxi/server/api/internal/chats/[id]/context.get.tslayers/nuxi/server/api/internal/session.get.tslayers/nuxi/server/utils/internal-api.tslayers/nuxi/server/utils/message-parts.tslayers/nuxi/server/utils/rate-limit.tspnpm-workspace.yamlshared/types/auth.d.ts
💤 Files with no reviewable changes (2)
- layers/nuxi/app/pages/dashboard/chat/[id].vue
- layers/nuxi/server/utils/internal-api.ts
✅ Files skipped from review due to trivial changes (2)
- layers/nuxi/package.json
- pnpm-workspace.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
- layers/nuxi/server/api/internal/chats/[id]/context.get.ts
- layers/nuxi/server/api/internal/session.get.ts
Reload rate-limit counters when the session principal changes, and restrict persisted source-url parts to http(s) links.
🔗 Linked issue
📚 Description