@copilotkit/web-inspector 1.61.1 → 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
 
@@ -1470,7 +1485,7 @@ export class ɵCpkThreadDetails extends LitElement {
1470
1485
  }
1471
1486
  try {
1472
1487
  const res = await fetch(
1473
- `${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/messages`,
1488
+ this.getThreadInspectionUrl(threadId, "messages"),
1474
1489
  { headers: { ...this.headers }, signal: controller.signal },
1475
1490
  );
1476
1491
  if (controller.signal.aborted || this.threadId !== threadId) return;
@@ -1505,10 +1520,10 @@ export class ɵCpkThreadDetails extends LitElement {
1505
1520
  this._loadingEvents = true;
1506
1521
  this._eventsError = null;
1507
1522
  try {
1508
- const res = await fetch(
1509
- `${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/events`,
1510
- { headers: { ...this.headers }, signal: controller.signal },
1511
- );
1523
+ const res = await fetch(this.getThreadInspectionUrl(threadId, "events"), {
1524
+ headers: { ...this.headers },
1525
+ signal: controller.signal,
1526
+ });
1512
1527
  // Drop results if a newer fetch superseded this one (thread switched
1513
1528
  // mid-flight). Without this, switching A→B can leave thread B's view
1514
1529
  // showing thread A's events when A's request resolves last.
@@ -1552,10 +1567,10 @@ export class ɵCpkThreadDetails extends LitElement {
1552
1567
  this._loadingState = true;
1553
1568
  this._stateError = null;
1554
1569
  try {
1555
- const res = await fetch(
1556
- `${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/state`,
1557
- { headers: { ...this.headers }, signal: controller.signal },
1558
- );
1570
+ const res = await fetch(this.getThreadInspectionUrl(threadId, "state"), {
1571
+ headers: { ...this.headers },
1572
+ signal: controller.signal,
1573
+ });
1559
1574
  if (controller.signal.aborted || this.threadId !== threadId) return;
1560
1575
  if (res.status === 501) {
1561
1576
  this._stateNotAvailable = true;
@@ -1581,6 +1596,13 @@ export class ɵCpkThreadDetails extends LitElement {
1581
1596
  }
1582
1597
  }
1583
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
+
1584
1606
  private mapMessages(messages: ApiThreadMessage[]): ConversationItem[] {
1585
1607
  const items: ConversationItem[] = [];
1586
1608
  const toolCallMap = new Map<string, ConversationToolCall>();
@@ -2446,6 +2468,7 @@ export class WebInspectorElement extends LitElement {
2446
2468
  // `${bannerId}:${cta}`) so copy-button retries and accidental multi-clicks
2447
2469
  // don't inflate funnel counts beyond one signal per intent type per banner.
2448
2470
  private clickedBannerIds: Set<string> = new Set();
2471
+ private viewedThreadsTelemetryStates: Set<string> = new Set();
2449
2472
 
2450
2473
  get core(): CopilotKitCore | null {
2451
2474
  return this._core;
@@ -2527,7 +2550,54 @@ export class WebInspectorElement extends LitElement {
2527
2550
  ];
2528
2551
  }
2529
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
+
2530
2599
  private subscribeToThreadStore(agentId: string, store: ɵThreadStore): void {
2600
+ if (!this.areThreadEndpointsAvailable()) return;
2531
2601
  if (this._threadStoreSubscriptions.has(agentId)) return;
2532
2602
  const threadsSub = store.select(ɵselectThreads).subscribe((threads) => {
2533
2603
  this._threadsByAgent.set(agentId, threads as ɵThread[]);
@@ -2587,7 +2657,7 @@ export class WebInspectorElement extends LitElement {
2587
2657
  if (this.core?.getThreadStore(agentId)) return;
2588
2658
  const core = this.core;
2589
2659
  if (!core?.runtimeUrl) return;
2590
- if (core.threadEndpoints?.list === false) return;
2660
+ if (!this.areThreadEndpointsAvailable()) return;
2591
2661
 
2592
2662
  const store = ɵcreateThreadStore({ fetch: globalThis.fetch });
2593
2663
  store.start();
@@ -2626,6 +2696,7 @@ export class WebInspectorElement extends LitElement {
2626
2696
  store.setContext({
2627
2697
  runtimeUrl: core.runtimeUrl,
2628
2698
  headers: { ...headers },
2699
+ wsUrl: core.intelligence?.wsUrl,
2629
2700
  agentId,
2630
2701
  });
2631
2702
  }
@@ -2661,7 +2732,7 @@ export class WebInspectorElement extends LitElement {
2661
2732
  maybeShowDisclosure();
2662
2733
  }
2663
2734
  this.flushPendingBannerViewed();
2664
- if (core.threadEndpoints?.list !== false) {
2735
+ if (this.areThreadEndpointsAvailable()) {
2665
2736
  for (const agentId of this._ownedThreadStores.keys()) {
2666
2737
  this.refreshOwnedThreadStore(agentId);
2667
2738
  }
@@ -2713,6 +2784,14 @@ export class WebInspectorElement extends LitElement {
2713
2784
  this.coreUnsubscribe = core.subscribe(this.coreSubscriber).unsubscribe;
2714
2785
  this.processAgentsChanged(core.agents);
2715
2786
 
2787
+ if (core.runtimeConnectionStatus === "connected") {
2788
+ if (!core.telemetryDisabled) {
2789
+ ensureTelemetryDistinctId();
2790
+ maybeShowDisclosure();
2791
+ }
2792
+ this.flushPendingBannerViewed();
2793
+ }
2794
+
2716
2795
  // Subscribe to any already-registered thread stores. `getThreadStores` was
2717
2796
  // added in the same release as this inspector; guard so consumers still on
2718
2797
  // an older @copilotkit/core don't throw when assigning `inspector.core`.
@@ -2859,8 +2938,11 @@ export class WebInspectorElement extends LitElement {
2859
2938
  onRunStartedEvent: ({ event }) => {
2860
2939
  this.recordAgentEvent(agentId, "RUN_STARTED", event);
2861
2940
  },
2862
- onRunFinishedEvent: ({ event, result }) => {
2863
- 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
+ });
2864
2946
  this.refreshOwnedThreadStore(agentId);
2865
2947
  },
2866
2948
  onRunErrorEvent: ({ event }) => {
@@ -3404,6 +3486,10 @@ ${argsString}</pre
3404
3486
  const base =
3405
3487
  "font-mono text-[10px] font-medium inline-flex items-center rounded-sm px-1.5 py-0.5 border";
3406
3488
 
3489
+ if (type === "RUN_ERROR") {
3490
+ return `${base} bg-rose-50 text-rose-700 border-rose-200`;
3491
+ }
3492
+
3407
3493
  if (type.startsWith("RUN_")) {
3408
3494
  return `${base} bg-blue-50 text-blue-700 border-blue-200`;
3409
3495
  }
@@ -3428,10 +3514,6 @@ ${argsString}</pre
3428
3514
  return `${base} bg-sky-50 text-sky-700 border-sky-200`;
3429
3515
  }
3430
3516
 
3431
- if (type === "RUN_ERROR") {
3432
- return `${base} bg-rose-50 text-rose-700 border-rose-200`;
3433
- }
3434
-
3435
3517
  return `${base} bg-gray-100 text-gray-600 border-gray-200`;
3436
3518
  }
3437
3519
 
@@ -5791,6 +5873,45 @@ ${argsString}</pre
5791
5873
  });
5792
5874
  }
5793
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
+
5794
5915
  private handleThreadDividerPointerDown = (event: PointerEvent) => {
5795
5916
  this.threadDividerResizing = true;
5796
5917
  this.threadDividerPointerId = event.pointerId;
@@ -5823,7 +5944,326 @@ ${argsString}</pre
5823
5944
  this.threadDividerResizing = false;
5824
5945
  };
5825
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
+
5826
6262
  private renderThreadsView() {
6263
+ if (!this.areThreadEndpointsAvailable()) {
6264
+ return this.renderThreadsLockedView();
6265
+ }
6266
+
5827
6267
  const displayThreads =
5828
6268
  this.selectedContext === "all-agents"
5829
6269
  ? this._threads
@@ -5847,91 +6287,110 @@ ${argsString}</pre
5847
6287
  ? (displayThreads.find((t) => t.id === this.selectedThreadId) ?? null)
5848
6288
  : null;
5849
6289
 
6290
+ if (!threadsErrorMessage) {
6291
+ this.trackThreadsViewStateOnce(
6292
+ displayThreads.length === 0 ? "empty_enabled" : "enabled",
6293
+ displayThreads.length,
6294
+ );
6295
+ }
6296
+
5850
6297
  return html`
5851
- <div style="display:flex;height:100%;overflow:hidden;">
5852
- <!-- Left sidebar: thread list -->
6298
+ <div style="display:flex;height:100%;overflow:hidden;flex-direction:column;">
5853
6299
  <div
5854
- 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;"
5855
6301
  >
5856
- <cpk-thread-list
5857
- style="height:100%;"
5858
- .threads=${displayThreads}
5859
- .selectedThreadId=${this.selectedThreadId}
5860
- .errorMessage=${threadsErrorMessage}
5861
- @threadSelected=${(e: CustomEvent<string>) => {
5862
- this.selectedThreadId = e.detail;
5863
- this.requestUpdate();
5864
- }}
5865
- ></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>
5866
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>
5867
6328
 
5868
- <!-- Resize divider -->
5869
- <div
5870
- style="width:4px;flex-shrink:0;cursor:col-resize;background:transparent;position:relative;z-index:1;"
5871
- @pointerdown=${this.handleThreadDividerPointerDown}
5872
- @pointermove=${this.handleThreadDividerPointerMove}
5873
- @pointerup=${this.handleThreadDividerPointerUp}
5874
- @pointercancel=${this.handleThreadDividerPointerUp}
5875
- ></div>
5876
-
5877
- <!-- Center + right: thread details or empty state -->
5878
- <div style="flex:1;min-width:0;overflow:hidden;display:flex;">
5879
- ${
5880
- this.selectedThreadId
5881
- ? html`<cpk-thread-details
5882
- style="flex:1;min-width:0;"
5883
- .threadId=${this.selectedThreadId}
5884
- .thread=${selectedThread}
5885
- .runtimeUrl=${this._core?.runtimeUrl ?? ""}
5886
- .headers=${this._core?.headers ?? {}}
5887
- .threadInspectionAvailable=${
5888
- this._core?.threadEndpoints?.inspect !== false
5889
- }
5890
- .liveMessageVersion=${
5891
- this.selectedThreadId
5892
- ? (this.liveMessageVersion.get(this.selectedThreadId) ??
5893
- 0)
5894
- : 0
5895
- }
5896
- .agentStateInput=${
5897
- selectedThread
5898
- ? this.getLatestStateForAgent(selectedThread.agentId)
5899
- : null
5900
- }
5901
- .agentEventsInput=${
5902
- selectedThread
5903
- ? (this.agentEvents.get(selectedThread.agentId) ?? [])
5904
- : []
5905
- }
5906
- ></cpk-thread-details>`
5907
- : html`
5908
- <div
5909
- style="
5910
- flex: 1;
5911
- display: flex;
5912
- flex-direction: column;
5913
- align-items: center;
5914
- justify-content: center;
5915
- gap: 8px;
5916
- color: #838389;
5917
- "
5918
- >
5919
- <svg
5920
- width="32"
5921
- height="32"
5922
- viewBox="0 0 24 24"
5923
- fill="none"
5924
- stroke="#c0c0c8"
5925
- stroke-width="1.5"
5926
- stroke-linecap="round"
5927
- 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
+ "
5928
6372
  >
5929
- <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
5930
- </svg>
5931
- <span style="font-size: 13px">${displayThreads.length === 0 ? "No threads yet" : "Select a thread to inspect"}</span>
5932
- </div>
5933
- `
5934
- }
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>
5935
6394
  </div>
5936
6395
  </div>
5937
6396
  `;
@@ -6603,8 +7062,8 @@ ${prettyEvent}</pre
6603
7062
  }
6604
7063
 
6605
7064
  if (key === "threads") {
6606
- if (this.selectedMenu !== "threads" && !this.core?.telemetryDisabled) {
6607
- trackThreadsTabClicked();
7065
+ if (previousMenu !== "threads" && !this.core?.telemetryDisabled) {
7066
+ trackThreadsTabClicked(this.getThreadsTelemetryProps());
6608
7067
  }
6609
7068
  this.autoSelectLatestThread();
6610
7069
  }
@@ -7284,6 +7743,22 @@ ${prettyEvent}</pre
7284
7743
  }
7285
7744
  </button>
7286
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>
7287
7762
  `
7288
7763
  : html`
7289
7764
  <div class="flex items-center justify-center py-4 text-xs text-gray-500">
@@ -7632,10 +8107,15 @@ ${prettyEvent}</pre
7632
8107
  ): Promise<string | null> {
7633
8108
  const renderer = new marked.Renderer();
7634
8109
  renderer.link = (href, title, text) => {
7635
- const safeHref = this.escapeHtmlAttr(this.appendRefParam(href ?? ""));
8110
+ const safeHref = this.escapeHtmlAttr(
8111
+ this.isSafeAnnouncementHref(href ?? "")
8112
+ ? this.appendRefParam(href ?? "")
8113
+ : "#",
8114
+ );
7636
8115
  const titleAttr = title ? ` title="${this.escapeHtmlAttr(title)}"` : "";
7637
8116
  return `<a href="${safeHref}" target="_blank" rel="noopener"${titleAttr}>${text}</a>`;
7638
8117
  };
8118
+ renderer.html = (html) => escapeHtml(html);
7639
8119
  renderer.code = (code, lang) => {
7640
8120
  const safeLang = (lang ?? "").replace(/[^a-z0-9-]/gi, "");
7641
8121
  const langClass = safeLang ? ` class="language-${safeLang}"` : "";
@@ -7646,6 +8126,24 @@ ${prettyEvent}</pre
7646
8126
  return marked.parse(markdown, { renderer, async: false });
7647
8127
  }
7648
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
+
7649
8147
  private copyResetTimeouts = new WeakMap<HTMLButtonElement, number>();
7650
8148
 
7651
8149
  private encodeBase64(value: string): string {
@@ -7710,8 +8208,9 @@ ${prettyEvent}</pre
7710
8208
  }
7711
8209
  };
7712
8210
 
7713
- private appendRefParam(href: string): string {
8211
+ private appendRefParam(href: string, ref = "cpk-inspector"): string {
7714
8212
  try {
8213
+ const isRootRelative = href.startsWith("/") && !href.startsWith("//");
7715
8214
  const url = new URL(
7716
8215
  href,
7717
8216
  typeof window !== "undefined"
@@ -7719,7 +8218,7 @@ ${prettyEvent}</pre
7719
8218
  : "https://copilotkit.ai",
7720
8219
  );
7721
8220
  if (!url.searchParams.has("ref")) {
7722
- url.searchParams.append("ref", "cpk-inspector");
8221
+ url.searchParams.append("ref", ref);
7723
8222
  }
7724
8223
  // Propagate the inspector's anonymous distinct-ID so the website /
7725
8224
  // Ops API can call posthog.alias(...) on signup-flow landing and
@@ -7728,19 +8227,28 @@ ${prettyEvent}</pre
7728
8227
  // suppresses cross-domain ID leaks too.
7729
8228
  if (
7730
8229
  !url.searchParams.has("posthog_distinct_id") &&
7731
- !this.core?.telemetryDisabled
8230
+ !this.core?.telemetryDisabled &&
8231
+ this.isCopilotKitDestination(url)
7732
8232
  ) {
7733
8233
  const distinctId = getTelemetryDistinctIdForUrl();
7734
8234
  if (distinctId) {
7735
8235
  url.searchParams.append("posthog_distinct_id", distinctId);
7736
8236
  }
7737
8237
  }
8238
+ if (isRootRelative) {
8239
+ return `${url.pathname}${url.search}${url.hash}`;
8240
+ }
7738
8241
  return url.toString();
7739
8242
  } catch {
7740
8243
  return href;
7741
8244
  }
7742
8245
  }
7743
8246
 
8247
+ private isCopilotKitDestination(url: URL): boolean {
8248
+ const hostname = url.hostname.toLowerCase();
8249
+ return hostname === "copilotkit.ai" || hostname.endsWith(".copilotkit.ai");
8250
+ }
8251
+
7744
8252
  private escapeHtmlAttr(value: string): string {
7745
8253
  return escapeHtml(value).replace(/"/g, "&quot;").replace(/'/g, "&#39;");
7746
8254
  }