Skip to content

Set up proper RAG with convex vector search#25

Merged
djanogly merged 13 commits intodevfrom
openspec/use-convex-vector-search
Mar 13, 2026
Merged

Set up proper RAG with convex vector search#25
djanogly merged 13 commits intodevfrom
openspec/use-convex-vector-search

Conversation

@djanogly
Copy link
Contributor

This pull request implements a major refactor of the AI agent's knowledge retrieval system to use Convex Vector Search instead of the legacy in-memory string-matching approach. This change significantly improves performance and scalability, especially for large workspaces, by leveraging existing embeddings infrastructure and Convex's native vector search capabilities. The old string-matching logic is removed, and new internal actions are introduced to handle knowledge retrieval via vector similarity.

Key changes include:

AI Knowledge Retrieval Refactor:

  • Replaced the in-memory string-matching logic (calculateRelevanceScore, collectRelevantKnowledge, etc.) in aiAgent.ts with a new vector similarity search approach using Convex's vectorSearch on the contentEmbeddings table. This improves response times and scalability for the AI agent. [1] [2] [3] [4]

  • Deprecated the old query-based knowledge retrieval endpoints (getRelevantKnowledge, getRelevantKnowledgeForRuntime) in favor of a new internal action-based endpoint (getRelevantKnowledgeForRuntimeAction). Calls to the deprecated endpoints now throw errors to enforce migration. [1] [2] [3]

New Vector Search Action Implementation:

  • Added aiAgentActionsKnowledge.ts with a new getRelevantKnowledgeForRuntimeAction internal action. This action embeds the incoming query, performs a vector search, filters and deduplicates results, and fetches the final content for the AI agent to use.

  • Updated aiAgentActions.ts to use the new action reference for runtime knowledge retrieval, including wiring up runAction and updating the main AI response generation flow to use the new vector-based action. [1] [2] [3] [4]

Documentation and Spec Updates:

  • Added and updated OpenSpec documentation, including design, proposal, requirements, and migration tasks, to reflect the move to vector search and removal of legacy string-matching. [1] [2] [3] [4] [5] [6]

Other Minor Changes:

  • Added a roadmap item to support widget deep linking via URL parameters.
  • Commented out the display of suggestion snippets in the widget conversation footer.

These changes collectively modernize the AI agent's retrieval capabilities, reduce latency, and prepare the codebase for future scaling and enhancements.

@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
opencom-landing Ready Ready Preview, Comment Mar 13, 2026 1:29pm
opencom-web Ready Ready Preview, Comment Mar 13, 2026 1:29pm

@djanogly djanogly requested a review from Copilot March 13, 2026 00:19
@qodo-code-review
Copy link

Review Summary by Qodo

Implement Convex Vector Search for AI Agent Knowledge Retrieval

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Replace legacy in-memory string-matching with Convex Vector Search for AI knowledge retrieval
• Create new aiAgentActionsKnowledge.ts action for vector-based knowledge retrieval
• Implement content chunking with overlap for improved embedding quality
• Add deduplication logic to vector search results across multiple functions
• Deprecate old query-based knowledge endpoints in favor of action-based approach
Diagram
flowchart LR
  Query["Customer Query"]
  Embed["Embed Query<br/>text-embedding-3-small"]
  VectorSearch["Vector Search<br/>contentEmbeddings"]
  Filter["Filter by<br/>Knowledge Sources"]
  Dedup["Deduplicate<br/>Results"]
  Fetch["Fetch Full<br/>Content"]
  Response["AI Response<br/>with Context"]
  
  Query --> Embed
  Embed --> VectorSearch
  VectorSearch --> Filter
  Filter --> Dedup
  Dedup --> Fetch
  Fetch --> Response
Loading

Grey Divider

File Changes

1. packages/convex/convex/_generated/api.d.ts ⚙️ Configuration changes +2/-0

Register new aiAgentActionsKnowledge module

packages/convex/convex/_generated/api.d.ts


2. packages/convex/convex/aiAgent.ts 🐞 Bug fix +7/-129

Remove legacy string-matching, deprecate old endpoints

packages/convex/convex/aiAgent.ts


3. packages/convex/convex/aiAgentActions.ts ✨ Enhancement +18/-7

Update to use vector search action instead of query

packages/convex/convex/aiAgentActions.ts


View more (11)
4. packages/convex/convex/aiAgentActionsKnowledge.ts ✨ Enhancement +154/-0

New action implementing vector search knowledge retrieval

packages/convex/convex/aiAgentActionsKnowledge.ts


5. packages/convex/convex/embeddings.ts ✨ Enhancement +149/-186

Implement content chunking and refactor batch processing

packages/convex/convex/embeddings.ts


6. packages/convex/convex/suggestions.ts ✨ Enhancement +59/-36

Add deduplication and improve vector search filtering

packages/convex/convex/suggestions.ts


7. ROADMAP.md 📝 Documentation +1/-0

Add widget deep linking feature to roadmap

ROADMAP.md


8. apps/widget/src/components/conversationView/Footer.tsx Miscellaneous +1/-1

Comment out suggestion snippet display

apps/widget/src/components/conversationView/Footer.tsx


9. openspec/changes/archive/2026-03-12-use-convex-vector-search/.openspec.yaml 📝 Documentation +2/-0

Archive specification metadata for vector search feature

openspec/changes/archive/2026-03-12-use-convex-vector-search/.openspec.yaml


10. openspec/changes/archive/2026-03-12-use-convex-vector-search/design.md 📝 Documentation +27/-0

Document design decisions for vector search implementation

openspec/changes/archive/2026-03-12-use-convex-vector-search/design.md


11. openspec/changes/archive/2026-03-12-use-convex-vector-search/proposal.md 📝 Documentation +18/-0

Archive proposal for vector search RAG system

openspec/changes/archive/2026-03-12-use-convex-vector-search/proposal.md


12. openspec/changes/archive/2026-03-12-use-convex-vector-search/specs/ai-agent-knowledge-retrieval/spec.md 📝 Documentation +14/-0

Archive modified requirements for vector search

openspec/changes/archive/2026-03-12-use-convex-vector-search/specs/ai-agent-knowledge-retrieval/spec.md


13. openspec/changes/archive/2026-03-12-use-convex-vector-search/tasks.md 📝 Documentation +10/-0

Archive implementation tasks for vector search

openspec/changes/archive/2026-03-12-use-convex-vector-search/tasks.md


14. openspec/specs/ai-agent-knowledge-retrieval/spec.md 📝 Documentation +13/-0

Define AI agent knowledge retrieval requirements

openspec/specs/ai-agent-knowledge-retrieval/spec.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 13, 2026

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Manual refs in aiAgentActionsKnowledge 📘 Rule violation ⛯ Reliability
Description
New backend cross-function calls are wired via string-based
makeFunctionReference("module:function") without any TS2589-hotspot justification. This bypasses
generated api/internal refs and increases drift/breakage risk when functions move/rename.
Code

packages/convex/convex/aiAgentActionsKnowledge.ts[R43-59]

+const GET_CONTENT_BY_ID_REF = makeFunctionReference<
+  "query",
+  { contentType: SuggestionContentType; contentId: string },
+  SuggestionContentRecord
+>("suggestions:getContentById") as unknown as InternalQueryRef<
+  { contentType: SuggestionContentType; contentId: string },
+  SuggestionContentRecord
+>;
+
+const GET_EMBEDDING_BY_ID_REF = makeFunctionReference<
+  "query",
+  { id: Id<"contentEmbeddings"> },
+  EmbeddingRecord
+>("suggestions:getEmbeddingById") as unknown as InternalQueryRef<
+  { id: Id<"contentEmbeddings"> },
+  EmbeddingRecord
+>;
Evidence
Compliance requires backend cross-function calls to prefer generated api/internal references;
the PR introduces new string-based makeFunctionReference refs (with no TS2589 comment/ticket) for
suggestions:* queries.

Rule 72008: Prefer generated api/internal references for backend cross-function calls
packages/convex/convex/aiAgentActionsKnowledge.ts[43-59]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`packages/convex/convex/aiAgentActionsKnowledge.ts` introduces new cross-function calls using string-based `makeFunctionReference(&amp;amp;amp;amp;amp;quot;module:function&amp;amp;amp;amp;amp;quot;)` without a TS2589-hotspot justification. Compliance requires using generated `api`/`internal` references for backend cross-function calls.
## Issue Context
The module currently defines `GET_CONTENT_BY_ID_REF` and `GET_EMBEDDING_BY_ID_REF` via `makeFunctionReference(...)` to call into the `suggestions` module.
## Fix Focus Areas
- packages/convex/convex/aiAgentActionsKnowledge.ts[43-59]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. as unknown as in runtime📘 Rule violation ✓ Correctness
Description
New/modified type-escape casts (as unknown as) are used directly in backend runtime/action modules
rather than being localized to a clearly named adapter/hotspot helper. This spreads unsafe typing
boundaries across business logic and makes type safety regressions harder to contain.
Code

packages/convex/convex/aiAgentActions.ts[R161-174]

+const GET_RELEVANT_KNOWLEDGE_FOR_RUNTIME_ACTION_REF = makeFunctionReference<
+  "action",
{
workspaceId: Id<"workspaces">;
query: string;
knowledgeSources?: KnowledgeSource[];
limit?: number;
},
RelevantKnowledgeResult[]
->("aiAgent:getRelevantKnowledgeForRuntime") as unknown as ConvexRef<
-  "query",
+>("aiAgentActionsKnowledge:getRelevantKnowledgeForRuntimeAction") as unknown as ConvexRef<
+  "action",
"internal",
{
workspaceId: Id<"workspaces">;
Evidence
The compliance rule restricts type-escape casts to dedicated adapter/hotspot modules, but the PR
adds/updates as unknown as casts inline in runtime modules (and repeats the pattern in additional
backend files).

Rule 72009: Localize type-escape casts to adapters or explicit hotspot helpers
packages/convex/convex/aiAgentActions.ts[161-180]
packages/convex/convex/aiAgentActionsKnowledge.ts[43-59]
packages/convex/convex/embeddings.ts[119-171]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR introduces/updates `as unknown as` type-escape casts directly in backend runtime/action modules. Compliance requires these escapes to be localized to adapter/bridge modules or explicit hotspot helpers.
## Issue Context
Casts are used to coerce `makeFunctionReference(...)` results into `InternalQueryRef` / `ConvexRef` types. This pattern appears across multiple runtime modules added/modified by this PR.
## Fix Focus Areas
- packages/convex/convex/aiAgentActions.ts[161-180]
- packages/convex/convex/aiAgentActionsKnowledge.ts[43-59]
- packages/convex/convex/embeddings.ts[119-171]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Deprecated knowledge query throws🐞 Bug ✓ Correctness
Description
aiAgent.getRelevantKnowledge now always throws, but the SDK and tests still call it as a query,
causing immediate runtime failures for consumers and CI.
Code

packages/convex/convex/aiAgent.ts[R325-329]

+  handler: async () => {
+    // Note: getRelevantKnowledge should ideally also use vector search, but since it is a query, we'd need a client-side action call instead for UI.
+    // However, it is not used in the runtime flow (which uses getRelevantKnowledgeForRuntimeAction).
+    // Let's return empty or throw to enforce migration, or simply deprecate. 
+    throw new Error("getRelevantKnowledge query is deprecated. Use vector search action instead.");
Evidence
The PR changes the public getRelevantKnowledge query to unconditionally throw, while the SDK
wrapper and tests still invoke that query reference, so callers will now error instead of receiving
results.

packages/convex/convex/aiAgent.ts[316-343]
packages/sdk-core/src/api/aiAgent.ts[9-14]
packages/sdk-core/src/api/aiAgent.ts[76-90]
packages/convex/tests/aiAgent.test.ts[118-149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`aiAgent:getRelevantKnowledge` is still referenced by the SDK and tests but now unconditionally throws, breaking consumers.
### Issue Context
This is a public surface area (`authQuery` with permission checks). If you want vector search, embedding requires an action; throwing in the query should only happen after all callers have been migrated.
### Fix Focus Areas
- packages/convex/convex/aiAgent.ts[316-343]
- packages/sdk-core/src/api/aiAgent.ts[9-14]
- packages/sdk-core/src/api/aiAgent.ts[76-90]
- packages/convex/tests/aiAgent.test.ts[118-149]
### Suggested fix approach
- Introduce a new **public** `authAction` (e.g. `aiAgent:getRelevantKnowledgeAction`) that performs embed + vectorSearch.
- Update `sdk-core` to call `client.action(...)` for the new endpoint.
- Keep `getRelevantKnowledge` query temporarily returning `[]` (or legacy results) without throwing, plus a deprecation warning, until the migration is complete.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. RAG failures abort response🐞 Bug ⛯ Reliability
Description
generateResponse calls the new RAG action without handling errors, so embedding/vector-search
failures will throw and prevent any AI response or handoff fallback.
Code

packages/convex/convex/aiAgentActions.ts[R576-580]

// Get relevant knowledge
-    const knowledgeResults = await runQuery(GET_RELEVANT_KNOWLEDGE_FOR_RUNTIME_REF, {
+    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FOR_RUNTIME_ACTION_REF, {
 workspaceId: args.workspaceId,
 query: args.query,
 knowledgeSources: settings.knowledgeSources,
Evidence
The runtime response action directly awaits the RAG action; that RAG action calls embed() (an
external call) which can throw. The only try/catch in generateResponse shown is around text
generation, not around knowledge retrieval, so failures in retrieval will bubble out and crash the
action.

packages/convex/convex/aiAgentActions.ts[572-590]
packages/convex/convex/aiAgentActionsKnowledge.ts[72-86]
packages/convex/convex/aiAgentActions.ts[781-799]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Knowledge retrieval failures currently abort the entire `generateResponse` action.
### Issue Context
`getRelevantKnowledgeForRuntimeAction` performs external embedding generation and may throw. `generateResponse` has error handling for generation, but not for retrieval.
### Fix Focus Areas
- packages/convex/convex/aiAgentActions.ts[572-590]
- packages/convex/convex/aiAgentActionsKnowledge.ts[72-86]
### Suggested fix approach
- Wrap the `runAction(GET_RELEVANT_KNOWLEDGE_FOR_RUNTIME_ACTION_REF, ...)` call in try/catch.
- On failure, record a runtime diagnostic (similar to other failure paths) and proceed with `knowledgeResults = []` (or trigger a handoff path) so the user still gets a response/fallback.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Hardcoded embedding model🐞 Bug ✓ Correctness
Description
The AI-agent RAG action always embeds queries with text-embedding-3-small, ignoring the
workspace’s configurable embeddingModel, which can silently degrade retrieval if the workspace
uses a different embedding model for stored vectors.
Code

packages/convex/convex/aiAgentActionsKnowledge.ts[R72-76]

+    // 1. Embed the query
+    const { embedding } = await embed({
+      model: aiClient.embedding(DEFAULT_EMBEDDING_MODEL),
+      value: args.query,
+    });
Evidence
Workspace AI settings include embeddingModel and suggestions already respect it when embedding
queries, but the new AI-agent knowledge action hard-codes a default model for query embedding. If
stored embeddings were generated with a different model, vector similarity becomes inconsistent.

packages/convex/convex/aiAgentActionsKnowledge.ts[8-9]
packages/convex/convex/aiAgentActionsKnowledge.ts[72-76]
packages/convex/convex/aiAgent.ts[150-169]
packages/convex/convex/aiAgent.ts[172-180]
packages/convex/convex/suggestions.ts[349-360]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The AI-agent knowledge retrieval embeds queries with a hard-coded model which may not match the model used to generate `contentEmbeddings` for a workspace.
### Issue Context
`aiAgentSettings.embeddingModel` exists and is already used elsewhere (suggestions). RAG should embed with the same model that produced the stored vectors.
### Fix Focus Areas
- packages/convex/convex/aiAgentActionsKnowledge.ts[8-9]
- packages/convex/convex/aiAgentActionsKnowledge.ts[61-86]
- packages/convex/convex/aiAgentActions.ts[502-582]
- packages/convex/convex/aiAgent.ts[172-180]
### Suggested fix approach
- Add `embeddingModel?: string` to `getRelevantKnowledgeForRuntimeAction` args and pass `settings.embeddingModel` from `generateResponse`.
- Alternatively, have the action `runQuery` `aiAgent:getRuntimeSettingsForWorkspace` and use the returned `embeddingModel`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Embedding delete before insert🐞 Bug ⛯ Reliability
Description
embeddings.generateInternal deletes all existing embeddings for a content item before inserting
new chunk embeddings, so any failure during insertion leaves the item with zero embeddings and
breaks vector search for it.
Code

packages/convex/convex/embeddings.ts[R283-304]

+    if (existing.length > 0) {
+      await runMutation(REMOVE_EMBEDDINGS_REF, {
+        contentType: args.contentType,
+        contentId: args.contentId,
+      });
+    }
-    if (existing) {
-      await runMutation(UPDATE_EMBEDDING_REF, {
-        id: existing._id,
-        embedding: embedding,
+    const insertedIds: Id<"contentEmbeddings">[] = [];
+    for (let index = 0; index < embeddings.length; index += 1) {
+      const embedding = embeddings[index];
+      const chunk = chunks[index];
+      const id = await runMutation(INSERT_EMBEDDING_REF, {
+        workspaceId: args.workspaceId,
+        contentType: args.contentType,
+        contentId: args.contentId,
+        embedding,
   textHash,
   title: args.title,
-        snippet: snippetText,
+        snippet: createSnippet(chunk),
 });
-      return { id: existing._id, skipped: false };
+      insertedIds.push(id);
}
Evidence
The function explicitly removes existing embeddings first, then performs multiple inserts. Because
these are separate mutations, partial failure can permanently remove embeddings until a future
regeneration occurs.

packages/convex/convex/embeddings.ts[282-304]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Current embedding regeneration can leave content without embeddings if insertion fails after deletion.
### Issue Context
`generateInternal` performs: vector generation → delete existing → insert many. Delete+inserts are separate mutations.
### Fix Focus Areas
- packages/convex/convex/embeddings.ts[255-311]
- packages/convex/convex/embeddings.ts[531-579]
### Suggested fix approach
- Prefer a single internal mutation that (a) inserts new docs with a new `generationId`/`updatedAt` marker and (b) deletes old docs after successful insert.
- If keeping multiple mutations, reverse the order: insert all new embeddings first, then remove old ones (and consider best-effort cleanup of partially inserted new docs on failure).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

7. Batch embedding very slow🐞 Bug ➹ Performance
Description
generateBatch/generateBatchInternal now run generateInternal sequentially per item, causing
many embedding API calls and many per-chunk inserts, which can time out or exceed action limits
during backfills.
Code

packages/convex/convex/embeddings.ts[R369-387]

+    const runAction = getShallowRunAction(ctx);
+    let processed = 0;
+    let skipped = 0;
+
+    for (const item of args.items) {
+      const result = await runAction(GENERATE_INTERNAL_REF, {
+        workspaceId: item.workspaceId,
+        contentType: item.contentType,
+        contentId: item.contentId,
+        title: item.title,
+        content: item.content,
+        model: args.model,
+      });
+      if (result.skipped) {
+        skipped += 1;
 } else {
-        await runMutation(INSERT_EMBEDDING_REF, {
-          workspaceId: item.workspaceId,
-          contentType: item.contentType,
-          contentId: item.contentId,
-          embedding,
-          textHash: item.textHash,
-          title: item.title,
-          snippet: item.snippet,
-        });
+        processed += 1;
 }
}
Evidence
The batch path processes items one-by-one, and backfills call generateBatchInternal with a default
batch size of 50; combined with chunking, this can explode into hundreds of inserts and dozens of
embedding calls in one action.

packages/convex/convex/embeddings.ts[369-390]
packages/convex/convex/embeddings.ts[404-472]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Batch embedding now scales poorly because it runs `generateInternal` per item sequentially, and `generateInternal` can insert multiple chunk embeddings per item.
### Issue Context
`backfillExisting` defaults to 50 items per batch. For long content, chunking multiplies DB inserts, and sequential embedding calls increase action duration.
### Fix Focus Areas
- packages/convex/convex/embeddings.ts[337-391]
- packages/convex/convex/embeddings.ts[485-529]
- packages/convex/convex/embeddings.ts[393-483]
### Suggested fix approach
- Add controlled concurrency (e.g., process items with a small pool size) or rebuild a true batch pipeline:
- build all chunkTexts across the batch,
- call `embedMany` once,
- insert results with minimal mutation calls.
- Consider lowering default `batchSize` or dynamically sizing based on estimated chunk count.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@qodo-code-review
Copy link

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: checks

Failed stage: Summarize check results [❌]

Failed test name: tests/aiAgentRuntimeSafety.test.ts > aiAgentActions runtime safety > clears prior diagnostics and continues generation when configuration is valid

Failure summary:

The GitHub Action failed in the pnpm test:convex step because Vitest reported failing Convex backend
tests (exit code 1).

Failures observed:
- tests/aiAgentRuntimeSafety.test.ts (8 failing tests): multiple cases crash with
TypeError: runAction is not a function, originating from convex/aiAgentActions.ts:577:36 (invocation
of runAction(GET_RELEVANT_KNOWLEDGE_...)). This indicates the test/runtime context did not provide a
valid runAction function (e.g., missing/incorrect mocking or incorrect handler context wiring).
-
tests/runtimeTypeHardeningGuard.test.ts (1 failing test): assertion failure expected ... to contain
"GET_BY_CONTENT_REF" at tests/runtimeTypeHardeningGuard.test.ts:187:30, meaning the inspected source
no longer includes the expected reference constant.

Although lint produced many warnings, it had 0 errors and the workflow summary shows
lint/typecheck/build gates passed; the blocking failure is specifically Convex backend tests failed.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

205:  packages/types lint: Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. Assuming latest React version for linting.
206:  packages/ui lint: Done
207:  packages/types lint: Done
208:  apps/landing lint$ next lint
209:  packages/convex lint$ eslint convex scripts tests --ext .ts
210:  apps/landing lint: `next lint` is deprecated and will be removed in Next.js 16.
211:  apps/landing lint: For new projects, use create-next-app to choose your preferred linter.
212:  apps/landing lint: For existing projects, migrate to the ESLint CLI:
213:  apps/landing lint: npx @next/codemod@canary next-lint-to-eslint-cli .
214:  packages/convex lint: Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. Assuming latest React version for linting.
215:  apps/landing lint:  ⚠ The Next.js plugin was not detected in your ESLint configuration. See https://nextjs.org/docs/app/api-reference/config/eslint#migrating-existing-config
216:  apps/landing lint: Attention: Next.js now collects completely anonymous telemetry regarding usage.
217:  apps/landing lint: This information is used to shape Next.js' roadmap and prioritize features.
218:  apps/landing lint: You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
219:  apps/landing lint: https://nextjs.org/telemetry
220:  apps/landing lint: ✔ No ESLint warnings or errors
221:  apps/landing lint: Done
...

376:  packages/convex lint:   90:12  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
377:  packages/convex lint: /home/runner/work/opencom/opencom/packages/convex/tests/visitorDirectoryAuthorizationSemantics.test.ts
378:  packages/convex lint:    62:81  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
379:  packages/convex lint:    74:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
380:  packages/convex lint:    77:81  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
381:  packages/convex lint:    91:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
382:  packages/convex lint:   116:13  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
383:  packages/convex lint:   138:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
384:  packages/convex lint:   165:13  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
385:  packages/convex lint:   187:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
386:  packages/convex lint:   200:65  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
387:  packages/convex lint:   215:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
388:  packages/convex lint:   233:65  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
389:  packages/convex lint:   254:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
390:  packages/convex lint:   348:65  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
391:  packages/convex lint: ✖ 146 problems (0 errors, 146 warnings)
392:  packages/convex lint: Done
393:  apps/mobile lint$ eslint . --ext .ts,.tsx
394:  apps/web lint$ eslint src --ext .ts,.tsx
395:  apps/mobile lint: /home/runner/work/opencom/opencom/apps/mobile/src/contexts/BackendContext.tsx
396:  apps/mobile lint:   33:6  warning  React Hook useEffect has a missing dependency: 'loadBackendStorage'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
397:  apps/mobile lint: ✖ 1 problem (0 errors, 1 warning)
398:  apps/mobile lint: Done
399:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/app/login/page.test.tsx
400:  apps/web lint:   45:42  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
401:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/app/settings/MessengerSettingsSection.test.tsx
402:  apps/web lint:   24:61  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
403:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/app/signup/page.test.tsx
404:  apps/web lint:   42:42  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
405:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/components/ResponsiveLayout.tsx
406:  apps/web lint:   152:6  warning  React Hook useEffect has a missing dependency: 'closePanel'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
407:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/contexts/BackendContext.tsx
408:  apps/web lint:   86:6  warning  React Hook useEffect has a missing dependency: 'loadBackendStorage'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
409:  apps/web lint: ✖ 5 problems (0 errors, 5 warnings)
410:  apps/web lint: Done
...

412:  apps/widget lint: /home/runner/work/opencom/opencom/apps/widget/src/components/ConversationView.test.tsx
413:  apps/widget lint:    53:6   warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
414:  apps/widget lint:    55:32  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
415:  apps/widget lint:    71:48  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
416:  apps/widget lint:    96:74  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
417:  apps/widget lint:   265:37  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
418:  apps/widget lint:   266:35  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
419:  apps/widget lint:   284:49  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
420:  apps/widget lint: /home/runner/work/opencom/opencom/apps/widget/src/components/ConversationView.tsx
421:  apps/widget lint:   162:6  warning  React Hook useEffect has missing dependencies: 'sessionTokenRef' and 'visitorId'. Either include them or remove the dependency array  react-hooks/exhaustive-deps
422:  apps/widget lint: /home/runner/work/opencom/opencom/apps/widget/src/components/HelpCenter.tsx
423:  apps/widget lint:   149:6  warning  React Hook useEffect has a missing dependency: 'setSelectedCollectionKey'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
424:  apps/widget lint:   158:6  warning  React Hook useEffect has a missing dependency: 'setSelectedCollectionKey'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
425:  apps/widget lint: /home/runner/work/opencom/opencom/apps/widget/src/hooks/useWidgetConversationFlow.ts
426:  apps/widget lint:   192:6  warning  React Hook useCallback has a missing dependency: 'visitorIdRef'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
427:  apps/widget lint: ✖ 11 problems (0 errors, 11 warnings)
428:  apps/widget lint: Done
...

493:  ##[endgroup]
494:  > opencom@0.1.0 security:headers-check /home/runner/work/opencom/opencom
495:  > node scripts/ci-security-headers-check.js
496:  [security-headers-check] OK: web and landing header requirements validated.
497:  ##[group]Run pnpm test:convex
498:  �[36;1mpnpm test:convex�[0m
499:  shell: /usr/bin/bash -e {0}
500:  env:
501:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
502:  ##[endgroup]
503:  > opencom@0.1.0 test:convex /home/runner/work/opencom/opencom
504:  > pnpm --filter @opencom/convex test
505:  > @opencom/convex@0.1.0 test /home/runner/work/opencom/opencom/packages/convex
506:  > vitest run
507:  �[1m�[46m RUN �[49m�[22m �[36mv4.0.17 �[39m�[90m/home/runner/work/opencom/opencom/packages/convex�[39m
508:  �[31m❯�[39m tests/runtimeTypeHardeningGuard.test.ts �[2m(�[22m�[2m24 tests�[22m�[2m | �[22m�[31m1 failed�[39m�[2m)�[22m�[32m 98�[2mms�[22m�[39m
509:  �[32m✓�[39m prevents broad any-casts in covered runtime-critical modules�[32m 4�[2mms�[22m�[39m
...

518:  �[31m     �[31m�[31m uses fixed typed refs for embedding self-dispatch and permission checks�[39m�[32m 66�[2mms�[22m�[39m
519:  �[32m✓�[39m uses fixed typed refs for push delivery boundaries�[32m 1�[2mms�[22m�[39m
520:  �[32m✓�[39m uses fixed typed refs for push campaign delivery orchestration�[32m 1�[2mms�[22m�[39m
521:  �[32m✓�[39m uses shared notification refs for message scheduling�[32m 0�[2mms�[22m�[39m
522:  �[32m✓�[39m uses shared notification refs for conversation scheduling�[32m 3�[2mms�[22m�[39m
523:  �[32m✓�[39m uses shared notification refs for AI handoff scheduling�[32m 0�[2mms�[22m�[39m
524:  �[32m✓�[39m uses shared embedding refs for article scheduling�[32m 0�[2mms�[22m�[39m
525:  �[32m✓�[39m uses shared embedding refs for internal article scheduling�[32m 0�[2mms�[22m�[39m
526:  �[32m✓�[39m uses fixed typed refs for snippet embedding scheduling�[32m 0�[2mms�[22m�[39m
527:  �[32m✓�[39m uses fixed typed refs for workspace invitation dispatch�[32m 0�[2mms�[22m�[39m
528:  �[32m✓�[39m uses fixed typed refs for visitor identity verification�[32m 0�[2mms�[22m�[39m
529:  �[32m✓�[39m uses shared fixed refs for notification routing and emitter scheduling�[32m 0�[2mms�[22m�[39m
530:  �[32m✓�[39m uses fixed typed refs in notification test helpers�[32m 0�[2mms�[22m�[39m
531:  �[32m✓�[39m uses shared fixed refs for embedding scheduling�[32m 0�[2mms�[22m�[39m
532:  �[32m✓�[39m keeps the dynamic test admin gateway scoped to test-only modules�[32m 0�[2mms�[22m�[39m
533:  �[31m❯�[39m tests/aiAgentRuntimeSafety.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m | �[22m�[31m8 failed�[39m�[2m)�[22m�[32m 46�[2mms�[22m�[39m
534:  �[32m✓�[39m returns explicit diagnostics for invalid runtime configuration�[32m 3�[2mms�[22m�[39m
535:  �[32m✓�[39m falls back to handoff and records diagnostics when configuration is invalid�[32m 5�[2mms�[22m�[39m
536:  �[32m✓�[39m hands off with a visible message when AI agent is disabled�[32m 1�[2mms�[22m�[39m
537:  �[32m✓�[39m skips AI generation when the conversation is already in handoff�[32m 2�[2mms�[22m�[39m
538:  �[32m✓�[39m skips AI generation after a human agent has responded in the thread�[32m 1�[2mms�[22m�[39m
539:  �[31m     �[31m�[31m clears prior diagnostics and continues generation when configuration is valid�[39m�[32m 16�[2mms�[22m�[39m
540:  �[31m     �[31m�[31m retries once when first generation returns empty text and succeeds on retry�[39m�[32m 3�[2mms�[22m�[39m
541:  �[31m     �[31m�[31m omits temperature for gpt-5 reasoning models�[39m�[32m 2�[2mms�[22m�[39m
542:  �[31m     �[31m�[31m stores the handoff message while retaining generated candidate context�[39m�[32m 4�[2mms�[22m�[39m
543:  �[31m     �[31m�[31m does not hand off when a resolved answer includes an optional human escalation offer�[39m�[32m 1�[2mms�[22m�[39m
544:  �[31m     �[31m�[31m persists a handoff message when generation fails�[39m�[32m 1�[2mms�[22m�[39m
545:  �[31m     �[31m�[31m falls back to a persisted bot message if handoff fails after generation error�[39m�[32m 1�[2mms�[22m�[39m
546:  �[31m     �[31m�[31m treats empty model output as a generation failure and hands off�[39m�[32m 1�[2mms�[22m�[39m
547:  �[32m✓�[39m rejects action execution when conversation workspace does not match request workspace�[32m 2�[2mms�[22m�[39m
548:  �[32m✓�[39m tests/reportingCsatEligibilitySemantics.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
549:  �[32m✓�[39m tests/visitorDirectoryAuthorizationSemantics.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
550:  �[90mstderr�[2m | tests/notificationRouting.test.ts�[2m > �[22m�[2mnotification routing�[2m > �[22m�[2mremoves invalid agent and visitor tokens after transport errors
551:  �[22m�[39m[Push] Failed to send to ExponentPushToken[agent-invalid]: DeviceNotRegistered: The device is not registered
552:  [Push] Failed to send to ExponentPushToken[visitor-invalid]: DeviceNotRegistered: The device is not registered
553:  �[32m✓�[39m tests/auditLogs.test.ts �[2m(�[22m�[2m17 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
...

566:  �[32m✓�[39m tests/helpCenterImportRewriteParity.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 23�[2mms�[22m�[39m
567:  �[32m✓�[39m tests/permissions.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
568:  �[32m✓�[39m tests/authWrappers.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
569:  �[32m✓�[39m tests/emailCampaignPolicyGuard.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
570:  �[32m✓�[39m tests/webhookSecurity.test.ts �[2m(�[22m�[2m10 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
571:  �[32m✓�[39m tests/identityVerification.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
572:  �[32m✓�[39m tests/reportingCsatMetricsSemantics.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
573:  �[32m✓�[39m tests/validatorMigrationRegression.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
574:  �[32m✓�[39m tests/notificationEmailBatching.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
575:  �[32m✓�[39m tests/aiAgentPublicSettings.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
576:  �[32m✓�[39m tests/discovery.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
577:  �[32m✓�[39m tests/aiAgentHandoffPath.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
578:  �[32m✓�[39m tests/testAdminSecretValidation.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
579:  �[32m✓�[39m tests/visitorReadableId.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 3�[2mms�[22m�[39m
580:  �[32m✓�[39m tests/aiAgentActionsPath.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 3�[2mms�[22m�[39m
581:  �[31m⎯⎯⎯⎯⎯⎯⎯�[39m�[1m�[41m Failed Tests 9 �[49m�[22m�[31m⎯⎯⎯⎯⎯⎯⎯�[39m
582:  �[41m�[1m FAIL �[22m�[49m tests/aiAgentRuntimeSafety.test.ts�[2m > �[22maiAgentActions runtime safety�[2m > �[22mclears prior diagnostics and continues generation when configuration is valid
583:  �[31m�[1mTypeError�[22m: runAction is not a function�[39m
584:  �[36m �[2m❯�[22m Function.handler [as _handler] convex/aiAgentActions.ts:�[2m577:36�[22m�[39m
585:  �[90m575| �[39m
586:  �[90m576| �[39m    �[90m// Get relevant knowledge�[39m
587:  �[90m577| �[39m    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FO…
588:  �[90m   | �[39m                                   �[31m^�[39m
589:  �[90m578| �[39m      workspaceId�[33m:�[39m args�[33m.�[39mworkspaceId�[33m,�[39m
590:  �[90m579| �[39m      query�[33m:�[39m args�[33m.�[39mquery�[33m,�[39m
591:  �[90m �[2m❯�[22m tests/aiAgentRuntimeSafety.test.ts:�[2m252:20�[22m�[39m
592:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/9]⎯�[22m�[39m
593:  �[41m�[1m FAIL �[22m�[49m tests/aiAgentRuntimeSafety.test.ts�[2m > �[22maiAgentActions runtime safety�[2m > �[22mretries once when first generation returns empty text and succeeds on retry
594:  �[31m�[1mTypeError�[22m: runAction is not a function�[39m
595:  �[36m �[2m❯�[22m Function.handler [as _handler] convex/aiAgentActions.ts:�[2m577:36�[22m�[39m
596:  �[90m575| �[39m
597:  �[90m576| �[39m    �[90m// Get relevant knowledge�[39m
598:  �[90m577| �[39m    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FO…
599:  �[90m   | �[39m                                   �[31m^�[39m
600:  �[90m578| �[39m      workspaceId�[33m:�[39m args�[33m.�[39mworkspaceId�[33m,�[39m
601:  �[90m579| �[39m      query�[33m:�[39m args�[33m.�[39mquery�[33m,�[39m
602:  �[90m �[2m❯�[22m tests/aiAgentRuntimeSafety.test.ts:�[2m334:20�[22m�[39m
603:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/9]⎯�[22m�[39m
604:  �[41m�[1m FAIL �[22m�[49m tests/aiAgentRuntimeSafety.test.ts�[2m > �[22maiAgentActions runtime safety�[2m > �[22momits temperature for gpt-5 reasoning models
605:  �[31m�[1mTypeError�[22m: runAction is not a function�[39m
606:  �[36m �[2m❯�[22m Function.handler [as _handler] convex/aiAgentActions.ts:�[2m577:36�[22m�[39m
607:  �[90m575| �[39m
608:  �[90m576| �[39m    �[90m// Get relevant knowledge�[39m
609:  �[90m577| �[39m    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FO…
610:  �[90m   | �[39m                                   �[31m^�[39m
611:  �[90m578| �[39m      workspaceId�[33m:�[39m args�[33m.�[39mworkspaceId�[33m,�[39m
612:  �[90m579| �[39m      query�[33m:�[39m args�[33m.�[39mquery�[33m,�[39m
613:  �[90m �[2m❯�[22m tests/aiAgentRuntimeSafety.test.ts:�[2m410:5�[22m�[39m
614:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/9]⎯�[22m�[39m
615:  �[41m�[1m FAIL �[22m�[49m tests/aiAgentRuntimeSafety.test.ts�[2m > �[22maiAgentActions runtime safety�[2m > �[22mstores the handoff message while retaining generated candidate context
616:  �[31m�[1mTypeError�[22m: runAction is not a function�[39m
617:  �[36m �[2m❯�[22m Function.handler [as _handler] convex/aiAgentActions.ts:�[2m577:36�[22m�[39m
618:  �[90m575| �[39m
619:  �[90m576| �[39m    �[90m// Get relevant knowledge�[39m
620:  �[90m577| �[39m    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FO…
621:  �[90m   | �[39m                                   �[31m^�[39m
622:  �[90m578| �[39m      workspaceId�[33m:�[39m args�[33m.�[39mworkspaceId�[33m,�[39m
623:  �[90m579| �[39m      query�[33m:�[39m args�[33m.�[39mquery�[33m,�[39m
624:  �[90m �[2m❯�[22m tests/aiAgentRuntimeSafety.test.ts:�[2m482:20�[22m�[39m
625:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/9]⎯�[22m�[39m
626:  �[41m�[1m FAIL �[22m�[49m tests/aiAgentRuntimeSafety.test.ts�[2m > �[22maiAgentActions runtime safety�[2m > �[22mdoes not hand off when a resolved answer includes an optional human escalation offer
627:  �[31m�[1mTypeError�[22m: runAction is not a function�[39m
628:  �[36m �[2m❯�[22m Function.handler [as _handler] convex/aiAgentActions.ts:�[2m577:36�[22m�[39m
629:  �[90m575| �[39m
630:  �[90m576| �[39m    �[90m// Get relevant knowledge�[39m
631:  �[90m577| �[39m    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FO…
632:  �[90m   | �[39m                                   �[31m^�[39m
633:  �[90m578| �[39m      workspaceId�[33m:�[39m args�[33m.�[39mworkspaceId�[33m,�[39m
634:  �[90m579| �[39m      query�[33m:�[39m args�[33m.�[39mquery�[33m,�[39m
635:  �[90m �[2m❯�[22m tests/aiAgentRuntimeSafety.test.ts:�[2m562:20�[22m�[39m
636:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/9]⎯�[22m�[39m
637:  �[41m�[1m FAIL �[22m�[49m tests/aiAgentRuntimeSafety.test.ts�[2m > �[22maiAgentActions runtime safety�[2m > �[22mpersists a handoff message when generation fails
638:  �[31m�[1mTypeError�[22m: runAction is not a function�[39m
639:  �[36m �[2m❯�[22m Function.handler [as _handler] convex/aiAgentActions.ts:�[2m577:36�[22m�[39m
640:  �[90m575| �[39m
641:  �[90m576| �[39m    �[90m// Get relevant knowledge�[39m
642:  �[90m577| �[39m    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FO…
643:  �[90m   | �[39m                                   �[31m^�[39m
644:  �[90m578| �[39m      workspaceId�[33m:�[39m args�[33m.�[39mworkspaceId�[33m,�[39m
645:  �[90m579| �[39m      query�[33m:�[39m args�[33m.�[39mquery�[33m,�[39m
646:  �[90m �[2m❯�[22m tests/aiAgentRuntimeSafety.test.ts:�[2m636:20�[22m�[39m
647:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/9]⎯�[22m�[39m
648:  �[41m�[1m FAIL �[22m�[49m tests/aiAgentRuntimeSafety.test.ts�[2m > �[22maiAgentActions runtime safety�[2m > �[22mfalls back to a persisted bot message if handoff fails after generation error
649:  �[31m�[1mTypeError�[22m: runAction is not a function�[39m
650:  �[36m �[2m❯�[22m Function.handler [as _handler] convex/aiAgentActions.ts:�[2m577:36�[22m�[39m
651:  �[90m575| �[39m
652:  �[90m576| �[39m    �[90m// Get relevant knowledge�[39m
653:  �[90m577| �[39m    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FO…
654:  �[90m   | �[39m                                   �[31m^�[39m
655:  �[90m578| �[39m      workspaceId�[33m:�[39m args�[33m.�[39mworkspaceId�[33m,�[39m
656:  �[90m579| �[39m      query�[33m:�[39m args�[33m.�[39mquery�[33m,�[39m
657:  �[90m �[2m❯�[22m tests/aiAgentRuntimeSafety.test.ts:�[2m711:20�[22m�[39m
658:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/9]⎯�[22m�[39m
659:  �[41m�[1m FAIL �[22m�[49m tests/aiAgentRuntimeSafety.test.ts�[2m > �[22maiAgentActions runtime safety�[2m > �[22mtreats empty model output as a generation failure and hands off
660:  �[31m�[1mTypeError�[22m: runAction is not a function�[39m
661:  �[36m �[2m❯�[22m Function.handler [as _handler] convex/aiAgentActions.ts:�[2m577:36�[22m�[39m
662:  �[90m575| �[39m
663:  �[90m576| �[39m    �[90m// Get relevant knowledge�[39m
664:  �[90m577| �[39m    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FO…
665:  �[90m   | �[39m                                   �[31m^�[39m
666:  �[90m578| �[39m      workspaceId�[33m:�[39m args�[33m.�[39mworkspaceId�[33m,�[39m
667:  �[90m579| �[39m      query�[33m:�[39m args�[33m.�[39mquery�[33m,�[39m
668:  �[90m �[2m❯�[22m tests/aiAgentRuntimeSafety.test.ts:�[2m810:20�[22m�[39m
669:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/9]⎯�[22m�[39m
670:  �[41m�[1m FAIL �[22m�[49m tests/runtimeTypeHardeningGuard.test.ts�[2m > �[22mruntime type hardening guards�[2m > �[22muses fixed typed refs for embedding self-dispatch and permission checks
671:  �[31m�[1mAssertionError�[22m: expected 'import { v } from "convex/values";\ni…' to contain 'GET_BY_CONTENT_REF'�[39m
672:  �[32m- Expected�[39m
...

966:  �[31m+       const embedding = embeddings[index];�[39m
967:  �[31m+       const chunk = chunks[index];�[39m
968:  �[31m+       const id = await runMutation(INSERT_EMBEDDING_REF, {�[39m
969:  �[31m+         workspaceId: args.workspaceId,�[39m
970:  �[31m+         contentType: args.contentType,�[39m
971:  �[31m+         contentId: args.contentId,�[39m
972:  �[31m+         embedding,�[39m
973:  �[31m+         textHash,�[39m
974:  �[31m+         title: args.title,�[39m
975:  �[31m+         snippet: createSnippet(chunk),�[39m
976:  �[31m+       });�[39m
977:  �[31m+       insertedIds.push(id);�[39m
978:  �[31m+     }�[39m
979:  �[31m+�[39m
980:  �[31m+     if (insertedIds.length === 0) {�[39m
981:  �[31m+       throw new Error("Failed to generate embeddings: no chunks were inserted");�[39m
982:  �[31m+     }�[39m
...

1327:  �[31m+     return (await ctx.db�[39m
1328:  �[31m+       .query("snippets")�[39m
1329:  �[31m+       .withIndex("by_workspace", (q) => q.eq("workspaceId", args.workspaceId))�[39m
1330:  �[31m+       .collect()) as Doc<"snippets">[];�[39m
1331:  �[31m+   },�[39m
1332:  �[31m+ });�[39m
1333:  �[31m+�[39m
1334:  �[36m �[2m❯�[22m tests/runtimeTypeHardeningGuard.test.ts:�[2m187:30�[22m�[39m
1335:  �[90m185| �[39m
1336:  �[90m186| �[39m    expect(embeddingsSource).not.toContain("function getInternalRef(na…
1337:  �[90m187| �[39m    �[34mexpect�[39m(embeddingsSource)�[33m.�[39m�[34mtoContain�[39m(�[32m"GET_BY_CONTENT_REF"�[39m)�[33m;�[39m
1338:  �[90m   | �[39m                             �[31m^�[39m
1339:  �[90m188| �[39m    �[34mexpect�[39m(embeddingsSource)�[33m.�[39m�[34mtoContain�[39m(�[32m"UPDATE_EMBEDDING_REF"�[39m)�[33m;�[39m
1340:  �[90m189| �[39m    �[34mexpect�[39m(embeddingsSource)�[33m.�[39m�[34mtoContain�[39m(�[32m"INSERT_EMBEDDING_REF"�[39m)�[33m;�[39m
1341:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/9]⎯�[22m�[39m
1342:  �[2m Test Files �[22m �[1m�[31m2 failed�[39m�[22m�[2m | �[22m�[1m�[32m30 passed�[39m�[22m�[90m (32)�[39m
1343:  �[2m      Tests �[22m �[1m�[31m9 failed�[39m�[22m�[2m | �[22m�[1m�[32m229 passed�[39m�[22m�[90m (238)�[39m
1344:  �[2m   Start at �[22m 00:20:33
1345:  �[2m   Duration �[22m 4.16s�[2m (transform 1.89s, setup 2.28s, import 3.26s, tests 1.01s, environment 6ms)�[22m
1346:  ##[error]TypeError: runAction is not a function
1347:   ❯ Function.handler [as _handler] convex/aiAgentActions.ts:577:36
1348:   ❯ tests/aiAgentRuntimeSafety.test.ts:252:20
1349:  
1350:  
1351:  ##[error]TypeError: runAction is not a function
1352:   ❯ Function.handler [as _handler] convex/aiAgentActions.ts:577:36
1353:   ❯ tests/aiAgentRuntimeSafety.test.ts:334:20
1354:  
1355:  
1356:  ##[error]TypeError: runAction is not a function
1357:   ❯ Function.handler [as _handler] convex/aiAgentActions.ts:577:36
1358:   ❯ tests/aiAgentRuntimeSafety.test.ts:410:5
1359:  
1360:  
1361:  ##[error]TypeError: runAction is not a function
1362:   ❯ Function.handler [as _handler] convex/aiAgentActions.ts:577:36
1363:   ❯ tests/aiAgentRuntimeSafety.test.ts:482:20
1364:  
1365:  
1366:  ##[error]TypeError: runAction is not a function
1367:   ❯ Function.handler [as _handler] convex/aiAgentActions.ts:577:36
1368:   ❯ tests/aiAgentRuntimeSafety.test.ts:562:20
1369:  
1370:  
1371:  ##[error]TypeError: runAction is not a function
1372:   ❯ Function.handler [as _handler] convex/aiAgentActions.ts:577:36
1373:   ❯ tests/aiAgentRuntimeSafety.test.ts:636:20
1374:  
1375:  
1376:  ##[error]TypeError: runAction is not a function
1377:   ❯ Function.handler [as _handler] convex/aiAgentActions.ts:577:36
1378:   ❯ tests/aiAgentRuntimeSafety.test.ts:711:20
1379:  
1380:  
1381:  ##[error]TypeError: runAction is not a function
1382:   ❯ Function.handler [as _handler] convex/aiAgentActions.ts:577:36
1383:   ❯ tests/aiAgentRuntimeSafety.test.ts:810:20
1384:  
1385:  
1386:  ##[error]AssertionError: expected 'import { v } from "convex/values";\ni…' to contain 'GET_BY_CONTENT_REF'
1387:  
...

1524:  +   InsertEmbeddingArgs,
1525:  +   Id<"contentEmbeddings">
1526:  + >;
1527:  +
1528:  + const GENERATE_INTERNAL_REF = makeFunctionReference<
1529:  +   "action",
1530:  +   GenerateEmbeddingArgs,
1531:  +   GenerateEmbeddingResult
1532:  + >("embeddings:generateInternal") as unknown as InternalActionRef<
1533:  +   GenerateEmbeddingArgs,
1534:  +   GenerateEmbeddingResult
1535:  + >;
1536:  +
1537:  + const REQUIRE_PERMISSION_FOR_ACTION_REF = makeFunctionRefe
1538:  /home/runner/work/opencom/opencom/packages/convex:
1539:  ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  @opencom/convex@0.1.0 test: `vitest run`
1540:  Exit status 1
1541:  ELIFECYCLE  Command failed with exit code 1.
1542:  ##[error]Process completed with exit code 1.
1543:  ##[group]Run pnpm --filter @opencom/web build
...

1628:  + First Load JS shared by all             102 kB
1629:  ├ chunks/186ceb5a-cd350ebda3c37bce.js  54.2 kB
1630:  ├ chunks/5470-86efb4adbf0b2de3.js      45.8 kB
1631:  └ other shared chunks (total)          1.93 kB
1632:  ○  (Static)   prerendered as static content
1633:  ƒ  (Dynamic)  server-rendered on demand
1634:  ##[group]Run node scripts/ci-audit-gate.js
1635:  �[36;1mnode scripts/ci-audit-gate.js�[0m
1636:  shell: /usr/bin/bash -e {0}
1637:  env:
1638:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
1639:  ##[endgroup]
1640:  [audit-gate] All 2 high/critical advisories are allowlisted.
1641:  - [critical] fast-xml-parser (id=1113567) allowlisted until 2026-05-31 (owner: mobile-sdk-team)
1642:  - [high] fast-xml-parser (id=1113570) allowlisted until 2026-05-31 (owner: mobile-sdk-team)
1643:  ##[group]Run failures=0
1644:  �[36;1mfailures=0�[0m
1645:  �[36;1m�[0m
1646:  �[36;1mreport_blocking() {�[0m
1647:  �[36;1m  name="$1"�[0m
1648:  �[36;1m  outcome="$2"�[0m
1649:  �[36;1m  if [ "$outcome" = "success" ]; then�[0m
1650:  �[36;1m    echo "::notice::$name passed"�[0m
1651:  �[36;1m  elif [ "$outcome" = "skipped" ]; then�[0m
1652:  �[36;1m    echo "::warning::$name skipped"�[0m
1653:  �[36;1m  else�[0m
1654:  �[36;1m    echo "::error::$name failed"�[0m
1655:  �[36;1m    failures=1�[0m
1656:  �[36;1m  fi�[0m
1657:  �[36;1m}�[0m
1658:  �[36;1m�[0m
1659:  �[36;1mreport_warning() {�[0m
1660:  �[36;1m  name="$1"�[0m
1661:  �[36;1m  outcome="$2"�[0m
1662:  �[36;1m  if [ "$outcome" = "success" ]; then�[0m
1663:  �[36;1m    echo "::notice::$name passed"�[0m
1664:  �[36;1m  elif [ "$outcome" = "skipped" ]; then�[0m
1665:  �[36;1m    echo "::warning::$name skipped"�[0m
1666:  �[36;1m  else�[0m
1667:  �[36;1m    echo "::warning::$name failed (warning only)"�[0m
1668:  �[36;1m  fi�[0m
...

1679:  �[36;1mreport_blocking "Dependency audit gate" "success"�[0m
1680:  �[36;1m�[0m
1681:  �[36;1mif [ "$failures" -ne 0 ]; then�[0m
1682:  �[36;1m  exit 1�[0m
1683:  �[36;1mfi�[0m
1684:  shell: /usr/bin/bash -e {0}
1685:  env:
1686:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
1687:  ##[endgroup]
1688:  ##[notice]Lint passed
1689:  ##[notice]Typecheck passed
1690:  ##[notice]Convex raw auth guard passed
1691:  ##[notice]Convex validator any guard passed
1692:  ##[notice]Secret scan gate passed
1693:  ##[notice]Security headers policy check passed
1694:  ##[error]Convex backend tests failed
1695:  ##[notice]Web production build passed
1696:  ##[notice]Dependency audit gate passed
1697:  ##[error]Process completed with exit code 1.
1698:  Post job cleanup.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors the AI agent’s knowledge retrieval from legacy in-memory string matching to Convex Vector Search over the contentEmbeddings table, aligning the runtime retrieval path with the existing embeddings infrastructure for improved scalability.

Changes:

  • Introduces a new internal action (getRelevantKnowledgeForRuntimeAction) that embeds the query and performs vector similarity search to retrieve knowledge context.
  • Updates embedding generation to chunk content and store multiple embeddings per content item (supporting higher-quality RAG + deduping).
  • Updates suggestions/widget vector-search paths to over-fetch, post-filter by content type, and dedupe results.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/convex/convex/aiAgentActionsKnowledge.ts Adds internal action implementing vector-based runtime knowledge retrieval.
packages/convex/convex/aiAgentActions.ts Switches runtime knowledge retrieval call site from query → internal action (runAction).
packages/convex/convex/aiAgent.ts Removes legacy string-matching retrieval and deprecates old endpoints by throwing.
packages/convex/convex/embeddings.ts Adds chunking and stores multiple embeddings per content; refactors batch generation to call per-item internal action.
packages/convex/convex/suggestions.ts Adjusts vector search over-fetching, content-type filtering, and deduping for suggestions and widget search.
packages/convex/convex/_generated/api.d.ts Wires new Convex module typing for the added actions module.
openspec/** Documents the new vector-search-based knowledge retrieval design and migration tasks.
apps/widget/src/components/conversationView/Footer.tsx Hides suggestion snippet rendering in the widget footer.
ROADMAP.md Adds roadmap item for widget deep-linking via URL parameters.
Comments suppressed due to low confidence (1)

packages/convex/convex/aiAgentActions.ts:581

  • generateResponse now calls an internal action via runAction, so unit tests/mocks invoking generateResponse._handler will need to provide a runAction stub and expected knowledge results. Without updating the tests, this change will throw at runtime when ctx.runAction is undefined.
    const knowledgeResults = await runAction(GET_RELEVANT_KNOWLEDGE_FOR_RUNTIME_ACTION_REF, {
      workspaceId: args.workspaceId,
      query: args.query,
      knowledgeSources: settings.knowledgeSources,
      limit: 5,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Unused ref warning in packages/convex/convex/embeddings.ts
Invalid vector filter chaining in packages/convex/convex/suggestions.ts
@djanogly djanogly merged commit e829a9d into dev Mar 13, 2026
2 of 4 checks passed
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.

2 participants