Fix/short jd lead capture#8
Conversation
…; fix calibration drift
- Adjacent agents: replace gradient icon-box cards with clean editorial rows (cream icon square, inline text, thin separators — no "AI template" look) - CTA section: forest-green TS card + purple Lua card with brand labels and specific copy; add "What's your next move?" section header - Brief card: fix bc-lbl contrast (#9AA39B → var(--ink-soft)), use var(--paper) bg for bc-box so content is distinct from wrapper - callScoreApi: widen tool-result parsing to cover .tool_results, .output, .name variants; add step[0] dump to console for diagnosing inconsistency - Fix restart() crash when #overlay element not present (null guard) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
File contained approved bash commands with API key in plain text. Added to .gitignore so it never gets committed again. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scan step.output and step.result directly (not just step.toolResults) - Shared extractScore() helper validates shape in one place - Match on any step that contains a valid scoring object, not just by tool name - Extra console logs: all tool result count + first tool result dump Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tions Invocation contract now explicit — every message is a programmatic API call. Ada must call the appropriate tool without asking for more info or responding in text. Eliminates the 'clarifying question' failure mode seen in smoke test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- netlify/functions/score-jd.js: calls Lua developer AI endpoint directly with the scoring system prompt + forced tool_choice. One LLM call instead of two (Ada routing + score_jd). Fits comfortably within 26s timeout. - netlify.toml: set functions timeout = 26 - index.html: callScoreApi() uses score-jd endpoint for text JDs; URL-pasted JDs still go through lua-chat (needs scraping path via Ada) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Embeds LUA_AGENT_ID, LUA_API_KEY, SCORING_SYSTEM, and SCORE_ROLE_TOOL directly in frontend JS. callScoreApi() calls Lua developer AI endpoint from the browser for text JDs, bypassing Netlify functions entirely to avoid the 504 timeout caused by the 2-LLM-call chain. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New fetch-jd Netlify function fetches job posting URL server-side (CORS proxy, no AI, fast). Browser then calls Lua AI directly with the extracted text — same single-LLM-call path as text JDs. Eliminates the Ada→scrape_jd→score_jd 3-call chain that caused 504s. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root causes fixed: 1. /developer/ai/generate silently drops tools/tool_choice — tool_use never appears in response, score always fails 2. Routing through Netlify proxy causes 504 (Ada skill chain ~20-40s exceeds Netlify's 26s limit) Solution: call Ada's chat/generate endpoint directly from the browser. No Netlify in the scoring path — no timeout. Ada invokes score_jd skill server-side with proper AI.generate + tool_choice. For URL JDs: fetch-jd Netlify function handles CORS (HTML fetch only, no AI, fast) then browser calls Ada with the extracted text. Removes SCORING_SYSTEM and SCORE_ROLE_TOOL from frontend — Ada owns the scoring rubric through her deployed skill. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…DPOINT LUA_CHAT_ENDPOINT was removed in previous commit but still referenced in three places (capture_lead, submit_cta, enrichment chat), causing silent JS errors. Replace all three with ADA_CHAT_URL so all Ada calls use the same direct browser→chat API path — no Netlify env vars needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
score-role.skill.ts:
- Remove tools/tool_choice from AI.generate — Lua platform drops these,
causing tool_choice-without-tools Anthropic 400 error (generation_failed)
- Switch to JSON text output: system prompt instructs Claude to return
a raw JSON object matching the scoring schema
- Add tryParseJson helper + update extractScoringResult to handle both
tool_use (future-proof) and text JSON responses
index.html:
- Replace static ADA_CHAT_URL with adaSessionUrl(prefix) function
- Each evaluation gets a unique channel=eval-{uuid} — eliminates
session contamination from prior failed calls (was 53k tokens/call)
- Enrichment chat uses state.enrichUrl (consistent across turns)
- capture_lead and submit_cta get fresh sessions (lead-/cta- prefix)
- Add Authorization header to all Ada calls (was missing on some paths)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… broken Root cause confirmed: skill's AI.generate() fails with generation_failed on every call (observed in 30 consecutive logs 09:36–11:06). Even after removing tools/tool_choice in v1.0.18, still fails. Developer endpoint tested directly — works perfectly for text + JSON output. Scoring path: Browser → score-jd.js Netlify → developer AI endpoint (JSON text output, no tools) → parse JSON → result. API key stays server-side. 15–20s per call fits within 26s timeout. Ada chat API still used for capture_lead, submit_cta, enrichment chat (those are single LLM steps, no nested AI.generate(), work fine). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New /api directory with Vercel function signatures (ESM, req/res):
* score-jd.js — JSON-via-text via /developer/ai endpoint (no tools/tool_choice
since runAiGeneration silently drops them); recomputes verdict band on
score-mismatch correction so verdict + score stay internally consistent
* lua-chat.js — Ada chat proxy, forwards ?channel= query so the per-call
session pattern (eval-/enrich-/lead-/cta-<uuid>) survives the proxy hop
* slack-notify.js — webhook proxy
* fetch-jd.js — URL scraper with SSRF guard (loopback/RFC1918/AWS metadata)
- Shared origin allowlist in api/_lib/origin.js — reads ALLOWED_ORIGINS env
plus Vercel's VERCEL_URL / VERCEL_BRANCH_URL / VERCEL_PROJECT_PRODUCTION_URL
- vercel.json: SPA rewrites for /results and /score, security headers
(X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
- index.html: remove hardcoded LUA_API_KEY/LUA_AGENT_ID, flip all endpoint URLs
to /api/*, drop client-side Authorization headers (proxy attaches Bearer),
delete dead scoreViaAda + extractScore helpers
- Delete netlify.toml + netlify/functions/*
Vercel Pro gives 60s function timeout (vs Netlify Pro 26s / Free 10s) which
removes the timeout pressure that motivated the earlier browser-direct revert.
Required env vars on Vercel (Production + Preview):
LUA_API_KEY, LUA_AGENT_ID, SLACK_LEADS_WEBHOOK_URL, ALLOWED_ORIGINS
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- score-jd.js: call Ada's chat API (/chat/generate) not developer endpoint,
unique eval-{uuid} channel per request prevents session contamination
- lua-chat.js: accept channel from request body for session isolation
- index.html: remove LUA_API_KEY + LUA_AGENT_ID from browser; all Ada calls
now go through /.netlify/functions/lua-chat (server-side credentials);
remove scoreViaAda(), adaSessionUrl(); add newChannelId() helper
- src/index.ts: embed full scoring rubric in Ada's persona so she scores
directly on 'Score this job description:' messages (no score_jd tool call);
remove scoreRoleSkill from skills array
- scrape-jd.skill.ts: add SSRF guard blocking RFC1918, loopback, link-local,
metadata endpoints; restrict to https:// only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Project's package.json has 'build: tsc' for the Lua skill workflow. Vercel auto-runs npm run build on deploy, which tried to invoke tsc (failed with permission error in their build env, but also unnecessary — the web deploy is static HTML + api/ JS, no TS compilation needed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Adopted Vercel hosting (api/ functions, vercel.json) from lua remote - api/score-jd.js: updated to call Ada's chat API (/chat/generate) instead of the developer AI endpoint — Ada scores directly using her persona rubric - Conflict resolution: Vercel /api/* endpoints, adaSessionUrl() channel pattern, removed client-side LUA_API_KEY/LUA_AGENT_ID and netlify functions - src/index.ts: Ada's persona now includes full scoring rubric; scores directly on 'Score this job description:' messages (no score_jd tool call) - scrape-jd.skill.ts: SSRF guard blocking RFC1918, loopback, link-local, metadata Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Netlify redirect /api/:name -> /.netlify/functions/:name so index.html's
/api/* calls work on both platforms simultaneously:
- netlify/functions/score-jd.js: Ada chat API, unique eval-{uuid} channel
- netlify/functions/lua-chat.js: channel from ?channel= query string
- netlify/functions/fetch-jd.js: URL scraper with SSRF guard
- netlify/functions/slack-notify.js: webhook proxy
- netlify.toml: redirect rule + static file publish + security headers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-deployed the agent to restore the inline-rubric persona + skill set after an accidental overwrite from a stale local push. Persona v20, capture-lead v1.0.18, submit-cta v1.0.16, ada-chat v1.0.13, brief-wizard v1.0.13, scrape-jd v1.0.14. score-role correctly absent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without an explicit outputDirectory, Vercel defaulted to public/ once a buildCommand was set — so index.html (at the repo root) was 404'd while api/* functions deployed fine. Point output at . so the static SPA is served. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dlers Hardens score-jd, lua-chat, fetch-jd, and slack-notify against abuse for the 60k-user launch. The existing isOriginAllowed check only stops casual browser abuse — curl with a forged Origin trivially bypasses it, and an unbounded loop against score-jd / lua-chat burns billable Sonnet tokens. Per-IP limits (stricter of two buckets wins; 429 with Retry-After + JSON body): - score-jd: 5/min, 30/hour - lua-chat: 10/min, 60/hour - fetch-jd: 5/min, 30/hour - slack-notify: 10/min, 60/hour Implementation: ZSET-based sliding window in @vercel/kv, atomic MULTI/EXEC (ZREMRANGEBYSCORE + ZADD + ZCARD + PEXPIRE + ZRANGE) per request. Defensive parsing tolerates both @upstash/redis ZRANGE WITHSCORES return shapes. Fails open (allowed:true) if KV env vars are missing or KV throws — better to serve a real user than 500 the whole site over a rate-limiter outage. Deploy prerequisite: connect a Redis store to the Vercel project via the Marketplace (Vercel KV was retired Dec 2024 and migrated to Upstash Redis; the @vercel/kv client still works, and Upstash injects KV_REST_API_URL + KV_REST_API_TOKEN automatically). Without these env vars the limiter no-ops. Note: api/*.js handlers may merge-conflict with Session C if both land. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Platform PR #533 (lua-core) added `structuredOutput: { schema }` to
AI.generate, so the score-role skill can now produce a typed object
directly instead of the JSON-via-text workaround.
- src/skills/score-role.skill.ts: rewrite to use structuredOutput. Drop
the OUTPUT INSTRUCTIONS block from the rubric (the schema enforces
shape). Self-correct verdict band on score mismatch. Local type
extension on AI.generate args (drops once new lua-cli release lands).
- src/index.ts: re-import scoreRoleSkill, restore "Score this job
description: → call score_jd" persona contract, drop the inline rubric
Scott had embedded as a workaround.
- index.html: rewrite callScoreApi to route both text and URL paths
through /api/lua-chat. New extractScoreFromAdaResponse helper walks
Ada's tool results.
- Delete api/score-jd.js — Ada owns scoring end-to-end now.
Agent push: score-role v1.0.20, persona v21.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- renderReportHtml(): email-safe inline-styled HTML for full evaluation - esc() helper for HTML escaping - Zod schemas: .passthrough() on candidate objects + scoringResult to preserve extra fields - sheets-apps-script.gs: add Verdict, Recommended CTA, JD, Analysis columns - lua.skill.yaml + backup manifest: sync to active version 42 - email-preview.html: standalone render preview Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the paste-box + separate enrichment screen with a single conversational hero: Ada greets first, the user's first message is the role (paste or describe), and thin descriptions trigger inline follow-up questions steered away from the step-2 calibration topics. - composer is full-width (matches the chat panel); "Try a sample JD" and "Upload PDF" are icon-only inside the box and expand on hover; Send sits inside at bottom-right - chat panel is a fixed-height scrolling box with the Ada avatar to its left - smaller headline, content shifted up, em dashes removed from user-facing copy - add docs/feature/merge_box_plus_ada.md (design notes) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(hero): chat-first intake with Ada leading the conversation
Add a no-op marker comment so the source differs from the identical a9d262e deployment, which Vercel was deduplicating and skipping. This forces a fresh production build of the reverted (pre-PR#4) UI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
capture_lead is invoked twice per evaluation: once right after score_jd (to record the evaluation to Data) and again when the visitor submits the lead form. The score-time call had no real contact details, yet the skill still posted Slack, appended a Google Sheets row, and sent a report email using placeholder values (e.g. rares@heylua.ai / "Lua") — producing a phantom row before every real lead, plus duplicate Slack posts and emails. Gate the outward signals on genuine lead details (name + title, which only the lead form supplies); the score-time call now records to Data and stops. Data recording and the quality-flag short-circuit are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update the LLM project guide with the chat-first hero redesign (on yash/fixes, reverted from prod), the Vercel dedup rollback episode, and the capture-lead premature-call fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records the deployed version after shipping the real-lead gate fix to production via the gated lua-deploy flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the Data.create below the real-lead gate so the score-time auto-call (placeholder rares@heylua.ai / "Lua", no name/title) no longer writes a phantom record to the evaluations Data primitive. Only genuine lead-form submissions are recorded now. Note: Data.create now also sits below the quality-flag short-circuit, so flagged evaluations (short_jd / non_english / suspected_fake) are no longer stored either. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shareable writeup of the phantom rares@heylua.ai bug: symptom, root cause, the real-lead gate (v1.0.34), and the follow-up moving Data.create below the gate (v1.0.35, pending deploy). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The score-time call no longer records to Data, so update the tool
description and skill context: Data write + Slack/Sheets/email happen only
for a genuine lead-form submission; score-time and flagged calls return
{ skipped } and do nothing else.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records the deployed version and marks the bug_fix.md follow-up as live. v1.0.35 moves Data.create below the real-lead gate so score-time auto-calls no longer write placeholder records to the evaluations primitive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add §13 covering the rares@heylua.ai bug, root cause, the two-part fix (v1.0.34 real-lead gate + v1.0.35 Data.create move), and the live state. Update the §4 capture_lead description to reflect the gating. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thin-JD enrichment chat now asks about judgment_complexity, regulatory_burden, relationship_depth, and system_integration — the four dimensions that previously got no direct user signal — as one numbered list, re-asking only the gaps (4-turn cap), then folds the answers into the JD context for score_jd. - index.html: rewrite enrichment seed (batch of 4, re-ask gaps); label folded answers by dimension in buildEnrichedJd; refresh s-enrich copy; render Ada messages with preserved line breaks (pre-wrap) and **bold**. - src/index.ts: add a scoped "intake / clarification mode" persona carve-out legitimizing the questioning; leave the score_jd / capture_lead / submit_cta hard triggers and their "never ask" rules unchanged. - docs/fixes/ada_persona.md: plan/writeup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring Ada's chat back to the first screen (reverted in d1f3d9a) but wired to the current enrichment brain instead of the old generic one that shipped with 2be603c: - s-hero is now an inline Ada chat (greeting + composer); the paste textarea / dropzone and the separate s-enrich screen are removed. - The thin-JD (<20 words) branch calls the existing startEnrichChat() so the 4 high-leverage dimension questions, **bold**/line-break rendering, and the dimension-labeled JD fold remain the single source of truth. - Detailed JDs (>=20 words) get a quick ack, then go straight to the calibration questions. - Chat panel enlarged (260px -> 440px, wider column, larger text) per the "make it bigger / cleaner" UI ask; Sample/PDF are icon-only hover-expand buttons inside the composer; sub-headline trimmed. - loadSample()/handlePdfUpload() retargeted to the composer; enrichEnterSend rewired to sendHeroMessage; dead handleScoreClick()/sendEnrichMessage() removed; renderScreen resets the chat on s-hero (Back/restart) and on boot. - Fix restart() null-ref now that #otherRoles is gone; drop the §12 redeploy marker since the redesign is shipping again. Plan: docs/fixes/UI_change.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Iterate on the re-shipped chat-first hero per design feedback: - Remove the "Free · No signup" eyebrow and trim the sub-headline; shift the whole hero block up (margin-top: -40px). - Merge the messages area and input into ONE bordered chat panel (avatar on the left). Reply area is a fixed 300px scroll region; input ~90px below it. - Turn the input into a rounded pill that floats inside the panel: round hover-expand tool icons (sample / PDF) on the left, text in the middle, and a circular up-arrow (↑) Send button on the right. - Auto-grow the composer with its content line-by-line up to a 120px cap (COMPOSER_MAX_H), then scroll; collapse back on send / Back / restart, and grow to fit PDF-extracted text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add §15 covering the feat/ui_changes re-ship: the chat-first hero merged with the §14 4-dimension intake brain, the single-panel pill composer (hover-expand icons + circular ↑ send), the auto-grow behaviour, and the flex/height gotcha. Mark §11 as superseded and point it at §15. Also folds in the pre-existing §14 intake-mode doc edit that was sitting in the working tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Shift the hero conversation box 10px→30px left (translateX on .hero-chat). - Enrichment seed (startEnrichChat): Ada now checks whether the brief description already names a job title; if not, question 1 becomes "What is the job title…?" and the four dimension questions follow as 2–5 (five total), otherwise just the four. Judged from the JD preview, no JS detection; the 4-reply cap is unaffected. - Document the conditional 5th question in §14. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…implementation - plan.md: scoped roadmap for 4-feature email + CTA landing slice - public/: track logo + social assets used by rendered report email - src/skills/capture-lead.skill.ts: full renderReportHtml inline email body - email-preview.html: align preview with renderReportHtml output
Read ?cta=lua|tech_safari on load and land directly on the matching CTA screen (Lua -> s-connect, Talent Safari -> s-brief), prefilling the lead from name/email/company query params. `cta` alone picks the screen; `rec` is intentionally unused (Option A). role/score are stashed into state.result for submit_cta's Slack-ping context. Also preserve the query string in the initial history.replaceState seed (line 905) so the deep-link isn't stripped back to '/' before the boot handler reads it. Frontend-only: no markup/CSS changes to the landing screens, no new env vars. Implements plan.md §5 F1-F5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(cta): deep-link report-email CTAs into landing screens
- capture-lead.skill.ts: switch email logos (TS, Lua, 3 social icons) to MIME CID inline attachments so Gmail renders without "display images" click. Five attachments via Resend `attachments` field. - email-assets.ts: new file holding base64 PNG payloads for the 5 logos. - submit-cta.skill.ts: F1 fix — gate execute() with no_evaluation_context check; reject calls where scoringResult is fabricated (role_title='Unknown' or score=0). Updates tool description + skill context with explicit precondition. - changes.md: Phase 3 history appended.
Auto-managed version bumps written by the lua push step ahead of the production deploy (capture-lead 1.0.36, submit-cta 1.0.32, persona/backup v47). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ersona v39) State manifest update from the production `lua deploy all` of plan B6 — production now renders the report email with CTA deep-links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real lead-form submissions with short JDs were being skipped by capture_lead — no Slack body, no Sheets row, no Data record, no report email — because the gate short-circuited on flags.short_jd. The flag is LLM-judged and misfired on borderline JDs (a 105-word JD was tagged short), so genuine leads (Benjamin/GG Mac, Nick/SDR, king/Discord CM) were lost while only the browser "evaluation starting" ping landed. Fix #1: drop short_jd from the capture gate; keep non_english / suspected_fake as the spam guard. Short but real JDs now post to Slack, append to Sheets, write to Data, and send the report + follow-up email. Fix #2: make short_jd deterministic — recompute from an actual word count (<80) in the score_jd wrapper and overwrite the LLM value, killing borderline false positives. short_jd is still computed and surfaced as a tag (Slack "⚠️ short JD", Sheets shortJd column) for downstream eyeballing. Persona / tool description / skill context updated to match. Phantom-lead protection (no_lead_details gate) is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add §16 covering the capture_lead short_jd gate fix + deterministic short_jd, and update the §4 skill notes, scoring-model flags line, and §10 gotcha so "never post on a flag" reflects spam-flags-only behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the Meta Pixel base code (id 1022960976820763, PageView) in <head> and fire funnel events from JS as the SPA advances through screens, since the pixel can't observe .screen transitions on its own: - StartEvaluation (custom) when scoring kicks off (runAnalysis) - ViewContent on first verdict reveal (not re-fired on Back-to-verdict) - Lead on the email-gate submit, alongside the existing LinkedIn fire - BriefTalentSafari / ConnectLua (custom) + Contact on CTA submit, distinguishing the human vs agent path Events go through guarded fbTrack/fbTrackCustom helpers (typeof + try/catch) so a blocked or absent pixel script can never break the app. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e693cbd. Configure here.
| if (done) break; | ||
| agg.push(decoder.decode(value, { stream: true })); | ||
| } | ||
| return agg.finish(); |
There was a problem hiding this comment.
Stream decoder missing final flush
Medium Severity
aggregateStreamResponse decodes every SSE chunk with TextDecoder in stream mode but never performs a final decode to flush buffered bytes. Trailing incomplete UTF-8 sequences at chunk boundaries can be dropped, so parsed tool-result lines may be lost and /api/lua-chat can return { steps: [{ toolResults: [] }] } even when scoring succeeded upstream.
Reviewed by Cursor Bugbot for commit e693cbd. Configure here.
| 'Accept': 'text/html,application/xhtml+xml,*/*', | ||
| }, | ||
| redirect: 'follow', | ||
| }); |
There was a problem hiding this comment.
Redirect bypasses SSRF URL checks
Medium Severity
fetch-jd validates the submitted URL with isSafeUrl but calls fetch with redirect: 'follow', so a public URL that redirects to loopback, RFC1918, or metadata hosts is still fetched. Initial-host blocking does not apply to later redirect targets.
Reviewed by Cursor Bugbot for commit e693cbd. Configure here.


Note
Medium Risk
Touches production API auth, rate limiting, and lead-capture gating; misconfiguration could block legit traffic or change which leads reach Slack/Sheets.
Overview
Adds a Vercel
/api/*backend so the static site can proxy Lua chat, JD URL fetching, and Slack without exposing keys: shared origin allowlisting, optional Vercel KV rate limits, andlua-chatstreaming (SSE aggregated server-side to the legacy{ text, steps }shape, with/generatefallback and long timeouts).Lead-capture fix (PR theme):
capture_leadno longer skips onshort_jd—only spam flags (non_english/suspected_fake) block Slack/Sheets/email; short JDs are tagged instead.score_jdoverwritesshort_jdwith a deterministic word count (<80 words) to stop borderline false positives.Also removes committed
.claude/settings.local.json(secrets), tightens.gitignore, addsdev-server.mjs+@vercel/kv, refreshesbuild.md, and ships a large docs/assets bundle (changes.md,llm.md, fix writeups, email preview, branding PNGs, Lua backup manifest).Reviewed by Cursor Bugbot for commit e693cbd. Bugbot is set up for automated code reviews on this repo. Configure here.