Make Sasquatch tell the truth: direct relay queries, not a CVM server

The chat's data layer (useCVMClient) queries Nostr relays directly from the
browser — the CVM/ContextVM server round-trip was removed earlier for speed.
But the UI and system prompt still claimed data came "from the CVM server",
which is no longer true. Aligned every user-facing claim with reality:

- System prompt: reframed "CVM Tools / MUST route through CVM" as "Live data
  tools that query the Nostr relays directly". Tool-calling behaviour unchanged.
- Streaming status: "Querying CVM for X" → "Searching the relays for X".
- Tool-failure message: no longer blames a non-existent "CVM server"; now
  correctly frames it as relays being slow, with a retry nudge.
- Empty results: stop asserting "not added yet" as fact — an empty result can
  also mean slow relays or a misspelled place. Now offers retry + add-a-spot,
  and names the place searched. Applied to the local formatter, the system
  prompt rules, and the second-pass AI instruction.
- Chat footer: "Powered by ContextVM & Nostr Relays Data" → "Live data from
  Nostr relays" (links the primary community relay).
- About page credit: "AI powered by ContextVM" → "Trust & crew chat via
  ContextVM" (its real role: Relatr trust scores + encrypted crew chat).
- Owner CVM health panel: clarified it probes the standalone MCP server and
  that the in-app chat does not depend on it.
- Updated stale internal comments in SasquatchGuide + CreateSpotPage.

ContextVM references that ARE accurate (Relatr trust scores, crew-chat Cordn
coordinator) were left intact. Build clean, no console errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-09-10 00:16:59 +07:00
parent ee23f3e063
commit 5506344893
4 changed files with 37 additions and 29 deletions

View file

@ -80,7 +80,9 @@ export function CVMHealthCheck() {
</span>
</p>
<p className="text-xs text-muted-foreground mt-0.5">
Health probe for the Sasquatch CVM data server
Heartbeat probe for the standalone ContextVM MCP server. Note: the
in-app Sasquatch chat reads Nostr relays directly and does not depend
on this server.
</p>
</div>
<span

View file

@ -2,8 +2,8 @@
* SasquatchGuide — AI-powered skate assistant
* ─────────────────────────────────────────────────────────────────────────────
* Uses OpenRouter free model (no credits needed, works for any user) for
* natural language understanding, then calls the SkateSpots CVM server on
* Nostr for live data (spots, events, coaches, gear).
* natural language understanding, then queries the Nostr relays directly
* (useCVMClient) for live data (spots, events, coaches, gear).
* Falls back to static FAQ if AI is unavailable.
*/
@ -126,15 +126,15 @@ interface ChatMessage {
}
// ─── System prompt for the AI ─────────────────────────────────────────────────
// Sasquatch is the user-facing agent. The CVM server is the data layer —
// it aggregates and crunches Nostr events across multiple relays, deduplicates,
// filters by features inside events (things Nostr filters alone can't do),
// and returns structured results. Sasquatch MUST always route data queries
// through CVM — never guess from training data.
// Sasquatch is the user-facing agent. The data tools below query Nostr relays
// directly from the browser (see useCVMClient) — they aggregate events across
// the community relays, deduplicate, and run feature filters that plain Nostr
// filters can't. Sasquatch MUST always answer data questions by calling a tool —
// never guess spots/events/people from training data.
const SYSTEM_PROMPT = `You are The Sasquatch, the AI guide for SkateSpots — a decentralized skateboarding platform built on Nostr. Chill, skater-friendly, concise.
## CVM Tools — query live data
## Live data tools — query the Nostr relays
Respond with ONLY a JSON tool call when you need data. Nothing else on that line.
{"tool":"<name>","args":{}}
@ -143,7 +143,7 @@ Args: city?(string), country?(string), difficulty?("beginner"|"intermediate"|"ad
- Filter checks: city tag, location text, spot title, description — all case-insensitive
- For country queries use city of the capital/main city too: Thailand → use location:"Bangkok"
- Always use limit:20+ for city/country searches
- 0 results = not added yet, encourage user to add via skate-spots.org/map
- 0 results could mean none listed yet OR the relays were slow — be honest, suggest a retry and adding via skate-spots.org/map (don't state "nobody added one" as a fact)
Example: {"tool":"find_spots","args":{"city":"Bangkok","limit":20}}
### find_events
@ -200,7 +200,7 @@ Note: this returns a rich itinerary CARD — after calling it, add a short one-l
3. App/protocol/how-to questions → answer directly (no tool). Wallet, Supporters, and the private Stash have NO live search tool — answer from the App facts below and point users to the right page. Spots, Events, the Lab (all crafts incl. judges), Gear, Crews, Shops and Tours all HAVE live tools — use them for "find/show" requests.
4. Be concise, skater-friendly
5. Never invent spot names, event dates, or user data
6. 0 results = honest "none listed yet" + encourage adding content
6. 0 results → be honest: it may be none listed yet OR the relays were slow. Suggest a retry + adding content; never assert "nobody's added one" as fact
## App facts (features & how they connect)
- Nostr = decentralized protocol; your cryptographic keys ARE your identity — one login works everywhere, nobody can ban you
@ -411,7 +411,14 @@ function formatLocalResults(intent: LocalIntent, result: unknown): string {
intent.tool === 'find_tours' ? 'tours' : 'gear listings';
if (!items.length) {
return `No ${label} listed there yet 🦧 — be the first to add some at [skate-spots.org](https://skate-spots.org).`;
// Be honest: an empty result can mean "none published" OR that the relays
// were slow/unreachable OR a misspelled place. Don't assert "not added yet"
// as fact — offer both the add path and a retry.
const place =
'location' in intent ? intent.location :
'search' in intent ? intent.search : '';
const where = place ? ` in **${place}**` : '';
return `Didn't find any ${label}${where} right now 🦧\n\nThat could mean none are listed yet — or the relays were slow to answer. Try again in a moment, double-check the spelling, or [add the first one on the map](https://skate-spots.org/map). 🛹`;
}
// Show a generous slice, then honestly summarise the rest so the headline
@ -1076,11 +1083,12 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
if (toolCall) {
const { tool: toolName, args: toolArgs } = toolCall;
setStreamingText(`🔍 Querying CVM for ${toolName.replace(/_/g, ' ')}...`);
setStreamingText(`🔍 Searching the relays for ${toolName.replace(/_/g, ' ')}...`);
try {
// Call the live CVM server — it aggregates across all relays, deduplicates,
// and runs complex feature filters that plain Nostr queries can't do
// Query the Nostr relays directly (useCVMClient) — aggregates across
// the community relays, deduplicates, and runs feature filters that
// plain Nostr filters can't do. No server round-trip.
let toolResult: unknown;
switch (toolName) {
case 'find_spots':
@ -1168,7 +1176,7 @@ Live data from ${toolName}:
${toolResultStr}
Instructions:
- If count is 0: say nothing found yet, encourage them to add content at skate-spots.org
- If count is 0: say you didn't find any right now — it may be none listed yet OR the relays were slow; suggest a quick retry and adding content at skate-spots.org (don't claim as fact that nobody has added one)
- If count > 0: list results as bullet points with name, brief description, and url link
- Keep it short and skater-friendly
- Never make up data not in the results above`,
@ -1177,8 +1185,8 @@ Instructions:
}
} catch (toolErr) {
setCvmConnected(false);
console.warn('[Sasquatch] CVM call failed:', toolErr);
accumulated = "Couldn't reach the live data server right now. Try again in a moment! The CVM server aggregates spots from all relays — it should be back shortly. 🛹";
console.warn('[Sasquatch] relay query failed:', toolErr);
accumulated = "The relays didn't answer in time right now 🛰️ — that's usually a slow connection, not a missing spot. Give it another go in a moment. 🛹";
}
} else {
// No tool call — AI answered directly from knowledge
@ -1199,9 +1207,9 @@ Instructions:
if (!aiSucceeded) {
// ── No-AI fallback #1: try to serve live data locally ──────────────
// Parse the intent + location ourselves and call CVM directly. This is
// the big free-tier win: "spots in barcelona" still works even when
// both AI providers are rate-limited.
// Parse the intent + location ourselves and query the relays directly.
// This is the big free-tier win: "spots in barcelona" still works even
// when both AI providers are rate-limited.
const intent = parseLocalIntent(question);
// Trip planning gets its own rich-card path (not a text blob).
@ -1640,17 +1648,15 @@ Instructions:
{/* Footer */}
<div className="flex items-center justify-between">
<p className="text-[9px] text-gray-600 flex items-center gap-1 flex-wrap">
Powered by{' '}
Live data from{' '}
<a
href="https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530"
href="https://goskatespots.nostr1.com"
target="_blank"
rel="noopener noreferrer"
className="text-[#ccff00]/60 hover:text-[#ccff00] underline underline-offset-1 transition-colors"
>
ContextVM
Nostr relays
</a>
{' & '}
Nostr Relays Data
</p>
{chatMessages.length > 0 && (
<button

View file

@ -265,7 +265,7 @@ Version: v0.1.0`;
{ label: 'Powered by', name: 'Nostr', url: 'https://nostr.com', desc: 'Decentralized protocol' },
{ label: 'Crowdfunding', name: 'Angor', url: 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z', desc: 'Decentralized Bitcoin crowdfunding' },
{ label: 'Distributed on', name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: 'Open-source app store' },
{ label: 'AI powered by', name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: 'Nostr AI infrastructure' },
{ label: 'Trust & crew chat via', name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: 'Relatr trust scores & encrypted crew-chat coordination' },
].map((item) => (
<a
key={item.name}

View file

@ -270,7 +270,7 @@ export default function CreateSpotPage() {
.slice(0, 50) + '-' + Date.now().toString(36);
// Reverse geocode to get structured city/country tags
// This powers the CVM server's city/country filtering
// These power Sasquatch's city/country filtering (relay queries)
let cityTag = '';
let countryTag = '';
let locationStr = location.trim();
@ -308,7 +308,7 @@ export default function CreateSpotPage() {
eventTags.push(['location', locationStr]);
}
// Structured city/country tags — critical for CVM server search
// Structured city/country tags — critical for Sasquatch relay search
if (cityTag) {
eventTags.push(['city', cityTag]);
}