@agent-native/core 0.98.2 → 0.98.3
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +10 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +2 -2
- package/corpus/core/src/client/index.ts +2 -0
- package/corpus/core/src/client/settings/DemoModeSection.tsx +6 -6
- package/corpus/core/src/client/settings/SettingsPanel.tsx +31 -211
- package/corpus/core/src/client/settings/agent-settings-search.ts +219 -0
- package/corpus/core/src/client/settings/index.ts +4 -0
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
- package/corpus/core/src/demo/config.ts +6 -6
- package/corpus/core/src/demo/redact.ts +18 -76
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +21 -4
- package/corpus/templates/analytics/AGENTS.md +6 -6
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +429 -244
- package/corpus/templates/analytics/app/components/layout/command-palette-search.ts +93 -0
- package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +2 -1
- package/corpus/templates/analytics/app/lib/demo-chart-trend.ts +46 -33
- package/corpus/templates/analytics/app/pages/Settings.tsx +8 -45
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +205 -70
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +32 -16
- package/corpus/templates/analytics/app/pages/settings/settings-search.ts +126 -0
- package/corpus/templates/analytics/changelog/2026-07-12-command-menu-loading-placeholders-now-stay-below-available-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-command-menu-search-now-prioritizes-the-best-match.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-demo-charts-preserve-source-movement.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-replay-storage-settings-stay-readable-at-narrow-widths.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-replays-preserve-recorded-styling-and-keep-malformed.md +6 -0
- package/dist/agent/production-agent.js +2 -2
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts +3 -3
- package/dist/client/settings/DemoModeSection.js +4 -4
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +41 -191
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/agent-settings-search.d.ts +16 -0
- package/dist/client/settings/agent-settings-search.d.ts.map +1 -0
- package/dist/client/settings/agent-settings-search.js +178 -0
- package/dist/client/settings/agent-settings-search.js.map +1 -0
- package/dist/client/settings/index.d.ts +1 -0
- package/dist/client/settings/index.d.ts.map +1 -1
- package/dist/client/settings/index.js +1 -0
- package/dist/client/settings/index.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/demo/actions/toggle-demo-mode.js +2 -2
- package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
- package/dist/demo/config.js +6 -6
- package/dist/demo/config.js.map +1 -1
- package/dist/demo/redact.d.ts +7 -5
- package/dist/demo/redact.d.ts.map +1 -1
- package/dist/demo/redact.js +19 -54
- package/dist/demo/redact.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure, dependency-free, deterministic demo-mode redactor.
|
|
3
3
|
*
|
|
4
|
-
* Replaces sensitive values (
|
|
4
|
+
* Replaces sensitive values (emails and free numbers) with stable fake
|
|
5
5
|
* substitutes so a demo looks coherent — the same input always maps to the
|
|
6
6
|
* same fake. Crucially, it NEVER rewrites identifiers, structural tokens, or
|
|
7
|
-
* timestamps
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* timestamps. Names and other free text are deliberately left alone because
|
|
8
|
+
* guessing whether arbitrary text is a person's name is too inaccurate. The
|
|
9
|
+
* string redactor uses a protect-first strategy (mask IDs with opaque
|
|
10
|
+
* placeholders before any transform runs, restore them byte-identical
|
|
11
|
+
* afterwards), and the structure-aware walker additionally protects leaf
|
|
12
|
+
* values by key name.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
export interface RedactOptions {
|
|
@@ -66,7 +68,7 @@ function pick<T>(rng: () => number, pool: readonly T[]): T {
|
|
|
66
68
|
* round-trips back through redaction (e.g. you edit a draft that's
|
|
67
69
|
* already showing fake text, it autosaves, then refetches) it is passed
|
|
68
70
|
* through UNCHANGED instead of being re-faked into something new. This
|
|
69
|
-
* is what stops
|
|
71
|
+
* is what stops emails drifting on every edit.
|
|
70
72
|
*
|
|
71
73
|
* Leak-free by construction: no timers, a hard size cap, and lazy TTL purge
|
|
72
74
|
* on write. Works the same per-tab in the browser and per-process on the
|
|
@@ -242,15 +244,6 @@ const LAST_NAMES: readonly string[] = [
|
|
|
242
244
|
* Fake value generators (deterministic in value + salt)
|
|
243
245
|
* ------------------------------------------------------------------ */
|
|
244
246
|
|
|
245
|
-
function fakeName(original: string, salt: string): string {
|
|
246
|
-
return memoFake("name", original, salt, () => {
|
|
247
|
-
const rng = seededRng(`name:${original}`, salt);
|
|
248
|
-
const first = pick(rng, FIRST_NAMES);
|
|
249
|
-
const last = pick(rng, LAST_NAMES);
|
|
250
|
-
return `${first} ${last}`;
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
247
|
// Realistic-looking but safe stand-in domains: real consumer providers (the
|
|
255
248
|
// local part is fake, so no real mailbox is implied) plus the canonical
|
|
256
249
|
// reserved fictional-company domains (Contoso/Fabrikam/Northwind), which read
|
|
@@ -484,10 +477,6 @@ function unprotect(text: string, restore: Map<string, string>): string {
|
|
|
484
477
|
|
|
485
478
|
const EMAIL_RE = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
486
479
|
|
|
487
|
-
// 2+ capitalized words (each starts uppercase, allows internal lowercase /
|
|
488
|
-
// apostrophes / hyphens). Single-letter middle initials allowed: `Sarah J Connor`.
|
|
489
|
-
const FULL_NAME_RE = /\b[A-Z][a-zA-Z'’-]*(?:\s+[A-Z](?:[a-zA-Z'’-]*)?){1,3}\b/g;
|
|
490
|
-
|
|
491
480
|
// A standalone numeric token: optional currency + sign, digits with optional
|
|
492
481
|
// comma grouping and a single optional decimal part. Bounded so it is NOT
|
|
493
482
|
// adjacent to a letter (placeholders already removed letter-mixed tokens, but
|
|
@@ -499,10 +488,6 @@ function transformEmails(text: string, salt: string): string {
|
|
|
499
488
|
return text.replace(EMAIL_RE, (match) => fakeEmail(match, salt));
|
|
500
489
|
}
|
|
501
490
|
|
|
502
|
-
function transformNames(text: string, salt: string): string {
|
|
503
|
-
return text.replace(FULL_NAME_RE, (match) => fakeName(match, salt));
|
|
504
|
-
}
|
|
505
|
-
|
|
506
491
|
function isProbablyYear(digits: string): boolean {
|
|
507
492
|
if (digits.length !== 4) return false;
|
|
508
493
|
const n = Number(digits);
|
|
@@ -547,23 +532,18 @@ function transformNumbers(text: string, salt: string): string {
|
|
|
547
532
|
* Public: string redactor
|
|
548
533
|
* ------------------------------------------------------------------ */
|
|
549
534
|
|
|
550
|
-
function redactDemoStringInternal(
|
|
551
|
-
text: string,
|
|
552
|
-
salt: string,
|
|
553
|
-
redactNames: boolean,
|
|
554
|
-
): string {
|
|
535
|
+
function redactDemoStringInternal(text: string, salt: string): string {
|
|
555
536
|
if (typeof text !== "string" || text.length === 0) return text;
|
|
556
537
|
|
|
557
538
|
const { text: masked, restore } = protect(text);
|
|
558
539
|
let out = masked;
|
|
559
540
|
out = transformEmails(out, salt);
|
|
560
|
-
if (redactNames) out = transformNames(out, salt);
|
|
561
541
|
out = transformNumbers(out, salt);
|
|
562
542
|
return unprotect(out, restore);
|
|
563
543
|
}
|
|
564
544
|
|
|
565
545
|
export function redactDemoString(text: string, opts?: RedactOptions): string {
|
|
566
|
-
return redactDemoStringInternal(text, opts?.salt ?? ""
|
|
546
|
+
return redactDemoStringInternal(text, opts?.salt ?? "");
|
|
567
547
|
}
|
|
568
548
|
|
|
569
549
|
/* ------------------------------------------------------------------ *
|
|
@@ -573,35 +553,14 @@ export function redactDemoString(text: string, opts?: RedactOptions): string {
|
|
|
573
553
|
// Keys whose leaf values must NEVER be transformed (still recurse into nested
|
|
574
554
|
// objects/arrays under them). Besides ids/urls/timestamps this also covers
|
|
575
555
|
// code/query-bearing keys (`sql`, `query`, `expression`, `formula`, `code`):
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
// RESULTS are what get redacted so the chart still shows fake values.
|
|
556
|
+
// redacting a SQL string mutates literals and can make the query semantically
|
|
557
|
+
// wrong or invalid. The query must run untouched; its RESULTS are what get
|
|
558
|
+
// redacted so the chart still shows fake values.
|
|
580
559
|
const PROTECTED_KEY_RE =
|
|
581
560
|
/^id$|(^|_)id$|Id$|Ids$|uuid|guid|slug|token|secret|password|passwd|apikey|api_key|hash|sha\d*|etag|cursor|nonce|sessionid|messageid|threadid|nodeid|(^|_)key$|keyid|(^|_)ref$|url$|uri$|href$|src$|path$|filename$|mimetype|mime|^sql$|sql$|query|expression|formula|^code$|createdat|updatedat|deletedat|expiresat|timestamp|.+at$|.+_at$/i;
|
|
582
561
|
|
|
583
|
-
// UI labels and chart/dashboard titles often look like person names ("Maya
|
|
584
|
-
// Davis (First-party)", "Clicks by Henry Moore") but are structural labels. Keep
|
|
585
|
-
// them stable while still redacting free-text and contact-style person fields.
|
|
586
|
-
const LABEL_TEXT_KEY_RE = /^(name|title|label)$/i;
|
|
587
|
-
|
|
588
562
|
const MAX_DEPTH = 64;
|
|
589
563
|
|
|
590
|
-
function isStandalonePersonNameLabel(text: string): boolean {
|
|
591
|
-
const trimmed = text.trim();
|
|
592
|
-
if (!trimmed) return false;
|
|
593
|
-
return /^[A-Z][a-zA-Z'’-]+(?:\s+[A-Z]\.?)?\s+[A-Z][a-zA-Z'’-]+$/.test(
|
|
594
|
-
trimmed,
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function shouldRedactNamesForChild(key: string, entry: unknown): boolean {
|
|
599
|
-
if (!LABEL_TEXT_KEY_RE.test(key)) return true;
|
|
600
|
-
// Preserve structural labels/titles, but do not let common contact-shaped
|
|
601
|
-
// fields like `{ name: "Olivia Parker" }` leak real person names.
|
|
602
|
-
return typeof entry === "string" ? isStandalonePersonNameLabel(entry) : true;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
564
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
606
565
|
if (value === null || typeof value !== "object") return false;
|
|
607
566
|
if (Array.isArray(value)) return false;
|
|
@@ -610,14 +569,6 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
|
610
569
|
return proto === Object.prototype || proto === null;
|
|
611
570
|
}
|
|
612
571
|
|
|
613
|
-
function redactDemoStringWithSalt(
|
|
614
|
-
text: string,
|
|
615
|
-
salt: string,
|
|
616
|
-
redactNames: boolean,
|
|
617
|
-
): string {
|
|
618
|
-
return redactDemoStringInternal(text, salt, redactNames);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
572
|
function redactNumberLeaf(value: number, salt: string): number {
|
|
622
573
|
if (!Number.isFinite(value)) return value;
|
|
623
574
|
const repr = String(value);
|
|
@@ -635,7 +586,6 @@ function walk(
|
|
|
635
586
|
salt: string,
|
|
636
587
|
depth: number,
|
|
637
588
|
seen: WeakSet<object>,
|
|
638
|
-
redactNames: boolean,
|
|
639
589
|
): unknown {
|
|
640
590
|
if (depth > MAX_DEPTH) return value;
|
|
641
591
|
|
|
@@ -644,7 +594,7 @@ function walk(
|
|
|
644
594
|
const t = typeof value;
|
|
645
595
|
|
|
646
596
|
if (t === "string") {
|
|
647
|
-
return
|
|
597
|
+
return redactDemoStringInternal(value as string, salt);
|
|
648
598
|
}
|
|
649
599
|
|
|
650
600
|
if (t === "number") {
|
|
@@ -660,9 +610,7 @@ function walk(
|
|
|
660
610
|
if (Array.isArray(value)) {
|
|
661
611
|
if (seen.has(value)) return value;
|
|
662
612
|
seen.add(value);
|
|
663
|
-
const out = value.map((item) =>
|
|
664
|
-
walk(item, salt, depth + 1, seen, redactNames),
|
|
665
|
-
);
|
|
613
|
+
const out = value.map((item) => walk(item, salt, depth + 1, seen));
|
|
666
614
|
seen.delete(value);
|
|
667
615
|
return out;
|
|
668
616
|
}
|
|
@@ -681,20 +629,14 @@ function walk(
|
|
|
681
629
|
) {
|
|
682
630
|
// Still recurse into nested structures, but the protected key does
|
|
683
631
|
// not transform its own leaf value.
|
|
684
|
-
out[key] = walk(entry, salt, depth + 1, seen
|
|
632
|
+
out[key] = walk(entry, salt, depth + 1, seen);
|
|
685
633
|
} else {
|
|
686
634
|
// Leaf under a protected key: pass through completely untouched.
|
|
687
635
|
out[key] = entry;
|
|
688
636
|
}
|
|
689
637
|
continue;
|
|
690
638
|
}
|
|
691
|
-
out[key] = walk(
|
|
692
|
-
entry,
|
|
693
|
-
salt,
|
|
694
|
-
depth + 1,
|
|
695
|
-
seen,
|
|
696
|
-
redactNames && shouldRedactNamesForChild(key, entry),
|
|
697
|
-
);
|
|
639
|
+
out[key] = walk(entry, salt, depth + 1, seen);
|
|
698
640
|
}
|
|
699
641
|
seen.delete(value);
|
|
700
642
|
return out;
|
|
@@ -706,7 +648,7 @@ function walk(
|
|
|
706
648
|
|
|
707
649
|
export function redactDemoData<T>(value: T, opts?: RedactOptions): T {
|
|
708
650
|
const salt = opts?.salt ?? "";
|
|
709
|
-
return walk(value, salt, 0, new WeakSet<object>()
|
|
651
|
+
return walk(value, salt, 0, new WeakSet<object>()) as T;
|
|
710
652
|
}
|
|
711
653
|
|
|
712
654
|
/**
|
|
@@ -104,10 +104,27 @@ agent answers about browser recordings in the Analytics template.
|
|
|
104
104
|
rebuilding the player mid-load desyncs the scrubber and playhead.
|
|
105
105
|
- Pass events to `Replayer` untouched. rrweb rebuilds them in a sandboxed iframe;
|
|
106
106
|
pre-processing DOM, stylesheet, resource, or mutation payloads makes playback
|
|
107
|
-
diverge from the captured page.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
diverge from the captured page. In particular, never rewrite `href`, `src`,
|
|
108
|
+
`_cssText`, CSS `url()`, or Meta URLs to `about:blank`; that exact remediation
|
|
109
|
+
broke historical replay CSS in PR #2040. Handle request privacy at capture or
|
|
110
|
+
the sandbox boundary instead of mutating stored rrweb events. Historical
|
|
111
|
+
captures without inlined resources require live stylesheet/image/font
|
|
112
|
+
requests for accurate rendering; the viewer accepts that fidelity tradeoff,
|
|
113
|
+
uses rrweb's script-disabled sandbox plus `referrerpolicy="no-referrer"`, and
|
|
114
|
+
must never add credentials or proxy those URLs through a privileged server.
|
|
115
|
+
- Let rrweb own normal iframe sizing via Meta / ViewportResize, and keep the
|
|
116
|
+
outer wrapper in that same coordinate system for fit-to-stage scaling. Some
|
|
117
|
+
legacy recordings contain demonstrably corrupt 4,000–7,500px-wide viewport
|
|
118
|
+
values from ordinary desktop sessions. Only widths of at least 4,000px with
|
|
119
|
+
aspect ratios above 4:1 are recovered to a 16:10 viewport; real 32:9 and
|
|
120
|
+
mobile portrait captures remain untouched. The resize handler applies the
|
|
121
|
+
recovery to **both** rrweb's iframe and the outer stage.
|
|
122
|
+
Never remove this recovery, clamp only one layer, or broadly rewrite event
|
|
123
|
+
geometry: those changes repeatedly recreated the ribbon/clipping regression.
|
|
124
|
+
- Keep the realistic fidelity and malformed-viewport tests in
|
|
125
|
+
`SessionDetailPage.spec.ts`. Do not change their expectations merely to bless
|
|
126
|
+
a new sanitizer or raw ultra-wide sizing; validate the affected replay in a
|
|
127
|
+
browser first.
|
|
111
128
|
- The event timeline soft-highlights the active marker, auto-scrolls it into
|
|
112
129
|
view (pausing briefly after manual scroll), and supports search. It appears
|
|
113
130
|
beside the player from ~880px content width upward.
|
|
@@ -41,12 +41,12 @@ details live in `.agents/skills/`.
|
|
|
41
41
|
unless the user explicitly asks to inspect the demo dashboard.
|
|
42
42
|
- Framework Demo mode is separate from the built-in `demo` source. While Demo
|
|
43
43
|
mode is enabled, Analytics line and area charts reshape each returned numeric
|
|
44
|
-
series into a deterministic
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
query results, tables, metrics,
|
|
48
|
-
unchanged, so do not cite the
|
|
49
|
-
retrieved source values.
|
|
44
|
+
series into a deterministic upward trend using that series' actual minimum,
|
|
45
|
+
maximum, and local step pattern. Source spikes and dips remain at the same
|
|
46
|
+
x-positions while the smallest necessary linear drift moves the series up and
|
|
47
|
+
to the right. This is presentation-only: query results, tables, metrics,
|
|
48
|
+
exports, and non-time-series charts remain unchanged, so do not cite the
|
|
49
|
+
displayed intermediate line/area points as retrieved source values.
|
|
50
50
|
- Every analytical answer should include enough audit context for the user to
|
|
51
51
|
trust it: source(s), time window, filters, sample size or row count,
|
|
52
52
|
join/match method when relevant, and caveats/gaps.
|