@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/src/index.ts CHANGED
@@ -28,7 +28,7 @@ import type {
28
28
  DockMode,
29
29
  Position,
30
30
  Size,
31
- } from "./lib/types";
31
+ } from "./lib/types.js";
32
32
  import {
33
33
  applyAnchorPosition as applyAnchorPositionHelper,
34
34
  centerContext as centerContextHelper,
@@ -37,7 +37,7 @@ import {
37
37
  updateAnchorFromPosition as updateAnchorFromPositionHelper,
38
38
  updateSizeFromElement,
39
39
  clampSize as clampSizeToViewport,
40
- } from "./lib/context-helpers";
40
+ } from "./lib/context-helpers.js";
41
41
  import {
42
42
  loadInspectorState,
43
43
  saveInspectorState,
@@ -45,17 +45,27 @@ import {
45
45
  isValidPosition,
46
46
  isValidSize,
47
47
  isValidDockMode,
48
- } from "./lib/persistence";
49
- import type { PersistedState } from "./lib/persistence";
48
+ } from "./lib/persistence.js";
49
+ import type { PersistedState } from "./lib/persistence.js";
50
50
  import {
51
51
  TELEMETRY_DOCS_URL,
52
52
  ensureTelemetryDistinctId,
53
+ getRuntimeUrlType,
53
54
  getTelemetryDistinctIdForUrl,
54
55
  maybeShowDisclosure,
55
56
  trackBannerClicked,
56
57
  trackBannerViewed,
58
+ trackTalkToEngineerClicked,
59
+ trackThreadsEmptyEnabledViewed,
60
+ trackThreadsEnabledViewed,
61
+ trackThreadsIntelligenceSignupClicked,
62
+ trackThreadsLockedViewed,
57
63
  trackThreadsTabClicked,
58
- } from "./lib/telemetry";
64
+ trackThreadsTalkToEngineerClicked,
65
+ } from "./lib/telemetry.js";
66
+ import type { InspectorThreadTelemetryProps } from "./lib/telemetry.js";
67
+
68
+ export type { Anchor } from "./lib/types.js";
59
69
 
60
70
  export const WEB_INSPECTOR_TAG = "cpk-web-inspector" as const;
61
71
 
@@ -88,6 +98,10 @@ const DEFAULT_WINDOW_SIZE: Size = { width: 840, height: 700 };
88
98
  const DOCKED_LEFT_WIDTH = 500; // Sensible width for left dock with collapsed sidebar
89
99
  const MAX_AGENT_EVENTS = 200;
90
100
  const MAX_TOTAL_EVENTS = 500;
101
+ const INTELLIGENCE_SIGNUP_URL = "https://go.copilotkit.ai/intelligence-signup";
102
+ const TALK_TO_ENGINEER_URL = "https://www.copilotkit.ai/talk-to-an-engineer";
103
+
104
+ type ThreadServiceStatus = "available" | "unavailable" | "unknown" | "error";
91
105
 
92
106
  type InspectorAgentEventType =
93
107
  | "RUN_STARTED"
@@ -334,9 +348,10 @@ function eventColors(type: string): { bg: string; fg: string } {
334
348
  return { bg: "rgba(133,236,206,0.15)", fg: "#189370" };
335
349
  if (type.startsWith("STATE"))
336
350
  return { bg: "rgba(190,194,255,0.102)", fg: "#5558B2" };
351
+ if (type === "RUN_ERROR" || type === "ERROR")
352
+ return { bg: "rgba(250,95,103,0.13)", fg: "#c0333a" };
337
353
  if (type.startsWith("RUN_") || type.startsWith("STEP_"))
338
354
  return { bg: "rgba(255,172,77,0.2)", fg: "#996300" };
339
- if (type === "ERROR") return { bg: "rgba(250,95,103,0.13)", fg: "#c0333a" };
340
355
  return { bg: "#F7F7F9", fg: "#838389" };
341
356
  }
342
357
 
@@ -674,6 +689,7 @@ export class ɵCpkThreadDetails extends LitElement {
674
689
  thread: { attribute: false },
675
690
  runtimeUrl: { attribute: false },
676
691
  headers: { attribute: false },
692
+ threadInspectionAvailable: { attribute: false },
677
693
  agentStateInput: { attribute: false },
678
694
  agentEventsInput: { attribute: false },
679
695
  liveMessageVersion: { attribute: false },
@@ -701,6 +717,7 @@ export class ɵCpkThreadDetails extends LitElement {
701
717
  thread: ɵThread | null = null;
702
718
  runtimeUrl = "";
703
719
  headers: Record<string, string> = {};
720
+ threadInspectionAvailable = false;
704
721
  agentStateInput: Record<string, unknown> | null = null;
705
722
  agentEventsInput: ApiAgentEvent[] = [];
706
723
  /**
@@ -1456,7 +1473,7 @@ export class ɵCpkThreadDetails extends LitElement {
1456
1473
  threadId: string,
1457
1474
  silent: boolean = false,
1458
1475
  ): Promise<void> {
1459
- if (!this.runtimeUrl) {
1476
+ if (!this.runtimeUrl || !this.threadInspectionAvailable) {
1460
1477
  if (!silent) this._conversation = [];
1461
1478
  return;
1462
1479
  }
@@ -1468,7 +1485,7 @@ export class ɵCpkThreadDetails extends LitElement {
1468
1485
  }
1469
1486
  try {
1470
1487
  const res = await fetch(
1471
- `${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/messages`,
1488
+ this.getThreadInspectionUrl(threadId, "messages"),
1472
1489
  { headers: { ...this.headers }, signal: controller.signal },
1473
1490
  );
1474
1491
  if (controller.signal.aborted || this.threadId !== threadId) return;
@@ -1494,7 +1511,7 @@ export class ɵCpkThreadDetails extends LitElement {
1494
1511
 
1495
1512
  private async fetchEvents(threadId: string): Promise<void> {
1496
1513
  this._eventsNotAvailable = false;
1497
- if (!this.runtimeUrl) {
1514
+ if (!this.runtimeUrl || !this.threadInspectionAvailable) {
1498
1515
  this._fetchedEvents = null;
1499
1516
  return;
1500
1517
  }
@@ -1503,10 +1520,10 @@ export class ɵCpkThreadDetails extends LitElement {
1503
1520
  this._loadingEvents = true;
1504
1521
  this._eventsError = null;
1505
1522
  try {
1506
- const res = await fetch(
1507
- `${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/events`,
1508
- { headers: { ...this.headers }, signal: controller.signal },
1509
- );
1523
+ const res = await fetch(this.getThreadInspectionUrl(threadId, "events"), {
1524
+ headers: { ...this.headers },
1525
+ signal: controller.signal,
1526
+ });
1510
1527
  // Drop results if a newer fetch superseded this one (thread switched
1511
1528
  // mid-flight). Without this, switching A→B can leave thread B's view
1512
1529
  // showing thread A's events when A's request resolves last.
@@ -1541,7 +1558,7 @@ export class ɵCpkThreadDetails extends LitElement {
1541
1558
 
1542
1559
  private async fetchState(threadId: string): Promise<void> {
1543
1560
  this._stateNotAvailable = false;
1544
- if (!this.runtimeUrl) {
1561
+ if (!this.runtimeUrl || !this.threadInspectionAvailable) {
1545
1562
  this._fetchedState = null;
1546
1563
  return;
1547
1564
  }
@@ -1550,10 +1567,10 @@ export class ɵCpkThreadDetails extends LitElement {
1550
1567
  this._loadingState = true;
1551
1568
  this._stateError = null;
1552
1569
  try {
1553
- const res = await fetch(
1554
- `${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/state`,
1555
- { headers: { ...this.headers }, signal: controller.signal },
1556
- );
1570
+ const res = await fetch(this.getThreadInspectionUrl(threadId, "state"), {
1571
+ headers: { ...this.headers },
1572
+ signal: controller.signal,
1573
+ });
1557
1574
  if (controller.signal.aborted || this.threadId !== threadId) return;
1558
1575
  if (res.status === 501) {
1559
1576
  this._stateNotAvailable = true;
@@ -1579,6 +1596,13 @@ export class ɵCpkThreadDetails extends LitElement {
1579
1596
  }
1580
1597
  }
1581
1598
 
1599
+ private getThreadInspectionUrl(
1600
+ threadId: string,
1601
+ resource: "messages" | "events" | "state",
1602
+ ): string {
1603
+ return `${this.runtimeUrl.replace(/\/+$/, "")}/threads/${encodeURIComponent(threadId)}/${resource}`;
1604
+ }
1605
+
1582
1606
  private mapMessages(messages: ApiThreadMessage[]): ConversationItem[] {
1583
1607
  const items: ConversationItem[] = [];
1584
1608
  const toolCallMap = new Map<string, ConversationToolCall>();
@@ -2444,6 +2468,7 @@ export class WebInspectorElement extends LitElement {
2444
2468
  // `${bannerId}:${cta}`) so copy-button retries and accidental multi-clicks
2445
2469
  // don't inflate funnel counts beyond one signal per intent type per banner.
2446
2470
  private clickedBannerIds: Set<string> = new Set();
2471
+ private viewedThreadsTelemetryStates: Set<string> = new Set();
2447
2472
 
2448
2473
  get core(): CopilotKitCore | null {
2449
2474
  return this._core;
@@ -2525,7 +2550,54 @@ export class WebInspectorElement extends LitElement {
2525
2550
  ];
2526
2551
  }
2527
2552
 
2553
+ private getThreadServiceStatus(): ThreadServiceStatus {
2554
+ if (!this._core) return "unknown";
2555
+ if (!this._core.threadEndpoints) return "unknown";
2556
+ return this._core.threadEndpoints?.list === false
2557
+ ? "unavailable"
2558
+ : "available";
2559
+ }
2560
+
2561
+ private areThreadEndpointsAvailable(): boolean {
2562
+ return this.getThreadServiceStatus() !== "unavailable";
2563
+ }
2564
+
2565
+ private getThreadsTelemetryProps(
2566
+ extra: Partial<InspectorThreadTelemetryProps> = {},
2567
+ options: { includeUrlAttribution?: boolean } = {},
2568
+ ): InspectorThreadTelemetryProps {
2569
+ const distinctId =
2570
+ options.includeUrlAttribution && !this.core?.telemetryDisabled
2571
+ ? getTelemetryDistinctIdForUrl()
2572
+ : null;
2573
+ const threadServiceStatus = this.getThreadServiceStatus();
2574
+ return {
2575
+ posthog_distinct_id: distinctId ?? undefined,
2576
+ intelligence_status:
2577
+ threadServiceStatus === "available"
2578
+ ? "intelligence_enabled"
2579
+ : threadServiceStatus === "unavailable"
2580
+ ? "intelligence_not_enabled"
2581
+ : "unknown",
2582
+ thread_service_status: threadServiceStatus,
2583
+ license_status: this.core?.licenseStatus ?? undefined,
2584
+ runtime_mode: this.core?.runtimeMode ?? undefined,
2585
+ runtime_url_type: getRuntimeUrlType(this.core?.runtimeUrl),
2586
+ telemetry_disabled: this.core?.telemetryDisabled ?? false,
2587
+ ...extra,
2588
+ };
2589
+ }
2590
+
2591
+ private getIntelligenceSignupUrl(): string {
2592
+ return this.appendRefParam(INTELLIGENCE_SIGNUP_URL, "cpk-inspector");
2593
+ }
2594
+
2595
+ private getTalkToEngineerUrl(): string {
2596
+ return this.appendRefParam(TALK_TO_ENGINEER_URL, "cpk-inspector-threads");
2597
+ }
2598
+
2528
2599
  private subscribeToThreadStore(agentId: string, store: ɵThreadStore): void {
2600
+ if (!this.areThreadEndpointsAvailable()) return;
2529
2601
  if (this._threadStoreSubscriptions.has(agentId)) return;
2530
2602
  const threadsSub = store.select(ɵselectThreads).subscribe((threads) => {
2531
2603
  this._threadsByAgent.set(agentId, threads as ɵThread[]);
@@ -2585,12 +2657,14 @@ export class WebInspectorElement extends LitElement {
2585
2657
  if (this.core?.getThreadStore(agentId)) return;
2586
2658
  const core = this.core;
2587
2659
  if (!core?.runtimeUrl) return;
2660
+ if (!this.areThreadEndpointsAvailable()) return;
2588
2661
 
2589
2662
  const store = ɵcreateThreadStore({ fetch: globalThis.fetch });
2590
2663
  store.start();
2591
2664
  store.setContext({
2592
2665
  runtimeUrl: core.runtimeUrl,
2593
- headers: {},
2666
+ headers: { ...core.headers },
2667
+ wsUrl: core.intelligence?.wsUrl,
2594
2668
  agentId,
2595
2669
  });
2596
2670
  this._ownedThreadStores.set(agentId, store);
@@ -2609,6 +2683,25 @@ export class WebInspectorElement extends LitElement {
2609
2683
  store.refresh();
2610
2684
  }
2611
2685
 
2686
+ // Keep inspector-owned thread stores in sync when the host updates headers
2687
+ // at runtime (e.g. a refreshed auth/CSRF token via core.setHeaders). Mirrors
2688
+ // useThreads(), which re-dispatches the context whenever core.headers change,
2689
+ // so the owned stores' /threads requests stay authorized.
2690
+ private updateOwnedThreadStoreHeaders(
2691
+ headers: Readonly<Record<string, string>>,
2692
+ ): void {
2693
+ const core = this.core;
2694
+ if (!core?.runtimeUrl) return;
2695
+ for (const [agentId, store] of this._ownedThreadStores) {
2696
+ store.setContext({
2697
+ runtimeUrl: core.runtimeUrl,
2698
+ headers: { ...headers },
2699
+ wsUrl: core.intelligence?.wsUrl,
2700
+ agentId,
2701
+ });
2702
+ }
2703
+ }
2704
+
2612
2705
  private removeOwnedThreadStore(agentId: string): void {
2613
2706
  const store = this._ownedThreadStores.get(agentId);
2614
2707
  if (!store) return;
@@ -2639,8 +2732,12 @@ export class WebInspectorElement extends LitElement {
2639
2732
  maybeShowDisclosure();
2640
2733
  }
2641
2734
  this.flushPendingBannerViewed();
2642
- for (const agentId of this._ownedThreadStores.keys()) {
2643
- this.refreshOwnedThreadStore(agentId);
2735
+ if (this.areThreadEndpointsAvailable()) {
2736
+ for (const agentId of this._ownedThreadStores.keys()) {
2737
+ this.refreshOwnedThreadStore(agentId);
2738
+ }
2739
+ } else {
2740
+ this.teardownOwnedThreadStores();
2644
2741
  }
2645
2742
  } else {
2646
2743
  // Clear stale thread data immediately when the server goes away
@@ -2653,6 +2750,9 @@ export class WebInspectorElement extends LitElement {
2653
2750
  this.coreProperties = properties;
2654
2751
  this.requestUpdate();
2655
2752
  },
2753
+ onHeadersChanged: ({ headers }) => {
2754
+ this.updateOwnedThreadStoreHeaders(headers);
2755
+ },
2656
2756
  onError: ({ code, error }) => {
2657
2757
  this.lastCoreError = { code, message: error.message };
2658
2758
  this.requestUpdate();
@@ -2684,6 +2784,14 @@ export class WebInspectorElement extends LitElement {
2684
2784
  this.coreUnsubscribe = core.subscribe(this.coreSubscriber).unsubscribe;
2685
2785
  this.processAgentsChanged(core.agents);
2686
2786
 
2787
+ if (core.runtimeConnectionStatus === "connected") {
2788
+ if (!core.telemetryDisabled) {
2789
+ ensureTelemetryDistinctId();
2790
+ maybeShowDisclosure();
2791
+ }
2792
+ this.flushPendingBannerViewed();
2793
+ }
2794
+
2687
2795
  // Subscribe to any already-registered thread stores. `getThreadStores` was
2688
2796
  // added in the same release as this inspector; guard so consumers still on
2689
2797
  // an older @copilotkit/core don't throw when assigning `inspector.core`.
@@ -2830,8 +2938,11 @@ export class WebInspectorElement extends LitElement {
2830
2938
  onRunStartedEvent: ({ event }) => {
2831
2939
  this.recordAgentEvent(agentId, "RUN_STARTED", event);
2832
2940
  },
2833
- onRunFinishedEvent: ({ event, result }) => {
2834
- this.recordAgentEvent(agentId, "RUN_FINISHED", { event, result });
2941
+ onRunFinishedEvent: (params) => {
2942
+ this.recordAgentEvent(agentId, "RUN_FINISHED", {
2943
+ event: params.event,
2944
+ result: "result" in params ? params.result : undefined,
2945
+ });
2835
2946
  this.refreshOwnedThreadStore(agentId);
2836
2947
  },
2837
2948
  onRunErrorEvent: ({ event }) => {
@@ -3375,6 +3486,10 @@ ${argsString}</pre
3375
3486
  const base =
3376
3487
  "font-mono text-[10px] font-medium inline-flex items-center rounded-sm px-1.5 py-0.5 border";
3377
3488
 
3489
+ if (type === "RUN_ERROR") {
3490
+ return `${base} bg-rose-50 text-rose-700 border-rose-200`;
3491
+ }
3492
+
3378
3493
  if (type.startsWith("RUN_")) {
3379
3494
  return `${base} bg-blue-50 text-blue-700 border-blue-200`;
3380
3495
  }
@@ -3399,10 +3514,6 @@ ${argsString}</pre
3399
3514
  return `${base} bg-sky-50 text-sky-700 border-sky-200`;
3400
3515
  }
3401
3516
 
3402
- if (type === "RUN_ERROR") {
3403
- return `${base} bg-rose-50 text-rose-700 border-rose-200`;
3404
- }
3405
-
3406
3517
  return `${base} bg-gray-100 text-gray-600 border-gray-200`;
3407
3518
  }
3408
3519
 
@@ -5762,6 +5873,45 @@ ${argsString}</pre
5762
5873
  });
5763
5874
  }
5764
5875
 
5876
+ private handleTalkToEngineerClick = (): void => {
5877
+ if (this.core?.telemetryDisabled) return;
5878
+ trackTalkToEngineerClicked(
5879
+ this.getThreadsTelemetryProps(
5880
+ {
5881
+ cta: "talk_to_engineer",
5882
+ cta_surface: "threads_header",
5883
+ },
5884
+ { includeUrlAttribution: true },
5885
+ ),
5886
+ );
5887
+ };
5888
+
5889
+ private handleThreadsIntelligenceSignupClick = (): void => {
5890
+ if (this.core?.telemetryDisabled) return;
5891
+ trackThreadsIntelligenceSignupClicked(
5892
+ this.getThreadsTelemetryProps(
5893
+ {
5894
+ cta: "signup",
5895
+ cta_surface: "threads_locked",
5896
+ },
5897
+ { includeUrlAttribution: true },
5898
+ ),
5899
+ );
5900
+ };
5901
+
5902
+ private handleThreadsTalkToEngineerClick = (): void => {
5903
+ if (this.core?.telemetryDisabled) return;
5904
+ trackThreadsTalkToEngineerClicked(
5905
+ this.getThreadsTelemetryProps(
5906
+ {
5907
+ cta: "talk_to_engineer",
5908
+ cta_surface: "threads_locked",
5909
+ },
5910
+ { includeUrlAttribution: true },
5911
+ ),
5912
+ );
5913
+ };
5914
+
5765
5915
  private handleThreadDividerPointerDown = (event: PointerEvent) => {
5766
5916
  this.threadDividerResizing = true;
5767
5917
  this.threadDividerPointerId = event.pointerId;
@@ -5794,7 +5944,326 @@ ${argsString}</pre
5794
5944
  this.threadDividerResizing = false;
5795
5945
  };
5796
5946
 
5947
+ private trackThreadsViewStateOnce(
5948
+ state: "locked" | "empty_enabled" | "enabled",
5949
+ threadCount: number,
5950
+ ): void {
5951
+ if (this.core?.telemetryDisabled) return;
5952
+ const key = `${state}:${this.getThreadServiceStatus()}`;
5953
+ if (this.viewedThreadsTelemetryStates.has(key)) return;
5954
+ this.viewedThreadsTelemetryStates.add(key);
5955
+ const props = this.getThreadsTelemetryProps({ thread_count: threadCount });
5956
+ if (state === "locked") {
5957
+ trackThreadsLockedViewed(props);
5958
+ } else if (state === "empty_enabled") {
5959
+ trackThreadsEmptyEnabledViewed(props);
5960
+ } else {
5961
+ trackThreadsEnabledViewed(props);
5962
+ }
5963
+ }
5964
+
5965
+ private renderThreadsLockedBackgroundMockup() {
5966
+ const threadRows = [
5967
+ { width: 74, accent: true },
5968
+ { width: 92 },
5969
+ { width: 68 },
5970
+ { width: 84 },
5971
+ { width: 58 },
5972
+ { width: 76 },
5973
+ ];
5974
+
5975
+ return html`
5976
+ <div
5977
+ aria-hidden="true"
5978
+ style="
5979
+ position: absolute;
5980
+ inset: 0;
5981
+ display: grid;
5982
+ grid-template-columns: minmax(180px, 28%) 1fr;
5983
+ overflow: hidden;
5984
+ opacity: 0.58;
5985
+ pointer-events: none;
5986
+ "
5987
+ >
5988
+ <div
5989
+ style="
5990
+ display: flex;
5991
+ flex-direction: column;
5992
+ gap: 12px;
5993
+ padding: 28px 24px;
5994
+ border-right: 1px solid #dbdbe5;
5995
+ background: #fafafa;
5996
+ "
5997
+ >
5998
+ ${threadRows.map(
5999
+ (row) => html`
6000
+ <div
6001
+ style="
6002
+ padding: 12px;
6003
+ border-radius: 8px;
6004
+ background: ${row.accent ? "#eee6fe" : "#ffffff"};
6005
+ box-shadow: inset 0 0 0 1px #eeeef4;
6006
+ "
6007
+ >
6008
+ <div
6009
+ style="
6010
+ height: 8px;
6011
+ width: ${row.width}%;
6012
+ border-radius: 99px;
6013
+ background: ${row.accent ? "#a984f5" : "#d7d7df"};
6014
+ "
6015
+ ></div>
6016
+ <div
6017
+ style="
6018
+ height: 6px;
6019
+ width: 88%;
6020
+ margin-top: 10px;
6021
+ border-radius: 99px;
6022
+ background: #e3e3eb;
6023
+ "
6024
+ ></div>
6025
+ <div
6026
+ style="
6027
+ height: 6px;
6028
+ width: 62%;
6029
+ margin-top: 7px;
6030
+ border-radius: 99px;
6031
+ background: #e8e8ef;
6032
+ "
6033
+ ></div>
6034
+ </div>
6035
+ `,
6036
+ )}
6037
+ </div>
6038
+ <div
6039
+ style="
6040
+ min-width: 0;
6041
+ padding: 42px 48px;
6042
+ background: #ffffff;
6043
+ "
6044
+ >
6045
+ <div
6046
+ style="
6047
+ height: 10px;
6048
+ width: 180px;
6049
+ border-radius: 99px;
6050
+ background: #d7d7df;
6051
+ "
6052
+ ></div>
6053
+ <div
6054
+ style="
6055
+ height: 8px;
6056
+ width: min(520px, 58%);
6057
+ margin-top: 28px;
6058
+ border-radius: 99px;
6059
+ background: #e3e3eb;
6060
+ "
6061
+ ></div>
6062
+ <div
6063
+ style="
6064
+ height: 8px;
6065
+ width: min(430px, 48%);
6066
+ margin-top: 12px;
6067
+ border-radius: 99px;
6068
+ background: #e8e8ef;
6069
+ "
6070
+ ></div>
6071
+ <div
6072
+ style="
6073
+ display: grid;
6074
+ grid-template-columns: repeat(2, minmax(0, 1fr));
6075
+ gap: 16px;
6076
+ max-width: 620px;
6077
+ margin-top: 30px;
6078
+ "
6079
+ >
6080
+ <div
6081
+ style="
6082
+ height: 116px;
6083
+ border-radius: 8px;
6084
+ background: #f5f5f8;
6085
+ box-shadow: inset 0 0 0 1px #eeeef4;
6086
+ "
6087
+ ></div>
6088
+ <div
6089
+ style="
6090
+ height: 116px;
6091
+ border-radius: 8px;
6092
+ background: #f5f5f8;
6093
+ box-shadow: inset 0 0 0 1px #eeeef4;
6094
+ "
6095
+ ></div>
6096
+ </div>
6097
+ <div
6098
+ style="
6099
+ height: 10px;
6100
+ width: min(680px, 74%);
6101
+ margin-top: 34px;
6102
+ border-radius: 99px;
6103
+ background: #e3e3eb;
6104
+ "
6105
+ ></div>
6106
+ <div
6107
+ style="
6108
+ height: 10px;
6109
+ width: min(560px, 60%);
6110
+ margin-top: 14px;
6111
+ border-radius: 99px;
6112
+ background: #e8e8ef;
6113
+ "
6114
+ ></div>
6115
+ </div>
6116
+ </div>
6117
+ `;
6118
+ }
6119
+
6120
+ private renderThreadsLockedView() {
6121
+ this.trackThreadsViewStateOnce("locked", 0);
6122
+ return html`
6123
+ <div
6124
+ style="
6125
+ position: relative;
6126
+ height: 100%;
6127
+ display: flex;
6128
+ align-items: center;
6129
+ justify-content: center;
6130
+ padding: 32px;
6131
+ overflow: hidden;
6132
+ background: #ffffff;
6133
+ "
6134
+ >
6135
+ ${this.renderThreadsLockedBackgroundMockup()}
6136
+ <div
6137
+ aria-hidden="true"
6138
+ style="
6139
+ position: absolute;
6140
+ inset: 0;
6141
+ pointer-events: none;
6142
+ background:
6143
+ radial-gradient(circle at center, rgba(255,255,255,0.9) 0, rgba(255,255,255,0.78) 24%, rgba(255,255,255,0.34) 48%, rgba(255,255,255,0.56) 100%);
6144
+ "
6145
+ ></div>
6146
+ <div
6147
+ style="
6148
+ position: relative;
6149
+ z-index: 1;
6150
+ max-width: 440px;
6151
+ text-align: center;
6152
+ color: #57575b;
6153
+ "
6154
+ >
6155
+ <div
6156
+ aria-hidden="true"
6157
+ style="
6158
+ margin: 0 auto 18px;
6159
+ display: flex;
6160
+ justify-content: center;
6161
+ "
6162
+ >
6163
+ <div
6164
+ style="
6165
+ display: flex;
6166
+ height: 44px;
6167
+ width: 44px;
6168
+ align-items: center;
6169
+ justify-content: center;
6170
+ border: 1px solid #dfd6fb;
6171
+ border-radius: 8px;
6172
+ background: #eee6fe;
6173
+ color: #57575b;
6174
+ box-shadow: 0 8px 18px rgba(87, 87, 91, 0.14);
6175
+ "
6176
+ >
6177
+ ${this.renderIcon("Lock")}
6178
+ </div>
6179
+ </div>
6180
+ <h2
6181
+ style="
6182
+ margin: 0 0 8px;
6183
+ font-size: 16px;
6184
+ line-height: 1.35;
6185
+ font-weight: 600;
6186
+ color: #010507;
6187
+ "
6188
+ >
6189
+ Enable Intelligence to inspect Threads.
6190
+ </h2>
6191
+ <p
6192
+ style="
6193
+ margin: 0 auto 18px;
6194
+ max-width: 380px;
6195
+ font-size: 13px;
6196
+ line-height: 1.55;
6197
+ color: #57575b;
6198
+ "
6199
+ >
6200
+ Persist conversations and inspect saved thread history from the
6201
+ Inspector.
6202
+ </p>
6203
+ <div
6204
+ style="
6205
+ display: flex;
6206
+ flex-wrap: wrap;
6207
+ justify-content: center;
6208
+ gap: 8px;
6209
+ "
6210
+ >
6211
+ <a
6212
+ href=${this.getTalkToEngineerUrl()}
6213
+ target="_blank"
6214
+ rel="noopener"
6215
+ style="
6216
+ display: inline-flex;
6217
+ min-height: 34px;
6218
+ align-items: center;
6219
+ justify-content: center;
6220
+ gap: 6px;
6221
+ border-radius: 6px;
6222
+ background: #010507;
6223
+ padding: 8px 12px;
6224
+ font-size: 12px;
6225
+ font-weight: 600;
6226
+ color: #ffffff;
6227
+ text-decoration: none;
6228
+ "
6229
+ @click=${this.handleThreadsTalkToEngineerClick}
6230
+ >
6231
+ Talk to an Engineer
6232
+ </a>
6233
+ <a
6234
+ href=${this.getIntelligenceSignupUrl()}
6235
+ target="_blank"
6236
+ rel="noopener"
6237
+ style="
6238
+ display: inline-flex;
6239
+ min-height: 34px;
6240
+ align-items: center;
6241
+ justify-content: center;
6242
+ gap: 6px;
6243
+ border-radius: 6px;
6244
+ border: 1px solid #dbdbe5;
6245
+ background: #ffffff;
6246
+ padding: 8px 12px;
6247
+ font-size: 12px;
6248
+ font-weight: 600;
6249
+ color: #57575b;
6250
+ text-decoration: none;
6251
+ "
6252
+ @click=${this.handleThreadsIntelligenceSignupClick}
6253
+ >
6254
+ Sign up for Intelligence
6255
+ </a>
6256
+ </div>
6257
+ </div>
6258
+ </div>
6259
+ `;
6260
+ }
6261
+
5797
6262
  private renderThreadsView() {
6263
+ if (!this.areThreadEndpointsAvailable()) {
6264
+ return this.renderThreadsLockedView();
6265
+ }
6266
+
5798
6267
  const displayThreads =
5799
6268
  this.selectedContext === "all-agents"
5800
6269
  ? this._threads
@@ -5818,88 +6287,110 @@ ${argsString}</pre
5818
6287
  ? (displayThreads.find((t) => t.id === this.selectedThreadId) ?? null)
5819
6288
  : null;
5820
6289
 
6290
+ if (!threadsErrorMessage) {
6291
+ this.trackThreadsViewStateOnce(
6292
+ displayThreads.length === 0 ? "empty_enabled" : "enabled",
6293
+ displayThreads.length,
6294
+ );
6295
+ }
6296
+
5821
6297
  return html`
5822
- <div style="display:flex;height:100%;overflow:hidden;">
5823
- <!-- Left sidebar: thread list -->
6298
+ <div style="display:flex;height:100%;overflow:hidden;flex-direction:column;">
5824
6299
  <div
5825
- style="width:${this.threadListWidth}px;flex-shrink:0;overflow:hidden;display:flex;flex-direction:column;border-right:1px solid #DBDBE5;"
6300
+ style="display:flex;align-items:center;justify-content:flex-end;border-bottom:1px solid #DBDBE5;background:#ffffff;padding:8px 12px;flex-shrink:0;"
5826
6301
  >
5827
- <cpk-thread-list
5828
- style="height:100%;"
5829
- .threads=${displayThreads}
5830
- .selectedThreadId=${this.selectedThreadId}
5831
- .errorMessage=${threadsErrorMessage}
5832
- @threadSelected=${(e: CustomEvent<string>) => {
5833
- this.selectedThreadId = e.detail;
5834
- this.requestUpdate();
5835
- }}
5836
- ></cpk-thread-list>
6302
+ <a
6303
+ href=${this.getTalkToEngineerUrl()}
6304
+ target="_blank"
6305
+ rel="noopener"
6306
+ style="display:inline-flex;align-items:center;gap:6px;border-radius:6px;border:1px solid #dbdbe5;background:#ffffff;padding:7px 10px;font-size:12px;font-weight:600;color:#57575b;text-decoration:none;"
6307
+ @click=${this.handleTalkToEngineerClick}
6308
+ >
6309
+ Talk to an Engineer
6310
+ </a>
5837
6311
  </div>
6312
+ <div style="display:flex;min-height:0;flex:1;overflow:hidden;">
6313
+ <!-- Left sidebar: thread list -->
6314
+ <div
6315
+ style="width:${this.threadListWidth}px;flex-shrink:0;overflow:hidden;display:flex;flex-direction:column;border-right:1px solid #DBDBE5;"
6316
+ >
6317
+ <cpk-thread-list
6318
+ style="height:100%;"
6319
+ .threads=${displayThreads}
6320
+ .selectedThreadId=${this.selectedThreadId}
6321
+ .errorMessage=${threadsErrorMessage}
6322
+ @threadSelected=${(e: CustomEvent<string>) => {
6323
+ this.selectedThreadId = e.detail;
6324
+ this.requestUpdate();
6325
+ }}
6326
+ ></cpk-thread-list>
6327
+ </div>
5838
6328
 
5839
- <!-- Resize divider -->
5840
- <div
5841
- style="width:4px;flex-shrink:0;cursor:col-resize;background:transparent;position:relative;z-index:1;"
5842
- @pointerdown=${this.handleThreadDividerPointerDown}
5843
- @pointermove=${this.handleThreadDividerPointerMove}
5844
- @pointerup=${this.handleThreadDividerPointerUp}
5845
- @pointercancel=${this.handleThreadDividerPointerUp}
5846
- ></div>
5847
-
5848
- <!-- Center + right: thread details or empty state -->
5849
- <div style="flex:1;min-width:0;overflow:hidden;display:flex;">
5850
- ${
5851
- this.selectedThreadId
5852
- ? html`<cpk-thread-details
5853
- style="flex:1;min-width:0;"
5854
- .threadId=${this.selectedThreadId}
5855
- .thread=${selectedThread}
5856
- .runtimeUrl=${this._core?.runtimeUrl ?? ""}
5857
- .headers=${this._core?.headers ?? {}}
5858
- .liveMessageVersion=${
5859
- this.selectedThreadId
5860
- ? (this.liveMessageVersion.get(this.selectedThreadId) ??
5861
- 0)
5862
- : 0
5863
- }
5864
- .agentStateInput=${
5865
- selectedThread
5866
- ? this.getLatestStateForAgent(selectedThread.agentId)
5867
- : null
5868
- }
5869
- .agentEventsInput=${
5870
- selectedThread
5871
- ? (this.agentEvents.get(selectedThread.agentId) ?? [])
5872
- : []
5873
- }
5874
- ></cpk-thread-details>`
5875
- : html`
5876
- <div
5877
- style="
5878
- flex: 1;
5879
- display: flex;
5880
- flex-direction: column;
5881
- align-items: center;
5882
- justify-content: center;
5883
- gap: 8px;
5884
- color: #838389;
5885
- "
5886
- >
5887
- <svg
5888
- width="32"
5889
- height="32"
5890
- viewBox="0 0 24 24"
5891
- fill="none"
5892
- stroke="#c0c0c8"
5893
- stroke-width="1.5"
5894
- stroke-linecap="round"
5895
- stroke-linejoin="round"
6329
+ <!-- Resize divider -->
6330
+ <div
6331
+ style="width:4px;flex-shrink:0;cursor:col-resize;background:transparent;position:relative;z-index:1;"
6332
+ @pointerdown=${this.handleThreadDividerPointerDown}
6333
+ @pointermove=${this.handleThreadDividerPointerMove}
6334
+ @pointerup=${this.handleThreadDividerPointerUp}
6335
+ @pointercancel=${this.handleThreadDividerPointerUp}
6336
+ ></div>
6337
+
6338
+ <!-- Center + right: thread details or empty state -->
6339
+ <div style="flex:1;min-width:0;overflow:hidden;display:flex;">
6340
+ ${
6341
+ selectedThread
6342
+ ? html`<cpk-thread-details
6343
+ style="flex:1;min-width:0;"
6344
+ .threadId=${selectedThread.id}
6345
+ .thread=${selectedThread}
6346
+ .runtimeUrl=${this._core?.runtimeUrl ?? ""}
6347
+ .headers=${this._core?.headers ?? {}}
6348
+ .threadInspectionAvailable=${
6349
+ this._core?.threadEndpoints?.inspect !== false
6350
+ }
6351
+ .liveMessageVersion=${
6352
+ this.liveMessageVersion.get(selectedThread.id) ?? 0
6353
+ }
6354
+ .agentStateInput=${this.getLatestStateForAgent(
6355
+ selectedThread.agentId,
6356
+ )}
6357
+ .agentEventsInput=${
6358
+ this.agentEvents.get(selectedThread.agentId) ?? []
6359
+ }
6360
+ ></cpk-thread-details>`
6361
+ : html`
6362
+ <div
6363
+ style="
6364
+ flex: 1;
6365
+ display: flex;
6366
+ flex-direction: column;
6367
+ align-items: center;
6368
+ justify-content: center;
6369
+ gap: 8px;
6370
+ color: #838389;
6371
+ "
5896
6372
  >
5897
- <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
5898
- </svg>
5899
- <span style="font-size: 13px">${displayThreads.length === 0 ? "No threads yet" : "Select a thread to inspect"}</span>
5900
- </div>
5901
- `
5902
- }
6373
+ <svg
6374
+ width="32"
6375
+ height="32"
6376
+ viewBox="0 0 24 24"
6377
+ fill="none"
6378
+ stroke="#c0c0c8"
6379
+ stroke-width="1.5"
6380
+ stroke-linecap="round"
6381
+ stroke-linejoin="round"
6382
+ >
6383
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
6384
+ </svg>
6385
+ <span style="font-size: 13px">${
6386
+ displayThreads.length === 0
6387
+ ? "No threads yet"
6388
+ : "Select a thread to inspect"
6389
+ }</span>
6390
+ </div>
6391
+ `
6392
+ }
6393
+ </div>
5903
6394
  </div>
5904
6395
  </div>
5905
6396
  `;
@@ -6571,8 +7062,8 @@ ${prettyEvent}</pre
6571
7062
  }
6572
7063
 
6573
7064
  if (key === "threads") {
6574
- if (this.selectedMenu !== "threads" && !this.core?.telemetryDisabled) {
6575
- trackThreadsTabClicked();
7065
+ if (previousMenu !== "threads" && !this.core?.telemetryDisabled) {
7066
+ trackThreadsTabClicked(this.getThreadsTelemetryProps());
6576
7067
  }
6577
7068
  this.autoSelectLatestThread();
6578
7069
  }
@@ -7252,6 +7743,22 @@ ${prettyEvent}</pre
7252
7743
  }
7253
7744
  </button>
7254
7745
  </div>
7746
+ <pre
7747
+ style="
7748
+ margin: 0;
7749
+ max-height: 180px;
7750
+ overflow: auto;
7751
+ white-space: pre-wrap;
7752
+ word-break: break-word;
7753
+ border-radius: 6px;
7754
+ border: 1px solid #eeeef4;
7755
+ background: #f7f7f9;
7756
+ padding: 10px;
7757
+ font-size: 11px;
7758
+ line-height: 1.5;
7759
+ color: #2d2d30;
7760
+ "
7761
+ >${this.formatContextValue(context.value)}</pre>
7255
7762
  `
7256
7763
  : html`
7257
7764
  <div class="flex items-center justify-center py-4 text-xs text-gray-500">
@@ -7600,10 +8107,15 @@ ${prettyEvent}</pre
7600
8107
  ): Promise<string | null> {
7601
8108
  const renderer = new marked.Renderer();
7602
8109
  renderer.link = (href, title, text) => {
7603
- const safeHref = this.escapeHtmlAttr(this.appendRefParam(href ?? ""));
8110
+ const safeHref = this.escapeHtmlAttr(
8111
+ this.isSafeAnnouncementHref(href ?? "")
8112
+ ? this.appendRefParam(href ?? "")
8113
+ : "#",
8114
+ );
7604
8115
  const titleAttr = title ? ` title="${this.escapeHtmlAttr(title)}"` : "";
7605
8116
  return `<a href="${safeHref}" target="_blank" rel="noopener"${titleAttr}>${text}</a>`;
7606
8117
  };
8118
+ renderer.html = (html) => escapeHtml(html);
7607
8119
  renderer.code = (code, lang) => {
7608
8120
  const safeLang = (lang ?? "").replace(/[^a-z0-9-]/gi, "");
7609
8121
  const langClass = safeLang ? ` class="language-${safeLang}"` : "";
@@ -7614,6 +8126,24 @@ ${prettyEvent}</pre
7614
8126
  return marked.parse(markdown, { renderer, async: false });
7615
8127
  }
7616
8128
 
8129
+ private isSafeAnnouncementHref(href: string): boolean {
8130
+ try {
8131
+ const url = new URL(
8132
+ href,
8133
+ typeof window !== "undefined"
8134
+ ? window.location.href
8135
+ : "https://copilotkit.ai",
8136
+ );
8137
+ return (
8138
+ url.protocol === "http:" ||
8139
+ url.protocol === "https:" ||
8140
+ url.protocol === "mailto:"
8141
+ );
8142
+ } catch {
8143
+ return false;
8144
+ }
8145
+ }
8146
+
7617
8147
  private copyResetTimeouts = new WeakMap<HTMLButtonElement, number>();
7618
8148
 
7619
8149
  private encodeBase64(value: string): string {
@@ -7678,8 +8208,9 @@ ${prettyEvent}</pre
7678
8208
  }
7679
8209
  };
7680
8210
 
7681
- private appendRefParam(href: string): string {
8211
+ private appendRefParam(href: string, ref = "cpk-inspector"): string {
7682
8212
  try {
8213
+ const isRootRelative = href.startsWith("/") && !href.startsWith("//");
7683
8214
  const url = new URL(
7684
8215
  href,
7685
8216
  typeof window !== "undefined"
@@ -7687,7 +8218,7 @@ ${prettyEvent}</pre
7687
8218
  : "https://copilotkit.ai",
7688
8219
  );
7689
8220
  if (!url.searchParams.has("ref")) {
7690
- url.searchParams.append("ref", "cpk-inspector");
8221
+ url.searchParams.append("ref", ref);
7691
8222
  }
7692
8223
  // Propagate the inspector's anonymous distinct-ID so the website /
7693
8224
  // Ops API can call posthog.alias(...) on signup-flow landing and
@@ -7696,19 +8227,28 @@ ${prettyEvent}</pre
7696
8227
  // suppresses cross-domain ID leaks too.
7697
8228
  if (
7698
8229
  !url.searchParams.has("posthog_distinct_id") &&
7699
- !this.core?.telemetryDisabled
8230
+ !this.core?.telemetryDisabled &&
8231
+ this.isCopilotKitDestination(url)
7700
8232
  ) {
7701
8233
  const distinctId = getTelemetryDistinctIdForUrl();
7702
8234
  if (distinctId) {
7703
8235
  url.searchParams.append("posthog_distinct_id", distinctId);
7704
8236
  }
7705
8237
  }
8238
+ if (isRootRelative) {
8239
+ return `${url.pathname}${url.search}${url.hash}`;
8240
+ }
7706
8241
  return url.toString();
7707
8242
  } catch {
7708
8243
  return href;
7709
8244
  }
7710
8245
  }
7711
8246
 
8247
+ private isCopilotKitDestination(url: URL): boolean {
8248
+ const hostname = url.hostname.toLowerCase();
8249
+ return hostname === "copilotkit.ai" || hostname.endsWith(".copilotkit.ai");
8250
+ }
8251
+
7712
8252
  private escapeHtmlAttr(value: string): string {
7713
8253
  return escapeHtml(value).replace(/"/g, "&quot;").replace(/'/g, "&#39;");
7714
8254
  }