From 0e6fdceda14c8450c385f4cfba38dbd76418fabc Mon Sep 17 00:00:00 2001 From: riaz37 Date: Sat, 20 Jun 2026 13:03:21 +0600 Subject: [PATCH 1/2] feat: universal website assistant with PageMemory, element resolver, and E2E matrix Ship incremental page context, deterministic element resolution, plain-page DOM heuristics, dangerous-click safety, and expanded contract/live E2E coverage. --- .changeset/production-hardening.md | 6 +- .github/workflows/publish.yml | 1 + .size-limit.json | 2 +- AGENTS.md | 16 +- SECURITY.md | 4 +- apps/docs/app/docs/getting-started/page.mdx | 58 ++ apps/docs/app/docs/observability/page.mdx | 21 + .../app/(main)/iframe-test/page.tsx | 27 + apps/example-nextjs/app/(main)/plain/page.tsx | 34 + .../app/(main)/spa-rescan/page.tsx | 39 + .../guidekit/test/invalidate-session/route.ts | 43 ++ .../app/guidekit-test-bridge.tsx | 7 + apps/example-nextjs/app/providers.tsx | 2 +- .../public/vanilla-csp-demo.html | 42 ++ docs/ARCHITECTURE_VISION.md | 700 ++++++++++++++++++ e2e/contract/click-safety.spec.ts | 54 ++ e2e/contract/custom-actions.spec.ts | 4 +- e2e/contract/hallucination-guard.spec.ts | 6 +- e2e/contract/iframe.spec.ts | 51 ++ e2e/contract/plain-page.spec.ts | 38 + e2e/contract/spa-rescan.spec.ts | 49 ++ e2e/contract/vanilla-csp.spec.ts | 12 + e2e/contract/vanilla-widget.spec.ts | 2 +- e2e/fixtures/audio/voice-prompt.wav | Bin 0 -> 53498 bytes e2e/fixtures/live-helpers.ts | 71 +- e2e/fixtures/mock-llm-proxy.ts | 29 +- e2e/fixtures/voice-e2e.ts | 8 +- e2e/live/accessibility.spec.ts | 69 ++ e2e/live/agent-click.spec.ts | 35 + e2e/live/agent-read-page.spec.ts | 18 + e2e/live/agent-tour.spec.ts | 26 + e2e/live/hallucination-guard.spec.ts | 49 ++ e2e/live/headless-custom-ui.spec.ts | 49 ++ e2e/live/highlight-dismiss.spec.ts | 33 + e2e/live/proxy.spec.ts | 64 +- e2e/live/vanilla-widget.spec.ts | 46 ++ e2e/live/voice.spec.ts | 37 +- packages/core/src/bus/index.ts | 21 + .../core/src/context/context-manager.test.ts | 27 +- packages/core/src/context/index.ts | 112 ++- packages/core/src/context/page-memory.test.ts | 125 ++++ packages/core/src/context/page-memory.ts | 348 +++++++++ packages/core/src/core.ts | 25 + packages/core/src/core/builtin-tools.ts | 147 +++- packages/core/src/core/options.ts | 4 + packages/core/src/core/runtime-init.ts | 6 +- .../core/src/dom/element-resolver.test.ts | 81 ++ packages/core/src/dom/element-resolver.ts | 135 ++++ packages/core/src/dom/iframe-scanner.ts | 13 +- packages/core/src/dom/index.ts | 24 +- packages/core/src/dom/rescan.ts | 46 ++ packages/core/src/index.ts | 28 +- packages/core/src/pipeline/extensions.ts | 27 + packages/core/src/pipeline/types.ts | 1 + packages/core/src/types/index.ts | 4 + packages/react/src/provider.tsx | 2 + packages/react/src/widget/index.tsx | 5 + packages/react/src/widget/styles.ts | 9 + packages/vanilla/src/index.ts | 24 +- playwright.config.ts | 22 +- scripts/release-check.sh | 4 +- 61 files changed, 2850 insertions(+), 142 deletions(-) create mode 100644 apps/example-nextjs/app/(main)/iframe-test/page.tsx create mode 100644 apps/example-nextjs/app/(main)/plain/page.tsx create mode 100644 apps/example-nextjs/app/(main)/spa-rescan/page.tsx create mode 100644 apps/example-nextjs/app/api/guidekit/test/invalidate-session/route.ts create mode 100644 apps/example-nextjs/public/vanilla-csp-demo.html create mode 100644 docs/ARCHITECTURE_VISION.md create mode 100644 e2e/contract/click-safety.spec.ts create mode 100644 e2e/contract/iframe.spec.ts create mode 100644 e2e/contract/plain-page.spec.ts create mode 100644 e2e/contract/spa-rescan.spec.ts create mode 100644 e2e/contract/vanilla-csp.spec.ts create mode 100644 e2e/fixtures/audio/voice-prompt.wav create mode 100644 e2e/live/accessibility.spec.ts create mode 100644 e2e/live/agent-click.spec.ts create mode 100644 e2e/live/agent-read-page.spec.ts create mode 100644 e2e/live/agent-tour.spec.ts create mode 100644 e2e/live/hallucination-guard.spec.ts create mode 100644 e2e/live/headless-custom-ui.spec.ts create mode 100644 e2e/live/highlight-dismiss.spec.ts create mode 100644 e2e/live/vanilla-widget.spec.ts create mode 100644 packages/core/src/context/page-memory.test.ts create mode 100644 packages/core/src/context/page-memory.ts create mode 100644 packages/core/src/dom/element-resolver.test.ts create mode 100644 packages/core/src/dom/element-resolver.ts create mode 100644 packages/core/src/dom/rescan.ts diff --git a/.changeset/production-hardening.md b/.changeset/production-hardening.md index 197fa1f..f78da9e 100644 --- a/.changeset/production-hardening.md +++ b/.changeset/production-hardening.md @@ -1,7 +1,9 @@ --- -"@guidekit/core": patch -"@guidekit/react": patch +"@guidekit/core": minor +"@guidekit/react": minor "@guidekit/server": patch --- +Universal website assistant: PageMemory + TurnDelta incremental context, element resolver with dangerous-click gate, plain-page DOM heuristics, cross-origin iframe limitations, scroll-and-rescan, contract E2E matrix (plain page, SPA rescan, iframe, click safety, CSP vanilla), reliability scorecard docs, and SPA router integration for Next.js App Router. + Production hardening: stable pipeline telemetry export and DevTools Telemetry tab; LLM/voice proxy permission and origin checks with request validation; example app Redis session-store path and operational docs (rate limits, failure modes, observability). diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6efc30d..e9309da 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -37,6 +37,7 @@ jobs: - name: Run live E2E suite env: LIVE_LLM: '1' + SKIP_LIVE_VOICE: '1' GUIDEKIT_SECRET: guidekit-example-e2e-secret-32-chars LLM_API_KEY: ${{ secrets.LLM_API_KEY }} SKIP_NPM_DRY_RUN: '1' diff --git a/.size-limit.json b/.size-limit.json index 7f35214..52539ab 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -2,7 +2,7 @@ { "name": "@guidekit/core (ESM)", "path": "packages/core/dist/index.js", - "limit": "85 KB", + "limit": "92 KB", "gzip": true }, { diff --git a/AGENTS.md b/AGENTS.md index 2fce5c9..7de4c3c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,27 +77,27 @@ e2e/ └── env.ts # .env.local + LIVE_LLM detection ``` -Voice E2E always mocks the browser Web Speech API — no Deepgram/ElevenLabs in Playwright. +Voice E2E: contract tier mocks Web Speech in Playwright; live tier uses real Web Speech via Chromium fake-audio-capture (no Deepgram/ElevenLabs). ### E2E coverage matrix (user-facing flows) | Flow | Contract | Live | |------|:--------:|:----:| -| Widget UI / a11y | yes | — | +| Widget UI / a11y | yes | yes | | Proxy health / token / LLM | yes | yes | | Text chat + streaming | mocked | yes | | Multi-turn memory | — | yes | | Agent tools (scroll, highlight, navigate, tour, clickElement) | yes | yes | | Platform Mode (RAG, plugin, cognitive page) | yes | yes | | Session recovery 401 | yes | yes | -| Voice (Web Speech mock → LLM) | yes | yes | -| Custom actions / form / readPage / dismiss | yes | partial | +| Voice (Web Speech → LLM) | mocked STT | real STT | +| Custom actions / form / readPage / dismiss | yes | yes | | STT/TTS proxy key minting | yes | — | -| Hallucination guard bus event | yes | — | -| Vanilla IIFE widget | yes | — | -| Headless custom UI | yes | — | +| Hallucination guard bus event | yes | yes | +| Vanilla IIFE widget | yes | yes | +| Headless custom UI | yes | yes | -Commands: `pnpm test:e2e:contract` (CI), `pnpm test:e2e:live` (local), `pnpm test:e2e:live:full` (publish gate). +Commands: `pnpm test:e2e:contract` (CI), `pnpm test:e2e:live` (local), `pnpm test:e2e:live:full` (publish gate). Set `SKIP_LIVE_VOICE=1` to skip the headed real-Web-Speech live voice spec when STT is unavailable. Before release, run `pnpm check:release` (runs live suite twice for the flake budget). Publish workflow uploads Playwright artifacts on failure. diff --git a/SECURITY.md b/SECURITY.md index 644ffcb..4ab13e8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,9 @@ | Version | Supported | | ------- | --------- | -| 0.1.x | Yes | +| 1.x | Yes | +| 0.3.x | Security fixes only | +| 0.1.x | No | ## Reporting a Vulnerability diff --git a/apps/docs/app/docs/getting-started/page.mdx b/apps/docs/app/docs/getting-started/page.mdx index c3f3bc8..e47ea60 100644 --- a/apps/docs/app/docs/getting-started/page.mdx +++ b/apps/docs/app/docs/getting-started/page.mdx @@ -167,6 +167,64 @@ For quick prototyping without server routes, pass API keys directly: | Create React App | Full | | Non-React (vanilla JS) | Via `@guidekit/vanilla` | +## SPA navigation (Next.js App Router) + +GuideKit detects URL changes via the Navigation API, `popstate`, and polling. For client-side route changes that bypass full page loads, pass your App Router instance so `navigate()` and post-nav rescans stay reliable: + +```tsx +'use client'; + +import { useRouter } from 'next/navigation'; +import { GuideKitProvider } from '@guidekit/react'; + +export function Providers({ children }: { children: React.ReactNode }) { + const router = useRouter(); + + return ( + router.push(href) } }} + > + {children} + + ); +} +``` + +On each route change, GuideKit clears **PageMemory**, rescans the DOM, and emits `dom:route-change` plus `context:memory-cleared`. + +For **in-page DOM swaps** (tabs, modals, virtual lists) without a URL change, call `core.rescanPage()` after updating the UI so the assistant sees fresh sections: + +```tsx +'use client'; + +import { useEffect, useState } from 'react'; +import { useGuideKitCore } from '@guidekit/react'; + +export function TabPanel({ activeTab }: { activeTab: string }) { + const core = useGuideKitCore(); + + useEffect(() => { + if (core?.isReady) core.rescanPage(); + }, [activeTab, core]); + + return
{/* tab content */}
; +} +``` + +## Universal site checklist + +1. Proxy mode: `tokenEndpoint` + `/api/guidekit/llm` (never ship LLM keys to the browser) +2. Optional `contentMap` for product facts the DOM cannot infer +3. Optional `data-guidekit-target` on critical CTAs (improves accuracy, not required) +4. `clickableSelectors.allow/deny` for production click boundaries +5. Redis session store for multi-instance deployments +6. `hallucinationGuard={true}` + `intelligence={true}` for Platform Mode on public sites + +See the example app routes `/plain`, `/spa-rescan`, and `/iframe-test` for unannotated page demos used in contract E2E. + ## Next Steps - [Provider Setup](/docs/provider) — Configuration options in depth diff --git a/apps/docs/app/docs/observability/page.mdx b/apps/docs/app/docs/observability/page.mdx index ad94c82..571ea20 100644 --- a/apps/docs/app/docs/observability/page.mdx +++ b/apps/docs/app/docs/observability/page.mdx @@ -56,6 +56,27 @@ GuideKit guarantees these attribute keys when applicable: Additional attributes may be added over time, but existing keys are stable. +## Reliability scorecard (Universal Assistant targets) + +Track these signals in contract and live E2E suites when rolling out to production: + +| Metric | Contract target | Live target | +|--------|-----------------|-------------| +| Claim grounding (hallucination guard clean) | ≥ 90% | ≥ 85% | +| Highlight accuracy on plain pages | ≥ 85% | ≥ 85% | +| Tool success (`highlight`, `scroll`, `readPageContent`) | ≥ 95% | ≥ 90% | +| Prompt tokens per turn (after PageMemory) | −40% vs full prompt | −40% | + +**Bus events for context and grounding:** + +- `context:memory-rebuild` — PageMemory rebuilt after route or hash change +- `context:delta` — incremental TurnDelta sent on subsequent turns +- `element:resolve` — semantic ref resolved to selector (`confidence`, `reason`) +- `action:confirmation-required` — dangerous click blocked pending user confirmation +- `validation:corrected` — high-severity hallucination issues appended to response + +Use DevTools **Events** or `window.__guidekitTest` in the example app to capture these during E2E debugging. + ## Debugging workflow 1. Reproduce the slow or failed message in dev with `options={{ debug: true }}`. diff --git a/apps/example-nextjs/app/(main)/iframe-test/page.tsx b/apps/example-nextjs/app/(main)/iframe-test/page.tsx new file mode 100644 index 0000000..edc03cb --- /dev/null +++ b/apps/example-nextjs/app/(main)/iframe-test/page.tsx @@ -0,0 +1,27 @@ +export default function IframeTestPage() { + return ( +
+

Iframe Grounding Demo

+

Same-origin iframe content is readable; cross-origin iframes are listed as limitations.

+ +
+

Same-origin embed

+