@copilotkit/web-inspector 1.62.3 → 1.63.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.
@@ -24,7 +24,7 @@ type InspectorInternals = {
24
24
 
25
25
  type InspectorThreadViewInternals = {
26
26
  isOpen: boolean;
27
- selectedMenu: "threads";
27
+ selectedMenu: "ag-ui-events" | "threads";
28
28
  selectedThreadId: string | null;
29
29
  _threads: Array<{
30
30
  id: string;
@@ -32,6 +32,15 @@ type InspectorThreadViewInternals = {
32
32
  agentId: string;
33
33
  updatedAt?: string | null;
34
34
  }>;
35
+ _threadsByAgent: Map<
36
+ string,
37
+ Array<{
38
+ id: string;
39
+ name?: string | null;
40
+ agentId: string;
41
+ updatedAt?: string | null;
42
+ }>
43
+ >;
35
44
  };
36
45
 
37
46
  type InspectorContextInternals = {
@@ -1530,6 +1539,14 @@ describe("WebInspectorElement owned thread store headers (#5581)", () => {
1530
1539
  properties: Record<string, unknown>;
1531
1540
  };
1532
1541
  });
1542
+ const threadListText = (inspector: WebInspectorElement) =>
1543
+ inspector.shadowRoot?.querySelector("cpk-thread-list")?.shadowRoot
1544
+ ?.textContent ?? "";
1545
+ const expectNoUtmParams = (url: URL) => {
1546
+ expect(url.searchParams.has("utm_source")).toBe(false);
1547
+ expect(url.searchParams.has("utm_medium")).toBe(false);
1548
+ expect(url.searchParams.has("utm_campaign")).toBe(false);
1549
+ };
1533
1550
 
1534
1551
  beforeEach(() => {
1535
1552
  document.body.innerHTML = "";
@@ -1546,6 +1563,7 @@ describe("WebInspectorElement owned thread store headers (#5581)", () => {
1546
1563
  });
1547
1564
 
1548
1565
  afterEach(() => {
1566
+ vi.useRealTimers();
1549
1567
  vi.unstubAllGlobals();
1550
1568
  });
1551
1569
 
@@ -1698,13 +1716,17 @@ describe("WebInspectorElement owned thread store headers (#5581)", () => {
1698
1716
  "Talk to an Engineer",
1699
1717
  "Sign up for Intelligence",
1700
1718
  ]);
1719
+ const engineer = inspector.shadowRoot?.querySelector<HTMLAnchorElement>(
1720
+ 'a[href^="https://www.copilotkit.ai/talk-to-an-engineer"]',
1721
+ );
1722
+ expect(engineer?.closest("#cpk-main-scroll")).toBeNull();
1701
1723
  expect(text).not.toContain("No threads yet");
1702
1724
  expect(
1703
1725
  fetchMock.mock.calls.some((call) => String(call[0]).includes("/threads")),
1704
1726
  ).toBe(false);
1705
1727
  });
1706
1728
 
1707
- it("adds ref and posthog distinct ID attribution to locked-state CTAs", async () => {
1729
+ it("adds inspector attribution to locked-state CTAs", async () => {
1708
1730
  const { agent } = createMockAgent("alpha");
1709
1731
  const harness = createHeaderMockCore(
1710
1732
  { alpha: agent },
@@ -1727,7 +1749,7 @@ describe("WebInspectorElement owned thread store headers (#5581)", () => {
1727
1749
  await inspector.updateComplete;
1728
1750
 
1729
1751
  const signup = inspector.shadowRoot?.querySelector<HTMLAnchorElement>(
1730
- 'a[href^="https://go.copilotkit.ai/intelligence-signup"]',
1752
+ 'a[href^="https://dashboard.operations.copilotkit.ai/sign-in"]',
1731
1753
  );
1732
1754
  const engineer = inspector.shadowRoot?.querySelector<HTMLAnchorElement>(
1733
1755
  'a[href^="https://www.copilotkit.ai/talk-to-an-engineer"]',
@@ -1737,7 +1759,10 @@ describe("WebInspectorElement owned thread store headers (#5581)", () => {
1737
1759
  expect(engineer).not.toBeNull();
1738
1760
 
1739
1761
  const signupUrl = new URL(signup!.href);
1762
+ expect(signupUrl.origin).toBe("https://dashboard.operations.copilotkit.ai");
1763
+ expect(signupUrl.pathname).toBe("/sign-in");
1740
1764
  expect(signupUrl.searchParams.get("ref")).toBe("cpk-inspector");
1765
+ expectNoUtmParams(signupUrl);
1741
1766
  const distinctId = signupUrl.searchParams.get("posthog_distinct_id");
1742
1767
  expect(distinctId).toMatch(/^[0-9a-f-]{36}$/);
1743
1768
 
@@ -1745,6 +1770,7 @@ describe("WebInspectorElement owned thread store headers (#5581)", () => {
1745
1770
  expect(engineerUrl.origin).toBe("https://www.copilotkit.ai");
1746
1771
  expect(engineerUrl.pathname).toBe("/talk-to-an-engineer");
1747
1772
  expect(engineerUrl.searchParams.get("ref")).toBe("cpk-inspector-threads");
1773
+ expectNoUtmParams(engineerUrl);
1748
1774
  expect(engineerUrl.searchParams.get("posthog_distinct_id")).toBe(
1749
1775
  distinctId,
1750
1776
  );
@@ -1795,7 +1821,7 @@ describe("WebInspectorElement owned thread store headers (#5581)", () => {
1795
1821
  }
1796
1822
  });
1797
1823
 
1798
- it("keeps the enabled empty Threads state when thread listing is available", async () => {
1824
+ it("renders example threads and the deselected overview when enabled thread history is empty", async () => {
1799
1825
  const { agent } = createMockAgent("alpha");
1800
1826
  const harness = createHeaderMockCore({ alpha: agent }, {}, {}, true);
1801
1827
 
@@ -1812,13 +1838,454 @@ describe("WebInspectorElement owned thread store headers (#5581)", () => {
1812
1838
  internals.handleMenuSelect("threads");
1813
1839
  await inspector.updateComplete;
1814
1840
 
1815
- expect(inspector.shadowRoot?.textContent ?? "").toContain("No threads yet");
1841
+ await vi.waitFor(() => {
1842
+ const text = threadListText(inspector);
1843
+ expect(text).toContain("Realtime thread sync");
1844
+ expect(text).toContain("Manage saved conversations");
1845
+ expect(text).toContain("Inspect durable run history");
1846
+ });
1847
+
1848
+ const text = inspector.shadowRoot?.textContent ?? "";
1849
+ expect(text).toContain("Threads are persistent, inspectable conversations");
1850
+ expect(text).toContain(
1851
+ "Take a tour with the example threads in the sidebar.",
1852
+ );
1853
+ const threadsDocs = inspector.shadowRoot?.querySelector<HTMLAnchorElement>(
1854
+ 'a[href^="https://docs.copilotkit.ai/threads"]',
1855
+ );
1856
+ expect(threadsDocs?.textContent?.trim()).toBe("Learn how Threads work");
1857
+ const threadsDocsUrl = new URL(threadsDocs!.href);
1858
+ expect(threadsDocsUrl.origin).toBe("https://docs.copilotkit.ai");
1859
+ expect(threadsDocsUrl.pathname).toBe("/threads");
1860
+ expect(threadsDocsUrl.searchParams.get("ref")).toBe(
1861
+ "cpk-inspector-threads",
1862
+ );
1863
+ expectNoUtmParams(threadsDocsUrl);
1864
+ const selfHosted = inspector.shadowRoot?.querySelector<HTMLAnchorElement>(
1865
+ 'a[href^="https://docs.copilotkit.ai/premium/self-hosting"]',
1866
+ );
1867
+ expect(selfHosted?.textContent?.trim()).toBe(
1868
+ "Explore self-hosted Intelligence",
1869
+ );
1870
+ const selfHostedUrl = new URL(selfHosted!.href);
1871
+ expect(selfHostedUrl.origin).toBe("https://docs.copilotkit.ai");
1872
+ expect(selfHostedUrl.pathname).toBe("/premium/self-hosting");
1873
+ expect(selfHostedUrl.searchParams.get("ref")).toBe("cpk-inspector-threads");
1874
+ expectNoUtmParams(selfHostedUrl);
1875
+ expect(threadListText(inspector)).toContain("Example");
1876
+ expect(text).not.toContain("No threads yet");
1877
+ expect(
1878
+ inspector.shadowRoot?.querySelector("cpk-thread-details"),
1879
+ ).toBeNull();
1880
+ expect(
1881
+ (inspector as unknown as InspectorThreadViewInternals).selectedThreadId,
1882
+ ).toBeNull();
1883
+
1816
1884
  const engineer = inspector.shadowRoot?.querySelector<HTMLAnchorElement>(
1817
1885
  'a[href^="https://www.copilotkit.ai/talk-to-an-engineer"]',
1818
1886
  );
1819
- expect(engineer?.href).toBe(
1820
- "https://www.copilotkit.ai/talk-to-an-engineer?ref=cpk-inspector-threads",
1887
+ const engineerUrl = new URL(engineer!.href);
1888
+ expect(engineerUrl.origin).toBe("https://www.copilotkit.ai");
1889
+ expect(engineerUrl.pathname).toBe("/talk-to-an-engineer");
1890
+ expect(engineerUrl.searchParams.get("ref")).toBe("cpk-inspector-threads");
1891
+ expectNoUtmParams(engineerUrl);
1892
+ expect(engineer?.closest("#cpk-main-scroll")).toBeNull();
1893
+ });
1894
+
1895
+ it("defers loading the empty overview video until after the overview paints", async () => {
1896
+ vi.useFakeTimers();
1897
+ vi.stubGlobal("matchMedia", () => ({ matches: false }));
1898
+
1899
+ const { agent } = createMockAgent("alpha");
1900
+ const harness = createHeaderMockCore({ alpha: agent }, {}, {}, false);
1901
+
1902
+ const inspector = new WebInspectorElement();
1903
+ document.body.appendChild(inspector);
1904
+ inspector.core = harness.core as unknown as WebInspectorElement["core"];
1905
+ harness.emitAgentsChanged();
1906
+
1907
+ const internals = inspector as unknown as {
1908
+ isOpen: boolean;
1909
+ handleMenuSelect: (key: "threads") => void;
1910
+ };
1911
+ internals.isOpen = true;
1912
+ internals.handleMenuSelect("threads");
1913
+ await inspector.updateComplete;
1914
+
1915
+ expect(inspector.shadowRoot?.textContent ?? "").toContain(
1916
+ "Threads are persistent, inspectable conversations",
1917
+ );
1918
+ expect(
1919
+ inspector.shadowRoot?.querySelector(".cpk-threads-overview-video-frame"),
1920
+ ).not.toBeNull();
1921
+ expect(
1922
+ inspector.shadowRoot?.querySelector(".cpk-threads-overview-video"),
1923
+ ).toBeNull();
1924
+
1925
+ await vi.advanceTimersByTimeAsync(450);
1926
+ await inspector.updateComplete;
1927
+
1928
+ const video = inspector.shadowRoot?.querySelector<HTMLVideoElement>(
1929
+ ".cpk-threads-overview-video",
1930
+ );
1931
+ expect(video?.src).toBe(
1932
+ "https://cdn.copilotkit.ai/corp-site/videos/copilotkit-generative-ui-agentic-frontend-demo.webm",
1933
+ );
1934
+ });
1935
+
1936
+ it("does not load the empty overview video when reduced motion is preferred", async () => {
1937
+ vi.useFakeTimers();
1938
+ vi.stubGlobal("matchMedia", () => ({ matches: true }));
1939
+
1940
+ const { agent } = createMockAgent("alpha");
1941
+ const harness = createHeaderMockCore({ alpha: agent }, {}, {}, false);
1942
+
1943
+ const inspector = new WebInspectorElement();
1944
+ document.body.appendChild(inspector);
1945
+ inspector.core = harness.core as unknown as WebInspectorElement["core"];
1946
+ harness.emitAgentsChanged();
1947
+
1948
+ const internals = inspector as unknown as {
1949
+ isOpen: boolean;
1950
+ handleMenuSelect: (key: "threads") => void;
1951
+ };
1952
+ internals.isOpen = true;
1953
+ internals.handleMenuSelect("threads");
1954
+ await inspector.updateComplete;
1955
+
1956
+ expect(inspector.shadowRoot?.textContent ?? "").toContain(
1957
+ "Threads are persistent, inspectable conversations",
1821
1958
  );
1959
+ expect(
1960
+ inspector.shadowRoot?.querySelector(".cpk-threads-overview-video-frame"),
1961
+ ).not.toBeNull();
1962
+
1963
+ await vi.advanceTimersByTimeAsync(1200);
1964
+ await inspector.updateComplete;
1965
+
1966
+ expect(
1967
+ inspector.shadowRoot?.querySelector(".cpk-threads-overview-video"),
1968
+ ).toBeNull();
1969
+ expect(vi.getTimerCount()).toBe(0);
1970
+ });
1971
+
1972
+ it("clears the deferred video timeout when disconnected before load", async () => {
1973
+ vi.useFakeTimers();
1974
+ vi.stubGlobal("matchMedia", () => ({ matches: false }));
1975
+ Object.defineProperty(window, "requestIdleCallback", {
1976
+ configurable: true,
1977
+ value: undefined,
1978
+ });
1979
+ const clearTimeoutSpy = vi.spyOn(window, "clearTimeout");
1980
+
1981
+ const { agent } = createMockAgent("alpha");
1982
+ const harness = createHeaderMockCore({ alpha: agent }, {}, {}, false);
1983
+
1984
+ const inspector = new WebInspectorElement();
1985
+ document.body.appendChild(inspector);
1986
+ inspector.core = harness.core as unknown as WebInspectorElement["core"];
1987
+ harness.emitAgentsChanged();
1988
+
1989
+ const internals = inspector as unknown as {
1990
+ isOpen: boolean;
1991
+ handleMenuSelect: (key: "threads") => void;
1992
+ };
1993
+ internals.isOpen = true;
1994
+ internals.handleMenuSelect("threads");
1995
+ await inspector.updateComplete;
1996
+
1997
+ expect(vi.getTimerCount()).toBe(1);
1998
+ inspector.remove();
1999
+ expect(clearTimeoutSpy).toHaveBeenCalled();
2000
+ expect(vi.getTimerCount()).toBe(0);
2001
+ });
2002
+
2003
+ it("cancels the deferred video idle callback when disconnected before load", async () => {
2004
+ vi.useFakeTimers();
2005
+ vi.stubGlobal("matchMedia", () => ({ matches: false }));
2006
+ const requestIdleCallback = vi.fn(() => 123);
2007
+ const cancelIdleCallback = vi.fn();
2008
+ Object.defineProperty(window, "requestIdleCallback", {
2009
+ configurable: true,
2010
+ value: requestIdleCallback,
2011
+ });
2012
+ Object.defineProperty(window, "cancelIdleCallback", {
2013
+ configurable: true,
2014
+ value: cancelIdleCallback,
2015
+ });
2016
+
2017
+ const { agent } = createMockAgent("alpha");
2018
+ const harness = createHeaderMockCore({ alpha: agent }, {}, {}, false);
2019
+
2020
+ const inspector = new WebInspectorElement();
2021
+ document.body.appendChild(inspector);
2022
+ inspector.core = harness.core as unknown as WebInspectorElement["core"];
2023
+ harness.emitAgentsChanged();
2024
+
2025
+ const internals = inspector as unknown as {
2026
+ isOpen: boolean;
2027
+ handleMenuSelect: (key: "threads") => void;
2028
+ };
2029
+ internals.isOpen = true;
2030
+ internals.handleMenuSelect("threads");
2031
+ await inspector.updateComplete;
2032
+
2033
+ expect(requestIdleCallback).toHaveBeenCalledTimes(1);
2034
+ inspector.remove();
2035
+ expect(cancelIdleCallback).toHaveBeenCalledWith(123);
2036
+ });
2037
+
2038
+ it("does not render example threads once real threads are present", async () => {
2039
+ const inspector = new WebInspectorElement();
2040
+ document.body.appendChild(inspector);
2041
+ inspector.core = createHeaderMockCore({}, {}, {}, true)
2042
+ .core as unknown as WebInspectorElement["core"];
2043
+
2044
+ const internals = inspector as unknown as InspectorThreadViewInternals;
2045
+ internals.isOpen = true;
2046
+ internals.selectedMenu = "threads";
2047
+ internals._threads = [
2048
+ {
2049
+ id: "real-thread",
2050
+ name: "Real customer thread",
2051
+ agentId: "alpha",
2052
+ updatedAt: "2026-06-25T10:00:00.000Z",
2053
+ },
2054
+ ];
2055
+ internals._threadsByAgent = new Map([["alpha", internals._threads]]);
2056
+ inspector.requestUpdate();
2057
+ await inspector.updateComplete;
2058
+
2059
+ const text = threadListText(inspector);
2060
+ expect(text).toContain("Real customer thread");
2061
+ expect(text).not.toContain("Realtime thread sync");
2062
+ expect(text).not.toContain("Example");
2063
+ });
2064
+
2065
+ it("selects an example thread, shows the tour, and toggles back to the overview on second click", async () => {
2066
+ const { agent } = createMockAgent("alpha");
2067
+ const harness = createHeaderMockCore({ alpha: agent }, {}, {}, true);
2068
+
2069
+ const inspector = new WebInspectorElement();
2070
+ document.body.appendChild(inspector);
2071
+ inspector.core = harness.core as unknown as WebInspectorElement["core"];
2072
+ harness.emitAgentsChanged();
2073
+
2074
+ const internals = inspector as unknown as {
2075
+ isOpen: boolean;
2076
+ handleMenuSelect: (key: "threads") => void;
2077
+ selectedThreadId: string | null;
2078
+ };
2079
+ internals.isOpen = true;
2080
+ internals.handleMenuSelect("threads");
2081
+ await inspector.updateComplete;
2082
+
2083
+ await vi.waitFor(() => {
2084
+ expect(threadListText(inspector)).toContain("Realtime thread sync");
2085
+ });
2086
+
2087
+ const threadList = inspector.shadowRoot?.querySelector("cpk-thread-list");
2088
+ const firstRow =
2089
+ threadList?.shadowRoot?.querySelector<HTMLElement>(".cpk-tl__item");
2090
+ expect(firstRow).toBeDefined();
2091
+
2092
+ firstRow!.click();
2093
+ await inspector.updateComplete;
2094
+ await vi.waitFor(() => {
2095
+ expect(internals.selectedThreadId).toBe("example-realtime-sync");
2096
+ expect(
2097
+ inspector.shadowRoot?.querySelector("cpk-thread-details"),
2098
+ ).not.toBe(null);
2099
+ expect(inspector.shadowRoot?.textContent ?? "").toContain(
2100
+ "Read the run as a story",
2101
+ );
2102
+ });
2103
+
2104
+ firstRow!.click();
2105
+ await inspector.updateComplete;
2106
+
2107
+ await vi.waitFor(() => {
2108
+ expect(internals.selectedThreadId).toBeNull();
2109
+ expect(
2110
+ inspector.shadowRoot?.querySelector("cpk-thread-details"),
2111
+ ).toBeNull();
2112
+ expect(inspector.shadowRoot?.textContent ?? "").toContain(
2113
+ "Threads are persistent, inspectable conversations",
2114
+ );
2115
+ });
2116
+ });
2117
+
2118
+ it("persists example tour dismissal so it does not auto-open again", async () => {
2119
+ const stored = new Map<string, string>();
2120
+ vi.stubGlobal("localStorage", {
2121
+ getItem: (key: string) => stored.get(key) ?? null,
2122
+ setItem: (key: string, value: string) => stored.set(key, value),
2123
+ removeItem: (key: string) => stored.delete(key),
2124
+ clear: () => stored.clear(),
2125
+ get length() {
2126
+ return stored.size;
2127
+ },
2128
+ key: (index: number) => Array.from(stored.keys())[index] ?? null,
2129
+ });
2130
+
2131
+ const { agent } = createMockAgent("alpha");
2132
+ const harness = createHeaderMockCore({ alpha: agent }, {}, {}, false);
2133
+
2134
+ const inspector = new WebInspectorElement();
2135
+ document.body.appendChild(inspector);
2136
+ inspector.core = harness.core as unknown as WebInspectorElement["core"];
2137
+ harness.emitAgentsChanged();
2138
+
2139
+ const internals = inspector as unknown as {
2140
+ isOpen: boolean;
2141
+ handleMenuSelect: (key: "threads") => void;
2142
+ };
2143
+ internals.isOpen = true;
2144
+ internals.handleMenuSelect("threads");
2145
+ await inspector.updateComplete;
2146
+
2147
+ await vi.waitFor(() => {
2148
+ expect(threadListText(inspector)).toContain("Realtime thread sync");
2149
+ });
2150
+
2151
+ const firstRow = inspector.shadowRoot
2152
+ ?.querySelector("cpk-thread-list")
2153
+ ?.shadowRoot?.querySelector<HTMLElement>(".cpk-tl__item");
2154
+ firstRow!.click();
2155
+ await inspector.updateComplete;
2156
+
2157
+ await vi.waitFor(() => {
2158
+ expect(inspector.shadowRoot?.textContent ?? "").toContain(
2159
+ "Read the run as a story",
2160
+ );
2161
+ });
2162
+
2163
+ const skip = Array.from(
2164
+ inspector.shadowRoot?.querySelectorAll<HTMLButtonElement>("button") ?? [],
2165
+ ).find((button) => button.textContent?.trim() === "Skip");
2166
+ expect(skip).toBeDefined();
2167
+ skip!.click();
2168
+ await inspector.updateComplete;
2169
+
2170
+ expect(stored.get("cpk:inspector:threads-example-tour:v1")).toContain(
2171
+ '"dismissed":true',
2172
+ );
2173
+
2174
+ const secondInspector = new WebInspectorElement();
2175
+ document.body.appendChild(secondInspector);
2176
+ secondInspector.core =
2177
+ harness.core as unknown as WebInspectorElement["core"];
2178
+ harness.emitAgentsChanged();
2179
+ const secondInternals = secondInspector as unknown as {
2180
+ isOpen: boolean;
2181
+ handleMenuSelect: (key: "threads") => void;
2182
+ };
2183
+ secondInternals.isOpen = true;
2184
+ secondInternals.handleMenuSelect("threads");
2185
+ await secondInspector.updateComplete;
2186
+
2187
+ await vi.waitFor(() => {
2188
+ expect(threadListText(secondInspector)).toContain("Realtime thread sync");
2189
+ });
2190
+
2191
+ const secondRow = secondInspector.shadowRoot
2192
+ ?.querySelector("cpk-thread-list")
2193
+ ?.shadowRoot?.querySelector<HTMLElement>(".cpk-tl__item");
2194
+ secondRow!.click();
2195
+ await secondInspector.updateComplete;
2196
+
2197
+ await vi.waitFor(() => {
2198
+ expect(
2199
+ secondInspector.shadowRoot?.querySelector("cpk-thread-details"),
2200
+ ).not.toBeNull();
2201
+ });
2202
+ expect(secondInspector.shadowRoot?.textContent ?? "").not.toContain(
2203
+ "Read the run as a story",
2204
+ );
2205
+ expect(secondInspector.shadowRoot?.textContent ?? "").toContain(
2206
+ "Show tour",
2207
+ );
2208
+
2209
+ const showTour = Array.from(
2210
+ secondInspector.shadowRoot?.querySelectorAll<HTMLButtonElement>(
2211
+ "button",
2212
+ ) ?? [],
2213
+ ).find((button) => button.textContent?.trim() === "Show tour");
2214
+ expect(showTour).toBeDefined();
2215
+ showTour!.click();
2216
+ await secondInspector.updateComplete;
2217
+
2218
+ await vi.waitFor(() => {
2219
+ expect(secondInspector.shadowRoot?.textContent ?? "").toContain(
2220
+ "Read the run as a story",
2221
+ );
2222
+ });
2223
+ });
2224
+
2225
+ it("tracks example thread selection and tour dismissal telemetry", async () => {
2226
+ const { agent } = createMockAgent("alpha");
2227
+ const harness = createHeaderMockCore({ alpha: agent }, {}, {}, false);
2228
+
2229
+ const inspector = new WebInspectorElement();
2230
+ document.body.appendChild(inspector);
2231
+ inspector.core = harness.core as unknown as WebInspectorElement["core"];
2232
+ harness.emitAgentsChanged();
2233
+
2234
+ const internals = inspector as unknown as {
2235
+ isOpen: boolean;
2236
+ handleMenuSelect: (key: "threads") => void;
2237
+ };
2238
+ internals.isOpen = true;
2239
+ internals.handleMenuSelect("threads");
2240
+ await inspector.updateComplete;
2241
+
2242
+ await vi.waitFor(() => {
2243
+ expect(threadListText(inspector)).toContain("Realtime thread sync");
2244
+ });
2245
+
2246
+ const firstRow = inspector.shadowRoot
2247
+ ?.querySelector("cpk-thread-list")
2248
+ ?.shadowRoot?.querySelector<HTMLElement>(".cpk-tl__item");
2249
+ firstRow!.click();
2250
+ await inspector.updateComplete;
2251
+
2252
+ await vi.waitFor(() => {
2253
+ expect(inspector.shadowRoot?.textContent ?? "").toContain(
2254
+ "Read the run as a story",
2255
+ );
2256
+ });
2257
+
2258
+ const skip = Array.from(
2259
+ inspector.shadowRoot?.querySelectorAll<HTMLButtonElement>("button") ?? [],
2260
+ ).find((button) => button.textContent?.trim() === "Skip");
2261
+ skip!.click();
2262
+ await inspector.updateComplete;
2263
+
2264
+ const posts = telemetryPosts();
2265
+ expect(
2266
+ posts.some(
2267
+ (post) => post.event === "oss.inspector.threads_example_selected",
2268
+ ),
2269
+ ).toBe(true);
2270
+ expect(
2271
+ posts.some(
2272
+ (post) => post.event === "oss.inspector.threads_example_tour_started",
2273
+ ),
2274
+ ).toBe(true);
2275
+ const stepViewed = posts.find(
2276
+ (post) => post.event === "oss.inspector.threads_example_tour_step_viewed",
2277
+ );
2278
+ expect(stepViewed?.properties).toMatchObject({
2279
+ example_thread_id: "example-realtime-sync",
2280
+ tour_step: 1,
2281
+ });
2282
+ const dismissed = posts.find(
2283
+ (post) => post.event === "oss.inspector.threads_example_tour_dismissed",
2284
+ );
2285
+ expect(dismissed?.properties).toMatchObject({
2286
+ example_thread_id: "example-realtime-sync",
2287
+ dismiss_method: "skip",
2288
+ });
1822
2289
  });
1823
2290
  });
1824
2291
 
@@ -2099,6 +2566,29 @@ describe("WebInspectorElement memories — view states", () => {
2099
2566
  ).toBeNull();
2100
2567
  });
2101
2568
 
2569
+ it("does not use Threads onboarding UTM attribution for locked memory CTAs", async () => {
2570
+ const core = makeCoreNoIntelligence();
2571
+ const el = await mountMemories(core);
2572
+
2573
+ const talkToEngineer = el.shadowRoot?.querySelector<HTMLAnchorElement>(
2574
+ 'a[href^="https://www.copilotkit.ai/talk-to-an-engineer"]',
2575
+ );
2576
+ const signup = el.shadowRoot?.querySelector<HTMLAnchorElement>(
2577
+ 'a[href^="https://go.copilotkit.ai/intelligence-signup"]',
2578
+ );
2579
+
2580
+ expect(talkToEngineer).not.toBeNull();
2581
+ expect(signup).not.toBeNull();
2582
+
2583
+ for (const href of [talkToEngineer!.href, signup!.href]) {
2584
+ const url = new URL(href);
2585
+ expect(url.searchParams.get("ref")).toBeTruthy();
2586
+ expect(url.searchParams.has("utm_source")).toBe(false);
2587
+ expect(url.searchParams.has("utm_medium")).toBe(false);
2588
+ expect(url.searchParams.has("utm_campaign")).toBe(false);
2589
+ }
2590
+ });
2591
+
2102
2592
  it("renders the locked teaser when memories are unavailable", async () => {
2103
2593
  const core = makeCoreWithMemory([], { available: false });
2104
2594
  const el = await mountMemories(core);