@copilotkit/web-inspector 1.61.0 → 1.61.2
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 +512 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +23 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +513 -79
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +614 -109
- package/dist/index.umd.js.map +1 -1
- package/dist/lib/context-helpers.cjs.map +1 -1
- package/dist/lib/context-helpers.mjs.map +1 -1
- package/dist/lib/persistence.cjs.map +1 -1
- package/dist/lib/persistence.mjs.map +1 -1
- package/dist/lib/telemetry.cjs +78 -12
- package/dist/lib/telemetry.cjs.map +1 -1
- package/dist/lib/telemetry.mjs +72 -13
- package/dist/lib/telemetry.mjs.map +1 -1
- package/dist/lib/types.d.cts +8 -0
- package/dist/lib/types.d.cts.map +1 -0
- package/dist/lib/types.d.mts +8 -0
- package/dist/lib/types.d.mts.map +1 -0
- package/dist/package.cjs +12 -0
- package/dist/package.cjs.map +1 -0
- package/dist/package.mjs +6 -0
- package/dist/package.mjs.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/web-inspector.spec.ts +399 -1
- package/src/index.ts +649 -109
- package/src/lib/__tests__/telemetry.test.ts +150 -10
- package/src/lib/context-helpers.ts +1 -1
- package/src/lib/persistence.ts +1 -1
- package/src/lib/telemetry.ts +162 -18
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebInspectorElement, ɵCpkThreadDetails } from "../index";
|
|
1
|
+
import { WebInspectorElement, ɵCpkThreadDetails } from "../index.js";
|
|
2
2
|
import type { CopilotKitCore } from "@copilotkit/core";
|
|
3
3
|
import { CopilotKitCoreRuntimeConnectionStatus } from "@copilotkit/core";
|
|
4
4
|
import type { CopilotKitCoreSubscriber } from "@copilotkit/core";
|
|
@@ -306,6 +306,9 @@ describe("WebInspectorElement", () => {
|
|
|
306
306
|
|
|
307
307
|
type ThreadDetailsInternals = {
|
|
308
308
|
threadId: string | null;
|
|
309
|
+
runtimeUrl: string;
|
|
310
|
+
headers: Record<string, string>;
|
|
311
|
+
threadInspectionAvailable: boolean;
|
|
309
312
|
liveMessageVersion: number;
|
|
310
313
|
_conversation: Array<Record<string, unknown>>;
|
|
311
314
|
_fetchedState: Record<string, unknown> | null;
|
|
@@ -318,6 +321,9 @@ type ThreadDetailsInternals = {
|
|
|
318
321
|
_loadingState: boolean;
|
|
319
322
|
_loadingEvents: boolean;
|
|
320
323
|
_panelTplCache: Map<string, { key: readonly unknown[]; tpl: unknown }>;
|
|
324
|
+
fetchMessages: (threadId: string) => Promise<void>;
|
|
325
|
+
fetchEvents: (threadId: string) => Promise<void>;
|
|
326
|
+
fetchState: (threadId: string) => Promise<void>;
|
|
321
327
|
renderConversation: () => unknown;
|
|
322
328
|
renderState: () => unknown;
|
|
323
329
|
renderEvents: () => unknown;
|
|
@@ -377,6 +383,67 @@ describe("ɵCpkThreadDetails caching", () => {
|
|
|
377
383
|
expect(internals._panelTplCache.size).toBe(0);
|
|
378
384
|
});
|
|
379
385
|
|
|
386
|
+
it("does not fetch messages, events, or state when threadInspectionAvailable is omitted", async () => {
|
|
387
|
+
const fetchSpy = vi
|
|
388
|
+
.spyOn(globalThis, "fetch")
|
|
389
|
+
.mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
|
|
390
|
+
try {
|
|
391
|
+
const { el, internals } = createThreadDetails();
|
|
392
|
+
|
|
393
|
+
internals.runtimeUrl = "http://localhost:4000";
|
|
394
|
+
internals.headers = { Authorization: "Bearer test-token" };
|
|
395
|
+
internals.threadId = "t1";
|
|
396
|
+
await el.updateComplete;
|
|
397
|
+
|
|
398
|
+
expect(internals.threadInspectionAvailable).toBe(false);
|
|
399
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
400
|
+
|
|
401
|
+
await internals.fetchEvents("t1");
|
|
402
|
+
await internals.fetchState("t1");
|
|
403
|
+
|
|
404
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
405
|
+
} finally {
|
|
406
|
+
fetchSpy.mockRestore();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("joins thread inspection URLs without double slashes when runtimeUrl has a trailing slash", async () => {
|
|
411
|
+
const fetchSpy = vi
|
|
412
|
+
.spyOn(globalThis, "fetch")
|
|
413
|
+
.mockImplementation(async (input) => {
|
|
414
|
+
const url = String(input);
|
|
415
|
+
if (url.endsWith("/messages")) {
|
|
416
|
+
return new Response(JSON.stringify({ messages: [] }), {
|
|
417
|
+
status: 200,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (url.endsWith("/events")) {
|
|
421
|
+
return new Response(JSON.stringify({ events: [] }), { status: 200 });
|
|
422
|
+
}
|
|
423
|
+
return new Response(JSON.stringify({ state: null }), { status: 200 });
|
|
424
|
+
});
|
|
425
|
+
try {
|
|
426
|
+
const { el, internals } = createThreadDetails();
|
|
427
|
+
internals.runtimeUrl = "http://localhost:4000/api/";
|
|
428
|
+
internals.threadInspectionAvailable = true;
|
|
429
|
+
internals.threadId = "thread one";
|
|
430
|
+
await el.updateComplete;
|
|
431
|
+
fetchSpy.mockClear();
|
|
432
|
+
|
|
433
|
+
await internals.fetchMessages("thread one");
|
|
434
|
+
await internals.fetchEvents("thread one");
|
|
435
|
+
await internals.fetchState("thread one");
|
|
436
|
+
|
|
437
|
+
expect(fetchSpy.mock.calls.map((call) => String(call[0]))).toEqual([
|
|
438
|
+
"http://localhost:4000/api/threads/thread%20one/messages",
|
|
439
|
+
"http://localhost:4000/api/threads/thread%20one/events",
|
|
440
|
+
"http://localhost:4000/api/threads/thread%20one/state",
|
|
441
|
+
]);
|
|
442
|
+
} finally {
|
|
443
|
+
fetchSpy.mockRestore();
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
380
447
|
it("conversation cache invalidates when _conversation is reassigned", async () => {
|
|
381
448
|
const { el, internals } = createThreadDetails();
|
|
382
449
|
await settleThread(el, internals, "t1");
|
|
@@ -575,3 +642,334 @@ describe("WebInspectorElement announcement preview dismissal", () => {
|
|
|
575
642
|
expect(a.hasUnseenAnnouncement).toBe(true);
|
|
576
643
|
});
|
|
577
644
|
});
|
|
645
|
+
|
|
646
|
+
// --- Owned thread store header forwarding (issue #5581) ---
|
|
647
|
+
//
|
|
648
|
+
// When useThreads() isn't mounted, the inspector creates its own thread store
|
|
649
|
+
// per agent (ensureOwnedThreadStore). That store's /threads requests must carry
|
|
650
|
+
// the headers configured on <CopilotKit> (e.g. X-CSRF / auth), otherwise the
|
|
651
|
+
// requests 403 in environments that enforce CSRF/auth checks.
|
|
652
|
+
|
|
653
|
+
type HeaderMockCore = {
|
|
654
|
+
agents: Record<string, AbstractAgent>;
|
|
655
|
+
context: Record<string, unknown>;
|
|
656
|
+
properties: Record<string, unknown>;
|
|
657
|
+
telemetryDisabled: boolean;
|
|
658
|
+
runtimeConnectionStatus: CopilotKitCoreRuntimeConnectionStatus;
|
|
659
|
+
runtimeUrl: string;
|
|
660
|
+
headers: Record<string, string>;
|
|
661
|
+
threadEndpoints: {
|
|
662
|
+
list: boolean;
|
|
663
|
+
inspect: boolean;
|
|
664
|
+
mutations: boolean;
|
|
665
|
+
realtimeMetadata: boolean;
|
|
666
|
+
};
|
|
667
|
+
subscribe: (subscriber: CopilotKitCoreSubscriber) => {
|
|
668
|
+
unsubscribe: () => void;
|
|
669
|
+
};
|
|
670
|
+
getThreadStores: () => Record<string, never>;
|
|
671
|
+
getThreadStore: (agentId: string) => undefined;
|
|
672
|
+
registerThreadStore: (agentId: string, store: unknown) => void;
|
|
673
|
+
unregisterThreadStore: (agentId: string) => void;
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
function createHeaderMockCore(
|
|
677
|
+
agents: Record<string, AbstractAgent>,
|
|
678
|
+
headers: Record<string, string>,
|
|
679
|
+
endpointOverrides: Partial<HeaderMockCore["threadEndpoints"]> = {},
|
|
680
|
+
telemetryDisabled = true,
|
|
681
|
+
) {
|
|
682
|
+
const subscribers = new Set<CopilotKitCoreSubscriber>();
|
|
683
|
+
const core: HeaderMockCore = {
|
|
684
|
+
agents,
|
|
685
|
+
context: {},
|
|
686
|
+
properties: {},
|
|
687
|
+
telemetryDisabled,
|
|
688
|
+
runtimeConnectionStatus: CopilotKitCoreRuntimeConnectionStatus.Connected,
|
|
689
|
+
runtimeUrl: "http://localhost/api",
|
|
690
|
+
headers,
|
|
691
|
+
threadEndpoints: {
|
|
692
|
+
list: true,
|
|
693
|
+
inspect: true,
|
|
694
|
+
mutations: true,
|
|
695
|
+
realtimeMetadata: true,
|
|
696
|
+
...endpointOverrides,
|
|
697
|
+
},
|
|
698
|
+
subscribe(subscriber: CopilotKitCoreSubscriber) {
|
|
699
|
+
subscribers.add(subscriber);
|
|
700
|
+
return { unsubscribe: () => subscribers.delete(subscriber) };
|
|
701
|
+
},
|
|
702
|
+
getThreadStores() {
|
|
703
|
+
return {};
|
|
704
|
+
},
|
|
705
|
+
getThreadStore() {
|
|
706
|
+
return undefined;
|
|
707
|
+
},
|
|
708
|
+
registerThreadStore() {},
|
|
709
|
+
unregisterThreadStore() {},
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
const asCore = () => core as unknown as CopilotKitCore;
|
|
713
|
+
return {
|
|
714
|
+
core,
|
|
715
|
+
emitAgentsChanged() {
|
|
716
|
+
subscribers.forEach((s) =>
|
|
717
|
+
s.onAgentsChanged?.({ copilotkit: asCore(), agents: core.agents }),
|
|
718
|
+
);
|
|
719
|
+
},
|
|
720
|
+
emitHeadersChanged(nextHeaders: Record<string, string>) {
|
|
721
|
+
core.headers = nextHeaders;
|
|
722
|
+
subscribers.forEach((s) =>
|
|
723
|
+
s.onHeadersChanged?.({ copilotkit: asCore(), headers: nextHeaders }),
|
|
724
|
+
);
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const headersOf = (call: unknown[]) =>
|
|
730
|
+
(call[1] as { headers?: Record<string, string> } | undefined)?.headers ?? {};
|
|
731
|
+
|
|
732
|
+
describe("WebInspectorElement owned thread store headers (#5581)", () => {
|
|
733
|
+
let fetchMock: ReturnType<typeof vi.fn>;
|
|
734
|
+
|
|
735
|
+
const threadListCalls = () =>
|
|
736
|
+
fetchMock.mock.calls.filter((call) =>
|
|
737
|
+
String(call[0]).includes("/threads?"),
|
|
738
|
+
);
|
|
739
|
+
const telemetryPosts = () =>
|
|
740
|
+
fetchMock.mock.calls
|
|
741
|
+
.filter(
|
|
742
|
+
(call) =>
|
|
743
|
+
String(call[0]) === "https://telemetry.copilotkit.ai/ingest" &&
|
|
744
|
+
(call[1] as RequestInit | undefined)?.method === "POST",
|
|
745
|
+
)
|
|
746
|
+
.map((call) => {
|
|
747
|
+
const body =
|
|
748
|
+
((call[1] as RequestInit | undefined)?.body as string) ?? "{}";
|
|
749
|
+
return JSON.parse(body) as {
|
|
750
|
+
event: string;
|
|
751
|
+
properties: Record<string, unknown>;
|
|
752
|
+
};
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
beforeEach(() => {
|
|
756
|
+
document.body.innerHTML = "";
|
|
757
|
+
fetchMock = vi.fn(() =>
|
|
758
|
+
Promise.resolve({
|
|
759
|
+
ok: true,
|
|
760
|
+
status: 200,
|
|
761
|
+
json: () => Promise.resolve({ threads: [] }),
|
|
762
|
+
}),
|
|
763
|
+
);
|
|
764
|
+
// The owned store captures globalThis.fetch when it's created, so stub
|
|
765
|
+
// before the inspector attaches to the core.
|
|
766
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
afterEach(() => {
|
|
770
|
+
vi.unstubAllGlobals();
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it("forwards core headers on the owned store's /threads request", async () => {
|
|
774
|
+
const { agent } = createMockAgent("alpha");
|
|
775
|
+
const harness = createHeaderMockCore(
|
|
776
|
+
{ alpha: agent },
|
|
777
|
+
{ "X-CSRF": "1", Authorization: "Bearer abc" },
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
const inspector = new WebInspectorElement();
|
|
781
|
+
document.body.appendChild(inspector);
|
|
782
|
+
inspector.core = harness.core as unknown as WebInspectorElement["core"];
|
|
783
|
+
harness.emitAgentsChanged();
|
|
784
|
+
|
|
785
|
+
await vi.waitFor(() => {
|
|
786
|
+
expect(threadListCalls().length).toBeGreaterThan(0);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
expect(headersOf(threadListCalls()[0]!)).toMatchObject({
|
|
790
|
+
"X-CSRF": "1",
|
|
791
|
+
Authorization: "Bearer abc",
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it("re-applies headers on the owned store when core headers change", async () => {
|
|
796
|
+
const { agent } = createMockAgent("alpha");
|
|
797
|
+
const harness = createHeaderMockCore({ alpha: agent }, { "X-CSRF": "1" });
|
|
798
|
+
|
|
799
|
+
const inspector = new WebInspectorElement();
|
|
800
|
+
document.body.appendChild(inspector);
|
|
801
|
+
inspector.core = harness.core as unknown as WebInspectorElement["core"];
|
|
802
|
+
harness.emitAgentsChanged();
|
|
803
|
+
|
|
804
|
+
await vi.waitFor(() => {
|
|
805
|
+
expect(threadListCalls().length).toBeGreaterThan(0);
|
|
806
|
+
});
|
|
807
|
+
const callsBefore = threadListCalls().length;
|
|
808
|
+
|
|
809
|
+
harness.emitHeadersChanged({ "X-CSRF": "2" });
|
|
810
|
+
|
|
811
|
+
await vi.waitFor(() => {
|
|
812
|
+
expect(threadListCalls().length).toBeGreaterThan(callsBefore);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
expect(headersOf(threadListCalls().at(-1)!)).toMatchObject({
|
|
816
|
+
"X-CSRF": "2",
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it("shows the locked Intelligence state when thread listing is unavailable without fetching threads", async () => {
|
|
821
|
+
const { agent } = createMockAgent("alpha");
|
|
822
|
+
const harness = createHeaderMockCore(
|
|
823
|
+
{ alpha: agent },
|
|
824
|
+
{ "X-CSRF": "1" },
|
|
825
|
+
{ list: false },
|
|
826
|
+
true,
|
|
827
|
+
);
|
|
828
|
+
|
|
829
|
+
const inspector = new WebInspectorElement();
|
|
830
|
+
document.body.appendChild(inspector);
|
|
831
|
+
inspector.core = harness.core as unknown as WebInspectorElement["core"];
|
|
832
|
+
harness.emitAgentsChanged();
|
|
833
|
+
|
|
834
|
+
const internals = inspector as unknown as {
|
|
835
|
+
isOpen: boolean;
|
|
836
|
+
handleMenuSelect: (key: "threads") => void;
|
|
837
|
+
};
|
|
838
|
+
internals.isOpen = true;
|
|
839
|
+
internals.handleMenuSelect("threads");
|
|
840
|
+
await inspector.updateComplete;
|
|
841
|
+
|
|
842
|
+
const text = inspector.shadowRoot?.textContent ?? "";
|
|
843
|
+
expect(text).toMatch(/Enable Intelligence to inspect Threads\./);
|
|
844
|
+
expect(text).toContain("Talk to an Engineer");
|
|
845
|
+
expect(text).toContain("Sign up for Intelligence");
|
|
846
|
+
const ctaLabels = Array.from(
|
|
847
|
+
inspector.shadowRoot?.querySelectorAll<HTMLAnchorElement>("a") ?? [],
|
|
848
|
+
).map((anchor) => anchor.textContent?.trim());
|
|
849
|
+
expect(ctaLabels).toEqual([
|
|
850
|
+
"Talk to an Engineer",
|
|
851
|
+
"Sign up for Intelligence",
|
|
852
|
+
]);
|
|
853
|
+
expect(text).not.toContain("No threads yet");
|
|
854
|
+
expect(
|
|
855
|
+
fetchMock.mock.calls.some((call) => String(call[0]).includes("/threads")),
|
|
856
|
+
).toBe(false);
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it("adds ref and posthog distinct ID attribution to locked-state CTAs", async () => {
|
|
860
|
+
const { agent } = createMockAgent("alpha");
|
|
861
|
+
const harness = createHeaderMockCore(
|
|
862
|
+
{ alpha: agent },
|
|
863
|
+
{},
|
|
864
|
+
{ list: false },
|
|
865
|
+
false,
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
const inspector = new WebInspectorElement();
|
|
869
|
+
document.body.appendChild(inspector);
|
|
870
|
+
inspector.core = harness.core as unknown as WebInspectorElement["core"];
|
|
871
|
+
harness.emitAgentsChanged();
|
|
872
|
+
|
|
873
|
+
const internals = inspector as unknown as {
|
|
874
|
+
isOpen: boolean;
|
|
875
|
+
handleMenuSelect: (key: "threads") => void;
|
|
876
|
+
};
|
|
877
|
+
internals.isOpen = true;
|
|
878
|
+
internals.handleMenuSelect("threads");
|
|
879
|
+
await inspector.updateComplete;
|
|
880
|
+
|
|
881
|
+
const signup = inspector.shadowRoot?.querySelector<HTMLAnchorElement>(
|
|
882
|
+
'a[href^="https://go.copilotkit.ai/intelligence-signup"]',
|
|
883
|
+
);
|
|
884
|
+
const engineer = inspector.shadowRoot?.querySelector<HTMLAnchorElement>(
|
|
885
|
+
'a[href^="https://www.copilotkit.ai/talk-to-an-engineer"]',
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
expect(signup).not.toBeNull();
|
|
889
|
+
expect(engineer).not.toBeNull();
|
|
890
|
+
|
|
891
|
+
const signupUrl = new URL(signup!.href);
|
|
892
|
+
expect(signupUrl.searchParams.get("ref")).toBe("cpk-inspector");
|
|
893
|
+
const distinctId = signupUrl.searchParams.get("posthog_distinct_id");
|
|
894
|
+
expect(distinctId).toMatch(/^[0-9a-f-]{36}$/);
|
|
895
|
+
|
|
896
|
+
const engineerUrl = new URL(engineer!.href);
|
|
897
|
+
expect(engineerUrl.origin).toBe("https://www.copilotkit.ai");
|
|
898
|
+
expect(engineerUrl.pathname).toBe("/talk-to-an-engineer");
|
|
899
|
+
expect(engineerUrl.searchParams.get("ref")).toBe("cpk-inspector-threads");
|
|
900
|
+
expect(engineerUrl.searchParams.get("posthog_distinct_id")).toBe(
|
|
901
|
+
distinctId,
|
|
902
|
+
);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
it("tracks Threads tab clicks through the rendered inspector menu", async () => {
|
|
906
|
+
const { agent } = createMockAgent("alpha");
|
|
907
|
+
const harness = createHeaderMockCore(
|
|
908
|
+
{ alpha: agent },
|
|
909
|
+
{},
|
|
910
|
+
{ list: false },
|
|
911
|
+
false,
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
const inspector = new WebInspectorElement();
|
|
915
|
+
document.body.appendChild(inspector);
|
|
916
|
+
inspector.core = harness.core as unknown as WebInspectorElement["core"];
|
|
917
|
+
harness.emitAgentsChanged();
|
|
918
|
+
|
|
919
|
+
const internals = inspector as unknown as { isOpen: boolean };
|
|
920
|
+
internals.isOpen = true;
|
|
921
|
+
inspector.requestUpdate();
|
|
922
|
+
await inspector.updateComplete;
|
|
923
|
+
|
|
924
|
+
const threadsButton = Array.from(
|
|
925
|
+
inspector.shadowRoot?.querySelectorAll<HTMLButtonElement>("button") ?? [],
|
|
926
|
+
).find((button) => button.textContent?.trim() === "Threads");
|
|
927
|
+
expect(threadsButton, "Threads menu button should render").toBeDefined();
|
|
928
|
+
|
|
929
|
+
threadsButton!.click();
|
|
930
|
+
await inspector.updateComplete;
|
|
931
|
+
await Promise.resolve();
|
|
932
|
+
|
|
933
|
+
const threadsTabClick = telemetryPosts().find(
|
|
934
|
+
(post) => post.event === "oss.inspector.threads_tab_clicked",
|
|
935
|
+
);
|
|
936
|
+
expect(threadsTabClick).toBeDefined();
|
|
937
|
+
expect(threadsTabClick!.properties).toMatchObject({
|
|
938
|
+
intelligence_status: "intelligence_not_enabled",
|
|
939
|
+
thread_service_status: "unavailable",
|
|
940
|
+
telemetry_disabled: false,
|
|
941
|
+
});
|
|
942
|
+
expect(threadsTabClick!.properties.distinct_id).toMatch(/^[0-9a-f-]{36}$/);
|
|
943
|
+
if (threadsTabClick!.properties.posthog_distinct_id !== undefined) {
|
|
944
|
+
expect(threadsTabClick!.properties.posthog_distinct_id).toBe(
|
|
945
|
+
threadsTabClick!.properties.distinct_id,
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
it("keeps the enabled empty Threads state when thread listing is available", async () => {
|
|
951
|
+
const { agent } = createMockAgent("alpha");
|
|
952
|
+
const harness = createHeaderMockCore({ alpha: agent }, {}, {}, true);
|
|
953
|
+
|
|
954
|
+
const inspector = new WebInspectorElement();
|
|
955
|
+
document.body.appendChild(inspector);
|
|
956
|
+
inspector.core = harness.core as unknown as WebInspectorElement["core"];
|
|
957
|
+
harness.emitAgentsChanged();
|
|
958
|
+
|
|
959
|
+
const internals = inspector as unknown as {
|
|
960
|
+
isOpen: boolean;
|
|
961
|
+
handleMenuSelect: (key: "threads") => void;
|
|
962
|
+
};
|
|
963
|
+
internals.isOpen = true;
|
|
964
|
+
internals.handleMenuSelect("threads");
|
|
965
|
+
await inspector.updateComplete;
|
|
966
|
+
|
|
967
|
+
expect(inspector.shadowRoot?.textContent ?? "").toContain("No threads yet");
|
|
968
|
+
const engineer = inspector.shadowRoot?.querySelector<HTMLAnchorElement>(
|
|
969
|
+
'a[href^="https://www.copilotkit.ai/talk-to-an-engineer"]',
|
|
970
|
+
);
|
|
971
|
+
expect(engineer?.href).toBe(
|
|
972
|
+
"https://www.copilotkit.ai/talk-to-an-engineer?ref=cpk-inspector-threads",
|
|
973
|
+
);
|
|
974
|
+
});
|
|
975
|
+
});
|