Release: pricing overhaul, insights pipeline improvements#463
Release: pricing overhaul, insights pipeline improvements#463izadoesdev wants to merge 4 commits into
Conversation
…and model routing * refactor(ai): extract shared OAuth token helpers, clean up GSC integration - Extract getOAuthToken + createCachedTokenFn into shared oauth-token.ts, replacing identical copy-pasted token-fetch logic in search-console.ts, github-tools.ts, and generation.ts - Extract getUserProviderToken in RPC integrations router, deduplicating the token lookup in checkSearchConsoleAccess and listGitHubRepos - Extract useOAuthConnect hook in dashboard integrations UI, deduplicating the linkSocial mutation between GSC and GitHub rows - Extract querySearchAnalytics as a standalone testable function - Move ALWAYS_ON_TOOLS set to module scope in generation.ts - Add 8 unit tests for search console query mapping, error handling, and request shape * feat(slack): show thinking indicator immediately on stream start Start the Slack stream eagerly with a task_update chunk (status: in_progress) before the agent produces output, so users see the animated thinking card instead of just a reaction emoji. Resolves thinking to "complete" on first text flush and "error" on failures. Also desloppified respond.ts: removed dead lazy-start logic (tryStartStream, shouldStream, streamStartAttempted), extracted flushAndStop/recoverFromError/logStreamError helpers, added early bail-out for pre-aborted signals, and added a streaming-unavailable fallback test. * chore(deps): align ai sdk versions * feat(insights): broaden investigation signals * feat(dashboard): show insight investigation context * chore(repo): tidy cleanup leftovers * feat(db): per-table column validation in SQL validator Replace BAD_EVENTS_COLUMN_REPLACEMENTS blocklist with proper AGENT_TABLE_COLUMNS schema map. Validates alias.column patterns against the actual table schema — catches cross-table misuse like es.browser_name on error_spans before it hits ClickHouse. * refactor(insights): extract prompts, deduplicate detection, improve readability - Extract prompt builders and data fetchers into prompts.ts (266 lines) - generation.ts: 1076 → 804 lines - detection.ts: replace if/else filter chain with METRIC_FILTERS lookup - detection.ts: deduplicate 5 signals.push() blocks with makeWowSignal() - detection.ts: 584 → 510 lines - validate.ts: replace inline array with SENTIMENT_DIVERGENCE_TYPES Set - Share OrgWebsiteRow interface between generation and prompts - Extract isEnabled() for tool filter readability * feat(ai): shared investigation tools, actions pipeline, chat agent upgrade - createInvestigationTools() shared across insights pipeline and chat agent - Chat agent now has scrape, GSC, and GitHub tools (was scrape only) - AgentContext gains organizationId for OAuth token lookups - Add actions schema to insights (fix_goal, add_custom_event, etc.) - Add actions column to analytics_insights table - Chat agent prompt updated with investigation tool guidance * feat(dashboard): render insight actions as clickable pills Add InsightAction type and render action buttons on insight cards. Actions include fix_goal, add_custom_event, create_funnel, etc. Currently shows a toast on click — wire to actual mutations next. * refactor(ai): unified toolkit, lint fixes, insights gets mutation tools - createToolkit() assembles tools from capabilities (analytics, investigation, mutations, memory, dashboard) - Chat agent simplified from 12 imports to one createToolkit() call - Insights pipeline gains mutation tools (create_annotation, fix_goal, create_funnel) for executing actions during investigation - Fix all lint errors (block statements, formatting) * feat(insights): session investigation tools, mutation prompt, richer actions - Add session query types to web_metrics description (interesting_sessions, session_list, session_flow, session_pages) - Investigation prompt now guides agent to use session-level queries for user behavior analysis (step 6 in strategy) - Agent told it can execute mutations directly (create_annotation with confirmed=true, update_goal for target mismatches) - Verified: agent generates specific add_custom_event actions with exact event names, elements, and pages * feat(insights): code_fix actions for cursor/claude-code integration - Add code_fix action type with prompt, file_hint, error_message params - Dashboard copies code_fix prompt to clipboard on click - investigate_further also copies prompt to clipboard - Agent generates cursor-ready prompts with exact files, changes, and error context from GitHub search results * refactor(insights): extract queryPeriodPair helper in enrichment Deduplicate period-pair query boilerplate in enrichSegments and enrichErrors. Both functions called queryFn 4 times with nearly identical params differing only in date range. The helper creates a curried function that handles the current/previous split. * fix(insights): address PR review feedback - OAuth token: scope preferUserId lookup to org membership (P1) - OAuth token: add 45-min TTL to cached tokens (P2) - Error detection: catch 0→N error spikes (P2) - Prompts: use ?? instead of || for maxInsights (P2) - Prompts: order dismissed patterns by recency (P2) - Validate: fix truncation overflow (P2) - Dashboard: handle clipboard write failures (P2) * fix(insights): address remaining review comments - Vitals enrichment: check previous-period sample size too (P2) - OAuth cache: negative-result caching with 5-min TTL to avoid repeated DB lookups when no account connected (P2)
…460) Previous insights are now included with description, rootCause, severity, change percent, and recurrence count. The agent can compare current findings against previous runs — noting what resolved, worsened, or persists. Deduplicates by subjectKey so recurring issues show "(reported 3x)" instead of appearing as separate entries.
…461) - Empty subjectKey no longer collapses unrelated insights (P1) - Fetch 50 rows then dedup to 12 unique subjects (was LIMIT 12 before dedup) - Remove unused runId from select
* feat(insights): inject site capabilities into investigation prompt Query custom events, errors, vitals, funnels, and goals upfront and include them in the prompt so the agent knows what data sources exist before making tool calls. Prevents wasted queries on unconfigured features (revenue, custom events on sites without them). * chore(docs): remove unused Wordmark component from footer The decorative SVG wordmark at the bottom of the footer was removed. Drops the component file and its import. * feat(docs): overhaul pricing table to reflect actual product features The comparison table was underselling the product. This adds all shipping features that were missing, organizes the table into sections, and links feature names to their dedicated pages. Changes: - Unhide Error Tracking from pricing (it ships on Hobby+) - Add "Analytics features" section with per-plan limits and unlimited features shown as checkmarks - Add "Platform" section: Uptime Monitoring, Short Links, Revenue Tracking, Alerts, Team Members, Websites, API Access, Slack, SDKs - Add "Enterprise" section header for SSO, Audit Logs, Support - Link feature names to /feature-flags, /web-vitals, /errors, /uptime, /links where dedicated pages exist - Update page subtitle and footer notes to reflect full product surface
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 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 |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Greptile SummaryThis PR overhauls the insights pipeline — introducing WoW signal detection for errors, revenue, and Web Vitals, restructuring the investigation agent with dedicated tools (Search Console, GitHub, scrape), adding machine-readable
Confidence Score: 4/5Safe to merge with one fix: the actions field must be added to the getInsightsFromDb return mapping and the history endpoint select before the feature is usable. The insights pipeline refactor and new tooling are well-structured. The one concrete defect is that the new machine-readable actions field is stored correctly in the DB and fetched in the query, but silently dropped before the API response is built, so it never reaches clients. Everything else — signal detection, enrichment, model routing, OAuth caching, SQL column validation — looks correct and is covered by tests. packages/rpc/src/routers/insights.ts — the getInsightsFromDb return mapping and the history endpoint select/mapping both need the actions field added to match what is stored and queried. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Scheduled insights run] --> B[detectSignals z-score + WoW]
B --> C[enrichSignals segments, errors, vitals, annotations]
C --> D[buildInvestigationPrompt]
D --> E{modelForTier}
E -->|fast| F[gpt-5.4-mini]
E -->|balanced + critical| G[claude-opus-4.7]
E -->|balanced| H[claude-sonnet-4.6]
F & G & H --> I[ToolLoopAgent emit_insight]
I --> J[validateInsight truncate or drop]
J --> K[persistWebsiteInsights dedupe + upsert]
K --> L[(analyticsInsights DB actions JSONB)]
L -->|getInsightsFromDb| M[API response actions dropped]
L -->|history endpoint| N[History response actions never selected]
|
| } | ||
| } | ||
|
|
||
| const aliasToTable = new Map<string, string>(); | ||
| for (const ref of refs) { | ||
| if (!cteNames.has(ref.name) && ref.name in AGENT_TABLE_COLUMNS) { | ||
| aliasToTable.set(ref.alias, ref.name); | ||
| } | ||
| } | ||
|
|
||
| const QUALIFIED_COLUMN = | ||
| /\b([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g; | ||
| QUALIFIED_COLUMN.lastIndex = 0; | ||
| let qm = QUALIFIED_COLUMN.exec(sanitized); | ||
| while (qm) { | ||
| const alias = qm[1].toLowerCase(); | ||
| const col = qm[2].toLowerCase(); | ||
| const table = aliasToTable.get(alias); | ||
| if (table) { | ||
| const validCols = AGENT_TABLE_COLUMNS[table]; | ||
| if (validCols && !validCols.has(col)) { | ||
| return { | ||
| valid: false, | ||
| reason: `Column "${qm[2]}" does not exist on ${table}. Valid columns: ${[...validCols].join(", ")}.`, | ||
| }; | ||
| } | ||
| } | ||
| qm = QUALIFIED_COLUMN.exec(sanitized); | ||
| } | ||
|
|
||
| const selectCount = sanitized.match(SELECT_KEYWORD_PATTERN)?.length ?? 0; | ||
| if (selectCount > 1 + cteNames.size) { | ||
| return { |
There was a problem hiding this comment.
CTE-name collision with implied table alias causes false positives
When a CTE has the same name as a real table's implied alias, the column validator incorrectly treats the CTE's columns as belonging to the real table. For example, a query like WITH events AS (SELECT client_id, COUNT(*) as cnt FROM analytics.events ...) SELECT events.cnt FROM events would be rejected because cnt is not in AGENT_TABLE_COLUMNS["analytics.events"]. The check !cteNames.has(ref.name) guards against ref.name ("analytics.events") being a CTE, but the implied alias "events" is still inserted into aliasToTable, shadowing the CTE. Valid AI-generated queries using computed CTE columns will be falsely rejected if they choose CTE names that match a table's leaf name.
There was a problem hiding this comment.
9 issues found across 49 files
Confidence score: 2/5
- Merge risk is high because
apps/insights/src/prompts.tscombines untrusted annotation/history text with prompt context that can authorize mutation tools, creating a credible prompt-injection path to unintended writes. - There are multiple concrete correctness regressions in core flows: token selection can return null despite valid tokens in
packages/ai/src/ai/tools/utils/oauth-token.ts, and schema constraints inpackages/ai/src/ai/schemas/smart-insights-output.tscan reject valid structured action payloads. packages/db/src/clickhouse/sql-validation.tshas several validation gaps/collision risks (legacy unqualified columns and alias/CTE handling) that can allow invalid references or wrongly reject valid queries, increasing runtime/query reliability risk.- Pay close attention to
apps/insights/src/prompts.ts,packages/ai/src/ai/tools/utils/oauth-token.ts,packages/ai/src/ai/schemas/smart-insights-output.ts, andpackages/db/src/clickhouse/sql-validation.ts- they contain the highest-impact security and regression concerns.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/ai/src/ai/tools/utils/oauth-token.ts">
<violation number="1" location="packages/ai/src/ai/tools/utils/oauth-token.ts:31">
P1: Fallback query should filter out NULL access tokens. Without this, a higher-priority member with a null token shadows valid tokens from lower-priority members, causing the function to return null when a valid token exists.</violation>
</file>
<file name="apps/insights/src/prompts.ts">
<violation number="1" location="apps/insights/src/prompts.ts:345">
P1: Untrusted annotation/history text is fed into the same prompt that authorizes confirmed mutation tools, creating a prompt-injection path for unintended writes.</violation>
</file>
<file name="apps/slack/src/slack/respond.ts">
<violation number="1" location="apps/slack/src/slack/respond.ts:360">
P2: `flushAndStop` only sends the first 3500 characters of `pending`, silently dropping the rest. The previous `flush(true)` looped (`do...while (force && pending)`) to drain all buffered content. Add a similar loop here to avoid truncating the user's partial response on error/abort.</violation>
</file>
<file name="apps/insights/src/enrichment.ts">
<violation number="1" location="apps/insights/src/enrichment.ts:380">
P2: `enrichVitals` treats `0` p75 values as missing data, which can suppress valid vitals changes from being reported.</violation>
</file>
<file name="packages/ai/src/ai/schemas/smart-insights-output.ts">
<violation number="1" location="packages/ai/src/ai/schemas/smart-insights-output.ts:148">
P1: `actions.params` is typed as string-only values, which conflicts with action payloads that require structured data (e.g. funnel `steps` arrays) and can cause valid emitted insights to fail schema validation.</violation>
</file>
<file name="packages/ai/src/ai/agents/analytics.ts">
<violation number="1" location="packages/ai/src/ai/agents/analytics.ts:69">
P2: Investigation tools are now gated on `organizationId`, which can silently remove `scrape_page` in org-less agent contexts compared to previous behavior.</violation>
</file>
<file name="packages/db/src/clickhouse/sql-validation.ts">
<violation number="1" location="packages/db/src/clickhouse/sql-validation.ts:21">
P2: Add a sync-breadcrumb comment on `AGENT_TABLE_COLUMNS` noting it must list the same tables as `AGENT_TENANT_COLUMN_BY_TABLE`. Without it, a future addition to one map but not the other will silently skip column validation for the new table.
(Based on your team's feedback about preserving cross-reference comments.) [FEEDBACK_USED]</violation>
<violation number="2" location="packages/db/src/clickhouse/sql-validation.ts:453">
P2: Also guard against CTE-name collisions on the alias key when populating `aliasToTable`; otherwise valid CTE-qualified columns can be validated against the wrong base table and rejected.</violation>
<violation number="3" location="packages/db/src/clickhouse/sql-validation.ts:458">
P1: Restore a check for bare legacy `analytics.events` column names; the new qualified-only validation misses unqualified invalid columns and lets old schema names through.</violation>
</file>
Architecture diagram
sequenceDiagram
participant U as User
participant D as Dashboard (Next.js)
participant I as Insights Service
participant DB as Database (Postgres)
participant CH as ClickHouse
participant AI as AI Model (Claude/GPT)
participant GH as GitHub API
participant SC as Google Search Console
participant Slack as Slack Client
Note over U,Slack: NEW: Investigation Pipeline with Tools & Actions
D->>I: POST /v1/agent (userId, organizationId, websiteId)
I->>I: createAgentConfig (includes organizationId)
I->>I: createToolkit(capabilities, domain, organizationId)
I->>I: createInvestigationTools(domain, orgId, userId)
I->>GH: githubFetch (via OAuth token)
I->>SC: querySearchAnalytics (via OAuth token)
I->>I: getCachedSiteContext(domain)
I->>I: scrapePage(domain, path) - fallback context
I->>CH: detectSignals(websiteId, timezone)
Note right of I: Queries events_by_date,<br/>summary_metrics, error_summary,<br/>revenue_overview, vitals_overview,<br/>custom_events_discovery
CH-->>I: DetectedSignal[]
I->>CH: enrichSignals(DetectedSignal[])
Note right of I: Queries segments, errors,<br/>vitals, annotations per signal
CH-->>I: EnrichedSignal[] (with vitalsContext, errorContext, segments)
I->>AI: modelForTier(tier, hasCriticalSignals)
alt hasCriticalSignals AND tier == "balanced"
I->>AI: Route to deep model (Claude Opus)
else
I->>AI: Route to configured tier model
end
I->>DB: fetchRecentAnnotations(websiteId)
I->>DB: fetchInsightHistory(orgId, websiteId)
I->>DB: fetchDismissedPatterns(orgId, websiteId)
I->>DB: fetchSiteCapabilities(websiteId) - funnels, goals, events
DB-->>I: Context for AI prompt
I->>I: buildInvestigationPrompt(...)
I->>I: buildSystemPrompt(config, { investigationMode: true })
Note left of I: NEW: Actions (fix_goal,<br/>code_fix, etc.), evidence,<br/>investigationDepth in schema
I->>AI: Generate insights (insightSchema with actions)
AI-->>I: ParsedInsight[] (with rootCause, evidence, actions)
I->>I: validateInsight (NEW: truncateAtSentence, MAX_DESCRIPTION=300)
I->>DB: storeAnalyticsSummary (stores actions JSONB)
DB-->>I: GeneratedWebsiteInsight[]
D->>I: GET /api/insights (RPC)
I->>DB: SELECT (including rootCause, evidence, investigationDepth, actions)
DB-->>I: Insight[] with new fields
I-->>D: InsightCardViewModel
Note over D: NEW: rootCause, investigationEvidence,<br/>actions[] in view model
D->>D: toInsightCardViewModel(insight)
alt action type is code_fix or investigate_further
D->>U: Show click-to-copy prompt button
U->>D: Click action pill
D->>U: navigator.clipboard.writeText(prompt)
else other action types
D->>U: Show toast.info(action.label)
end
Note over D,Slack: NEW: Slack streaming with thinking indicator
Slack->>D: app_mention with text
D->>D: startThinkingStream(...)
D->>Slack: chat.startStream with task_update "in_progress"
D->>D: Stream agent response
D->>Slack: chat.appendStream (resolve thinking, add text)
alt error occurs
D->>Slack: chat.appendStream with "error" task_update
Slack-->>D: stopStream with user-facing error message
else success
D->>Slack: chat.appendStream with "complete" task_update
D->>Slack: chat.stopStream
end
Note over U,DB: NEW: Pricing Page - Platform Section
D->>U: Show Pricing Table
Note right of U: NEW: Unhide Error Tracking<br/>NEW: Platform section with 9 features<br/>NEW: Feature labels link to pages<br/>NEW: Check icons for unlimited
Shadow auto-approve: would not auto-approve because issues were found.
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| const [fallback] = await db | ||
| .select({ accessToken: account.accessToken }) | ||
| .from(account) | ||
| .innerJoin(member, eq(member.userId, account.userId)) | ||
| .where( | ||
| and( | ||
| eq(member.organizationId, organizationId), | ||
| eq(account.providerId, providerId) | ||
| ) | ||
| ) | ||
| .orderBy(ROLE_PRIORITY) | ||
| .limit(1); | ||
|
|
There was a problem hiding this comment.
P1: Fallback query should filter out NULL access tokens. Without this, a higher-priority member with a null token shadows valid tokens from lower-priority members, causing the function to return null when a valid token exists.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/ai/src/ai/tools/utils/oauth-token.ts, line 31:
<comment>Fallback query should filter out NULL access tokens. Without this, a higher-priority member with a null token shadows valid tokens from lower-priority members, causing the function to return null when a valid token exists.</comment>
<file context>
@@ -0,0 +1,66 @@
+ }
+ }
+
+ const [fallback] = await db
+ .select({ accessToken: account.accessToken })
+ .from(account)
</file context>
| const [fallback] = await db | |
| .select({ accessToken: account.accessToken }) | |
| .from(account) | |
| .innerJoin(member, eq(member.userId, account.userId)) | |
| .where( | |
| and( | |
| eq(member.organizationId, organizationId), | |
| eq(account.providerId, providerId) | |
| ) | |
| ) | |
| .orderBy(ROLE_PRIORITY) | |
| .limit(1); | |
| const [fallback] = await db | |
| .select({ accessToken: account.accessToken }) | |
| .from(account) | |
| .innerJoin(member, eq(member.userId, account.userId)) | |
| .where( | |
| and( | |
| eq(member.organizationId, organizationId), | |
| eq(account.providerId, providerId), | |
| sql`${account.accessToken} IS NOT NULL` | |
| ) | |
| ) | |
| .orderBy(ROLE_PRIORITY) | |
| .limit(1); |
| - Confidence > 0.7 requires segment isolation or temporal correlation. | ||
| - Actions: include when fixable (fix_goal, add_custom_event, create_annotation, create_funnel, add_tracking, investigate_further, code_fix). | ||
| - code_fix: when you find a bug with a clear fix, emit a code_fix action with params {prompt, file_hint, error_message}. The prompt should be paste-ready for Cursor or Claude Code — include the exact file to change, what to change, and why. | ||
| - You have mutation tools: call create_annotation directly to mark deploys or incidents on the timeline. Call update_goal to fix goal target mismatches. Use confirmed=true to execute.${ |
There was a problem hiding this comment.
P1: Untrusted annotation/history text is fed into the same prompt that authorizes confirmed mutation tools, creating a prompt-injection path for unintended writes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/insights/src/prompts.ts, line 345:
<comment>Untrusted annotation/history text is fed into the same prompt that authorizes confirmed mutation tools, creating a prompt-injection path for unintended writes.</comment>
<file context>
@@ -0,0 +1,454 @@
+- Confidence > 0.7 requires segment isolation or temporal correlation.
+- Actions: include when fixable (fix_goal, add_custom_event, create_annotation, create_funnel, add_tracking, investigate_further, code_fix).
+- code_fix: when you find a bug with a clear fix, emit a code_fix action with params {prompt, file_hint, error_message}. The prompt should be paste-ready for Cursor or Claude Code — include the exact file to change, what to change, and why.
+- You have mutation tools: call create_annotation directly to mark deploys or incidents on the timeline. Call update_goal to fix goal target mismatches. Use confirmed=true to execute.${
+ options?.investigationMode
+ ? "\n- Investigate detected signals using tools. Call emit_insight for each finding. Drop noise."
</file context>
| ]), | ||
| label: z.string().describe("Button label (e.g. 'Fix goal target')"), | ||
| params: z | ||
| .record(z.string(), z.string()) |
There was a problem hiding this comment.
P1: actions.params is typed as string-only values, which conflicts with action payloads that require structured data (e.g. funnel steps arrays) and can cause valid emitted insights to fail schema validation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/ai/src/ai/schemas/smart-insights-output.ts, line 148:
<comment>`actions.params` is typed as string-only values, which conflicts with action payloads that require structured data (e.g. funnel `steps` arrays) and can cause valid emitted insights to fail schema validation.</comment>
<file context>
@@ -121,21 +123,41 @@ export const insightSchema = z.object({
+ ]),
+ label: z.string().describe("Button label (e.g. 'Fix goal target')"),
+ params: z
+ .record(z.string(), z.string())
+ .describe(
+ "Action-specific parameters. code_fix: {prompt, file_hint, error_message} — generates a cursor/claude-code-ready prompt."
</file context>
| } | ||
| } | ||
|
|
||
| const QUALIFIED_COLUMN = |
There was a problem hiding this comment.
P1: Restore a check for bare legacy analytics.events column names; the new qualified-only validation misses unqualified invalid columns and lets old schema names through.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/src/clickhouse/sql-validation.ts, line 458:
<comment>Restore a check for bare legacy `analytics.events` column names; the new qualified-only validation misses unqualified invalid columns and lets old schema names through.</comment>
<file context>
@@ -371,6 +448,33 @@ export function validateAgentSQL(sql: string): {
+ }
+ }
+
+ const QUALIFIED_COLUMN =
+ /\b([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
+ QUALIFIED_COLUMN.lastIndex = 0;
</file context>
| } | ||
| stopText?: string | ||
| ): Promise<void> { | ||
| if (pending.trim()) { |
There was a problem hiding this comment.
P2: flushAndStop only sends the first 3500 characters of pending, silently dropping the rest. The previous flush(true) looped (do...while (force && pending)) to drain all buffered content. Add a similar loop here to avoid truncating the user's partial response on error/abort.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/slack/src/slack/respond.ts, line 360:
<comment>`flushAndStop` only sends the first 3500 characters of `pending`, silently dropping the rest. The previous `flush(true)` looped (`do...while (force && pending)`) to drain all buffered content. Add a similar loop here to avoid truncating the user's partial response on error/abort.</comment>
<file context>
@@ -358,85 +318,175 @@ async function finishStreamedResponse(
- }
+ stopText?: string
+): Promise<void> {
+ if (pending.trim()) {
+ await client.chat
+ .appendStream({
</file context>
| const curVal = cur?.p75 ?? 0; | ||
| const prevVal = prev?.p75 ?? 0; | ||
| if ( | ||
| curVal === 0 || | ||
| prevVal === 0 || | ||
| (cur?.samples ?? 0) < 5 || | ||
| (prev?.samples ?? 0) < 5 | ||
| ) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
P2: enrichVitals treats 0 p75 values as missing data, which can suppress valid vitals changes from being reported.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/insights/src/enrichment.ts, line 380:
<comment>`enrichVitals` treats `0` p75 values as missing data, which can suppress valid vitals changes from being reported.</comment>
<file context>
@@ -373,24 +352,72 @@ async function enrichAnnotations(
+ for (const name of ["LCP", "INP", "CLS", "FCP", "TTFB"]) {
+ const cur = currentMap.get(name);
+ const prev = previousMap.get(name);
+ const curVal = cur?.p75 ?? 0;
+ const prevVal = prev?.p75 ?? 0;
+ if (
</file context>
| const curVal = cur?.p75 ?? 0; | |
| const prevVal = prev?.p75 ?? 0; | |
| if ( | |
| curVal === 0 || | |
| prevVal === 0 || | |
| (cur?.samples ?? 0) < 5 || | |
| (prev?.samples ?? 0) < 5 | |
| ) { | |
| continue; | |
| } | |
| const curVal = cur?.p75; | |
| const prevVal = prev?.p75; | |
| if ( | |
| curVal == null || | |
| prevVal == null || | |
| (cur?.samples ?? 0) < 5 || | |
| (prev?.samples ?? 0) < 5 | |
| ) { | |
| continue; | |
| } |
| "dashboard", | ||
| ], | ||
| domain: context.websiteDomain, | ||
| organizationId: context.organizationId, |
There was a problem hiding this comment.
P2: Investigation tools are now gated on organizationId, which can silently remove scrape_page in org-less agent contexts compared to previous behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/ai/src/ai/agents/analytics.ts, line 69:
<comment>Investigation tools are now gated on `organizationId`, which can silently remove `scrape_page` in org-less agent contexts compared to previous behavior.</comment>
<file context>
@@ -80,12 +57,18 @@ export function createConfig(
+ "dashboard",
+ ],
+ domain: context.websiteDomain,
+ organizationId: context.organizationId,
+ userId: context.userId,
+ }),
</file context>
| "analytics.link_visits": "client_id", | ||
| }; | ||
|
|
||
| export const AGENT_TABLE_COLUMNS: Readonly< |
There was a problem hiding this comment.
P2: Add a sync-breadcrumb comment on AGENT_TABLE_COLUMNS noting it must list the same tables as AGENT_TENANT_COLUMN_BY_TABLE. Without it, a future addition to one map but not the other will silently skip column validation for the new table.
(Based on your team's feedback about preserving cross-reference comments.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/src/clickhouse/sql-validation.ts, line 21:
<comment>Add a sync-breadcrumb comment on `AGENT_TABLE_COLUMNS` noting it must list the same tables as `AGENT_TENANT_COLUMN_BY_TABLE`. Without it, a future addition to one map but not the other will silently skip column validation for the new table.
(Based on your team's feedback about preserving cross-reference comments.) </comment>
<file context>
@@ -18,6 +18,100 @@ export const AGENT_TENANT_COLUMN_BY_TABLE: Readonly<Record<string, string>> = {
"analytics.link_visits": "client_id",
};
+export const AGENT_TABLE_COLUMNS: Readonly<
+ Record<string, ReadonlySet<string>>
+> = {
</file context>
| export const AGENT_TABLE_COLUMNS: Readonly< | |
| /** | |
| * Column allowlist for each table in AGENT_TENANT_COLUMN_BY_TABLE. | |
| * Keep this map in sync — every table listed above must also appear here. | |
| */ | |
| export const AGENT_TABLE_COLUMNS: Readonly< |
|
|
||
| const aliasToTable = new Map<string, string>(); | ||
| for (const ref of refs) { | ||
| if (!cteNames.has(ref.name) && ref.name in AGENT_TABLE_COLUMNS) { |
There was a problem hiding this comment.
P2: Also guard against CTE-name collisions on the alias key when populating aliasToTable; otherwise valid CTE-qualified columns can be validated against the wrong base table and rejected.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/src/clickhouse/sql-validation.ts, line 453:
<comment>Also guard against CTE-name collisions on the alias key when populating `aliasToTable`; otherwise valid CTE-qualified columns can be validated against the wrong base table and rejected.</comment>
<file context>
@@ -371,6 +448,33 @@ export function validateAgentSQL(sql: string): {
+ const aliasToTable = new Map<string, string>();
+ for (const ref of refs) {
+ if (!cteNames.has(ref.name) && ref.name in AGENT_TABLE_COLUMNS) {
+ aliasToTable.set(ref.alias, ref.name);
+ }
</file context>
| if (!cteNames.has(ref.name) && ref.name in AGENT_TABLE_COLUMNS) { | |
| if (!cteNames.has(ref.name) && !cteNames.has(ref.alias) && ref.name in AGENT_TABLE_COLUMNS) { |
Summary
Commits
feat(docs): overhaul pricing table and remove unused wordmark (#462)fix(insights): history dedup uses title fallback, limits after dedup (#461)feat(insights): replace flat title history with full insight context (#460)feat(insights): investigation pipeline overhaul with tools, actions, and model routingSummary by cubic
Revamps the pricing page and rebuilds the insights pipeline with investigation tools, actionable steps, and richer history for clearer, faster root-cause analysis. Also adds Slack “thinking” streaming, stronger SQL validation, and shared OAuth/tooling used across agents.
New Features
fix_goal,create_funnel,add_custom_event,create_annotation,update_config,add_tracking,investigate_further,code_fix), richer history (root cause, evidence, depth), improved detection/enrichment, session-level queries, and dedup fix.Refactors
getOAuthToken/createCachedTokenFnwith TTL and org scoping; used by GitHub and Search Console;AgentContextnow includesorganizationId.createToolkitto assemble analytics/investigation/mutation tools; addsearch-console,investigation-tools; extract prompts; bumpaito^6.0.188.evidence,investigationDepth,rootCause, andactions; dashboard renders action pills; integrations settings unify OAuth connect; remove unused Wordmark; minor Nuxt path fix.Written for commit 3f4afcf. Summary will update on new commits.