@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.
- package/dist/index.cjs +680 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +38 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +681 -49
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +757 -75
- package/dist/index.umd.js.map +1 -1
- package/dist/lib/telemetry.cjs +36 -1
- package/dist/lib/telemetry.cjs.map +1 -1
- package/dist/lib/telemetry.mjs +30 -2
- package/dist/lib/telemetry.mjs.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/styles/generated.cjs +1 -1
- package/dist/styles/generated.cjs.map +1 -1
- package/dist/styles/generated.mjs +1 -1
- package/dist/styles/generated.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/web-inspector.spec.ts +497 -7
- package/src/index.ts +892 -58
- package/src/lib/telemetry.ts +61 -0
|
@@ -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
|
|
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://
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
1820
|
-
|
|
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);
|