@copilotkit/web-inspector 1.68.1 → 1.68.3-canary.1786716392

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.mjs CHANGED
@@ -3,10 +3,11 @@ import inspector_logo_default from "./assets/inspector-logo.mjs";
3
3
  import inspector_logo_icon_default from "./assets/inspector-logo-icon.mjs";
4
4
  import { applyAnchorPosition, centerContext, clampSize, constrainToViewport, keepPositionWithinViewport, updateAnchorFromPosition, updateSizeFromElement } from "./lib/context-helpers.mjs";
5
5
  import { isValidAnchor, isValidDockMode, isValidPosition, isValidSize, loadInspectorState, saveInspectorState } from "./lib/persistence.mjs";
6
+ import { buildPopOutFeatures, ensureBrandFont, openPopOutWindow } from "./lib/pop-out.mjs";
6
7
  import { projectInspectorMetadata } from "./lib/inspector-metadata.mjs";
7
8
  import { selectVisibleRealThreadId } from "./lib/thread-selection.mjs";
8
9
  import { TELEMETRY_DOCS_URL, ensureTelemetryDistinctId, getRuntimeUrlType, getTelemetryDistinctIdForUrl, maybeShowDisclosure, trackBannerClicked, trackBannerDismissed, trackBannerViewed, trackInspectorOpened, trackMemoriesTabClicked, trackMetadataActionClicked, trackMetadataModuleViewed, trackTalkToEngineerClicked, trackThreadsEmptyEnabledViewed, trackThreadsEnabledViewed, trackThreadsExampleSelected, trackThreadsExampleTourCompleted, trackThreadsExampleTourDismissed, trackThreadsExampleTourReopened, trackThreadsExampleTourStarted, trackThreadsExampleTourStepViewed, trackThreadsExampleViewed, trackThreadsIntelligenceSignupClicked, trackThreadsLockedViewed, trackThreadsTabClicked, trackThreadsTalkToEngineerClicked } from "./lib/telemetry.mjs";
9
- import { LitElement, css, html, nothing, unsafeCSS } from "lit";
10
+ import { LitElement, css, html, nothing, render, unsafeCSS } from "lit";
10
11
  import { marked } from "marked";
11
12
  import { styleMap } from "lit/directives/style-map.js";
12
13
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
@@ -469,7 +470,24 @@ function formatTimestamp(ts) {
469
470
  hour12: false
470
471
  }) + "." + ms;
471
472
  }
472
- var CpkThreadList = class extends LitElement {
473
+ /**
474
+ * Lit's constructable stylesheets belong to the document that created them.
475
+ * These child elements move between the app and pop-out documents, so keep
476
+ * their styles as shadow-root style nodes that travel with the live element.
477
+ */
478
+ var PortableLitElement = class extends LitElement {
479
+ createRenderRoot() {
480
+ const elementClass = this.constructor;
481
+ const renderRoot = this.shadowRoot ?? this.attachShadow(elementClass.shadowRootOptions);
482
+ for (const style of elementClass.elementStyles) {
483
+ const styleElement = this.ownerDocument.createElement("style");
484
+ styleElement.textContent = "cssText" in style ? style.cssText : Array.from(style.cssRules, (rule) => rule.cssText).join("");
485
+ renderRoot.append(styleElement);
486
+ }
487
+ return renderRoot;
488
+ }
489
+ };
490
+ var CpkThreadList = class extends PortableLitElement {
473
491
  constructor(..._args) {
474
492
  super(..._args);
475
493
  this.threads = [];
@@ -767,7 +785,7 @@ var CpkThreadList = class extends LitElement {
767
785
  `;
768
786
  }
769
787
  };
770
- var CpkThreadInspector = class CpkThreadInspector extends LitElement {
788
+ var CpkThreadInspector = class CpkThreadInspector extends PortableLitElement {
771
789
  constructor(..._args2) {
772
790
  super(..._args2);
773
791
  this.threadId = null;
@@ -779,6 +797,8 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
779
797
  this.agentStateInput = null;
780
798
  this.agentEventsInput = [];
781
799
  this.liveMessageVersion = 0;
800
+ this.focusMessageId = null;
801
+ this.focusRequestId = 0;
782
802
  this._tab = "timeline";
783
803
  this._fetchedMetadata = null;
784
804
  this._conversation = [];
@@ -798,6 +818,8 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
798
818
  this._detailPanelWidth = 250;
799
819
  this._eventsNotAvailable = false;
800
820
  this._stateNotAvailable = false;
821
+ this._scrolledFocusRequestId = 0;
822
+ this._highlightedFocusRequestId = -1;
801
823
  this._panelInitializing = false;
802
824
  this._activatedTabs = new Set(["timeline"]);
803
825
  this._panelTplCache = /* @__PURE__ */ new Map();
@@ -848,6 +870,8 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
848
870
  agentStateInput: { attribute: false },
849
871
  agentEventsInput: { attribute: false },
850
872
  liveMessageVersion: { attribute: false },
873
+ focusMessageId: { attribute: false },
874
+ focusRequestId: { attribute: false },
851
875
  _tab: { state: true },
852
876
  _fetchedMetadata: { state: true },
853
877
  _conversation: { state: true },
@@ -1222,6 +1246,37 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1222
1246
  color: #c0333a;
1223
1247
  }
1224
1248
 
1249
+ @keyframes cpk-td-focus-pulse {
1250
+ 0% {
1251
+ outline-color: rgba(100, 48, 171, 0);
1252
+ box-shadow: 0 0 0 rgba(100, 48, 171, 0);
1253
+ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
1254
+ }
1255
+ 24%,
1256
+ 50% {
1257
+ outline-color: rgba(100, 48, 171, 0.78);
1258
+ box-shadow: 0 7px 20px rgba(100, 48, 171, 0.2);
1259
+ }
1260
+ 100% {
1261
+ outline-color: rgba(100, 48, 171, 0);
1262
+ box-shadow: 0 0 0 rgba(100, 48, 171, 0);
1263
+ }
1264
+ }
1265
+
1266
+ .cpk-td__focus-pulse {
1267
+ position: relative;
1268
+ z-index: 1;
1269
+ outline: 2px solid transparent;
1270
+ outline-offset: 3px;
1271
+ animation: cpk-td-focus-pulse 760ms linear;
1272
+ }
1273
+
1274
+ @media (prefers-reduced-motion: reduce) {
1275
+ .cpk-td__focus-pulse {
1276
+ animation-duration: 320ms;
1277
+ }
1278
+ }
1279
+
1225
1280
  /* ── Conversation bubbles ────────────────────────────────────────── */
1226
1281
  .cpk-td__bubble {
1227
1282
  display: flex;
@@ -1757,6 +1812,31 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1757
1812
  this._messagesAbort = null;
1758
1813
  this.fetchMessages(this.threadId, true);
1759
1814
  }
1815
+ const focusedContentChanged = _changed.has("_fetchedEvents") || _changed.has("agentEventsInput") || _changed.has("_conversation");
1816
+ if (this.focusMessageId && (this.focusRequestId > this._scrolledFocusRequestId || focusedContentChanged)) {
1817
+ if (this._tab !== "timeline") {
1818
+ this._activatedTabs = new Set([...this._activatedTabs, "timeline"]);
1819
+ this._tab = "timeline";
1820
+ this.requestUpdate();
1821
+ }
1822
+ requestAnimationFrame(() => this.scrollToFocusedMessage());
1823
+ }
1824
+ }
1825
+ scrollToFocusedMessage() {
1826
+ if (!this.focusMessageId) return;
1827
+ const message = Array.from(this.shadowRoot?.querySelectorAll("[data-message-id]") ?? []).find((candidate) => candidate.dataset.messageId === this.focusMessageId);
1828
+ if (!message) return;
1829
+ message.scrollIntoView?.({ block: "center" });
1830
+ this._scrolledFocusRequestId = this.focusRequestId;
1831
+ this.pulseFocusedMessage(message);
1832
+ }
1833
+ pulseFocusedMessage(message) {
1834
+ if (this.focusRequestId === this._highlightedFocusRequestId) return;
1835
+ this._highlightedFocusRequestId = this.focusRequestId;
1836
+ message.classList.remove("cpk-td__focus-pulse");
1837
+ message.offsetWidth;
1838
+ message.classList.add("cpk-td__focus-pulse");
1839
+ message.addEventListener("animationend", () => message.classList.remove("cpk-td__focus-pulse"), { once: true });
1760
1840
  }
1761
1841
  connectedCallback() {
1762
1842
  super.connectedCallback();
@@ -2104,6 +2184,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2104
2184
  if (!item) {
2105
2185
  item = {
2106
2186
  id: `message-${key}`,
2187
+ messageId: key,
2107
2188
  kind: "message",
2108
2189
  title: `${role || "message"} message`,
2109
2190
  body: "",
@@ -2633,6 +2714,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2633
2714
  return html`
2634
2715
  <div
2635
2716
  class="cpk-td__timeline-item ${isWarning ? "cpk-td__timeline-item--warning" : ""}"
2717
+ data-message-id=${item.messageId ?? nothing}
2636
2718
  >
2637
2719
  <div class="cpk-td__timeline-header">
2638
2720
  <span class="cpk-td__timeline-kind"
@@ -2769,6 +2851,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2769
2851
  return html`
2770
2852
  <div
2771
2853
  class="cpk-td__bubble ${isUser ? "cpk-td__bubble--user" : "cpk-td__bubble--assistant"}"
2854
+ data-message-id=${item.id}
2772
2855
  >
2773
2856
  <div
2774
2857
  class="cpk-td__bubble-inner ${isUser ? "cpk-td__bubble-inner--user" : "cpk-td__bubble-inner--assistant"}"
@@ -3125,7 +3208,7 @@ function maxRecallScore(memories) {
3125
3208
  function relevanceBarWidth(relevance) {
3126
3209
  return Math.max(6, Math.min(100, Math.round(relevance * 100)));
3127
3210
  }
3128
- var CpkMemoryList = class extends LitElement {
3211
+ var CpkMemoryList = class extends PortableLitElement {
3129
3212
  constructor(..._args3) {
3130
3213
  super(..._args3);
3131
3214
  this.memories = [];
@@ -3682,7 +3765,7 @@ var ɵCpkThreadDetails = class extends CpkThreadInspector {};
3682
3765
  function defineElementOnce(registry, tag, ctor) {
3683
3766
  if (!registry.get(tag)) registry.define(tag, ctor);
3684
3767
  }
3685
- var WebInspectorElement = class extends LitElement {
3768
+ var WebInspectorElement = class WebInspectorElement extends LitElement {
3686
3769
  constructor(..._args4) {
3687
3770
  super(..._args4);
3688
3771
  this._core = null;
@@ -3738,6 +3821,9 @@ var WebInspectorElement = class extends LitElement {
3738
3821
  this.selectedThreadId = null;
3739
3822
  this.selectedRealThreadIsExplicit = false;
3740
3823
  this.selectedLocalExampleThreadId = null;
3824
+ this.requestedThreadId = null;
3825
+ this.focusedThreadMessageId = null;
3826
+ this.threadFocusRequestId = 0;
3741
3827
  this.threadListWidth = 290;
3742
3828
  this.threadDividerResizing = false;
3743
3829
  this.threadDividerPointerId = -1;
@@ -3753,6 +3839,8 @@ var WebInspectorElement = class extends LitElement {
3753
3839
  this.threadCapabilityGeneration = 0;
3754
3840
  this.contextMenuOpen = false;
3755
3841
  this.dockMode = "floating";
3842
+ this.popOut = null;
3843
+ this.inspectorPortal = null;
3756
3844
  this.previousBodyMargins = null;
3757
3845
  this.transitionTimeoutId = null;
3758
3846
  this.bodyTransitionTimeoutIds = /* @__PURE__ */ new Set();
@@ -3933,6 +4021,45 @@ var WebInspectorElement = class extends LitElement {
3933
4021
  this.handleCloseClick = () => {
3934
4022
  this.closeInspector();
3935
4023
  };
4024
+ this.requestPopOut = () => {
4025
+ if (this.isPoppedOut) return;
4026
+ const size = this.contextState.window.size;
4027
+ const handle = openPopOutWindow({
4028
+ open: window.open.bind(window),
4029
+ features: buildPopOutFeatures(size),
4030
+ title: "CopilotKit Inspector",
4031
+ cssTexts: this.getPopOutCssTexts(),
4032
+ sourceDocument: document,
4033
+ onClose: () => this.handlePopOutClosed()
4034
+ });
4035
+ this.popOut = handle;
4036
+ try {
4037
+ defineWebInspector(handle.win.customElements);
4038
+ if (this.dockMode !== "floating") this.removeDockStyles();
4039
+ handle.win.addEventListener("pointerdown", this.handleGlobalPointerDown);
4040
+ this.syncInspectorPortal();
4041
+ this.requestUpdate();
4042
+ } catch (error) {
4043
+ this.popOut = null;
4044
+ this.unbindPopOutPointerDown(handle.win);
4045
+ handle.close();
4046
+ if (this.isConnected && this.isOpen && this.dockMode !== "floating") this.applyDockStyles();
4047
+ this.requestUpdate();
4048
+ throw error;
4049
+ }
4050
+ };
4051
+ this.handlePopOutClosed = () => {
4052
+ const handle = this.popOut;
4053
+ if (!handle) return;
4054
+ this.popOut = null;
4055
+ this.unbindPopOutPointerDown(handle.win);
4056
+ this.syncInspectorPortal();
4057
+ if (this.isConnected && this.isOpen && this.dockMode !== "floating") this.applyDockStyles();
4058
+ this.requestUpdate();
4059
+ };
4060
+ this.handleAppBeforeUnload = () => {
4061
+ this.closePopOut();
4062
+ };
3936
4063
  this.handleResizePointerDown = (event) => {
3937
4064
  event.stopPropagation();
3938
4065
  event.preventDefault();
@@ -3993,6 +4120,7 @@ var WebInspectorElement = class extends LitElement {
3993
4120
  this.resetResizeTracking();
3994
4121
  };
3995
4122
  this.handleResize = () => {
4123
+ if (this.isPoppedOut) return;
3996
4124
  this.measureContext("button");
3997
4125
  this.applyAnchorPosition("button");
3998
4126
  this.measureContext("window");
@@ -4086,7 +4214,7 @@ var WebInspectorElement = class extends LitElement {
4086
4214
  }
4087
4215
  this.playThreadsExampleOverviewVideo(video, lifecycleGeneration);
4088
4216
  };
4089
- this.handleThreadsSetupPromptCopy = async () => {
4217
+ this.handleThreadsSetupPromptCopy = async (event) => {
4090
4218
  const generation = this.threadsSetupPromptCopyGeneration += 1;
4091
4219
  if (this.threadsSetupPromptCopyResetTimeoutId !== null) {
4092
4220
  window.clearTimeout(this.threadsSetupPromptCopyResetTimeoutId);
@@ -4094,12 +4222,13 @@ var WebInspectorElement = class extends LitElement {
4094
4222
  }
4095
4223
  this.threadsSetupPromptCopyState = "idle";
4096
4224
  this.requestUpdate();
4097
- if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
4225
+ const clipboard = this.getClipboard(event);
4226
+ if (!clipboard?.writeText) {
4098
4227
  this.showThreadsSetupPromptCopyState("error", generation);
4099
4228
  return;
4100
4229
  }
4101
4230
  try {
4102
- await navigator.clipboard.writeText(THREADS_RUNTIME_SETUP_PROMPT);
4231
+ await clipboard.writeText(THREADS_RUNTIME_SETUP_PROMPT);
4103
4232
  this.showThreadsSetupPromptCopyState("copied", generation);
4104
4233
  } catch {
4105
4234
  this.showThreadsSetupPromptCopyState("error", generation);
@@ -4120,7 +4249,8 @@ var WebInspectorElement = class extends LitElement {
4120
4249
  this.handleGlobalPointerDown = (event) => {
4121
4250
  if (!this.contextMenuOpen) return;
4122
4251
  if (!event.composedPath().some((node) => {
4123
- return node instanceof HTMLElement && node.dataset?.contextDropdownRoot === "true";
4252
+ const candidate = node;
4253
+ return typeof candidate.getAttribute === "function" && candidate.getAttribute("data-context-dropdown-root") === "true";
4124
4254
  })) {
4125
4255
  this.contextMenuOpen = false;
4126
4256
  this.requestUpdate();
@@ -4132,8 +4262,10 @@ var WebInspectorElement = class extends LitElement {
4132
4262
  };
4133
4263
  this.copyResetTimeouts = /* @__PURE__ */ new WeakMap();
4134
4264
  this.handleAnnouncementContentClick = (event) => {
4135
- const button = (event.target instanceof HTMLElement ? event.target : null)?.closest(".announcement-code__copy");
4136
- if (!(button instanceof HTMLButtonElement)) {
4265
+ const target = event.target;
4266
+ const closest = typeof target?.closest === "function" ? target.closest(".announcement-code__copy") : null;
4267
+ const button = closest?.tagName === "BUTTON" ? closest : null;
4268
+ if (!button) {
4137
4269
  this.trackBannerClickedOnce({ cta: "body" });
4138
4270
  return;
4139
4271
  }
@@ -4143,12 +4275,13 @@ var WebInspectorElement = class extends LitElement {
4143
4275
  const code = this.decodeBase64(encoded);
4144
4276
  if (!code) return;
4145
4277
  const showCopied = () => {
4278
+ const view = button.ownerDocument.defaultView ?? window;
4146
4279
  const existing = this.copyResetTimeouts.get(button);
4147
- if (existing !== void 0) window.clearTimeout(existing);
4280
+ if (existing !== void 0) view.clearTimeout(existing);
4148
4281
  button.setAttribute("data-copied", "true");
4149
4282
  button.setAttribute("aria-label", "Code copied");
4150
4283
  button.textContent = "Copied";
4151
- const id = window.setTimeout(() => {
4284
+ const id = view.setTimeout(() => {
4152
4285
  button.removeAttribute("data-copied");
4153
4286
  button.setAttribute("aria-label", "Copy code");
4154
4287
  button.textContent = "Copy";
@@ -4156,7 +4289,8 @@ var WebInspectorElement = class extends LitElement {
4156
4289
  }, 1500);
4157
4290
  this.copyResetTimeouts.set(button, id);
4158
4291
  };
4159
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) navigator.clipboard.writeText(code).then(showCopied, () => {});
4292
+ const clipboard = this.getClipboard(event);
4293
+ if (clipboard?.writeText) clipboard.writeText(code).then(showCopied, () => {});
4160
4294
  };
4161
4295
  }
4162
4296
  static {
@@ -4439,6 +4573,12 @@ var WebInspectorElement = class extends LitElement {
4439
4573
  if (!this.areThreadEndpointsAvailable()) return;
4440
4574
  const { displayThreads } = this.getActiveThreadsState();
4441
4575
  const previousSelectedThreadId = this.selectedThreadId;
4576
+ if (this.requestedThreadId !== null) {
4577
+ this.selectedThreadId = this.requestedThreadId;
4578
+ this.selectedRealThreadIsExplicit = true;
4579
+ if (displayThreads.some((thread) => thread.id === this.requestedThreadId)) this.requestedThreadId = null;
4580
+ return;
4581
+ }
4442
4582
  if (this.selectedLocalExampleThreadId !== null && previousSelectedThreadId === this.selectedLocalExampleThreadId && displayThreads.length === 0) {
4443
4583
  this.selectedRealThreadIsExplicit = false;
4444
4584
  return;
@@ -5048,6 +5188,26 @@ var WebInspectorElement = class extends LitElement {
5048
5188
  if (this.selectedContext === "all-agents") return this.flattenedEvents;
5049
5189
  return this.agentEvents.get(this.selectedContext) ?? [];
5050
5190
  }
5191
+ focusThread(options) {
5192
+ if (!options.threadId) return;
5193
+ this.pendingPersistedMenu = null;
5194
+ this.selectedMenu = "threads";
5195
+ this.settingsOpen = false;
5196
+ this.lastSelectedMenuByGroup.threads = "threads";
5197
+ this.contextMenuOpen = false;
5198
+ this.selectedLocalExampleThreadId = null;
5199
+ this.exampleTourActive = false;
5200
+ this.selectedContext = options.agentId && this.contextOptions.some((option) => option.key === options.agentId) ? options.agentId : "all-agents";
5201
+ this.requestedThreadId = options.threadId;
5202
+ this.selectedThreadId = options.threadId;
5203
+ this.selectedRealThreadIsExplicit = true;
5204
+ this.focusedThreadMessageId = options.messageId ?? null;
5205
+ this.threadFocusRequestId += 1;
5206
+ const { displayThreads } = this.getActiveThreadsState();
5207
+ if (displayThreads.some((thread) => thread.id === options.threadId)) this.requestedThreadId = null;
5208
+ this.persistState();
5209
+ this.requestUpdate();
5210
+ }
5051
5211
  filterEvents(events) {
5052
5212
  const query = this.eventFilterText.trim().toLowerCase();
5053
5213
  return events.filter((event) => {
@@ -5189,9 +5349,24 @@ ${argsString}</pre
5189
5349
  if (payload && typeof payload === "object" && "event" in payload) return payload.event;
5190
5350
  return payload;
5191
5351
  }
5192
- async copyToClipboard(text, eventId) {
5352
+ /** Prefer the window that owns the UI: the popup while popped out. */
5353
+ getClipboard(event) {
5354
+ if (this.isPoppedOut) {
5355
+ const popped = this.popOut?.win.navigator.clipboard;
5356
+ if (popped) return popped;
5357
+ }
5358
+ const viewClipboard = (event && "view" in event ? event.view : null)?.navigator.clipboard;
5359
+ if (viewClipboard) return viewClipboard;
5360
+ if (typeof navigator !== "undefined" && "clipboard" in navigator) return navigator.clipboard;
5361
+ }
5362
+ async copyToClipboard(text, eventId, event) {
5363
+ const clipboard = this.getClipboard(event);
5364
+ if (!clipboard) {
5365
+ console.error("Failed to copy to clipboard:", "Clipboard API is not available");
5366
+ return;
5367
+ }
5193
5368
  try {
5194
- await navigator.clipboard.writeText(text);
5369
+ await clipboard.writeText(text);
5195
5370
  this.copiedEvents.add(eventId);
5196
5371
  this.requestUpdate();
5197
5372
  setTimeout(() => {
@@ -6009,6 +6184,9 @@ ${argsString}</pre
6009
6184
  this.ensureBrandFonts();
6010
6185
  window.addEventListener("resize", this.handleResize);
6011
6186
  window.addEventListener("pointerdown", this.handleGlobalPointerDown);
6187
+ window.addEventListener("beforeunload", this.handleAppBeforeUnload);
6188
+ const viteHot = import.meta.hot;
6189
+ if (viteHot) viteHot.on("vite:beforeUpdate", this.handleAppBeforeUnload);
6012
6190
  this.hydrateStateFromStorageEarly();
6013
6191
  this.exampleTourDismissed = this.readThreadsExampleTourDismissed();
6014
6192
  this.tryAutoAttachCore();
@@ -6017,19 +6195,15 @@ ${argsString}</pre
6017
6195
  this.requestUpdate();
6018
6196
  }
6019
6197
  ensureBrandFonts() {
6020
- const FONT_LINK_ID = "cpk-inspector-brand-fonts";
6021
- if (document.getElementById(FONT_LINK_ID)) return;
6022
- const link = document.createElement("link");
6023
- link.id = FONT_LINK_ID;
6024
- link.rel = "stylesheet";
6025
- link.href = "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600&family=Spline+Sans+Mono:wght@600&display=swap";
6026
- document.head.appendChild(link);
6198
+ ensureBrandFont(document);
6027
6199
  }
6028
6200
  disconnectedCallback() {
6029
6201
  super.disconnectedCallback();
6202
+ this.closePopOut();
6030
6203
  if (typeof window !== "undefined") {
6031
6204
  window.removeEventListener("resize", this.handleResize);
6032
6205
  window.removeEventListener("pointerdown", this.handleGlobalPointerDown);
6206
+ window.removeEventListener("beforeunload", this.handleAppBeforeUnload);
6033
6207
  }
6034
6208
  for (const id of this.bodyTransitionTimeoutIds) clearTimeout(id);
6035
6209
  this.bodyTransitionTimeoutIds.clear();
@@ -6079,19 +6253,22 @@ ${argsString}</pre
6079
6253
  this.updateHostTransform(this.isOpen ? "window" : "button");
6080
6254
  }
6081
6255
  render() {
6082
- return this.isOpen ? this.renderWindow() : this.renderButton();
6256
+ return this.isOpen ? html`
6257
+ <div data-inspector-portal-anchor></div>
6258
+ ` : this.renderButton();
6083
6259
  }
6084
6260
  willUpdate() {
6085
6261
  this.reconcileSelectedMenuVisibility();
6086
6262
  }
6087
6263
  updated() {
6264
+ this.syncInspectorPortal();
6088
6265
  this.syncThreadsExampleOverviewVideo();
6089
6266
  this.maybeTrackInspectorMetadataViews();
6090
6267
  if (!this.isOpen || this.selectedGroup !== "agents") {
6091
6268
  this.lastScrolledAgentNavigationLayout = null;
6092
6269
  return;
6093
6270
  }
6094
- const navigation = this.shadowRoot?.querySelector("nav[aria-label=\"Agent navigation\"]");
6271
+ const navigation = this.activeRoot.querySelector("nav[aria-label=\"Agent navigation\"]");
6095
6272
  if (!navigation) return;
6096
6273
  const activeControl = navigation.querySelector("[aria-current=\"page\"]");
6097
6274
  if (!activeControl) return;
@@ -6221,8 +6398,19 @@ ${argsString}</pre
6221
6398
  renderWindow() {
6222
6399
  const windowState = this.contextState.window;
6223
6400
  const isDocked = this.dockMode !== "floating";
6401
+ const isPoppedOut = this.isPoppedOut;
6224
6402
  const isTransitioning = this.hasAttribute("data-transitioning");
6225
- const windowStyles = isDocked ? {
6403
+ const disableDrag = isDocked || isPoppedOut;
6404
+ const windowStyles = isPoppedOut ? {
6405
+ position: "fixed",
6406
+ inset: "0",
6407
+ width: "100%",
6408
+ height: "100%",
6409
+ minWidth: "0",
6410
+ minHeight: "0",
6411
+ borderRadius: "0",
6412
+ overflowX: "hidden"
6413
+ } : isDocked ? {
6226
6414
  ...this.getDockedWindowStyles(),
6227
6415
  overflowX: "hidden"
6228
6416
  } : {
@@ -6250,7 +6438,7 @@ ${argsString}</pre
6250
6438
  data-docked=${isDocked}
6251
6439
  data-transitioning=${isTransitioning}
6252
6440
  >
6253
- ${isDocked ? html`
6441
+ ${isDocked && !isPoppedOut ? html`
6254
6442
  <div
6255
6443
  class="dock-resize-handle pointer-events-auto"
6256
6444
  role="presentation"
@@ -6265,12 +6453,12 @@ ${argsString}</pre
6265
6453
  class="flex flex-1 flex-col overflow-hidden bg-white text-gray-800"
6266
6454
  >
6267
6455
  <div
6268
- class="drag-handle relative z-30 flex flex-col border-b border-gray-200 bg-white/95 backdrop-blur-sm ${isDocked ? "" : this.isDragging && this.pointerContext === "window" ? "cursor-grabbing" : "cursor-grab"}"
6456
+ class="drag-handle relative z-30 flex flex-col border-b border-gray-200 bg-white/95 backdrop-blur-sm ${disableDrag ? "" : this.isDragging && this.pointerContext === "window" ? "cursor-grabbing" : "cursor-grab"}"
6269
6457
  data-drag-context="window"
6270
- @pointerdown=${isDocked ? void 0 : this.handlePointerDown}
6271
- @pointermove=${isDocked ? void 0 : this.handlePointerMove}
6272
- @pointerup=${isDocked ? void 0 : this.handlePointerUp}
6273
- @pointercancel=${isDocked ? void 0 : this.handlePointerCancel}
6458
+ @pointerdown=${disableDrag ? void 0 : this.handlePointerDown}
6459
+ @pointermove=${disableDrag ? void 0 : this.handlePointerMove}
6460
+ @pointerup=${disableDrag ? void 0 : this.handlePointerUp}
6461
+ @pointercancel=${disableDrag ? void 0 : this.handlePointerCancel}
6274
6462
  >
6275
6463
  <div
6276
6464
  class="inspector-account-strip flex flex-wrap items-center gap-3 px-4 py-3"
@@ -6291,27 +6479,46 @@ ${argsString}</pre
6291
6479
  ${agentSelector}
6292
6480
  </div>
6293
6481
  <div class="flex items-center gap-1">
6294
- ${this.renderDockControls()}
6482
+ ${isPoppedOut ? nothing : html`
6483
+ ${this.renderDockControls()}
6484
+ <button
6485
+ class="inspector-account-control flex h-8 w-8 items-center justify-center rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2"
6486
+ type="button"
6487
+ aria-label="Detach Inspector into its own window"
6488
+ title="Detach into its own window"
6489
+ data-testid="cpk-inspector-pop-out"
6490
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6491
+ .click=${this.requestPopOut}
6492
+ @click=${this.requestPopOut}
6493
+ >
6494
+ ${this.renderIcon("PictureInPicture2")}
6495
+ </button>
6496
+ `}
6295
6497
  <button
6296
6498
  class="inspector-account-control flex h-8 w-8 items-center justify-center rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 ${this.settingsOpen ? "inspector-account-control-active" : ""}"
6297
6499
  type="button"
6298
6500
  aria-label="Settings"
6299
6501
  aria-pressed=${this.settingsOpen}
6502
+ title="Settings"
6300
6503
  style=${INTERACTIVE_FOCUS_BASE_STYLE}
6301
6504
  @click=${this.handleSettingsToggle}
6302
6505
  >
6303
- ${this.renderIcon("Settings")}
6304
- </button>
6305
- <button
6306
- class="inspector-account-control flex h-8 w-8 items-center justify-center rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2"
6307
- type="button"
6308
- aria-label="Close Web Inspector"
6309
- style=${INTERACTIVE_FOCUS_BASE_STYLE}
6310
- @pointerdown=${this.handleClosePointerDown}
6311
- @click=${this.handleCloseClick}
6312
- >
6313
- ${this.renderIcon("X")}
6506
+ <span class="inspector-account-control-icon" aria-hidden="true">
6507
+ ${this.renderIcon("Settings")}
6508
+ </span>
6314
6509
  </button>
6510
+ ${isPoppedOut ? nothing : html`
6511
+ <button
6512
+ class="inspector-account-control flex h-8 w-8 items-center justify-center rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2"
6513
+ type="button"
6514
+ aria-label="Close Web Inspector"
6515
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6516
+ @pointerdown=${this.handleClosePointerDown}
6517
+ @click=${this.handleCloseClick}
6518
+ >
6519
+ ${this.renderIcon("X")}
6520
+ </button>
6521
+ `}
6315
6522
  </div>
6316
6523
  </div>
6317
6524
  </div>
@@ -6406,27 +6613,29 @@ ${argsString}</pre
6406
6613
  </div>
6407
6614
  </div>
6408
6615
  </div>
6409
- <div
6410
- class="resize-handle pointer-events-auto absolute bottom-1 right-1 flex h-5 w-5 cursor-nwse-resize items-center justify-center text-gray-400 transition hover:text-gray-600"
6411
- role="presentation"
6412
- aria-hidden="true"
6413
- @pointerdown=${this.handleResizePointerDown}
6414
- @pointermove=${this.handleResizePointerMove}
6415
- @pointerup=${this.handleResizePointerUp}
6416
- @pointercancel=${this.handleResizePointerCancel}
6417
- >
6418
- <svg
6419
- class="h-3 w-3"
6420
- viewBox="0 0 16 16"
6421
- fill="none"
6422
- stroke="currentColor"
6423
- stroke-linecap="round"
6424
- stroke-width="1.5"
6425
- >
6426
- <path d="M5 15L15 5" />
6427
- <path d="M9 15L15 9" />
6428
- </svg>
6429
- </div>
6616
+ ${isPoppedOut ? nothing : html`
6617
+ <div
6618
+ class="resize-handle pointer-events-auto absolute bottom-1 right-1 flex h-5 w-5 cursor-nwse-resize items-center justify-center text-gray-400 transition hover:text-gray-600"
6619
+ role="presentation"
6620
+ aria-hidden="true"
6621
+ @pointerdown=${this.handleResizePointerDown}
6622
+ @pointermove=${this.handleResizePointerMove}
6623
+ @pointerup=${this.handleResizePointerUp}
6624
+ @pointercancel=${this.handleResizePointerCancel}
6625
+ >
6626
+ <svg
6627
+ class="h-3 w-3"
6628
+ viewBox="0 0 16 16"
6629
+ fill="none"
6630
+ stroke="currentColor"
6631
+ stroke-linecap="round"
6632
+ stroke-width="1.5"
6633
+ >
6634
+ <path d="M5 15L15 5" />
6635
+ <path d="M9 15L15 9" />
6636
+ </svg>
6637
+ </div>
6638
+ `}
6430
6639
  </section>
6431
6640
  `;
6432
6641
  }
@@ -6490,6 +6699,62 @@ ${argsString}</pre
6490
6699
  get activeContext() {
6491
6700
  return this.isOpen ? "window" : "button";
6492
6701
  }
6702
+ get isPoppedOut() {
6703
+ return this.popOut !== null && !this.popOut.win.closed;
6704
+ }
6705
+ get activeRoot() {
6706
+ if (this.isPoppedOut) return this.popOut.win.document;
6707
+ return this.renderRoot;
6708
+ }
6709
+ getPopOutCssTexts() {
6710
+ const fromStatic = WebInspectorElement.styles.map((sheet) => "cssText" in sheet ? String(sheet.cssText) : "");
6711
+ const overlay = `
6712
+ html, body { margin: 0; height: 100%; background: #ffffff; }
6713
+ .inspector-window {
6714
+ position: fixed !important;
6715
+ inset: 0 !important;
6716
+ width: 100% !important;
6717
+ height: 100% !important;
6718
+ min-width: 0 !important;
6719
+ min-height: 0 !important;
6720
+ border-radius: 0 !important;
6721
+ }
6722
+ `;
6723
+ return [...fromStatic.filter(Boolean), overlay];
6724
+ }
6725
+ unbindPopOutPointerDown(win) {
6726
+ try {
6727
+ win.removeEventListener("pointerdown", this.handleGlobalPointerDown);
6728
+ } catch {}
6729
+ }
6730
+ closePopOut() {
6731
+ const handle = this.popOut;
6732
+ if (!handle) return;
6733
+ this.handlePopOutClosed();
6734
+ handle.close();
6735
+ }
6736
+ syncInspectorPortal() {
6737
+ if (!this.inspectorPortal) {
6738
+ const portal = (this.ownerDocument ?? document).createElement("div");
6739
+ portal.dataset.inspectorPortal = "true";
6740
+ portal.style.display = "contents";
6741
+ this.inspectorPortal = portal;
6742
+ }
6743
+ if (!this.isOpen) {
6744
+ render(nothing, this.inspectorPortal, {
6745
+ host: this,
6746
+ creationScope: this.ownerDocument ?? document
6747
+ });
6748
+ this.inspectorPortal.remove();
6749
+ return;
6750
+ }
6751
+ render(this.renderWindow(), this.inspectorPortal, {
6752
+ host: this,
6753
+ creationScope: this.ownerDocument ?? document
6754
+ });
6755
+ const target = this.isPoppedOut ? this.popOut.win.document.body : this.renderRoot.querySelector("[data-inspector-portal-anchor]");
6756
+ if (target && this.inspectorPortal.parentNode !== target) target.appendChild(this.inspectorPortal);
6757
+ }
6493
6758
  measureContext(context) {
6494
6759
  const selector = context === "window" ? ".inspector-window" : ".console-button";
6495
6760
  const element = this.renderRoot?.querySelector(selector);
@@ -6627,6 +6892,7 @@ ${argsString}</pre
6627
6892
  } else document.body.style.transition = "";
6628
6893
  }
6629
6894
  updateHostTransform(context = this.activeContext) {
6895
+ if (this.isPoppedOut) return;
6630
6896
  if (context !== this.activeContext) return;
6631
6897
  if (this.isOpen && this.dockMode === "docked-left") this.style.transform = `translate3d(0, 0, 0)`;
6632
6898
  else {
@@ -6663,6 +6929,7 @@ ${argsString}</pre
6663
6929
  this.applyAnchorPosition("button");
6664
6930
  }
6665
6931
  applyAnchorPosition(context) {
6932
+ if (this.isPoppedOut) return;
6666
6933
  if (typeof window === "undefined") return;
6667
6934
  const viewport = this.getViewportSize();
6668
6935
  applyAnchorPosition(this.contextState[context], viewport, EDGE_MARGIN);
@@ -6682,7 +6949,9 @@ ${argsString}</pre
6682
6949
  this.setDragging(false);
6683
6950
  this.draggedDuringInteraction = false;
6684
6951
  }
6685
- openInspector(source) {
6952
+ openInspector(source, options = {}) {
6953
+ if (options.threadId) this.focusThread(options);
6954
+ if (this.isPoppedOut) return;
6686
6955
  if (this.isOpen) return;
6687
6956
  const hadUnseenAnnouncement = this.hasUnseenAnnouncement;
6688
6957
  this.showAnnouncementPreview = false;
@@ -6702,6 +6971,7 @@ ${argsString}</pre
6702
6971
  });
6703
6972
  }
6704
6973
  closeInspector() {
6974
+ if (this.isPoppedOut) return;
6705
6975
  if (!this.isOpen) return;
6706
6976
  this.isOpen = false;
6707
6977
  if (this.dockMode !== "floating") this.removeDockStyles();
@@ -7177,7 +7447,7 @@ ${argsString}</pre
7177
7447
  const step = THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0];
7178
7448
  if (!step) return;
7179
7449
  this.updateComplete.then(() => {
7180
- (this.shadowRoot?.querySelector("cpk-thread-details"))?.selectTab?.(step.tab);
7450
+ this.activeRoot.querySelector("cpk-thread-details")?.selectTab?.(step.tab);
7181
7451
  });
7182
7452
  }
7183
7453
  startExampleTour(autoStarted) {
@@ -7224,6 +7494,8 @@ ${argsString}</pre
7224
7494
  this.requestUpdate();
7225
7495
  }
7226
7496
  handleThreadsThreadSelected(threadId, showingExamples) {
7497
+ this.requestedThreadId = null;
7498
+ this.focusedThreadMessageId = null;
7227
7499
  if (showingExamples && this.selectedThreadId === threadId && this.selectedLocalExampleThreadId === threadId) {
7228
7500
  this.selectedThreadId = null;
7229
7501
  this.selectedRealThreadIsExplicit = false;
@@ -7417,7 +7689,7 @@ ${argsString}</pre
7417
7689
  }
7418
7690
  /** Reconciles the rendered media node with its current lifecycle. */
7419
7691
  syncThreadsExampleOverviewVideo() {
7420
- const video = this.shadowRoot?.querySelector(".cpk-threads-overview-video");
7692
+ const video = this.activeRoot.querySelector(".cpk-threads-overview-video");
7421
7693
  if (!video) {
7422
7694
  if (this.threadsExampleOverviewVideoElement || this.threadsExampleOverviewVideoState !== "deferred" || this.threadsExampleOverviewVideoLoadTimer !== null || this.threadsExampleOverviewVideoIdleCallbackId !== null || this.threadsExampleOverviewVideoPlayOnNextBind) this.cleanupThreadsExampleOverviewVideo();
7423
7695
  return;
@@ -8329,6 +8601,8 @@ ${argsString}</pre
8329
8601
  .headers=${this._core?.headers ?? {}}
8330
8602
  .threadInspectionAvailable=${selectedThreadIsLocalExample || this.areThreadEndpointsAvailable() && this._core?.threadEndpoints?.inspect !== false}
8331
8603
  .liveMessageVersion=${this.liveMessageVersion.get(selectedThread.id) ?? 0}
8604
+ .focusMessageId=${this.focusedThreadMessageId}
8605
+ .focusRequestId=${this.threadFocusRequestId}
8332
8606
  .agentStateInput=${this.getLatestStateForAgent(selectedThread.agentId)}
8333
8607
  .agentEventsInput=${this.agentEvents.get(selectedThread.agentId) ?? []}
8334
8608
  ></cpk-thread-details>
@@ -8518,7 +8792,7 @@ ${argsString}</pre
8518
8792
  return html`
8519
8793
  <tr
8520
8794
  class="${rowBg} cursor-pointer transition hover:bg-blue-50/50"
8521
- @click=${() => this.toggleRowExpansion(event.id)}
8795
+ @click=${(clickEvent) => this.toggleRowExpansion(event.id, clickEvent)}
8522
8796
  >
8523
8797
  <td
8524
8798
  class="border-l border-r border-b border-gray-200 px-3 py-2"
@@ -8551,7 +8825,7 @@ ${prettyEvent}</pre
8551
8825
  class="absolute right-0 top-0 cursor-pointer rounded px-2 py-1 text-[10px] opacity-0 transition group-hover:opacity-100 ${this.copiedEvents.has(event.id) ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-900"}"
8552
8826
  @click=${(e) => {
8553
8827
  e.stopPropagation();
8554
- this.copyToClipboard(prettyEvent, event.id);
8828
+ this.copyToClipboard(prettyEvent, event.id, e);
8555
8829
  }}
8556
8830
  >
8557
8831
  ${this.copiedEvents.has(event.id) ? html`
@@ -8896,7 +9170,7 @@ ${prettyEvent}</pre
8896
9170
  if (previousMenu !== "memories" && !this.core?.telemetryDisabled) trackMemoriesTabClicked(this.getMemoriesTelemetryProps());
8897
9171
  }
8898
9172
  if (key === "ag-ui-events" || key === "agents") requestAnimationFrame(() => {
8899
- const scroller = this.shadowRoot?.getElementById("cpk-main-scroll");
9173
+ const scroller = this.activeRoot.querySelector("#cpk-main-scroll");
8900
9174
  if (scroller) scroller.scrollTop = 0;
8901
9175
  });
8902
9176
  this.contextMenuOpen = false;
@@ -9401,7 +9675,7 @@ ${prettyEvent}</pre
9401
9675
  class="cpk-copy-btn"
9402
9676
  @click=${(e) => {
9403
9677
  e.stopPropagation();
9404
- this.copyContextValue(id, `${id}:id`);
9678
+ this.copyContextValue(id, `${id}:id`, e);
9405
9679
  }}
9406
9680
  >
9407
9681
  ${this.copiedContextItems.has(`${id}:id`) ? "✓" : "Copy"}
@@ -9417,7 +9691,7 @@ ${prettyEvent}</pre
9417
9691
  class="cpk-copy-btn"
9418
9692
  @click=${(e) => {
9419
9693
  e.stopPropagation();
9420
- this.copyContextValue(context.value, id);
9694
+ this.copyContextValue(context.value, id, e);
9421
9695
  }}
9422
9696
  >
9423
9697
  ${this.copiedContextItems.has(id) ? "Copied" : "Copy JSON"}
@@ -9471,14 +9745,15 @@ ${prettyEvent}</pre
9471
9745
  return String(value);
9472
9746
  }
9473
9747
  }
9474
- async copyContextValue(value, contextId) {
9475
- if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
9748
+ async copyContextValue(value, contextId, event) {
9749
+ const clipboard = this.getClipboard(event);
9750
+ if (!clipboard?.writeText) {
9476
9751
  console.warn("Clipboard API is not available in this environment.");
9477
9752
  return;
9478
9753
  }
9479
9754
  const serialized = this.formatContextValue(value);
9480
9755
  try {
9481
- await navigator.clipboard.writeText(serialized);
9756
+ await clipboard.writeText(serialized);
9482
9757
  this.copiedContextItems.add(contextId);
9483
9758
  this.requestUpdate();
9484
9759
  setTimeout(() => {
@@ -9494,8 +9769,9 @@ ${prettyEvent}</pre
9494
9769
  else this.expandedContextItems.add(contextId);
9495
9770
  this.requestUpdate();
9496
9771
  }
9497
- toggleRowExpansion(eventId) {
9498
- const selection = window.getSelection();
9772
+ toggleRowExpansion(eventId, event) {
9773
+ const target = event?.currentTarget;
9774
+ const selection = (target && "ownerDocument" in target ? target.ownerDocument : this.ownerDocument)?.getSelection();
9499
9775
  if (selection && selection.toString().length > 0) return;
9500
9776
  if (this.expandedRows.has(eventId)) this.expandedRows.delete(eventId);
9501
9777
  else this.expandedRows.add(eventId);