@mushi-mushi/core 0.8.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -97,6 +97,69 @@ interface MushiCaptureConfig {
97
97
  screenshot?: 'on-report' | 'auto' | 'off';
98
98
  elementSelector?: boolean;
99
99
  replay?: 'sentry' | 'rrweb' | 'lite' | 'off';
100
+ /**
101
+ * Mushi Mushi v2.1 (whitepaper §6 hybrid mode): passive inventory
102
+ * discovery. When enabled the SDK observes navigations and emits a
103
+ * tiny payload — `(route, title, testids[], outbound api paths[],
104
+ * dom_summary[≤200 chars])` — back to `/v1/sdk/discovery`. The
105
+ * server aggregates this over a 30-day rolling window and the
106
+ * admin can ask Claude to propose an `inventory.yaml` from it.
107
+ *
108
+ * Default: off. Recommended: enable in dev/preview/staging from the
109
+ * day you install the SDK so the proposer has data when you go to
110
+ * generate your first inventory.
111
+ *
112
+ * What is sent (per navigation, throttled to ≤1/route/min):
113
+ * - `location.pathname` template-normalized (`/practice/abc-123`
114
+ * → `/practice/[id]` via uuid/numeric/hex heuristics + an
115
+ * optional framework hint if the host app sets
116
+ * `discoverInventory.routeTemplates`)
117
+ * - `document.title`
118
+ * - All `[data-testid]` values currently in the DOM
119
+ * - Recent fetch/XHR paths the existing network capturer saw
120
+ * - The first text run of `<h1>` / `<title>` / `<main>` truncated
121
+ * to 200 chars (helps Claude name stories well)
122
+ * - Sanitized query-param keys (key names only, never values)
123
+ * - A SHA-256 of `userId || sessionId` so the server can dedupe
124
+ * distinct users without ever seeing identity
125
+ *
126
+ * Nothing else. No DOM beyond the summary, no query values, no PII.
127
+ */
128
+ discoverInventory?: boolean | MushiDiscoverInventoryConfig;
129
+ }
130
+ /**
131
+ * Fine-grained controls for `discoverInventory`. Defaults are tuned
132
+ * to be quiet enough for production but dense enough for the proposer
133
+ * to produce useful drafts.
134
+ */
135
+ interface MushiDiscoverInventoryConfig {
136
+ enabled?: boolean;
137
+ /**
138
+ * Minimum gap between two emissions for the same route (ms). Defaults
139
+ * to 60_000. Set to a larger value for high-traffic SPAs to keep the
140
+ * ingest volume manageable.
141
+ */
142
+ throttleMs?: number;
143
+ /**
144
+ * Static route templates the host framework knows about. When set,
145
+ * a visit to `/practice/abc-123` matches `/practice/[id]` and gets
146
+ * normalized to that template. Without this we fall back to a
147
+ * heuristic (uuid / numeric / 24-char-hex segments collapse to
148
+ * `[id]`). Provide this when you have a static manifest — Next.js'
149
+ * `next.config` route export, React Router's route config, etc.
150
+ */
151
+ routeTemplates?: string[];
152
+ /**
153
+ * Override the SHA-256 user-id-hash input. Defaults to
154
+ * `mushi.userId || sessionId`. Set to `null` to opt out of distinct-
155
+ * user counting entirely.
156
+ */
157
+ userIdSource?: 'auto' | 'session-only' | 'none';
158
+ /**
159
+ * If false, the DOM summary (≤200 chars from h1/title/main) is not
160
+ * captured. Default true.
161
+ */
162
+ captureDomSummary?: boolean;
100
163
  }
101
164
  interface MushiPrivacyConfig {
102
165
  /** DOM nodes to visually mask in screenshots before upload. */
@@ -227,11 +290,128 @@ interface MushiReport {
227
290
  /** npm package version that submitted the report, e.g. `0.8.0`. */
228
291
  sdkVersion?: string;
229
292
  proactiveTrigger?: string;
293
+ /**
294
+ * Mushi-side breadcrumbs maintained by the SDK in a 50-entry ring buffer.
295
+ * Captured automatically (SDK lifecycle events, console errors, route
296
+ * changes, `[data-testid]` clicks) and via `Mushi.addBreadcrumb()`.
297
+ * Independent of Sentry's breadcrumbs (those land in
298
+ * `sentryContext.breadcrumbs`) so a host using Mushi without Sentry
299
+ * still gets a timeline.
300
+ */
301
+ breadcrumbs?: MushiBreadcrumb[];
302
+ /**
303
+ * Sticky tags set via `Mushi.setTag()` / `Mushi.setTags()`. Mirrors
304
+ * the Sentry / DataDog "tag" vocabulary — short string keys with
305
+ * scalar values. The Triage LLM treats these as high-signal hints
306
+ * ("checkout-flow=redesign-v2" tells the LLM which feature flag the
307
+ * report came from). Plumbed through to `reports.metadata.tags`
308
+ * server-side until a dedicated `tags` column is added.
309
+ */
310
+ tags?: Record<string, string | number | boolean>;
311
+ /**
312
+ * Rich Sentry context captured at report time. Replaces the legacy
313
+ * `sentryEventId`/`sentryReplayId` pair (those are kept for
314
+ * back-compat — server unifies them). When the host has Sentry
315
+ * installed (v7, v8, or v9), every Mushi report carries enough trace
316
+ * data for the admin to pivot into Sentry's MCP / web UI without
317
+ * a manual paste.
318
+ */
319
+ sentryContext?: MushiSentryContext;
320
+ /** @deprecated use `sentryContext.eventId` — kept for back-compat. */
230
321
  sentryEventId?: string;
322
+ /** @deprecated use `sentryContext.replayId` — kept for back-compat. */
231
323
  sentryReplayId?: string;
232
324
  queuedAt?: string;
233
325
  createdAt: string;
234
326
  }
327
+ /**
328
+ * Single breadcrumb entry. Shape follows the Sentry breadcrumb schema
329
+ * 1:1 so an admin tooling layer (or the Triage LLM) can treat Mushi
330
+ * breadcrumbs and Sentry breadcrumbs interchangeably without a
331
+ * normalisation pass.
332
+ */
333
+ interface MushiBreadcrumb {
334
+ /** Unix epoch ms when the breadcrumb fired. */
335
+ timestamp: number;
336
+ /**
337
+ * Coarse bucket for filtering / coloring in the report drawer.
338
+ * - `navigation` — route or url change
339
+ * - `ui.click` — user clicked a `[data-testid]` element
340
+ * - `console` — `console.error` / `console.warn` callsite
341
+ * - `xhr` / `fetch` — network request that errored or 4xx/5xx
342
+ * - `lifecycle` — Mushi SDK init / open / submit / queue
343
+ * - `custom` — host called `Mushi.addBreadcrumb()`
344
+ */
345
+ category: 'navigation' | 'ui.click' | 'console' | 'xhr' | 'fetch' | 'lifecycle' | 'custom';
346
+ /**
347
+ * Severity — `info` is the default. `warning` / `error` map to a
348
+ * coloured pill in the drawer; the Triage LLM uses these to decide
349
+ * which breadcrumbs to feature in the "what happened" summary.
350
+ */
351
+ level: 'debug' | 'info' | 'warning' | 'error';
352
+ /** Free-form short summary, capped at 500 chars at submit time. */
353
+ message: string;
354
+ /** Optional structured payload — kept small to keep ingest cheap. */
355
+ data?: Record<string, unknown>;
356
+ }
357
+ /**
358
+ * Snapshot of Sentry's current scope at report submission. Captured by
359
+ * `captureSentryContext()` in the web SDK; designed to be cheap to
360
+ * serialise and exhaustively useful when the admin pivots into Sentry
361
+ * MCP via `find_organizations` → `search_issues` → `get_event_attachment`.
362
+ *
363
+ * Every field is optional so a host without Sentry installed (and a
364
+ * Sentry SDK that exposes only a subset of these globals) still
365
+ * produces a partial — but useful — payload.
366
+ */
367
+ interface MushiSentryContext {
368
+ /** Sentry SDK version family detected at capture time. */
369
+ sdk?: 'v7' | 'v8' | 'v9' | 'unknown';
370
+ /** `Sentry.lastEventId()` (v8+) / `getLastEventId()` (v7). */
371
+ eventId?: string;
372
+ /** Replay session id from `Sentry.getReplay()?.getReplayId()`. */
373
+ replayId?: string;
374
+ /** Distributed-tracing trace id (32 hex). */
375
+ traceId?: string;
376
+ /** Active span id at capture time (16 hex). */
377
+ spanId?: string;
378
+ /** `transaction` field from the active scope, e.g. `GET /checkout`. */
379
+ transactionName?: string;
380
+ /** Build identifier set via `Sentry.init({ release })`. */
381
+ release?: string;
382
+ /** Logical environment, e.g. `production`. */
383
+ environment?: string;
384
+ /** Session id when Sentry session-tracking is enabled. */
385
+ sessionId?: string;
386
+ /** User context from Sentry's scope (id/email/username/ip). */
387
+ user?: {
388
+ id?: string;
389
+ email?: string;
390
+ username?: string;
391
+ ip_address?: string;
392
+ };
393
+ /** Tags attached to Sentry's current scope. */
394
+ tags?: Record<string, string | number | boolean>;
395
+ /**
396
+ * Last N Sentry breadcrumbs (default cap = 30). Already-formatted
397
+ * Sentry breadcrumbs, not Mushi's — the two are surfaced side by
398
+ * side in the admin drawer so users can correlate.
399
+ */
400
+ breadcrumbs?: Array<{
401
+ timestamp?: number;
402
+ category?: string;
403
+ level?: string;
404
+ message?: string;
405
+ type?: string;
406
+ data?: Record<string, unknown>;
407
+ }>;
408
+ /**
409
+ * Issue url (deeplink) when the SDK can derive it from the event id
410
+ * + DSN. Lets the admin jump straight to the Sentry issue page
411
+ * without the user pasting a link.
412
+ */
413
+ issueUrl?: string;
414
+ }
235
415
  interface MushiReportBuilder {
236
416
  addMetadata(key: string, value: unknown): void;
237
417
  setCategory(category: MushiReportCategory): void;
@@ -256,6 +436,118 @@ interface MushiEnvironment {
256
436
  };
257
437
  deviceMemory?: number;
258
438
  hardwareConcurrency?: number;
439
+ /**
440
+ * v2 inventory hints (whitepaper §4.7).
441
+ *
442
+ * `route` — the bare pathname (no query / hash). Pinned to the
443
+ * inventory's `Page.path` so the Triage LLM can shortcut from a
444
+ * freeform "the streak counter is broken" report to the right page.
445
+ *
446
+ * `nearestTestid` — the closest ancestor of the active element with
447
+ * a `data-testid`, captured at widget-open time. This pins the
448
+ * report to one Action node when more than one page has the same
449
+ * page path (e.g. a shared "Buy Pro" CTA on landing + dashboard).
450
+ *
451
+ * Both are best-effort; freeform reports with no active element will
452
+ * have `route` only.
453
+ */
454
+ route?: string;
455
+ nearestTestid?: string;
456
+ /**
457
+ * SDK boost (2026-05-07): richer per-report context so the Triage LLM
458
+ * and the admin /reports detail drawer have something to reason about
459
+ * beyond `userAgent`. Every field is optional so legacy SDKs and
460
+ * non-browser callers (CLI, server-to-server) keep validating.
461
+ */
462
+ /**
463
+ * Parsed UA-CH (User-Agent Client Hints) high-entropy values when the
464
+ * browser supports `navigator.userAgentData.getHighEntropyValues`.
465
+ * This is the modern, reliable way to identify Chromium browsers — UA
466
+ * sniffing is unreliable post-Chrome 100 because the UA string was
467
+ * frozen for privacy. Safari + Firefox don't expose UA-CH so we still
468
+ * fall back to UA parsing for those (handled server-side).
469
+ */
470
+ userAgentData?: {
471
+ /** Best-effort browser brand (e.g. "Chrome", "Edge", "Brave"). */
472
+ browser?: string;
473
+ /** Browser version (full semver where available, e.g. "131.0.6778.86"). */
474
+ browserVersion?: string;
475
+ /** OS family (e.g. "macOS", "Windows", "Android", "iOS"). */
476
+ os?: string;
477
+ /** OS version when the browser exposes it (e.g. "14.5.0"). */
478
+ osVersion?: string;
479
+ /** Whether the device self-identifies as mobile (UA-CH `mobile`). */
480
+ mobile?: boolean;
481
+ /** Device model when the OS exposes it (Android only, e.g. "Pixel 8"). */
482
+ model?: string;
483
+ /** CPU architecture (e.g. "x86", "arm"). */
484
+ architecture?: string;
485
+ /** CPU bitness ("32" or "64"). */
486
+ bitness?: string;
487
+ };
488
+ /**
489
+ * Physical device pixels behind the viewport. `viewport` already
490
+ * captures the CSS pixel box; this lets us tell a 1080p MacBook from a
491
+ * Retina iPhone and explain "looks fine on my screen, broken on
492
+ * theirs" by surfacing devicePixelRatio mismatches.
493
+ */
494
+ screen?: {
495
+ /** `screen.width` — outer device width in CSS px. */
496
+ width?: number;
497
+ /** `screen.height` — outer device height in CSS px. */
498
+ height?: number;
499
+ /** `window.devicePixelRatio` — physical / CSS px ratio. */
500
+ devicePixelRatio?: number;
501
+ /** `screen.colorDepth` — bits per pixel (typically 24/30). */
502
+ colorDepth?: number;
503
+ /** Active orientation type from `screen.orientation`. */
504
+ orientation?: 'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary' | string;
505
+ };
506
+ /**
507
+ * Accessibility / display preferences resolved via media queries.
508
+ * These are reproduction hints — a bug that only repros under
509
+ * `prefers-reduced-motion: reduce` or `forced-colors: active` is one
510
+ * the developer would otherwise spend hours hunting for.
511
+ */
512
+ prefersColorScheme?: 'dark' | 'light' | 'no-preference';
513
+ prefersReducedMotion?: boolean;
514
+ prefersReducedData?: boolean;
515
+ prefersContrast?: 'more' | 'less' | 'no-preference' | 'custom';
516
+ forcedColors?: boolean;
517
+ /** `navigator.onLine` at capture time. False = the report was filed offline. */
518
+ online?: boolean;
519
+ /**
520
+ * `(display-mode: standalone | minimal-ui | fullscreen | browser)`
521
+ * resolved via media queries. Tells us whether the user filed the
522
+ * report from a regular browser tab, an installed PWA, or a TWA — a
523
+ * different code path on iOS Safari for each.
524
+ */
525
+ displayMode?: 'browser' | 'minimal-ui' | 'standalone' | 'fullscreen';
526
+ /** `document.title` at capture time. Surface for "what page were they on?". */
527
+ documentTitle?: string;
528
+ /**
529
+ * Optional opt-in build identifier from `<meta name="mushi:build" content="...">`.
530
+ * Hosts that already expose a git SHA / build number to their HTML
531
+ * (e.g. `<meta name="mushi:build" content="abc123def">`) get it
532
+ * threaded through automatically — no SDK config required. Pairs with
533
+ * `appVersion` to pin reports to a specific deploy.
534
+ */
535
+ buildId?: string;
536
+ /**
537
+ * Snapshot of the Navigation Timing entry. Reports that come in
538
+ * during a slow page load look very different from steady-state
539
+ * reports, and the LLM can't tell the difference from a stack alone.
540
+ */
541
+ pageLoadTiming?: {
542
+ /** `domContentLoadedEventEnd - startTime` in ms. */
543
+ domContentLoadedMs?: number;
544
+ /** `loadEventEnd - startTime` in ms. */
545
+ loadCompleteMs?: number;
546
+ /** `responseStart - startTime` in ms (TTFB). */
547
+ timeToFirstByteMs?: number;
548
+ /** Navigation type (`navigate`, `reload`, `back_forward`, `prerender`). */
549
+ navigationType?: string;
550
+ };
259
551
  }
260
552
  interface MushiConsoleEntry {
261
553
  level: 'log' | 'warn' | 'error' | 'info' | 'debug';
@@ -294,6 +586,18 @@ interface MushiSelectedElement {
294
586
  width: number;
295
587
  height: number;
296
588
  };
589
+ /**
590
+ * `data-testid` of the closest ancestor that has one. Mushi v2 uses this
591
+ * to map a report → Action node in the bidirectional graph (whitepaper §4.7).
592
+ * Falls back to `undefined` when no ancestor declares a testid — the v2
593
+ * Triage LLM still classifies these reports, just without the inventory
594
+ * grounding shortcut.
595
+ */
596
+ nearestTestid?: string;
597
+ /** Path of the page the user reported from (`window.location.pathname`).
598
+ * Combined with `nearestTestid` it pins a report to one Action even when
599
+ * the same testid exists on multiple pages. */
600
+ route?: string;
297
601
  }
298
602
  type MushiTimelineKind = 'route' | 'click' | 'request' | 'log' | 'screen';
299
603
  interface MushiTimelineEntry {
@@ -353,6 +657,20 @@ interface MushiSDKInstance {
353
657
  * Returns the server-assigned report id when the submit succeeds.
354
658
  */
355
659
  captureEvent(event: MushiCaptureEventInput): Promise<string | null>;
660
+ /**
661
+ * 2026-05-07 — `try/catch`-friendly sugar over `captureEvent`. Pass
662
+ * a thrown value directly and Mushi normalises it (any exotic
663
+ * throw shape: `Error`, string, plain object, `null`) into a report
664
+ * with category `bug`, severity `high` (overridable), and the
665
+ * stack trace folded into both `description` (compact) and
666
+ * `metadata.error.stack` (full).
667
+ *
668
+ * Pairs with Sentry: when `config.sentry` is present, the same
669
+ * call-site can flush to Sentry (`Sentry.captureException(err)`)
670
+ * and Mushi (`Mushi.captureException(err)`) — the two reports get
671
+ * cross-linked via `sentryContext.eventId` automatically.
672
+ */
673
+ captureException(error: unknown, options?: MushiCaptureExceptionOptions): Promise<string | null>;
356
674
  /**
357
675
  * Wave G4 — sugar alias for `setUser()`. Name mirrors the
358
676
  * identify/track/capture vocabulary that PostHog/Segment/Mixpanel
@@ -363,6 +681,47 @@ interface MushiSDKInstance {
363
681
  name?: string;
364
682
  [k: string]: unknown;
365
683
  }): void;
684
+ /**
685
+ * Sentry-grade observability surface (2026-05-07).
686
+ *
687
+ * Hosts can drop a breadcrumb on every meaningful state change in
688
+ * their app — feature-flag toggles, route transitions, optimistic
689
+ * UI commits, server reconciliation — and the buffer's last 50
690
+ * entries automatically attach to every report. Useful even when
691
+ * Sentry isn't installed; when it is, Mushi reports also carry
692
+ * Sentry's breadcrumbs alongside Mushi's own.
693
+ */
694
+ addBreadcrumb(crumb: Omit<MushiBreadcrumb, 'timestamp'> & {
695
+ timestamp?: number;
696
+ }): void;
697
+ /** Snapshot of the current breadcrumb ring buffer (oldest first). */
698
+ getBreadcrumbs(): MushiBreadcrumb[];
699
+ /**
700
+ * Set a sticky tag that lands on every subsequent report. Numeric
701
+ * and boolean values are accepted; they're coerced to strings on
702
+ * the wire so the Triage LLM can read them without type juggling.
703
+ */
704
+ setTag(key: string, value: string | number | boolean): void;
705
+ /** Bulk variant of `setTag`. Replaces existing values for shared keys. */
706
+ setTags(tags: Record<string, string | number | boolean>): void;
707
+ /** Remove a single tag, or all tags when called with no argument. */
708
+ clearTag(key?: string): void;
709
+ }
710
+ interface MushiCaptureExceptionOptions {
711
+ /** Override the default `'bug'` category (e.g. `'slow'` for timeouts). */
712
+ category?: MushiReportCategory;
713
+ /** Default `'high'`. Use `'critical'` for boot-time errors, `'low'` for known recoverables. */
714
+ severity?: 'critical' | 'high' | 'medium' | 'low';
715
+ /** Affected component / page area, surfaces in the admin reports list. */
716
+ component?: string;
717
+ /** Optional human-readable summary that overrides the auto-derived one. */
718
+ description?: string;
719
+ /** Per-call tags merged with sticky tags. */
720
+ tags?: Record<string, string | number | boolean>;
721
+ /** Free-form metadata folded into `reports.metadata`. */
722
+ metadata?: Record<string, unknown>;
723
+ /** Source label — defaults to `'captureException'`. */
724
+ source?: string;
366
725
  }
367
726
  interface MushiCaptureEventInput {
368
727
  /** Human-readable summary; becomes `reports.description`. */
@@ -391,6 +750,16 @@ interface MushiApiClient {
391
750
  }>>;
392
751
  getSdkConfig(): Promise<MushiApiResponse<MushiRuntimeSdkConfig>>;
393
752
  getLatestSdkVersion(packageName: string): Promise<MushiApiResponse<MushiSdkVersionInfo>>;
753
+ /**
754
+ * Mushi v2.1: ship a single passive-discovery observation (route +
755
+ * testids + outbound APIs + DOM summary). Best-effort fire-and-forget;
756
+ * the caller should not block on the response. The server rate-limits
757
+ * per (project, route) to keep ingest cheap, so it's fine for clients
758
+ * to over-emit on the throttle window — the server picks the freshest.
759
+ */
760
+ postDiscoveryEvent(event: MushiDiscoveryEventPayload): Promise<MushiApiResponse<{
761
+ accepted: boolean;
762
+ }>>;
394
763
  listReporterReports(reporterToken: string): Promise<MushiApiResponse<{
395
764
  reports: MushiReporterReport[];
396
765
  }>>;
@@ -401,6 +770,22 @@ interface MushiApiClient {
401
770
  comment: MushiReporterComment;
402
771
  }>>;
403
772
  }
773
+ /**
774
+ * Wire shape of a single discovery event sent by the SDK to
775
+ * `POST /v1/sdk/discovery`. Mirrored server-side in
776
+ * `_shared/schemas.ts::discoveryEventSchema`.
777
+ */
778
+ interface MushiDiscoveryEventPayload {
779
+ route: string;
780
+ page_title?: string | null;
781
+ dom_summary?: string | null;
782
+ testids: string[];
783
+ network_paths: string[];
784
+ query_param_keys: string[];
785
+ user_id_hash?: string | null;
786
+ sdk_version?: string;
787
+ observed_at: string;
788
+ }
404
789
  interface MushiApiResponse<T> {
405
790
  ok: boolean;
406
791
  data?: T;
@@ -462,7 +847,7 @@ interface ApiClientOptions {
462
847
  declare const DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
463
848
  declare const MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
464
849
  declare const MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
465
- type MushiInternalRequestKind = 'sdk-config' | 'report-submit' | 'report-status' | 'reporter-poll' | 'diagnose';
850
+ type MushiInternalRequestKind = 'sdk-config' | 'report-submit' | 'report-status' | 'reporter-poll' | 'diagnose' | 'discovery';
466
851
  declare function createApiClient(options: ApiClientOptions): MushiApiClient;
467
852
 
468
853
  /**
@@ -519,6 +904,21 @@ interface OfflineQueue {
519
904
  }
520
905
  declare function createOfflineQueue(config?: MushiOfflineConfig): OfflineQueue;
521
906
 
907
+ /**
908
+ * Capture a snapshot of the runtime environment. Called once per
909
+ * report submission, so every read here must be:
910
+ *
911
+ * 1. Synchronous (UA-Client Hints `getHighEntropyValues()` is the
912
+ * sole exception — see `kickOffUserAgentData` below; the result
913
+ * is memoised and folded back in on the next capture).
914
+ * 2. SSR-safe (every `window`/`document`/`navigator` access must be
915
+ * gated on `typeof X !== 'undefined'` because the SDK is
916
+ * imported by Next.js / Remix server bundles).
917
+ * 3. Tolerant of missing APIs (Safari doesn't have `Network
918
+ * Information`, Firefox doesn't have UA-CH, screen.orientation is
919
+ * flaky on iOS) — every individual field is optional so a
920
+ * partially-supported browser still produces a useful payload.
921
+ */
522
922
  declare function captureEnvironment(): MushiEnvironment;
523
923
 
524
924
  declare function getReporterToken(): string;
@@ -588,6 +988,73 @@ declare function createPiiScrubber(config?: PiiScrubberConfig): {
588
988
  };
589
989
  declare function scrubPii(text: string, config?: PiiScrubberConfig): string;
590
990
 
991
+ /**
992
+ * Ring-buffer breadcrumb store. Capped at `max` entries (default 50);
993
+ * once full, every new add evicts the oldest. We deliberately don't
994
+ * use a Map — order matters and is the only thing we ever read, so a
995
+ * plain array we slice-copy on snapshot is the right shape.
996
+ *
997
+ * Design notes:
998
+ * - `add()` is O(1) on the steady-state hot path (just `push`). When
999
+ * we hit `max` we evict the oldest with `shift()` — that's O(n)
1000
+ * in the worst case, but with `max=50` the constant is tiny and
1001
+ * it only fires once per insert past the cap. We deliberately
1002
+ * append-then-shift (rather than head-insert) so `getAll()` can
1003
+ * return the underlying array in oldest-first order without an
1004
+ * extra reverse copy on every snapshot.
1005
+ * - `getAll()` returns a *copy* so callers can't mutate the buffer
1006
+ * after a report is composed. Reports send the snapshot, not the
1007
+ * live ring.
1008
+ * - Long messages are truncated at 500 chars *at insert time*
1009
+ * because a runaway log line shouldn't push useful breadcrumbs
1010
+ * out of the buffer just by virtue of taking the whole entry.
1011
+ * - PII concerns: this buffer is intentionally *not* scrubbed at
1012
+ * insert time — `getBreadcrumbs()` should return the host's own
1013
+ * values verbatim so they're useful for in-app debugging. The
1014
+ * scrubbing pass runs at *report-snapshot time* in
1015
+ * `packages/web/src/mushi.ts`, applying the same `createPiiScrubber()`
1016
+ * used on `description` to every breadcrumb message + every string
1017
+ * field in `data`. That way emails / Stripe keys / JWTs in a
1018
+ * breadcrumb never leave the SDK process.
1019
+ */
1020
+ interface BreadcrumbBuffer {
1021
+ /** Append a single breadcrumb; auto-fills `timestamp` when omitted. */
1022
+ add(crumb: Omit<MushiBreadcrumb, 'timestamp'> & {
1023
+ timestamp?: number;
1024
+ }): void;
1025
+ /** Return a copy of every retained breadcrumb, oldest first. */
1026
+ getAll(): MushiBreadcrumb[];
1027
+ /** Drop every entry. Useful for tests and `Mushi.destroy()`. */
1028
+ clear(): void;
1029
+ /** Number of entries currently held. */
1030
+ size(): number;
1031
+ }
1032
+ interface BreadcrumbBufferOptions {
1033
+ /** Hard cap on retained entries. Default 50. */
1034
+ max?: number;
1035
+ /** Hard cap on `message` length, in chars. Default 500. */
1036
+ maxMessageLength?: number;
1037
+ }
1038
+ declare function createBreadcrumbBuffer(options?: BreadcrumbBufferOptions): BreadcrumbBuffer;
1039
+
1040
+ /**
1041
+ * Normalise *anything* a host might throw — `Error`, string, plain
1042
+ * object, `null`, frozen DOMException — into the shape Mushi reports
1043
+ * use. Mirrors Sentry's own internal normaliser; lets `captureException`
1044
+ * be a thin sugar layer over `captureEvent`.
1045
+ *
1046
+ * Truncates the stack at 8 KB so a runaway long stack (recursive React
1047
+ * render error during a hot reload, for example) doesn't blow the
1048
+ * description budget when callers fall back to the stack as the body.
1049
+ */
1050
+ interface NormalisedException {
1051
+ name: string;
1052
+ message: string;
1053
+ stack?: string;
1054
+ cause?: unknown;
1055
+ }
1056
+ declare function normaliseThrown(thrown: unknown): NormalisedException;
1057
+
591
1058
  /**
592
1059
  * FILE: logger.ts
593
1060
  * PURPOSE: Zero-dependency structured logger for the mushi-mushi SDK ecosystem.
@@ -654,4 +1121,4 @@ declare function createLogger(options: LoggerOptions): Logger;
654
1121
  */
655
1122
  declare const noopLogger: Logger;
656
1123
 
657
- export { type ApiClientOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiTimelineEntry, type MushiTimelineKind, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, resolveRegionEndpoint, scrubPii };
1124
+ export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTimelineEntry, type MushiTimelineKind, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };