@copilotkit/web-inspector 1.66.2 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +92 -13
  2. package/dist/index.cjs +1444 -429
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +118 -8
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.mts +118 -8
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +1446 -431
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/index.umd.js +1729 -533
  11. package/dist/index.umd.js.map +1 -1
  12. package/dist/lib/inspector-metadata.cjs +60 -0
  13. package/dist/lib/inspector-metadata.cjs.map +1 -0
  14. package/dist/lib/inspector-metadata.mjs +60 -0
  15. package/dist/lib/inspector-metadata.mjs.map +1 -0
  16. package/dist/lib/telemetry.cjs +117 -32
  17. package/dist/lib/telemetry.cjs.map +1 -1
  18. package/dist/lib/telemetry.mjs +116 -33
  19. package/dist/lib/telemetry.mjs.map +1 -1
  20. package/dist/lib/thread-selection.cjs +36 -0
  21. package/dist/lib/thread-selection.cjs.map +1 -0
  22. package/dist/lib/thread-selection.mjs +35 -0
  23. package/dist/lib/thread-selection.mjs.map +1 -0
  24. package/dist/package.cjs +1 -1
  25. package/dist/package.mjs +1 -1
  26. package/dist/styles/generated.cjs +1 -1
  27. package/dist/styles/generated.cjs.map +1 -1
  28. package/dist/styles/generated.mjs +1 -1
  29. package/dist/styles/generated.mjs.map +1 -1
  30. package/package.json +9 -4
  31. package/CHANGELOG.md +0 -505
  32. package/dev/css-raw-import.spec.ts +0 -31
  33. package/dev/index.html +0 -115
  34. package/dev/main.ts +0 -165
  35. package/dev/vite.config.ts +0 -30
  36. package/src/__tests__/memory-recall.spec.ts +0 -76
  37. package/src/__tests__/telemetry-egress-guard.spec.ts +0 -41
  38. package/src/__tests__/web-inspector.spec.ts +0 -3911
  39. package/src/assets/inspector-logo-icon.svg +0 -8
  40. package/src/assets/inspector-logo.svg +0 -40
  41. package/src/components.d.ts +0 -20
  42. package/src/index.ts +0 -11839
  43. package/src/lib/__tests__/telemetry.test.ts +0 -557
  44. package/src/lib/context-helpers.ts +0 -166
  45. package/src/lib/persistence.ts +0 -206
  46. package/src/lib/telemetry.ts +0 -474
  47. package/src/lib/testing/telemetry-egress-guard.ts +0 -33
  48. package/src/lib/types.ts +0 -19
  49. package/src/styles/generated.css +0 -2
  50. package/src/styles/tailwind.css +0 -23
  51. package/src/types/css.d.ts +0 -4
  52. package/src/types/svg.d.ts +0 -4
  53. package/tsconfig.json +0 -14
  54. package/tsdown.config.ts +0 -45
  55. package/vitest.config.ts +0 -66
  56. package/vitest.setup.ts +0 -84
package/dist/index.mjs CHANGED
@@ -3,23 +3,61 @@ 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 { TELEMETRY_DOCS_URL, ensureTelemetryDistinctId, getRuntimeUrlType, getTelemetryDistinctIdForUrl, maybeShowDisclosure, trackBannerClicked, trackBannerDismissed, trackBannerViewed, trackInspectorOpened, trackMemoriesTabClicked, trackTalkToEngineerClicked, trackThreadsEmptyEnabledViewed, trackThreadsEnabledViewed, trackThreadsExampleSelected, trackThreadsExampleTourCompleted, trackThreadsExampleTourDismissed, trackThreadsExampleTourReopened, trackThreadsExampleTourStarted, trackThreadsExampleTourStepViewed, trackThreadsExampleViewed, trackThreadsIntelligenceSignupClicked, trackThreadsLockedViewed, trackThreadsTabClicked, trackThreadsTalkToEngineerClicked } from "./lib/telemetry.mjs";
6
+ import { projectInspectorMetadata } from "./lib/inspector-metadata.mjs";
7
+ import { selectVisibleRealThreadId } from "./lib/thread-selection.mjs";
8
+ 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";
7
9
  import { LitElement, css, html, nothing, unsafeCSS } from "lit";
8
10
  import { marked } from "marked";
9
11
  import { styleMap } from "lit/directives/style-map.js";
10
12
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
11
13
  import { icons } from "lucide";
12
- import { CopilotKitCoreRuntimeConnectionStatus, ɵcreateThreadStore, ɵselectMemories, ɵselectMemoriesAvailable, ɵselectMemoriesError, ɵselectMemoriesIsLoading, ɵselectMemoriesRealtimeStatus, ɵselectThreads, ɵselectThreadsError } from "@copilotkit/core";
14
+ import { CopilotKitCoreRuntimeConnectionStatus, ɵcreateThreadStore, ɵselectMemories, ɵselectMemoriesAvailable, ɵselectMemoriesError, ɵselectMemoriesIsLoading, ɵselectMemoriesRealtimeStatus, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading } from "@copilotkit/core";
13
15
 
14
16
  //#region src/index.ts
15
17
  const WEB_INSPECTOR_TAG = "cpk-web-inspector";
16
18
  const THREAD_INSPECTOR_TAG = "cpk-thread-inspector";
17
19
  /**
18
- * User-facing label for the memory surface (nav item + view header). The menu
19
- * KEY stays "memories" for persistence/telemetry stability; only the label
20
- * changed from "Learning" to "Memory".
20
+ * User-facing label for the memory view header. The legacy menu key stays
21
+ * "memories" for persistence and telemetry stability.
21
22
  */
22
23
  const MEMORY_VIEW_LABEL = "Memory";
24
+ const INSPECTOR_GROUPS = {
25
+ threads: ["threads"],
26
+ agents: [
27
+ "ag-ui-events",
28
+ "agents",
29
+ "frontend-tools",
30
+ "capabilities",
31
+ "agent-context"
32
+ ],
33
+ learning: ["memories"]
34
+ };
35
+ const INSPECTOR_MENU_KEYS = [
36
+ ...INSPECTOR_GROUPS.threads,
37
+ ...INSPECTOR_GROUPS.agents,
38
+ ...INSPECTOR_GROUPS.learning
39
+ ];
40
+ /** Return whether persisted state names a legacy Inspector leaf. */
41
+ function isInspectorMenuKey(value) {
42
+ return typeof value === "string" && INSPECTOR_MENU_KEYS.some((menuKey) => menuKey === value);
43
+ }
44
+ const INSPECTOR_PRIMARY_NAVIGATION = [
45
+ {
46
+ key: "threads",
47
+ label: "Threads",
48
+ icon: "MessageSquare"
49
+ },
50
+ {
51
+ key: "agents",
52
+ label: "Agents",
53
+ icon: "Bot"
54
+ },
55
+ {
56
+ key: "learning",
57
+ label: "Learning",
58
+ icon: "Brain"
59
+ }
60
+ ];
23
61
  const EDGE_MARGIN = 16;
24
62
  const DRAG_THRESHOLD = 6;
25
63
  const MIN_WINDOW_WIDTH = 600;
@@ -38,21 +76,55 @@ const DEFAULT_WINDOW_SIZE = {
38
76
  };
39
77
  const DOCKED_LEFT_WIDTH = 500;
40
78
  const MAX_AGENT_EVENTS = 200;
79
+ const INTERACTIVE_FOCUS_BASE_STYLE = "outline-style:solid;outline-width:2px;outline-color:transparent;outline-offset:2px;cursor:pointer;";
41
80
  const MAX_PENDING_BANNER_VIEWED = 20;
42
81
  const MAX_TOTAL_EVENTS = 500;
43
82
  const INTELLIGENCE_SIGNUP_URL = "https://go.copilotkit.ai/intelligence-signup";
44
- const THREADS_INTELLIGENCE_SIGNIN_URL = "https://dashboard.operations.copilotkit.ai/sign-in";
45
83
  const TALK_TO_ENGINEER_URL = "https://www.copilotkit.ai/talk-to-an-engineer";
46
84
  const CAPABILITIES_TAB_LABEL = "Capabilities";
47
85
  const THREADS_DOCS_URL = "https://docs.copilotkit.ai/threads";
86
+ const THREADS_RUNTIME_SETUP_DOCS_URL = "https://docs.copilotkit.ai/backend/runtime-endpoints#enable-rich-threads-routes";
87
+ const THREADS_RUNTIME_SETUP_PROMPT = [
88
+ `Read ${THREADS_RUNTIME_SETUP_DOCS_URL} and finish setting up Rich Threads in this repository.`,
89
+ "",
90
+ "First inspect the repository's agent instructions, installed CopilotKit versions, Runtime adapter, frontend provider, route or proxy setup, and existing authentication. Preserve the current framework and deployment model. Preserve existing authentication middleware and access checks on every Runtime route.",
91
+ "",
92
+ "Follow the guide to enable the multi-route Runtime, align the frontend transport, scope identifyUser to the existing server-verified signed-in application user, and expose the full Runtime subtree for GET, POST, PATCH, and DELETE. Never use a fixed demo identity in production. If no trusted user identity exists, stop and ask me which auth source to use.",
93
+ "",
94
+ "Start the app and verify GET {basePath}/info reports threadEndpoints.list, inspect, mutations, and realtimeMetadata as true. Run focused tests, lint, and typecheck. Report the files changed, commands run, and verification result. If blocked, explain the missing input; do not invent setup."
95
+ ].join("\n");
48
96
  const SELF_HOSTED_INTELLIGENCE_URL = "https://docs.copilotkit.ai/premium/self-hosting";
49
97
  const THREADS_EXAMPLE_OVERVIEW_VIDEO_URL = "https://cdn.copilotkit.ai/corp-site/videos/copilotkit-generative-ui-agentic-frontend-demo.webm";
98
+ const THREADS_EXAMPLE_OVERVIEW_VIDEO_FALLBACK = "The demo video is unavailable. Use the example threads to explore Messages, AG-UI Events, and State.";
50
99
  const THREADS_EXAMPLE_TOUR_STORAGE_KEY = "cpk:inspector:threads-example-tour:v1";
51
100
  const THREADS_EXAMPLE_AGENT_ID = "threads-feature";
101
+ /**
102
+ * Selects the Thread store status with a stable object identity so loading-only
103
+ * transitions reach one of the Inspector's two existing store subscriptions.
104
+ */
105
+ function createThreadStoreStatusSelector() {
106
+ let previousError;
107
+ let previousIsLoading;
108
+ let previousStatus;
109
+ return (state) => {
110
+ const error = ɵselectThreadsError(state);
111
+ const isLoading = ɵselectThreadsIsLoading(state);
112
+ if (previousStatus && previousError === error && previousIsLoading === isLoading) return previousStatus;
113
+ previousError = error;
114
+ previousIsLoading = isLoading;
115
+ previousStatus = {
116
+ error,
117
+ isLoading
118
+ };
119
+ return previousStatus;
120
+ };
121
+ }
52
122
  const AGENT_EVENT_TYPES = [
53
123
  "RUN_STARTED",
54
124
  "RUN_FINISHED",
55
125
  "RUN_ERROR",
126
+ "STEP_STARTED",
127
+ "STEP_FINISHED",
56
128
  "TEXT_MESSAGE_START",
57
129
  "TEXT_MESSAGE_CONTENT",
58
130
  "TEXT_MESSAGE_END",
@@ -132,6 +204,15 @@ const THREADS_EXAMPLE_THREADS = [
132
204
  isExample: true
133
205
  }
134
206
  ];
207
+ /** Map Sam's fixed example IDs to a closed telemetry vocabulary. */
208
+ function getExampleKind(threadId) {
209
+ switch (threadId) {
210
+ case "example-realtime-sync": return "realtime_sync";
211
+ case "example-manage-history": return "manage_history";
212
+ case "example-inspect-runs": return "inspect_runs";
213
+ default: return;
214
+ }
215
+ }
135
216
  const THREADS_EXAMPLE_DETAILS = {
136
217
  "example-realtime-sync": {
137
218
  messages: [{
@@ -275,13 +356,13 @@ const THREADS_EXAMPLE_DETAILS = {
275
356
  const THREADS_EXAMPLE_TOUR_STEPS = [
276
357
  {
277
358
  tab: "timeline",
278
- label: "Timeline",
359
+ label: "Messages",
279
360
  title: "Read the run as a story",
280
361
  body: "The timeline turns messages, tool calls, state changes, and run markers into a scannable debugging trail."
281
362
  },
282
363
  {
283
364
  tab: "raw-events",
284
- label: "Raw AG-UI Events",
365
+ label: "AG-UI Events",
285
366
  title: "Drop into the protocol payloads",
286
367
  body: "Raw events show the exact AG-UI stream behind the timeline when you need to verify ordering or payload shape."
287
368
  },
@@ -292,6 +373,28 @@ const THREADS_EXAMPLE_TOUR_STEPS = [
292
373
  body: "The state tab shows the saved values that make a thread resumable across sessions."
293
374
  }
294
375
  ];
376
+ /** Return only the three supported tour step/tab pairs. */
377
+ function getExampleTourTelemetryPair(index) {
378
+ switch (index) {
379
+ case 0: return {
380
+ tour_step: 1,
381
+ tour_tab: "timeline"
382
+ };
383
+ case 1: return {
384
+ tour_step: 2,
385
+ tour_tab: "raw-events"
386
+ };
387
+ case 2: return {
388
+ tour_step: 3,
389
+ tour_tab: "state"
390
+ };
391
+ default: return;
392
+ }
393
+ }
394
+ /** Convert rendered action placement to its stable telemetry key. */
395
+ function getMetadataActionPlacement(placement) {
396
+ return placement === "threads-footer" ? "threads_footer" : "threads_locked";
397
+ }
295
398
  function escapeHtml(s) {
296
399
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
297
400
  }
@@ -303,10 +406,10 @@ function highlightedJson(obj) {
303
406
  }
304
407
  const colors = {
305
408
  key: "#5558B2",
306
- str: "#189370",
307
- num: "#996300",
409
+ str: "#087653",
410
+ num: "#8a5900",
308
411
  bool: "#c0333a",
309
- nil: "#838389"
412
+ nil: "#68686e"
310
413
  };
311
414
  const json = JSON.stringify(obj, null, 2);
312
415
  if (!json) return "";
@@ -336,7 +439,7 @@ function eventColors(type) {
336
439
  };
337
440
  if (type.startsWith("TOOL_CALL")) return {
338
441
  bg: "rgba(133,236,206,0.15)",
339
- fg: "#189370"
442
+ fg: "#087653"
340
443
  };
341
444
  if (type.startsWith("STATE")) return {
342
445
  bg: "rgba(190,194,255,0.102)",
@@ -348,11 +451,11 @@ function eventColors(type) {
348
451
  };
349
452
  if (type.startsWith("RUN_") || type.startsWith("STEP_")) return {
350
453
  bg: "rgba(255,172,77,0.2)",
351
- fg: "#996300"
454
+ fg: "#8a5900"
352
455
  };
353
456
  return {
354
457
  bg: "#F7F7F9",
355
- fg: "#838389"
458
+ fg: "#68686e"
356
459
  };
357
460
  }
358
461
  function formatTimestamp(ts) {
@@ -372,6 +475,7 @@ var CpkThreadList = class extends LitElement {
372
475
  this.threads = [];
373
476
  this.selectedThreadId = null;
374
477
  this.errorMessage = null;
478
+ this.suppressEmptyState = false;
375
479
  this._query = "";
376
480
  this.onSearchInput = (event) => {
377
481
  this._query = event.target.value;
@@ -382,6 +486,7 @@ var CpkThreadList = class extends LitElement {
382
486
  threads: { attribute: false },
383
487
  selectedThreadId: { attribute: false },
384
488
  errorMessage: { attribute: false },
489
+ suppressEmptyState: { attribute: false },
385
490
  _query: { state: true }
386
491
  };
387
492
  }
@@ -438,7 +543,18 @@ var CpkThreadList = class extends LitElement {
438
543
 
439
544
  /* ── Thread item ── */
440
545
  .cpk-tl__item {
546
+ appearance: none;
547
+ display: block;
548
+ box-sizing: border-box;
549
+ width: 100%;
550
+ margin: 0;
551
+ border: 0;
552
+ border-radius: 0;
441
553
  padding: 11px 13px;
554
+ background: transparent;
555
+ color: inherit;
556
+ font: inherit;
557
+ text-align: left;
442
558
  cursor: pointer;
443
559
  border-bottom: 1px solid #e9e9ef;
444
560
  border-left: 3px solid transparent;
@@ -458,6 +574,13 @@ var CpkThreadList = class extends LitElement {
458
574
  background: #bec2ff33;
459
575
  }
460
576
 
577
+ .cpk-tl__item:focus-visible {
578
+ outline-color: #5558b2;
579
+ outline-offset: -2px;
580
+ outline-style: solid;
581
+ outline-width: 2px;
582
+ }
583
+
461
584
  .cpk-tl__row1 {
462
585
  display: flex;
463
586
  align-items: center;
@@ -476,7 +599,7 @@ var CpkThreadList = class extends LitElement {
476
599
  }
477
600
 
478
601
  .cpk-tl__name--unnamed {
479
- color: #838389;
602
+ color: #68686e;
480
603
  font-style: italic;
481
604
  font-weight: 400;
482
605
  }
@@ -484,7 +607,7 @@ var CpkThreadList = class extends LitElement {
484
607
  .cpk-tl__time {
485
608
  font-family: "Spline Sans Mono", monospace;
486
609
  font-size: 10px;
487
- color: #838389;
610
+ color: #68686e;
488
611
  flex-shrink: 0;
489
612
  }
490
613
 
@@ -509,14 +632,14 @@ var CpkThreadList = class extends LitElement {
509
632
 
510
633
  .cpk-tl__pill--example {
511
634
  background: rgba(133, 236, 206, 0.22);
512
- color: #189370;
635
+ color: #087653;
513
636
  }
514
637
 
515
638
  /* ── Empty state ── */
516
639
  .cpk-tl__empty {
517
640
  padding: 32px 16px;
518
641
  text-align: center;
519
- color: #838389;
642
+ color: #68686e;
520
643
  font-size: 12px;
521
644
  display: flex;
522
645
  flex-direction: column;
@@ -570,11 +693,13 @@ var CpkThreadList = class extends LitElement {
570
693
  <!-- Thread list -->
571
694
  <div class="cpk-tl__list">
572
695
  ${filtered.map((thread) => html`
573
- <div
696
+ <button
697
+ type="button"
698
+ aria-current=${this.selectedThreadId === thread.id ? "true" : nothing}
574
699
  class="cpk-tl__item ${this.selectedThreadId === thread.id ? "cpk-tl__item--active" : ""}"
575
700
  @click=${() => this.onThreadClick(thread.id)}
576
701
  >
577
- <div class="cpk-tl__row1">
702
+ <span class="cpk-tl__row1">
578
703
  <span
579
704
  class="cpk-tl__name ${!thread.name ? "cpk-tl__name--unnamed" : ""}"
580
705
  >${thread.name ?? "Untitled"}</span
@@ -582,16 +707,16 @@ var CpkThreadList = class extends LitElement {
582
707
  <span class="cpk-tl__time"
583
708
  >${this.relativeTime(thread.updatedAt)}</span
584
709
  >
585
- </div>
586
- <div class="cpk-tl__meta">
710
+ </span>
711
+ <span class="cpk-tl__meta">
587
712
  <span class="cpk-tl__pill">${thread.agentId}</span>
588
713
  ${thread.isExample ? html`
589
714
  <span class="cpk-tl__pill cpk-tl__pill--example">Example</span>
590
715
  ` : nothing}
591
- </div>
592
- </div>
716
+ </span>
717
+ </button>
593
718
  `)}
594
- ${filtered.length === 0 ? html`
719
+ ${filtered.length === 0 && !this.suppressEmptyState ? html`
595
720
  <div class="cpk-tl__empty">
596
721
  ${this.errorMessage ? html`
597
722
  <svg
@@ -686,10 +811,12 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
686
811
  this._messagesAbort = null;
687
812
  this._eventsAbort = null;
688
813
  this._stateAbort = null;
814
+ this._hasConnectedOnce = false;
689
815
  this._dividerResizing = false;
690
816
  this._dividerPointerId = -1;
691
817
  this._dividerStartX = 0;
692
818
  this._dividerStartWidth = 0;
819
+ this.domIdPrefix = `cpk-thread-detail-${CpkThreadInspector.nextDomId++}`;
693
820
  this.onDetailDividerDown = (event) => {
694
821
  this._dividerResizing = true;
695
822
  this._dividerPointerId = event.pointerId;
@@ -744,6 +871,9 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
744
871
  _activatedTabs: { state: true }
745
872
  };
746
873
  }
874
+ static {
875
+ this.nextDomId = 1;
876
+ }
747
877
  static {
748
878
  this.COLLAPSE_THRESHOLD = 800;
749
879
  }
@@ -751,11 +881,11 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
751
881
  this.TAB_LIST = [
752
882
  {
753
883
  id: "timeline",
754
- label: "Timeline"
884
+ label: "Messages"
755
885
  },
756
886
  {
757
887
  id: "raw-events",
758
- label: "Raw AG-UI Events"
888
+ label: "AG-UI Events"
759
889
  },
760
890
  {
761
891
  id: "state",
@@ -798,6 +928,30 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
798
928
  if (id === "state") return this.renderState();
799
929
  return this.renderEvents();
800
930
  }
931
+ /** Returns the stable DOM ID for one tab in this inspector instance. */
932
+ tabDomId(id) {
933
+ return `${this.domIdPrefix}-tab-${id}`;
934
+ }
935
+ /** Returns the stable DOM ID for the panel controlled by one tab. */
936
+ panelDomId(id) {
937
+ return `${this.domIdPrefix}-panel-${id}`;
938
+ }
939
+ /** Selects and focuses the tab targeted by an ARIA tabs navigation key. */
940
+ handleTabKeyDown(event, currentId) {
941
+ const currentIndex = CpkThreadInspector.TAB_LIST.findIndex((tab) => tab.id === currentId);
942
+ if (currentIndex < 0) return;
943
+ let targetIndex = null;
944
+ if (event.key === "ArrowRight") targetIndex = (currentIndex + 1) % CpkThreadInspector.TAB_LIST.length;
945
+ else if (event.key === "ArrowLeft") targetIndex = (currentIndex - 1 + CpkThreadInspector.TAB_LIST.length) % CpkThreadInspector.TAB_LIST.length;
946
+ else if (event.key === "Home") targetIndex = 0;
947
+ else if (event.key === "End") targetIndex = CpkThreadInspector.TAB_LIST.length - 1;
948
+ if (targetIndex === null) return;
949
+ const target = CpkThreadInspector.TAB_LIST[targetIndex];
950
+ if (!target) return;
951
+ event.preventDefault();
952
+ this.activateTab(target.id);
953
+ this.shadowRoot?.querySelector(`#${this.tabDomId(target.id)}`)?.focus();
954
+ }
801
955
  activateTab(id) {
802
956
  if (this._tab === id) return;
803
957
  const isFirstActivation = !this._activatedTabs.has(id);
@@ -887,7 +1041,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
887
1041
  border-bottom: 2px solid transparent;
888
1042
  cursor: pointer;
889
1043
  background: transparent;
890
- color: #838389;
1044
+ color: #68686e;
891
1045
  transition:
892
1046
  color 0.12s,
893
1047
  border-color 0.12s;
@@ -898,6 +1052,12 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
898
1052
  color: #010507;
899
1053
  }
900
1054
 
1055
+ .cpk-td__tab:focus-visible {
1056
+ outline: 2px solid #5558b2;
1057
+ outline-offset: -3px;
1058
+ border-radius: 4px;
1059
+ }
1060
+
901
1061
  .cpk-td__tab--active {
902
1062
  color: #010507;
903
1063
  border-bottom-color: #bec2ff;
@@ -917,7 +1077,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
917
1077
  border: none;
918
1078
  border-left: 1px solid #dbdbe5;
919
1079
  background: transparent;
920
- color: #838389;
1080
+ color: #68686e;
921
1081
  cursor: pointer;
922
1082
  flex-shrink: 0;
923
1083
  transition:
@@ -986,7 +1146,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
986
1146
  }
987
1147
 
988
1148
  .cpk-td__metadata-label {
989
- color: #838389;
1149
+ color: #68686e;
990
1150
  text-transform: uppercase;
991
1151
  font-size: 9px;
992
1152
  }
@@ -996,6 +1156,18 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
996
1156
  text-overflow: ellipsis;
997
1157
  }
998
1158
 
1159
+ .cpk-td__metadata-pill--wrap {
1160
+ max-width: 100%;
1161
+ white-space: normal;
1162
+ }
1163
+
1164
+ .cpk-td__metadata-value--wrap {
1165
+ overflow: visible;
1166
+ text-overflow: clip;
1167
+ white-space: normal;
1168
+ overflow-wrap: anywhere;
1169
+ }
1170
+
999
1171
  /*
1000
1172
  * Each tab's content is wrapped in this panel so the keep-mounted
1001
1173
  * inactive panels can be hidden via display:none without disturbing
@@ -1013,6 +1185,10 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1013
1185
  flex-shrink: 0;
1014
1186
  }
1015
1187
 
1188
+ .cpk-td__panel[hidden] {
1189
+ display: none;
1190
+ }
1191
+
1016
1192
  /* ── Empty state ─────────────────────────────────────────────────── */
1017
1193
  .cpk-td__empty-state {
1018
1194
  flex: 1;
@@ -1021,14 +1197,14 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1021
1197
  align-items: center;
1022
1198
  justify-content: center;
1023
1199
  gap: 8px;
1024
- color: #838389;
1200
+ color: #68686e;
1025
1201
  font-size: 13px;
1026
1202
  padding: 40px 0;
1027
1203
  }
1028
1204
 
1029
1205
  .cpk-td__empty-hint {
1030
1206
  font-size: 11px;
1031
- color: #838389;
1207
+ color: #68686e;
1032
1208
  text-align: center;
1033
1209
  max-width: 220px;
1034
1210
  line-height: 1.5;
@@ -1038,7 +1214,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1038
1214
  .cpk-td__status {
1039
1215
  padding: 16px;
1040
1216
  font-size: 12px;
1041
- color: #838389;
1217
+ color: #68686e;
1042
1218
  text-align: center;
1043
1219
  }
1044
1220
 
@@ -1117,7 +1293,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1117
1293
  font-family: "Spline Sans Mono", monospace;
1118
1294
  font-size: 10px;
1119
1295
  font-weight: 500;
1120
- color: #189370;
1296
+ color: #087653;
1121
1297
  text-transform: uppercase;
1122
1298
  flex: 1;
1123
1299
  }
@@ -1126,15 +1302,15 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1126
1302
  font-family: "Spline Sans Mono", monospace;
1127
1303
  font-size: 9px;
1128
1304
  text-transform: uppercase;
1129
- color: #189370;
1305
+ color: #087653;
1130
1306
  }
1131
1307
 
1132
1308
  .cpk-td__tool-status--pending {
1133
- color: #996300;
1309
+ color: #8a5900;
1134
1310
  }
1135
1311
 
1136
1312
  .cpk-td__tool-chevron {
1137
- color: #838389;
1313
+ color: #68686e;
1138
1314
  font-size: 10px;
1139
1315
  }
1140
1316
 
@@ -1148,7 +1324,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1148
1324
  font-family: "Spline Sans Mono", monospace;
1149
1325
  font-size: 9px;
1150
1326
  font-weight: 500;
1151
- color: #838389;
1327
+ color: #68686e;
1152
1328
  text-transform: uppercase;
1153
1329
  margin-bottom: 4px;
1154
1330
  letter-spacing: 0.3px;
@@ -1180,7 +1356,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1180
1356
  background: rgba(133, 236, 206, 0.15);
1181
1357
  font-family: "Spline Sans Mono", monospace;
1182
1358
  font-size: 10px;
1183
- color: #189370;
1359
+ color: #087653;
1184
1360
  text-transform: uppercase;
1185
1361
  font-weight: 500;
1186
1362
  border-bottom: 1px solid #e9e9ef;
@@ -1202,7 +1378,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1202
1378
  align-items: center;
1203
1379
  gap: 8px;
1204
1380
  padding: 5px 0;
1205
- color: #838389;
1381
+ color: #68686e;
1206
1382
  font-family: "Spline Sans Mono", monospace;
1207
1383
  font-size: 9px;
1208
1384
  text-transform: uppercase;
@@ -1259,7 +1435,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1259
1435
  .cpk-td__timeline-time {
1260
1436
  font-family: "Spline Sans Mono", monospace;
1261
1437
  font-size: 9px;
1262
- color: #838389;
1438
+ color: #68686e;
1263
1439
  flex-shrink: 0;
1264
1440
  }
1265
1441
 
@@ -1437,7 +1613,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1437
1613
  .cpk-td__event-time {
1438
1614
  font-family: "Spline Sans Mono", monospace;
1439
1615
  font-size: 9px;
1440
- color: #838389;
1616
+ color: #68686e;
1441
1617
  }
1442
1618
 
1443
1619
  .cpk-td__event-payload {
@@ -1509,7 +1685,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1509
1685
  font-family: "Spline Sans Mono", monospace;
1510
1686
  font-size: 10px;
1511
1687
  font-weight: 500;
1512
- color: #838389;
1688
+ color: #68686e;
1513
1689
  text-transform: uppercase;
1514
1690
  letter-spacing: 0.6px;
1515
1691
  margin-bottom: 8px;
@@ -1530,7 +1706,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1530
1706
  }
1531
1707
 
1532
1708
  .cpk-tdp__label {
1533
- color: #838389;
1709
+ color: #68686e;
1534
1710
  font-size: 11px;
1535
1711
  white-space: nowrap;
1536
1712
  flex-shrink: 0;
@@ -1559,6 +1735,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1559
1735
  `;
1560
1736
  }
1561
1737
  updated(_changed) {
1738
+ if (!this.isConnected) return;
1562
1739
  const loadKey = this.currentLoadKey();
1563
1740
  if (loadKey !== this._lastLoadKey) {
1564
1741
  this._lastLoadKey = loadKey;
@@ -1581,6 +1758,16 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1581
1758
  this.fetchMessages(this.threadId, true);
1582
1759
  }
1583
1760
  }
1761
+ connectedCallback() {
1762
+ super.connectedCallback();
1763
+ if (this._hasConnectedOnce) this.requestUpdate();
1764
+ this._hasConnectedOnce = true;
1765
+ }
1766
+ disconnectedCallback() {
1767
+ super.disconnectedCallback();
1768
+ this.resetLoadedThreadData();
1769
+ this._lastLoadKey = null;
1770
+ }
1584
1771
  canFetchMessages() {
1585
1772
  return !!this.provider?.getMessages || !!this.runtimeUrl && this.threadInspectionAvailable;
1586
1773
  }
@@ -2221,12 +2408,22 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2221
2408
  <div class="cpk-td__left">
2222
2409
  <!-- Tab bar -->
2223
2410
  <div class="cpk-td__tabs-header">
2224
- <div class="cpk-td__tab-group" role="tablist">
2411
+ <div
2412
+ class="cpk-td__tab-group"
2413
+ role="tablist"
2414
+ aria-label="Thread detail views"
2415
+ >
2225
2416
  ${CpkThreadInspector.TAB_LIST.map((tab) => html`
2226
2417
  <button
2418
+ id=${this.tabDomId(tab.id)}
2419
+ type="button"
2227
2420
  role="tab"
2421
+ aria-controls=${this.panelDomId(tab.id)}
2422
+ aria-selected=${this._tab === tab.id ? "true" : "false"}
2423
+ tabindex=${this._tab === tab.id ? "0" : "-1"}
2228
2424
  class="cpk-td__tab ${this._tab === tab.id ? "cpk-td__tab--active" : ""}"
2229
2425
  @click=${() => this.activateTab(tab.id)}
2426
+ @keydown=${(event) => this.handleTabKeyDown(event, tab.id)}
2230
2427
  >
2231
2428
  ${tab.label}
2232
2429
  </button>
@@ -2242,8 +2439,11 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2242
2439
  <div class="cpk-td__status">Loading…</div>
2243
2440
  ` : nothing}
2244
2441
  ${CpkThreadInspector.TAB_LIST.map((tab) => this._activatedTabs.has(tab.id) ? html`<div
2442
+ id=${this.panelDomId(tab.id)}
2245
2443
  class="cpk-td__panel"
2246
- style=${this._tab === tab.id && !this._panelInitializing ? "" : "display:none"}
2444
+ role="tabpanel"
2445
+ aria-labelledby=${this.tabDomId(tab.id)}
2446
+ ?hidden=${this._tab !== tab.id || this._panelInitializing}
2247
2447
  >
2248
2448
  ${this.renderTabContent(tab.id)}
2249
2449
  </div>` : nothing)}
@@ -2278,34 +2478,53 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2278
2478
  }
2279
2479
  renderMetadataStrip() {
2280
2480
  const metadata = this.metadata;
2281
- const pills = [
2282
- {
2283
- label: "Thread",
2284
- value: metadata?.id ?? this.threadId
2285
- },
2481
+ const pills = [{
2482
+ label: "Name",
2483
+ value: metadata?.name ?? this.thread?.name ?? "Untitled"
2484
+ }, {
2485
+ label: "ID",
2486
+ value: metadata?.id ?? this.threadId ?? "—",
2487
+ wrap: true
2488
+ }];
2489
+ for (const fact of [
2286
2490
  {
2287
2491
  label: "Agent",
2288
2492
  value: metadata?.agentId
2289
2493
  },
2290
2494
  {
2291
- label: "End user",
2292
- value: metadata?.endUserId ?? metadata?.createdById
2495
+ label: "Created",
2496
+ value: metadata?.createdAt
2293
2497
  },
2294
2498
  {
2295
- label: "Status",
2296
- value: metadata?.status
2499
+ label: "Updated",
2500
+ value: metadata?.updatedAt
2297
2501
  }
2298
- ].filter((pill) => pill.value != null && pill.value !== "");
2502
+ ]) {
2503
+ if (fact.value == null || fact.value === "") continue;
2504
+ pills.push({
2505
+ label: fact.label,
2506
+ value: fact.label === "Created" || fact.label === "Updated" ? this.fmtTime(fact.value) : fact.value
2507
+ });
2508
+ }
2299
2509
  const bulkControls = this.renderActiveBulkControls();
2300
- if (pills.length === 0 && bulkControls === nothing) return nothing;
2301
2510
  return html`
2302
- <div class="cpk-td__metadata-strip" aria-label="Thread metadata">
2511
+ <div
2512
+ class="cpk-td__metadata-strip"
2513
+ role="group"
2514
+ aria-label="Thread metadata"
2515
+ >
2303
2516
  <div class="cpk-td__metadata-pills">
2304
2517
  ${pills.map((pill) => html`
2305
- <span class="cpk-td__metadata-pill" title=${pill.value ?? ""}>
2518
+ <span
2519
+ class="cpk-td__metadata-pill ${pill.wrap ? "cpk-td__metadata-pill--wrap" : ""}"
2520
+ role="group"
2521
+ title=${pill.value}
2522
+ aria-label=${`${pill.label}: ${pill.value}`}
2523
+ >
2306
2524
  <span class="cpk-td__metadata-label">${pill.label}</span>
2307
- <span class="cpk-td__metadata-value"
2308
- >${this.shortId(pill.value)}</span
2525
+ <span
2526
+ class="cpk-td__metadata-value ${pill.wrap ? "cpk-td__metadata-value--wrap" : ""}"
2527
+ >${pill.value}</span
2309
2528
  >
2310
2529
  </span>
2311
2530
  `)}
@@ -2400,8 +2619,8 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2400
2619
  <div class="cpk-td__empty-state">
2401
2620
  <span>No timeline events captured</span>
2402
2621
  <span class="cpk-td__empty-hint"
2403
- >Timeline rows are normalized from AG-UI events. Open Raw AG-UI Events or
2404
- State to inspect the available thread data.</span
2622
+ >Timeline rows are normalized from AG-UI events. Open AG-UI Events or State
2623
+ to inspect the available thread data.</span
2405
2624
  >
2406
2625
  </div>
2407
2626
  `;
@@ -2575,7 +2794,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2575
2794
  <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
2576
2795
  <path
2577
2796
  d="M1 9C1 9 2 7 5 7C8 7 9 9 9 9M5 1C5 1 7 2.5 7 4.5C7 6.5 5 7 5 7C5 7 3 6.5 3 4.5C3 2.5 5 1 5 1Z"
2578
- stroke="#189370"
2797
+ stroke="#087653"
2579
2798
  stroke-width="1.2"
2580
2799
  stroke-linecap="round"
2581
2800
  stroke-linejoin="round"
@@ -2807,7 +3026,7 @@ ${unsafeHTML(highlightedJson(stateValue))}</pre
2807
3026
  <div class="cpk-tdp__row">
2808
3027
  <span class="cpk-tdp__label">ID</span>
2809
3028
  <span class="cpk-tdp__value cpk-tdp__value--wrap"
2810
- >${this.shortId(metadata?.id)}</span
3029
+ >${metadata?.id ?? this.threadId ?? "—"}</span
2811
3030
  >
2812
3031
  </div>
2813
3032
  <div class="cpk-tdp__row">
@@ -3047,7 +3266,7 @@ var CpkMemoryList = class extends LitElement {
3047
3266
  font-family: "Spline Sans Mono", monospace;
3048
3267
  font-size: 9px;
3049
3268
  margin-left: 4px;
3050
- color: #838389;
3269
+ color: #68686e;
3051
3270
  }
3052
3271
 
3053
3272
  /* ── List ── */
@@ -3114,7 +3333,7 @@ var CpkMemoryList = class extends LitElement {
3114
3333
  font-weight: 500;
3115
3334
  white-space: nowrap;
3116
3335
  background: #f0f0f5;
3117
- color: #838389;
3336
+ color: #68686e;
3118
3337
  }
3119
3338
 
3120
3339
  /* Content */
@@ -3136,7 +3355,7 @@ var CpkMemoryList = class extends LitElement {
3136
3355
 
3137
3356
  .cpk-ml__footer-threads {
3138
3357
  font-size: 10px;
3139
- color: #838389;
3358
+ color: #68686e;
3140
3359
  }
3141
3360
 
3142
3361
  .cpk-ml__footer-id {
@@ -3149,7 +3368,7 @@ var CpkMemoryList = class extends LitElement {
3149
3368
  .cpk-ml__empty {
3150
3369
  padding: 32px 16px;
3151
3370
  text-align: center;
3152
- color: #838389;
3371
+ color: #68686e;
3153
3372
  font-size: 12px;
3154
3373
  display: flex;
3155
3374
  flex-direction: column;
@@ -3230,7 +3449,7 @@ var CpkMemoryList = class extends LitElement {
3230
3449
  .cpk-ml__recall-clear {
3231
3450
  font-family: "Plus Jakarta Sans", sans-serif;
3232
3451
  font-size: 10px;
3233
- color: #838389;
3452
+ color: #68686e;
3234
3453
  background: none;
3235
3454
  border: none;
3236
3455
  cursor: pointer;
@@ -3241,7 +3460,7 @@ var CpkMemoryList = class extends LitElement {
3241
3460
  }
3242
3461
  .cpk-ml__recall-msg {
3243
3462
  font-size: 11px;
3244
- color: #838389;
3463
+ color: #68686e;
3245
3464
  line-height: 1.45;
3246
3465
  }
3247
3466
  .cpk-ml__recall-msg--error {
@@ -3265,7 +3484,7 @@ var CpkMemoryList = class extends LitElement {
3265
3484
  /* ── Scope badge variants ── */
3266
3485
  .cpk-ml__scope-badge--user {
3267
3486
  background: #f0f0f5;
3268
- color: #838389;
3487
+ color: #68686e;
3269
3488
  }
3270
3489
  .cpk-ml__scope-badge--project {
3271
3490
  background: #fef3c7;
@@ -3484,6 +3703,8 @@ var WebInspectorElement = class extends LitElement {
3484
3703
  this._recallQuery = "";
3485
3704
  this._recallSeq = 0;
3486
3705
  this.runtimeStatus = null;
3706
+ this.inspectorMetadataProjection = projectInspectorMetadata(void 0, void 0);
3707
+ this.metadataTelemetryFingerprints = /* @__PURE__ */ new Map();
3487
3708
  this.coreProperties = {};
3488
3709
  this.lastCoreError = null;
3489
3710
  this.agentSubscriptions = /* @__PURE__ */ new Map();
@@ -3505,8 +3726,19 @@ var WebInspectorElement = class extends LitElement {
3505
3726
  this.isOpen = false;
3506
3727
  this.draggedDuringInteraction = false;
3507
3728
  this.ignoreNextButtonClick = false;
3508
- this.selectedMenu = "ag-ui-events";
3729
+ this.selectedMenu = "threads";
3730
+ this.pendingPersistedMenu = null;
3731
+ this.hasResolvedCore = false;
3732
+ this.settingsOpen = false;
3733
+ this.lastSelectedMenuByGroup = {
3734
+ threads: "threads",
3735
+ agents: "ag-ui-events",
3736
+ learning: "memories"
3737
+ };
3738
+ this.lastScrolledAgentNavigationLayout = null;
3509
3739
  this.selectedThreadId = null;
3740
+ this.selectedRealThreadIsExplicit = false;
3741
+ this.selectedLocalExampleThreadId = null;
3510
3742
  this.threadListWidth = 290;
3511
3743
  this.threadDividerResizing = false;
3512
3744
  this.threadDividerPointerId = -1;
@@ -3516,7 +3748,10 @@ var WebInspectorElement = class extends LitElement {
3516
3748
  this._threadStoreSubscriptions = /* @__PURE__ */ new Map();
3517
3749
  this._threadsByAgent = /* @__PURE__ */ new Map();
3518
3750
  this._threadsErrorByAgent = /* @__PURE__ */ new Map();
3751
+ this._threadsLoadingByAgent = /* @__PURE__ */ new Map();
3519
3752
  this._ownedThreadStores = /* @__PURE__ */ new Map();
3753
+ this.threadCapabilityEnabled = null;
3754
+ this.threadCapabilityGeneration = 0;
3520
3755
  this.contextMenuOpen = false;
3521
3756
  this.dockMode = "floating";
3522
3757
  this.previousBodyMargins = null;
@@ -3549,18 +3784,28 @@ var WebInspectorElement = class extends LitElement {
3549
3784
  this.pendingBannerViewed = [];
3550
3785
  this.clickedBannerIds = /* @__PURE__ */ new Set();
3551
3786
  this.viewedThreadsTelemetryStates = /* @__PURE__ */ new Set();
3552
- this.viewedExampleThreadIds = /* @__PURE__ */ new Set();
3553
- this.selectedExampleThreadIds = /* @__PURE__ */ new Set();
3787
+ this.viewedExampleKinds = /* @__PURE__ */ new Set();
3788
+ this.selectedExampleKinds = /* @__PURE__ */ new Set();
3554
3789
  this.viewedExampleTourSteps = /* @__PURE__ */ new Set();
3555
3790
  this.exampleThreadProviders = /* @__PURE__ */ new Map();
3556
3791
  this.exampleTourDismissed = false;
3557
3792
  this.exampleTourActive = false;
3558
3793
  this.exampleTourStep = 0;
3559
3794
  this.exampleTourAutoShown = false;
3560
- this.threadsExampleOverviewVideoShouldLoad = false;
3561
- this.threadsExampleOverviewVideoReady = false;
3795
+ this.threadsExampleOverviewVideoState = "deferred";
3796
+ this.threadsExampleOverviewVideoLoaded = false;
3797
+ this.threadsExampleOverviewVideoReducedMotion = false;
3562
3798
  this.threadsExampleOverviewVideoLoadTimer = null;
3563
3799
  this.threadsExampleOverviewVideoIdleCallbackId = null;
3800
+ this.threadsExampleOverviewVideoElement = null;
3801
+ this.threadsExampleOverviewVideoListeners = null;
3802
+ this.threadsExampleOverviewVideoLifecycleGeneration = 0;
3803
+ this.threadsExampleOverviewVideoPlayAttemptGeneration = 0;
3804
+ this.threadsExampleOverviewVideoPlayPromise = null;
3805
+ this.threadsExampleOverviewVideoPlayOnNextBind = false;
3806
+ this.threadsSetupPromptCopyState = "idle";
3807
+ this.threadsSetupPromptCopyResetTimeoutId = null;
3808
+ this.threadsSetupPromptCopyGeneration = 0;
3564
3809
  this.contextState = {
3565
3810
  button: {
3566
3811
  position: {
@@ -3607,7 +3852,7 @@ var WebInspectorElement = class extends LitElement {
3607
3852
  const target = event.currentTarget;
3608
3853
  const context = target?.dataset.dragContext === "window" ? "window" : "button";
3609
3854
  const eventTarget = event.target;
3610
- if (context === "window" && eventTarget?.closest("button")) return;
3855
+ if (context === "window" && eventTarget?.closest("button, a, nav")) return;
3611
3856
  this.pointerContext = context;
3612
3857
  this.measureContext(context);
3613
3858
  event.preventDefault();
@@ -3752,8 +3997,10 @@ var WebInspectorElement = class extends LitElement {
3752
3997
  this.measureContext("button");
3753
3998
  this.applyAnchorPosition("button");
3754
3999
  this.measureContext("window");
4000
+ this.contextState.window.size = this.clampWindowSize(this.contextState.window.size);
3755
4001
  if (this.hasCustomPosition.window) this.applyAnchorPosition("window");
3756
4002
  else this.centerContext("window");
4003
+ this.requestUpdate();
3757
4004
  this.updateHostTransform();
3758
4005
  };
3759
4006
  this.contextOptions = [{
@@ -3766,26 +4013,32 @@ var WebInspectorElement = class extends LitElement {
3766
4013
  this.expandedTools = /* @__PURE__ */ new Set();
3767
4014
  this.expandedContextItems = /* @__PURE__ */ new Set();
3768
4015
  this.copiedContextItems = /* @__PURE__ */ new Set();
4016
+ this.handleInspectorMetadataActionClick = (action, placement) => {
4017
+ if (!this.isOpen || this.runtimeStatus !== CopilotKitCoreRuntimeConnectionStatus.Connected || this.core?.telemetryDisabled) return;
4018
+ if (action.kind === "enable_intelligence") {
4019
+ this.handleThreadsIntelligenceSignupClick();
4020
+ return;
4021
+ }
4022
+ try {
4023
+ trackMetadataActionClicked({
4024
+ action_kind: action.kind,
4025
+ license_bucket: this.inspectorMetadataProjection.licenseState,
4026
+ ...this.getMetadataTelemetryContext(),
4027
+ action_placement: getMetadataActionPlacement(placement)
4028
+ });
4029
+ } catch {}
4030
+ };
3769
4031
  this.handleTalkToEngineerClick = () => {
3770
4032
  if (this.core?.telemetryDisabled) return;
3771
- trackTalkToEngineerClicked(this.getThreadsTelemetryProps({
3772
- cta: "talk_to_engineer",
3773
- cta_surface: "threads_header"
3774
- }, { includeUrlAttribution: true }));
4033
+ trackTalkToEngineerClicked(this.getThreadsCtaTelemetryProps("talk_to_engineer", "threads_header"));
3775
4034
  };
3776
4035
  this.handleThreadsIntelligenceSignupClick = () => {
3777
4036
  if (this.core?.telemetryDisabled) return;
3778
- trackThreadsIntelligenceSignupClicked(this.getThreadsTelemetryProps({
3779
- cta: "signup",
3780
- cta_surface: "threads_locked"
3781
- }, { includeUrlAttribution: true }));
4037
+ trackThreadsIntelligenceSignupClicked(this.getThreadsCtaTelemetryProps("signup", "threads_locked"));
3782
4038
  };
3783
4039
  this.handleThreadsTalkToEngineerClick = () => {
3784
4040
  if (this.core?.telemetryDisabled) return;
3785
- trackThreadsTalkToEngineerClicked(this.getThreadsTelemetryProps({
3786
- cta: "talk_to_engineer",
3787
- cta_surface: "threads_locked"
3788
- }, { includeUrlAttribution: true }));
4041
+ trackThreadsTalkToEngineerClicked(this.getThreadsCtaTelemetryProps("talk_to_engineer", "threads_locked"));
3789
4042
  };
3790
4043
  this.handleThreadDividerPointerDown = (event) => {
3791
4044
  this.threadDividerResizing = true;
@@ -3807,9 +4060,51 @@ var WebInspectorElement = class extends LitElement {
3807
4060
  if (target.hasPointerCapture(this.threadDividerPointerId)) target.releasePointerCapture(this.threadDividerPointerId);
3808
4061
  this.threadDividerResizing = false;
3809
4062
  };
3810
- this.handleThreadsExampleOverviewVideoLoaded = () => {
3811
- this.threadsExampleOverviewVideoReady = true;
4063
+ this.handleThreadsExampleOverviewVideoControl = () => {
4064
+ const video = this.threadsExampleOverviewVideoElement;
4065
+ if (!video) return;
4066
+ if (this.threadsExampleOverviewVideoState === "playing") {
4067
+ this.invalidateThreadsExampleOverviewVideoPlay();
4068
+ try {
4069
+ video.pause();
4070
+ } catch {}
4071
+ this.threadsExampleOverviewVideoState = "ready";
4072
+ this.requestUpdate();
4073
+ return;
4074
+ }
4075
+ if (this.threadsExampleOverviewVideoState === "failed") {
4076
+ this.cleanupThreadsExampleOverviewVideo();
4077
+ if (!this.isConnected || !video.isConnected) return;
4078
+ this.threadsExampleOverviewVideoPlayOnNextBind = true;
4079
+ this.requestUpdate();
4080
+ return;
4081
+ }
4082
+ this.cancelThreadsExampleOverviewVideoGate();
4083
+ const lifecycleGeneration = this.threadsExampleOverviewVideoLifecycleGeneration;
4084
+ if (this.threadsExampleOverviewVideoState === "deferred") {
4085
+ this.activateThreadsExampleOverviewVideo(video, lifecycleGeneration, true);
4086
+ return;
4087
+ }
4088
+ this.playThreadsExampleOverviewVideo(video, lifecycleGeneration);
4089
+ };
4090
+ this.handleThreadsSetupPromptCopy = async () => {
4091
+ const generation = this.threadsSetupPromptCopyGeneration += 1;
4092
+ if (this.threadsSetupPromptCopyResetTimeoutId !== null) {
4093
+ window.clearTimeout(this.threadsSetupPromptCopyResetTimeoutId);
4094
+ this.threadsSetupPromptCopyResetTimeoutId = null;
4095
+ }
4096
+ this.threadsSetupPromptCopyState = "idle";
3812
4097
  this.requestUpdate();
4098
+ if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
4099
+ this.showThreadsSetupPromptCopyState("error", generation);
4100
+ return;
4101
+ }
4102
+ try {
4103
+ await navigator.clipboard.writeText(THREADS_RUNTIME_SETUP_PROMPT);
4104
+ this.showThreadsSetupPromptCopyState("copied", generation);
4105
+ } catch {
4106
+ this.showThreadsSetupPromptCopyState("error", generation);
4107
+ }
3813
4108
  };
3814
4109
  this.handleClearEvents = () => {
3815
4110
  if (this.selectedContext === "all-agents") {
@@ -3882,7 +4177,11 @@ var WebInspectorElement = class extends LitElement {
3882
4177
  const oldValue = this._core;
3883
4178
  if (oldValue === value) return;
3884
4179
  this.detachFromCore();
4180
+ const hadResolvedCore = this.hasResolvedCore;
3885
4181
  this._core = value ?? null;
4182
+ if (this._core) this.hasResolvedCore = true;
4183
+ if (!hadResolvedCore && this._core) this.resolvePendingPersistedMenu();
4184
+ else if (hadResolvedCore) this.reconcileSelectedMenuVisibility();
3886
4185
  this.requestUpdate("core", oldValue);
3887
4186
  if (this._core) this.attachToCore(this._core);
3888
4187
  }
@@ -3928,36 +4227,141 @@ var WebInspectorElement = class extends LitElement {
3928
4227
  }
3929
4228
  ];
3930
4229
  }
4230
+ /** Return the primary navigation group that owns a legacy leaf key. */
4231
+ getGroupForMenu(key) {
4232
+ for (const group of INSPECTOR_PRIMARY_NAVIGATION) if (INSPECTOR_GROUPS[group.key].some((menuKey) => menuKey === key)) return group.key;
4233
+ return "threads";
4234
+ }
4235
+ /** Return the primary group for the current legacy leaf selection. */
4236
+ get selectedGroup() {
4237
+ return this.getGroupForMenu(this.selectedMenu);
4238
+ }
4239
+ /** Return only currently visible legacy leaves owned by a group. */
4240
+ getVisibleMenuItemsForGroup(group) {
4241
+ return this.menuItems.filter((item) => INSPECTOR_GROUPS[group].some((menuKey) => menuKey === item.key));
4242
+ }
4243
+ /** Resolve a group's last visible leaf, falling back to its first leaf. */
4244
+ getMenuForGroup(group) {
4245
+ const visibleItems = this.getVisibleMenuItemsForGroup(group);
4246
+ const rememberedMenu = this.lastSelectedMenuByGroup[group];
4247
+ return visibleItems.find((item) => item.key === rememberedMenu)?.key ?? visibleItems[0]?.key ?? "threads";
4248
+ }
4249
+ /** Replace a leaf that became hidden with its group's first visible leaf. */
4250
+ reconcileSelectedMenuVisibility() {
4251
+ if (this.menuItems.some((item) => item.key === this.selectedMenu)) return;
4252
+ const group = this.getGroupForMenu(this.selectedMenu);
4253
+ this.selectedMenu = this.getVisibleMenuItemsForGroup(group)[0]?.key ?? "threads";
4254
+ this.lastSelectedMenuByGroup[group] = this.selectedMenu;
4255
+ this.persistState();
4256
+ }
4257
+ /** Open a primary group at its last currently visible legacy leaf. */
4258
+ handleGroupSelect(group) {
4259
+ this.handleMenuSelect(this.getMenuForGroup(group));
4260
+ }
4261
+ /** Toggle Settings without replacing or persisting the active legacy leaf. */
4262
+ handleSettingsToggle() {
4263
+ this.settingsOpen = !this.settingsOpen;
4264
+ this.contextMenuOpen = false;
4265
+ this.requestUpdate();
4266
+ }
3931
4267
  getThreadServiceStatus() {
3932
4268
  if (!this._core) return "unknown";
3933
4269
  if (!this._core.threadEndpoints) return "unknown";
3934
4270
  return this._core.threadEndpoints?.list === false ? "unavailable" : "available";
3935
4271
  }
3936
4272
  areThreadEndpointsAvailable() {
3937
- return this.getThreadServiceStatus() !== "unavailable";
4273
+ const endpoints = this._core?.threadEndpoints;
4274
+ return endpoints !== null && typeof endpoints === "object" && endpoints.list !== false;
4275
+ }
4276
+ synchronizeThreadCapability() {
4277
+ const enabled = this.areThreadEndpointsAvailable();
4278
+ if (this.threadCapabilityEnabled === enabled) return;
4279
+ this.threadCapabilityEnabled = enabled;
4280
+ this.threadCapabilityGeneration += 1;
4281
+ if (enabled) {
4282
+ const core = this.core;
4283
+ if (!core) return;
4284
+ const threadStores = typeof core.getThreadStores === "function" ? core.getThreadStores() : {};
4285
+ for (const [agentId, store] of Object.entries(threadStores)) this.subscribeToThreadStore(agentId, store);
4286
+ for (const agent of Object.values(core.agents)) if (agent?.agentId) this.ensureOwnedThreadStore(agent.agentId);
4287
+ return;
4288
+ }
4289
+ this.teardownThreadStoreSubscriptions();
4290
+ this.teardownOwnedThreadStores();
4291
+ if (this.selectedThreadId !== this.selectedLocalExampleThreadId) {
4292
+ this.selectedThreadId = null;
4293
+ this.selectedLocalExampleThreadId = null;
4294
+ }
4295
+ this.selectedRealThreadIsExplicit = false;
4296
+ this.requestUpdate();
3938
4297
  }
3939
4298
  getActiveThreadsState() {
3940
4299
  const displayThreads = this.selectedContext === "all-agents" ? this._threads : this._threadsByAgent.get(this.selectedContext) ?? [];
3941
4300
  let threadsErrorMessage = null;
3942
4301
  if (this.selectedContext === "all-agents") threadsErrorMessage = this._threadsErrorByAgent.values().next().value?.message ?? null;
3943
4302
  else threadsErrorMessage = this._threadsErrorByAgent.get(this.selectedContext)?.message ?? null;
4303
+ const threadsLoading = this.selectedContext === "all-agents" ? Array.from(this._threadsLoadingByAgent.values()).some(Boolean) : this._threadsLoadingByAgent.get(this.selectedContext) ?? false;
3944
4304
  return {
3945
4305
  displayThreads,
3946
- threadsErrorMessage
4306
+ threadsErrorMessage,
4307
+ threadsLoading
3947
4308
  };
3948
4309
  }
3949
- getThreadsTelemetryProps(extra = {}, options = {}) {
3950
- const distinctId = options.includeUrlAttribution && !this.core?.telemetryDisabled ? getTelemetryDistinctIdForUrl() : null;
4310
+ /** Bucket trusted usage without retaining exact counts or limits. */
4311
+ getThreadsUsageBucket() {
4312
+ const usage = this.inspectorMetadataProjection.usage;
4313
+ if (!usage) return "absent";
4314
+ if (usage.used === 0) return "empty";
4315
+ if (usage.limit.kind === "finite") return usage.used < usage.limit.value ? "within_limit" : "at_or_over_limit";
4316
+ if (usage.limit.kind === "unlimited") return "unlimited";
4317
+ return "unknown_limit";
4318
+ }
4319
+ /** Classify finite usage for render-only capacity copy and color. */
4320
+ getThreadsCapacityState() {
4321
+ const usage = this.inspectorMetadataProjection.usage;
4322
+ if (!usage || usage.limit.kind !== "finite") return void 0;
4323
+ if (usage.used >= usage.limit.value) return "critical";
4324
+ const warningThreshold = usage.limit.value - Math.floor(usage.limit.value / 10);
4325
+ return usage.used >= warningThreshold ? "warning" : "normal";
4326
+ }
4327
+ /** Bucket trusted expiry data independently from the usage-limit bucket. */
4328
+ getThreadsExpiryBucket() {
4329
+ const usage = this.inspectorMetadataProjection.usage;
4330
+ if (!usage || !Object.prototype.hasOwnProperty.call(usage, "expiringSoonCount")) return "unavailable";
4331
+ if (usage.expiringSoonCount === 0) return "zero";
4332
+ return typeof usage.expiringSoonCount === "number" && usage.expiringSoonCount > 0 ? "positive" : "unavailable";
4333
+ }
4334
+ /** Report only real rows visible in the active, settled Threads view. */
4335
+ hasActiveVisibleThreads() {
4336
+ if (this.selectedMenu !== "threads" || this.settingsOpen || !this.areThreadEndpointsAvailable()) return false;
4337
+ const { displayThreads, threadsErrorMessage, threadsLoading } = this.getActiveThreadsState();
4338
+ return !threadsErrorMessage && !threadsLoading && displayThreads.length > 0;
4339
+ }
4340
+ /** Build the closed common property set shared by all Thread events. */
4341
+ getThreadsTelemetryProps() {
3951
4342
  const threadServiceStatus = this.getThreadServiceStatus();
3952
4343
  return {
3953
- posthog_distinct_id: distinctId ?? void 0,
3954
4344
  intelligence_status: threadServiceStatus === "available" ? "intelligence_enabled" : threadServiceStatus === "unavailable" ? "intelligence_not_enabled" : "unknown",
3955
4345
  thread_service_status: threadServiceStatus,
3956
4346
  license_status: this.core?.licenseStatus ?? void 0,
3957
4347
  runtime_mode: this.core?.runtimeMode ?? void 0,
3958
4348
  runtime_url_type: getRuntimeUrlType(this.core?.runtimeUrl),
3959
- telemetry_disabled: this.core?.telemetryDisabled ?? false,
3960
- ...extra
4349
+ telemetry_disabled: false,
4350
+ has_threads: this.hasActiveVisibleThreads(),
4351
+ usage_bucket: this.getThreadsUsageBucket(),
4352
+ expiry_bucket: this.getThreadsExpiryBucket(),
4353
+ group_key: "threads",
4354
+ leaf_key: "threads"
4355
+ };
4356
+ }
4357
+ /** Add the frozen CTA leaves and anonymous URL attribution when allowed. */
4358
+ getThreadsCtaTelemetryProps(cta, ctaSurface) {
4359
+ const distinctId = getTelemetryDistinctIdForUrl();
4360
+ return {
4361
+ ...this.getThreadsTelemetryProps(),
4362
+ cta,
4363
+ cta_surface: ctaSurface,
4364
+ posthog_distinct_id: distinctId ?? void 0
3961
4365
  };
3962
4366
  }
3963
4367
  getMemoriesTelemetryProps() {
@@ -3970,9 +4374,6 @@ var WebInspectorElement = class extends LitElement {
3970
4374
  getIntelligenceSignupUrl() {
3971
4375
  return this.appendRefParam(INTELLIGENCE_SIGNUP_URL, "cpk-inspector");
3972
4376
  }
3973
- getThreadsIntelligenceSignupUrl() {
3974
- return this.appendRefParam(THREADS_INTELLIGENCE_SIGNIN_URL, "cpk-inspector");
3975
- }
3976
4377
  getTalkToEngineerUrl() {
3977
4378
  return this.appendRefParam(TALK_TO_ENGINEER_URL, "cpk-inspector-threads");
3978
4379
  }
@@ -3982,29 +4383,53 @@ var WebInspectorElement = class extends LitElement {
3982
4383
  getThreadsDocsUrl() {
3983
4384
  return this.appendRefParam(THREADS_DOCS_URL, "cpk-inspector-threads");
3984
4385
  }
4386
+ getThreadsRuntimeSetupDocsUrl() {
4387
+ return this.appendRefParam(THREADS_RUNTIME_SETUP_DOCS_URL, "cpk-inspector-threads");
4388
+ }
4389
+ getThreadsIntelligenceSignupUrl() {
4390
+ return this.appendRefParam(INTELLIGENCE_SIGNUP_URL, "cpk-inspector-threads");
4391
+ }
3985
4392
  getSelfHostedIntelligenceUrl() {
3986
4393
  return this.appendRefParam(SELF_HOSTED_INTELLIGENCE_URL, "cpk-inspector-threads");
3987
4394
  }
4395
+ /** Selects the onboarding path for an enabled runtime with no saved Threads. */
4396
+ getThreadsEmptyOnboardingAction() {
4397
+ const planCode = this.inspectorMetadataProjection.plan?.code.trim().toLowerCase();
4398
+ if (planCode === "team_self_hosted" || planCode === "team-self-hosted") return {
4399
+ href: this.getSelfHostedIntelligenceUrl(),
4400
+ label: "Explore self-hosted Intelligence"
4401
+ };
4402
+ return {
4403
+ href: this.getThreadsIntelligenceSignupUrl(),
4404
+ label: "Sign up for Intelligence"
4405
+ };
4406
+ }
3988
4407
  subscribeToThreadStore(agentId, store) {
3989
4408
  if (!this.areThreadEndpointsAvailable()) return;
3990
4409
  if (this._threadStoreSubscriptions.has(agentId)) return;
4410
+ const capabilityGeneration = this.threadCapabilityGeneration;
3991
4411
  const threadsSub = store.select(ɵselectThreads).subscribe((threads) => {
4412
+ if (capabilityGeneration !== this.threadCapabilityGeneration || !this.areThreadEndpointsAvailable()) return;
3992
4413
  this._threadsByAgent.set(agentId, threads);
3993
4414
  this._threads = Array.from(this._threadsByAgent.values()).flat();
3994
4415
  this.autoSelectLatestThread();
3995
4416
  this.requestUpdate();
3996
4417
  });
3997
- const errorSub = store.select(ɵselectThreadsError).subscribe((error) => {
4418
+ const statusSub = store.select(createThreadStoreStatusSelector()).subscribe(({ error, isLoading }) => {
4419
+ if (capabilityGeneration !== this.threadCapabilityGeneration || !this.areThreadEndpointsAvailable()) return;
3998
4420
  if (error) this._threadsErrorByAgent.set(agentId, error);
3999
4421
  else this._threadsErrorByAgent.delete(agentId);
4422
+ this._threadsLoadingByAgent.set(agentId, isLoading);
4000
4423
  this.requestUpdate();
4001
4424
  });
4002
4425
  this._threadStoreSubscriptions.set(agentId, () => {
4003
4426
  threadsSub.unsubscribe();
4004
- errorSub.unsubscribe();
4427
+ statusSub.unsubscribe();
4005
4428
  });
4429
+ if (capabilityGeneration !== this.threadCapabilityGeneration || !this.areThreadEndpointsAvailable()) return;
4006
4430
  const initialState = store.getState();
4007
4431
  this._threadsByAgent.set(agentId, ɵselectThreads(initialState));
4432
+ this._threadsLoadingByAgent.set(agentId, ɵselectThreadsIsLoading(initialState));
4008
4433
  const initialError = ɵselectThreadsError(initialState);
4009
4434
  if (initialError) this._threadsErrorByAgent.set(agentId, initialError);
4010
4435
  else this._threadsErrorByAgent.delete(agentId);
@@ -4012,22 +4437,37 @@ var WebInspectorElement = class extends LitElement {
4012
4437
  this.autoSelectLatestThread();
4013
4438
  }
4014
4439
  autoSelectLatestThread() {
4015
- if (this._threads.length === 0) return;
4016
- if (!(this.selectedThreadId != null && !this.isExampleThreadId(this.selectedThreadId) && this._threads.some((t) => t.id === this.selectedThreadId))) this.selectedThreadId = this._threads[0].id;
4440
+ if (!this.areThreadEndpointsAvailable()) return;
4441
+ const { displayThreads } = this.getActiveThreadsState();
4442
+ const previousSelectedThreadId = this.selectedThreadId;
4443
+ if (this.selectedLocalExampleThreadId !== null && previousSelectedThreadId === this.selectedLocalExampleThreadId && displayThreads.length === 0) {
4444
+ this.selectedRealThreadIsExplicit = false;
4445
+ return;
4446
+ }
4447
+ if (this.selectedLocalExampleThreadId !== null) this.exampleTourActive = false;
4448
+ this.selectedLocalExampleThreadId = null;
4449
+ const explicitSelectedThreadId = this.selectedRealThreadIsExplicit ? previousSelectedThreadId : null;
4450
+ const nextSelectedThreadId = selectVisibleRealThreadId({
4451
+ threads: displayThreads,
4452
+ selectedThreadId: explicitSelectedThreadId
4453
+ });
4454
+ this.selectedThreadId = nextSelectedThreadId;
4455
+ this.selectedRealThreadIsExplicit = explicitSelectedThreadId !== null && nextSelectedThreadId === explicitSelectedThreadId;
4017
4456
  }
4018
4457
  teardownThreadStoreSubscriptions() {
4019
4458
  for (const unsub of this._threadStoreSubscriptions.values()) unsub();
4020
4459
  this._threadStoreSubscriptions.clear();
4021
4460
  this._threadsByAgent.clear();
4022
4461
  this._threadsErrorByAgent.clear();
4462
+ this._threadsLoadingByAgent.clear();
4023
4463
  this._threads = [];
4024
4464
  }
4025
4465
  ensureOwnedThreadStore(agentId) {
4466
+ if (!this.areThreadEndpointsAvailable()) return;
4026
4467
  if (this._ownedThreadStores.has(agentId)) return;
4027
4468
  if (this.core?.getThreadStore(agentId)) return;
4028
4469
  const core = this.core;
4029
4470
  if (!core?.runtimeUrl) return;
4030
- if (!this.areThreadEndpointsAvailable()) return;
4031
4471
  const store = ɵcreateThreadStore({ fetch: globalThis.fetch });
4032
4472
  store.start();
4033
4473
  store.setContext({
@@ -4041,11 +4481,13 @@ var WebInspectorElement = class extends LitElement {
4041
4481
  core.registerThreadStore(agentId, store);
4042
4482
  }
4043
4483
  refreshOwnedThreadStore(agentId) {
4484
+ if (!this.areThreadEndpointsAvailable()) return;
4044
4485
  const store = this._ownedThreadStores.get(agentId);
4045
4486
  if (!store) return;
4046
4487
  store.refresh();
4047
4488
  }
4048
4489
  updateOwnedThreadStoreHeaders(headers) {
4490
+ if (!this.areThreadEndpointsAvailable()) return;
4049
4491
  const core = this.core;
4050
4492
  if (!core?.runtimeUrl) return;
4051
4493
  for (const [agentId, store] of this._ownedThreadStores) store.setContext({
@@ -4058,32 +4500,62 @@ var WebInspectorElement = class extends LitElement {
4058
4500
  removeOwnedThreadStore(agentId) {
4059
4501
  const store = this._ownedThreadStores.get(agentId);
4060
4502
  if (!store) return;
4061
- store.stop();
4062
- this.core?.unregisterThreadStore(agentId);
4063
4503
  this._ownedThreadStores.delete(agentId);
4504
+ store.stop();
4505
+ if (this.core?.getThreadStore(agentId) === store) this.core.unregisterThreadStore(agentId);
4064
4506
  }
4065
4507
  teardownOwnedThreadStores() {
4066
- for (const [agentId, store] of this._ownedThreadStores) {
4508
+ const ownedThreadStores = Array.from(this._ownedThreadStores);
4509
+ this._ownedThreadStores.clear();
4510
+ for (const [agentId, store] of ownedThreadStores) {
4067
4511
  store.stop();
4068
- this.core?.unregisterThreadStore(agentId);
4512
+ if (this.core?.getThreadStore(agentId) === store) this.core.unregisterThreadStore(agentId);
4069
4513
  }
4070
- this._ownedThreadStores.clear();
4514
+ }
4515
+ coreSupportsInspectorMetadata(core) {
4516
+ try {
4517
+ return "inspectorMetadata" in core;
4518
+ } catch {
4519
+ return false;
4520
+ }
4521
+ }
4522
+ readCoreInspectorMetadata(core) {
4523
+ if (!this.coreSupportsInspectorMetadata(core)) return;
4524
+ try {
4525
+ return core.inspectorMetadata;
4526
+ } catch {
4527
+ return;
4528
+ }
4529
+ }
4530
+ updateInspectorMetadataProjection(value) {
4531
+ this.inspectorMetadataValue = value;
4532
+ let runtimeLicense;
4533
+ try {
4534
+ runtimeLicense = this._core?.licenseStatus;
4535
+ } catch {
4536
+ runtimeLicense = void 0;
4537
+ }
4538
+ this.inspectorMetadataProjection = projectInspectorMetadata(value, runtimeLicense);
4071
4539
  }
4072
4540
  attachToCore(core) {
4073
4541
  this.runtimeStatus = core.runtimeConnectionStatus;
4074
4542
  this.coreProperties = core.properties;
4075
4543
  this.lastCoreError = null;
4544
+ const supportsInspectorMetadata = this.coreSupportsInspectorMetadata(core);
4545
+ this.updateInspectorMetadataProjection(this.readCoreInspectorMetadata(core));
4076
4546
  this.coreSubscriber = {
4077
4547
  onRuntimeConnectionStatusChanged: ({ status }) => {
4078
4548
  this.runtimeStatus = status;
4549
+ this.updateInspectorMetadataProjection(this.inspectorMetadataValue);
4550
+ const threadCapabilityWasEnabled = this.threadCapabilityEnabled === true;
4551
+ this.synchronizeThreadCapability();
4079
4552
  if (status === "connected") {
4080
4553
  if (!core.telemetryDisabled) {
4081
4554
  ensureTelemetryDistinctId();
4082
4555
  maybeShowDisclosure();
4083
4556
  }
4084
4557
  this.flushPendingBannerViewed();
4085
- if (this.areThreadEndpointsAvailable()) for (const agentId of this._ownedThreadStores.keys()) this.refreshOwnedThreadStore(agentId);
4086
- else this.teardownOwnedThreadStores();
4558
+ if (threadCapabilityWasEnabled && this.areThreadEndpointsAvailable()) for (const agentId of this._ownedThreadStores.keys()) this.refreshOwnedThreadStore(agentId);
4087
4559
  } else {
4088
4560
  this._threadsByAgent.clear();
4089
4561
  this._threads = [];
@@ -4098,6 +4570,11 @@ var WebInspectorElement = class extends LitElement {
4098
4570
  this.updateOwnedThreadStoreHeaders(headers);
4099
4571
  this.requestUpdate();
4100
4572
  },
4573
+ ...supportsInspectorMetadata ? { onInspectorMetadataChanged: ({ inspectorMetadata }) => {
4574
+ if (this._core !== core) return;
4575
+ this.updateInspectorMetadataProjection(inspectorMetadata);
4576
+ this.requestUpdate();
4577
+ } } : {},
4101
4578
  onError: ({ code, error }) => {
4102
4579
  this.lastCoreError = {
4103
4580
  code,
@@ -4116,18 +4593,25 @@ var WebInspectorElement = class extends LitElement {
4116
4593
  this.requestUpdate();
4117
4594
  },
4118
4595
  onThreadStoreRegistered: ({ agentId, store }) => {
4596
+ if (!this.areThreadEndpointsAvailable()) return;
4119
4597
  this.subscribeToThreadStore(agentId, store);
4120
4598
  this.requestUpdate();
4121
4599
  },
4122
- onThreadStoreUnregistered: ({ agentId }) => {
4600
+ onThreadStoreUnregistered: ({ agentId, prevStore }) => {
4123
4601
  const unsub = this._threadStoreSubscriptions.get(agentId);
4124
4602
  if (unsub) {
4125
4603
  unsub();
4126
4604
  this._threadStoreSubscriptions.delete(agentId);
4127
4605
  }
4606
+ if (this._ownedThreadStores.get(agentId) === prevStore) {
4607
+ this._ownedThreadStores.delete(agentId);
4608
+ prevStore.stop();
4609
+ }
4128
4610
  this._threadsByAgent.delete(agentId);
4129
4611
  this._threadsErrorByAgent.delete(agentId);
4612
+ this._threadsLoadingByAgent.delete(agentId);
4130
4613
  this._threads = Array.from(this._threadsByAgent.values()).flat();
4614
+ this.autoSelectLatestThread();
4131
4615
  this.requestUpdate();
4132
4616
  }
4133
4617
  };
@@ -4141,7 +4625,7 @@ var WebInspectorElement = class extends LitElement {
4141
4625
  this.flushPendingBannerViewed();
4142
4626
  }
4143
4627
  const threadStores = typeof core.getThreadStores === "function" ? core.getThreadStores() : {};
4144
- for (const [agentId, store] of Object.entries(threadStores)) this.subscribeToThreadStore(agentId, store);
4628
+ if (this.areThreadEndpointsAvailable()) for (const [agentId, store] of Object.entries(threadStores)) this.subscribeToThreadStore(agentId, store);
4145
4629
  if (core.context) this.contextStore = this.normalizeContextStore(core.context);
4146
4630
  if (this.selectedMenu === "memories") this.ensureMemorySubscription();
4147
4631
  }
@@ -4243,6 +4727,13 @@ var WebInspectorElement = class extends LitElement {
4243
4727
  this.requestUpdate();
4244
4728
  }
4245
4729
  detachFromCore() {
4730
+ this.threadCapabilityGeneration += 1;
4731
+ this.threadCapabilityEnabled = null;
4732
+ if (this.selectedThreadId !== this.selectedLocalExampleThreadId) {
4733
+ this.selectedThreadId = null;
4734
+ this.selectedLocalExampleThreadId = null;
4735
+ }
4736
+ this.selectedRealThreadIsExplicit = false;
4246
4737
  if (this.coreUnsubscribe) {
4247
4738
  this.coreUnsubscribe();
4248
4739
  this.coreUnsubscribe = null;
@@ -4263,6 +4754,9 @@ var WebInspectorElement = class extends LitElement {
4263
4754
  this._recallQuery = "";
4264
4755
  this.coreSubscriber = null;
4265
4756
  this.runtimeStatus = null;
4757
+ this.inspectorMetadataValue = void 0;
4758
+ this.inspectorMetadataProjection = projectInspectorMetadata(void 0, void 0);
4759
+ this.metadataTelemetryFingerprints.clear();
4266
4760
  this.lastCoreError = null;
4267
4761
  this.coreProperties = {};
4268
4762
  this.cachedTools = [];
@@ -4281,6 +4775,7 @@ var WebInspectorElement = class extends LitElement {
4281
4775
  this.eventCounter = 0;
4282
4776
  }
4283
4777
  processAgentsChanged(agents) {
4778
+ this.synchronizeThreadCapability();
4284
4779
  const seenAgentIds = /* @__PURE__ */ new Set();
4285
4780
  for (const agent of Object.values(agents)) {
4286
4781
  if (!agent?.agentId) continue;
@@ -4345,11 +4840,17 @@ var WebInspectorElement = class extends LitElement {
4345
4840
  event: params.event,
4346
4841
  result: "result" in params ? params.result : void 0
4347
4842
  });
4348
- this.refreshOwnedThreadStore(agentId);
4843
+ if (this.areThreadEndpointsAvailable()) this.refreshOwnedThreadStore(agentId);
4349
4844
  },
4350
4845
  onRunErrorEvent: ({ event }) => {
4351
4846
  this.recordAgentEvent(agentId, "RUN_ERROR", event);
4352
4847
  },
4848
+ onStepStartedEvent: ({ event }) => {
4849
+ this.recordAgentEvent(agentId, "STEP_STARTED", event);
4850
+ },
4851
+ onStepFinishedEvent: ({ event }) => {
4852
+ this.recordAgentEvent(agentId, "STEP_FINISHED", event);
4853
+ },
4353
4854
  onTextMessageStartEvent: ({ event }) => {
4354
4855
  this.recordAgentEvent(agentId, "TEXT_MESSAGE_START", event);
4355
4856
  },
@@ -4505,6 +5006,7 @@ var WebInspectorElement = class extends LitElement {
4505
5006
  }
4506
5007
  }
4507
5008
  updateContextOptions(agentIds) {
5009
+ let selectedContextChanged = false;
4508
5010
  const nextOptions = [{
4509
5011
  key: "all-agents",
4510
5012
  label: "All Agents"
@@ -4518,12 +5020,14 @@ var WebInspectorElement = class extends LitElement {
4518
5020
  if ((pendingContext === "all-agents" || agentIds.has(pendingContext)) && (pendingContext === "all-agents" || agentIds.size === 1)) {
4519
5021
  if (this.selectedContext !== pendingContext) {
4520
5022
  this.selectedContext = pendingContext;
5023
+ selectedContextChanged = true;
4521
5024
  this.expandedRows.clear();
4522
5025
  }
4523
5026
  this.pendingSelectedContext = null;
4524
5027
  } else if (agentIds.size > 0) {
4525
5028
  if (this.selectedContext !== "all-agents") {
4526
5029
  this.selectedContext = "all-agents";
5030
+ selectedContextChanged = true;
4527
5031
  this.expandedRows.clear();
4528
5032
  }
4529
5033
  this.pendingSelectedContext = null;
@@ -4534,10 +5038,12 @@ var WebInspectorElement = class extends LitElement {
4534
5038
  if (agentIds.size === 1) nextSelected = Array.from(agentIds)[0];
4535
5039
  if (this.selectedContext !== nextSelected) {
4536
5040
  this.selectedContext = nextSelected;
5041
+ selectedContextChanged = true;
4537
5042
  this.expandedRows.clear();
4538
5043
  this.persistState();
4539
5044
  }
4540
5045
  }
5046
+ if (selectedContextChanged) this.autoSelectLatestThread();
4541
5047
  }
4542
5048
  getEventsForSelectedContext() {
4543
5049
  if (this.selectedContext === "all-agents") return this.flattenedEvents;
@@ -4858,7 +5364,7 @@ ${argsString}</pre
4858
5364
  align-items: center;
4859
5365
  justify-content: center;
4860
5366
  border-radius: 6px;
4861
- color: #838389;
5367
+ color: #68686e;
4862
5368
  cursor: pointer;
4863
5369
  transition:
4864
5370
  background 120ms ease,
@@ -4879,7 +5385,7 @@ ${argsString}</pre
4879
5385
  background: none;
4880
5386
  border: none;
4881
5387
  cursor: pointer;
4882
- color: #838389;
5388
+ color: #68686e;
4883
5389
  width: 28px;
4884
5390
  height: 28px;
4885
5391
  display: flex;
@@ -4928,7 +5434,7 @@ ${argsString}</pre
4928
5434
  height: 24px;
4929
5435
  border-radius: 50%;
4930
5436
  background-color: #f0f0f4;
4931
- color: #838389;
5437
+ color: #68686e;
4932
5438
  flex-shrink: 0;
4933
5439
  transition: transform 0.2s;
4934
5440
  }
@@ -5045,7 +5551,7 @@ ${argsString}</pre
5045
5551
  }
5046
5552
 
5047
5553
  .announcement-content a {
5048
- color: #757cf2;
5554
+ color: #5558b2;
5049
5555
  text-decoration: underline;
5050
5556
  }
5051
5557
 
@@ -5169,7 +5675,7 @@ ${argsString}</pre
5169
5675
  font-family: "Plus Jakarta Sans", system-ui, sans-serif;
5170
5676
  font-size: 12px;
5171
5677
  font-weight: 500;
5172
- color: #757cf2;
5678
+ color: #5558b2;
5173
5679
  cursor: pointer;
5174
5680
  text-align: center;
5175
5681
  }
@@ -5240,14 +5746,14 @@ ${argsString}</pre
5240
5746
  align-items: center;
5241
5747
  }
5242
5748
  .cpk-tab-active .cpk-tab-icon {
5243
- color: #757cf2;
5749
+ color: #5558b2;
5244
5750
  }
5245
5751
  .cpk-tab-inactive {
5246
5752
  background-color: transparent;
5247
5753
  color: #2b2b2b;
5248
5754
  }
5249
5755
  .cpk-tab-inactive .cpk-tab-icon {
5250
- color: #838389;
5756
+ color: #68686e;
5251
5757
  }
5252
5758
  .cpk-tab-inactive:hover {
5253
5759
  background-color: rgba(190, 194, 255, 0.08);
@@ -5280,24 +5786,40 @@ ${argsString}</pre
5280
5786
  width: 100%;
5281
5787
  height: 100%;
5282
5788
  object-fit: cover;
5283
- opacity: 0;
5284
- transition: opacity 220ms ease;
5285
- }
5286
- .cpk-threads-overview-video[data-ready="true"] {
5287
- opacity: 1;
5288
5789
  }
5289
5790
 
5290
- /* ── Header control buttons (dock, close) — first row only ───── */
5291
- .drag-handle > div:first-child button {
5292
- color: #838389 !important;
5791
+ /* ── Header controls on the dark account strip ──────────────── */
5792
+ .drag-handle > div[data-inspector-account-strip] button {
5793
+ color: #afafb7 !important;
5794
+ cursor: pointer;
5293
5795
  }
5294
- .drag-handle > div:first-child button:hover {
5295
- background-color: #f0f0f4 !important;
5296
- color: #57575b !important;
5796
+ .drag-handle > div[data-inspector-account-strip] button,
5797
+ .inspector-nav-control,
5798
+ [data-inspector-thread-cta] {
5799
+ outline: 2px solid transparent;
5800
+ outline-offset: 2px;
5297
5801
  }
5298
- .drag-handle > div:first-child button:focus-visible {
5802
+ .drag-handle > div[data-inspector-account-strip] button:hover {
5803
+ background-color: rgba(255, 255, 255, 0.12) !important;
5804
+ color: #ffffff !important;
5805
+ }
5806
+ .drag-handle > div[data-inspector-account-strip] button:focus-visible {
5299
5807
  outline-color: #bec2ff !important;
5300
5808
  }
5809
+ .drag-handle > div[data-inspector-account-strip] button:focus,
5810
+ .drag-handle > div[data-inspector-account-strip] button:focus-visible {
5811
+ outline: 2px solid #bec2ff !important;
5812
+ outline-offset: 2px;
5813
+ }
5814
+ .inspector-nav-control:focus,
5815
+ .inspector-nav-control:focus-visible,
5816
+ [data-inspector-thread-cta]:focus,
5817
+ [data-inspector-thread-cta]:focus-visible,
5818
+ [data-inspector-action-placement="threads-footer"]:focus,
5819
+ [data-inspector-action-placement="threads-footer"]:focus-visible {
5820
+ outline: 2px solid #6430ab !important;
5821
+ outline-offset: 2px;
5822
+ }
5301
5823
 
5302
5824
  /* ── Agent/context dropdown ──────────────────────────────────── */
5303
5825
  [data-context-dropdown-root="true"] > button {
@@ -5309,7 +5831,7 @@ ${argsString}</pre
5309
5831
  background-color: #f7f7f9 !important;
5310
5832
  }
5311
5833
  [data-context-dropdown-root="true"] > button > span:last-child {
5312
- color: #838389 !important;
5834
+ color: #68686e !important;
5313
5835
  }
5314
5836
  [data-context-dropdown-root="true"] > div {
5315
5837
  border-color: #dbdbe5 !important;
@@ -5319,6 +5841,28 @@ ${argsString}</pre
5319
5841
  [data-context-dropdown-root="true"] > div button:focus {
5320
5842
  background-color: #f7f7f9 !important;
5321
5843
  }
5844
+ [data-inspector-account-strip]
5845
+ .inspector-agent-selector
5846
+ > [data-context-dropdown-root="true"]
5847
+ > button {
5848
+ border-color: rgba(255, 255, 255, 0.28) !important;
5849
+ background-color: transparent !important;
5850
+ color: #e7e7ec !important;
5851
+ }
5852
+ [data-inspector-account-strip]
5853
+ .inspector-agent-selector
5854
+ > [data-context-dropdown-root="true"]
5855
+ > button:hover {
5856
+ border-color: rgba(190, 194, 255, 0.7) !important;
5857
+ background-color: rgba(255, 255, 255, 0.08) !important;
5858
+ }
5859
+ [data-inspector-account-strip]
5860
+ .inspector-agent-selector
5861
+ > [data-context-dropdown-root="true"]
5862
+ > button
5863
+ > span:last-child {
5864
+ color: #afafb7 !important;
5865
+ }
5322
5866
 
5323
5867
  /* ── Status bar (bottom chrome) ──────────────────────────────── */
5324
5868
  .inspector-window > div > div:last-child {
@@ -5328,7 +5872,7 @@ ${argsString}</pre
5328
5872
 
5329
5873
  /* ── Resize handle ───────────────────────────────────────────── */
5330
5874
  .resize-handle {
5331
- color: #838389 !important;
5875
+ color: #68686e !important;
5332
5876
  }
5333
5877
  .resize-handle:hover {
5334
5878
  color: #57575b !important;
@@ -5349,7 +5893,7 @@ ${argsString}</pre
5349
5893
  /* Copy "copied" state: generic green → brand mint */
5350
5894
  button[class*="bg-green-100"] {
5351
5895
  background-color: rgba(133, 236, 206, 0.2) !important;
5352
- color: #189370 !important;
5896
+ color: #087653 !important;
5353
5897
  }
5354
5898
 
5355
5899
  /* ── Agents tab ──────────────────────────────────────────────── */
@@ -5358,14 +5902,14 @@ ${argsString}</pre
5358
5902
  background-color: rgba(190, 194, 255, 0.15) !important;
5359
5903
  }
5360
5904
  span[class*="text-blue-600"] {
5361
- color: #757cf2 !important;
5905
+ color: #5558b2 !important;
5362
5906
  }
5363
5907
  /* Running badge: emerald → mint */
5364
5908
  span[class*="bg-emerald-50"] {
5365
5909
  background-color: rgba(133, 236, 206, 0.15) !important;
5366
5910
  }
5367
5911
  span[class*="text-emerald-700"] {
5368
- color: #189370 !important;
5912
+ color: #087653 !important;
5369
5913
  }
5370
5914
  /* Running status dot */
5371
5915
  span[class*="bg-emerald-500"] {
@@ -5385,7 +5929,7 @@ ${argsString}</pre
5385
5929
  span[class*="bg-green-100"][class*="text-green-800"] {
5386
5930
  background-color: rgba(133, 236, 206, 0.18) !important;
5387
5931
  border: 1px solid rgba(133, 236, 206, 0.4) !important;
5388
- color: #189370 !important;
5932
+ color: #087653 !important;
5389
5933
  }
5390
5934
  /* Tool role badge (amber → orange brand) */
5391
5935
  span[class*="bg-amber-100"][class*="text-amber-800"] {
@@ -5426,13 +5970,17 @@ ${argsString}</pre
5426
5970
  > div:last-child:has(div[class*="bg-emerald-50"]) {
5427
5971
  background-color: rgba(91, 228, 187, 0.08) !important;
5428
5972
  border-top-color: rgba(91, 228, 187, 0.3) !important;
5429
- color: #189370 !important;
5973
+ color: #087653 !important;
5430
5974
  }
5431
5975
  /* Inner badge — slightly more opaque on the mint bg */
5432
5976
  div[class*="bg-emerald-50"][class*="border-emerald-200"] {
5433
5977
  background-color: rgba(91, 228, 187, 0.12) !important;
5434
5978
  border-color: rgba(91, 228, 187, 0.4) !important;
5435
- color: #189370 !important;
5979
+ color: #087653 !important;
5980
+ }
5981
+ div[class*="bg-emerald-50"][class*="border-emerald-200"]
5982
+ span[class*="opacity-80"] {
5983
+ opacity: 1 !important;
5436
5984
  }
5437
5985
  /* Icon bubble inside connected badge → mint tint */
5438
5986
  div[class*="bg-emerald-50"] span[class*="bg-white"] {
@@ -5447,7 +5995,7 @@ ${argsString}</pre
5447
5995
  span[class*="bg-slate-900"],
5448
5996
  div[class*="bg-slate-900"] {
5449
5997
  background-color: #eee6fe !important;
5450
- color: #757cf2 !important;
5998
+ color: #5558b2 !important;
5451
5999
  }
5452
6000
  span[class*="text-slate-800"],
5453
6001
  div[class*="text-slate-800"] {
@@ -5458,6 +6006,7 @@ ${argsString}</pre
5458
6006
  connectedCallback() {
5459
6007
  super.connectedCallback();
5460
6008
  if (typeof window !== "undefined") {
6009
+ this.threadsExampleOverviewVideoReducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
5461
6010
  this.ensureBrandFonts();
5462
6011
  window.addEventListener("resize", this.handleResize);
5463
6012
  window.addEventListener("pointerdown", this.handleGlobalPointerDown);
@@ -5466,6 +6015,7 @@ ${argsString}</pre
5466
6015
  this.tryAutoAttachCore();
5467
6016
  this.ensureAnnouncementLoading();
5468
6017
  }
6018
+ this.requestUpdate();
5469
6019
  }
5470
6020
  ensureBrandFonts() {
5471
6021
  const FONT_LINK_ID = "cpk-inspector-brand-fonts";
@@ -5488,14 +6038,13 @@ ${argsString}</pre
5488
6038
  clearTimeout(this.transitionTimeoutId);
5489
6039
  this.transitionTimeoutId = null;
5490
6040
  }
5491
- if (this.threadsExampleOverviewVideoLoadTimer !== null) {
5492
- window.clearTimeout(this.threadsExampleOverviewVideoLoadTimer);
5493
- this.threadsExampleOverviewVideoLoadTimer = null;
5494
- }
5495
- if (this.threadsExampleOverviewVideoIdleCallbackId !== null && typeof window.cancelIdleCallback === "function") {
5496
- window.cancelIdleCallback(this.threadsExampleOverviewVideoIdleCallbackId);
5497
- this.threadsExampleOverviewVideoIdleCallbackId = null;
6041
+ this.threadsSetupPromptCopyGeneration += 1;
6042
+ if (this.threadsSetupPromptCopyResetTimeoutId !== null) {
6043
+ window.clearTimeout(this.threadsSetupPromptCopyResetTimeoutId);
6044
+ this.threadsSetupPromptCopyResetTimeoutId = null;
5498
6045
  }
6046
+ this.threadsSetupPromptCopyState = "idle";
6047
+ this.cleanupThreadsExampleOverviewVideo();
5499
6048
  this.removeDockStyles(true);
5500
6049
  this.detachFromCore();
5501
6050
  }
@@ -5521,6 +6070,7 @@ ${argsString}</pre
5521
6070
  y: EDGE_MARGIN
5522
6071
  };
5523
6072
  this.hydrateStateFromStorage();
6073
+ this.contextState.window.size = this.clampWindowSize(this.contextState.window.size);
5524
6074
  if (this.selectedMenu === "memories") this.ensureMemorySubscription();
5525
6075
  if (this.isOpen && this.dockMode !== "floating") this.applyDockStyles(true);
5526
6076
  this.applyAnchorPosition("button");
@@ -5532,6 +6082,35 @@ ${argsString}</pre
5532
6082
  render() {
5533
6083
  return this.isOpen ? this.renderWindow() : this.renderButton();
5534
6084
  }
6085
+ willUpdate() {
6086
+ this.reconcileSelectedMenuVisibility();
6087
+ }
6088
+ updated() {
6089
+ this.syncThreadsExampleOverviewVideo();
6090
+ this.maybeTrackInspectorMetadataViews();
6091
+ if (!this.isOpen || this.selectedGroup !== "agents") {
6092
+ this.lastScrolledAgentNavigationLayout = null;
6093
+ return;
6094
+ }
6095
+ const navigation = this.shadowRoot?.querySelector("nav[aria-label=\"Agent navigation\"]");
6096
+ if (!navigation) return;
6097
+ const activeControl = navigation.querySelector("[aria-current=\"page\"]");
6098
+ if (!activeControl) return;
6099
+ const layoutKey = [
6100
+ this.selectedMenu,
6101
+ this.dockMode,
6102
+ Math.round(this.contextState.window.size.width),
6103
+ typeof window === "undefined" ? 0 : window.innerWidth,
6104
+ navigation.clientWidth,
6105
+ navigation.scrollWidth
6106
+ ].join(":");
6107
+ if (this.lastScrolledAgentNavigationLayout === layoutKey) return;
6108
+ if (typeof activeControl.scrollIntoView === "function") activeControl.scrollIntoView({
6109
+ block: "nearest",
6110
+ inline: "nearest"
6111
+ });
6112
+ this.lastScrolledAgentNavigationLayout = layoutKey;
6113
+ }
5535
6114
  renderButton() {
5536
6115
  return html`
5537
6116
  <div class="console-button-wrapper">
@@ -5587,24 +6166,79 @@ ${argsString}</pre
5587
6166
  </button>
5588
6167
  ${this.renderAnnouncementPreview()}
5589
6168
  </div>
6169
+ `;
6170
+ }
6171
+ /** Render a trusted action with optional context-specific copy. */
6172
+ renderInspectorAction(action, placement, displayLabel = action.label) {
6173
+ return html`
6174
+ <a
6175
+ data-inspector-action-placement=${placement}
6176
+ data-inspector-action-intent=${(displayLabel === "Upgrade Your Plan" ? "upgrade" : void 0) ?? nothing}
6177
+ href=${action.url}
6178
+ target="_blank"
6179
+ rel="noopener noreferrer"
6180
+ aria-label="${displayLabel} (opens in a new tab)"
6181
+ style=${placement === "threads-footer" ? "" : "display:inline-flex;min-height:34px;align-items:center;justify-content:center;gap:6px;border:1px solid #dbdbe5;border-radius:6px;background:#ffffff;padding:8px 12px;color:#57575b;font-size:12px;font-weight:600;text-decoration:none;outline-style:solid;outline-width:2px;outline-color:transparent;outline-offset:2px;cursor:pointer;"}
6182
+ @click=${() => this.handleInspectorMetadataActionClick(action, placement)}
6183
+ >
6184
+ ${displayLabel}
6185
+ </a>
6186
+ `;
6187
+ }
6188
+ renderInspectorMetadataHeader() {
6189
+ const { identity, plan } = this.inspectorMetadataProjection;
6190
+ if (!identity && !plan) return nothing;
6191
+ return html`
6192
+ <div
6193
+ style="display:flex;min-width:0;flex:1 1 220px;flex-wrap:wrap;align-items:center;gap:6px 8px;"
6194
+ role="group"
6195
+ aria-label="Inspector account details"
6196
+ >
6197
+ ${identity ? html`
6198
+ <div
6199
+ data-inspector-metadata="identity"
6200
+ style="display:flex;min-width:0;max-width:100%;align-items:center;gap:6px;color:#e7e7ec;font-size:11px;line-height:1.3;"
6201
+ title="${identity.organizationName} / ${identity.projectName}"
6202
+ >
6203
+ <span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"
6204
+ >${identity.organizationName}</span
6205
+ >
6206
+ <span aria-hidden="true" style="color:#68686e;">/</span>
6207
+ <span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"
6208
+ >${identity.projectName}</span
6209
+ >
6210
+ </div>
6211
+ ` : nothing}
6212
+ ${plan ? html`
6213
+ <span
6214
+ data-inspector-metadata="plan"
6215
+ style="display:inline-flex;min-height:22px;align-items:center;border:1px solid rgba(133,236,206,0.5);border-radius:4px;background:rgba(133,236,206,0.14);padding:2px 7px;color:#85ecce;font-size:10px;font-weight:600;line-height:1.2;white-space:nowrap;"
6216
+ >${plan.label}</span
6217
+ >
6218
+ ` : nothing}
6219
+ </div>
5590
6220
  `;
5591
6221
  }
5592
6222
  renderWindow() {
5593
6223
  const windowState = this.contextState.window;
5594
6224
  const isDocked = this.dockMode !== "floating";
5595
6225
  const isTransitioning = this.hasAttribute("data-transitioning");
5596
- const windowStyles = isDocked ? this.getDockedWindowStyles() : {
6226
+ const windowStyles = isDocked ? {
6227
+ ...this.getDockedWindowStyles(),
6228
+ overflowX: "hidden"
6229
+ } : {
5597
6230
  width: `${Math.round(windowState.size.width)}px`,
5598
6231
  height: `${Math.round(windowState.size.height)}px`,
5599
6232
  minWidth: `${MIN_WINDOW_WIDTH}px`,
5600
- minHeight: `${MIN_WINDOW_HEIGHT}px`
6233
+ minHeight: `${MIN_WINDOW_HEIGHT}px`,
6234
+ overflowX: "hidden"
5601
6235
  };
5602
- const hasContextDropdown = this.contextOptions.length > 0;
6236
+ const hasContextDropdown = this.contextOptions.some((option) => option.key !== "all-agents");
5603
6237
  const contextDropdown = hasContextDropdown ? this.renderContextDropdown() : nothing;
5604
6238
  const coreStatus = this.getCoreStatusSummary();
5605
6239
  const agentSelector = hasContextDropdown ? contextDropdown : html`
5606
6240
  <div
5607
- class="flex items-center gap-2 rounded-md border border-dashed border-gray-200 px-2 py-1 text-xs text-gray-400"
6241
+ class="inspector-agent-placeholder flex items-center gap-2 rounded-md border border-dashed px-2 py-1 text-xs"
5608
6242
  >
5609
6243
  <span>${this.renderIcon("Bot")}</span>
5610
6244
  <span class="truncate">No agents available</span>
@@ -5639,32 +6273,41 @@ ${argsString}</pre
5639
6273
  @pointerup=${isDocked ? void 0 : this.handlePointerUp}
5640
6274
  @pointercancel=${isDocked ? void 0 : this.handlePointerCancel}
5641
6275
  >
5642
- <div class="flex flex-wrap items-center gap-3 px-4 py-3">
6276
+ <div
6277
+ class="inspector-account-strip flex flex-wrap items-center gap-3 px-4 py-3"
6278
+ data-inspector-account-strip
6279
+ style="width:100%;min-width:0;background-color:#010507;color:#ffffff;"
6280
+ >
5643
6281
  <div class="flex items-center min-w-0">
5644
6282
  <img
5645
6283
  src=${inspector_logo_default}
5646
6284
  alt="Inspector logo"
5647
- class="h-6 w-auto"
6285
+ class="inspector-account-logo h-6 w-auto"
5648
6286
  loading="lazy"
5649
6287
  />
5650
6288
  </div>
6289
+ ${this.renderInspectorMetadataHeader()}
5651
6290
  <div class="ml-auto flex min-w-0 items-center gap-2">
5652
- <div class="min-w-[160px] max-w-xs">${agentSelector}</div>
6291
+ <div class="inspector-agent-selector min-w-[160px] max-w-xs">
6292
+ ${agentSelector}
6293
+ </div>
5653
6294
  <div class="flex items-center gap-1">
5654
6295
  ${this.renderDockControls()}
5655
6296
  <button
5656
- class="flex h-8 w-8 items-center justify-center rounded-md transition hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400 ${this.selectedMenu === "settings" ? "bg-gray-100 text-gray-700" : "text-gray-400 hover:text-gray-600"}"
6297
+ 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" : ""}"
5657
6298
  type="button"
5658
6299
  aria-label="Settings"
5659
- aria-pressed=${this.selectedMenu === "settings"}
5660
- @click=${() => this.handleMenuSelect(this.selectedMenu === "settings" ? "ag-ui-events" : "settings")}
6300
+ aria-pressed=${this.settingsOpen}
6301
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6302
+ @click=${this.handleSettingsToggle}
5661
6303
  >
5662
6304
  ${this.renderIcon("Settings")}
5663
6305
  </button>
5664
6306
  <button
5665
- class="flex h-8 w-8 items-center justify-center rounded-md text-gray-400 transition hover:bg-gray-100 hover:text-gray-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400"
6307
+ 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"
5666
6308
  type="button"
5667
6309
  aria-label="Close Web Inspector"
6310
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
5668
6311
  @pointerdown=${this.handleClosePointerDown}
5669
6312
  @click=${this.handleCloseClick}
5670
6313
  >
@@ -5673,37 +6316,72 @@ ${argsString}</pre
5673
6316
  </div>
5674
6317
  </div>
5675
6318
  </div>
5676
- <div
5677
- class="flex flex-wrap items-center gap-2 border-t border-gray-100 px-3 py-2 text-xs"
6319
+ <nav
6320
+ class="inspector-primary-navigation"
6321
+ aria-label="Inspector primary navigation"
6322
+ style="overflow-x:auto;overflow-y:hidden;cursor:default;"
5678
6323
  >
5679
- ${this.menuItems.map(({ key, label, icon }) => {
5680
- const isSelected = this.selectedMenu === key;
6324
+ ${INSPECTOR_PRIMARY_NAVIGATION.map(({ key, label, icon }) => {
6325
+ const isSelected = this.selectedGroup === key;
5681
6326
  return html`
5682
6327
  <button
5683
6328
  type="button"
5684
- class=${["inline-flex items-center gap-2 rounded-md px-3 py-2 transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300", isSelected ? "cpk-tab-active" : "cpk-tab-inactive"].join(" ")}
5685
- aria-pressed=${isSelected}
5686
- @click=${() => this.handleMenuSelect(key)}
6329
+ class="inspector-nav-control inspector-primary-control ${isSelected ? "inspector-nav-control-active" : ""}"
6330
+ data-inspector-group=${key}
6331
+ data-inspector-menu-key=${(key === "threads" ? "threads" : key === "learning" ? "memories" : void 0) ?? nothing}
6332
+ aria-current=${isSelected ? "page" : nothing}
6333
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6334
+ @click=${() => this.handleGroupSelect(key)}
5687
6335
  >
5688
- <span class="cpk-tab-icon">
5689
- ${key in this.customTabIcons ? unsafeHTML(this.customTabIcons[key]) : this.renderIcon(icon)}
6336
+ <span class="inspector-nav-icon" aria-hidden="true">
6337
+ ${key === "threads" ? unsafeHTML(this.customTabIcons.threads) : this.renderIcon(icon)}
5690
6338
  </span>
5691
6339
  <span>${label}</span>
5692
6340
  </button>
5693
6341
  `;
5694
6342
  })}
5695
- ${this.selectedMenu === "threads" ? html`
6343
+ ${this.selectedGroup === "threads" ? html`
5696
6344
  <a
6345
+ data-inspector-thread-cta
5697
6346
  href=${this.getThreadsTalkToEngineerUrl()}
5698
6347
  target="_blank"
5699
- rel="noopener"
5700
- style="margin-left:auto;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;"
6348
+ rel="noopener noreferrer"
6349
+ aria-label="Talk to an Engineer (opens in a new tab)"
6350
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
5701
6351
  @click=${this.handleTalkToEngineerClick}
5702
6352
  >
5703
6353
  Talk to an Engineer
5704
6354
  </a>
5705
6355
  ` : nothing}
5706
- </div>
6356
+ </nav>
6357
+ ${this.selectedGroup === "agents" ? html`
6358
+ <nav
6359
+ class="inspector-child-navigation"
6360
+ aria-label="Agent navigation"
6361
+ style="overflow-x:auto;overflow-y:hidden;white-space:nowrap;cursor:default;"
6362
+ >
6363
+ ${this.getVisibleMenuItemsForGroup("agents").map(({ key, label, icon }) => {
6364
+ const isSelected = this.selectedMenu === key;
6365
+ return html`
6366
+ <button
6367
+ type="button"
6368
+ class="inspector-nav-control inspector-child-control ${isSelected ? "inspector-nav-control-active" : ""}"
6369
+ data-inspector-menu-key=${key}
6370
+ aria-current=${isSelected ? "page" : nothing}
6371
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6372
+ @click=${() => this.handleMenuSelect(key)}
6373
+ >
6374
+ <span
6375
+ class="inspector-nav-icon"
6376
+ aria-hidden="true"
6377
+ >${this.renderIcon(icon)}</span
6378
+ >
6379
+ <span>${label}</span>
6380
+ </button>
6381
+ `;
6382
+ })}
6383
+ </nav>
6384
+ ` : nothing}
5707
6385
  </div>
5708
6386
  <div class="flex flex-1 flex-col overflow-hidden">
5709
6387
  <div id="cpk-main-scroll" class="flex-1 overflow-auto">
@@ -5759,10 +6437,7 @@ ${argsString}</pre
5759
6437
  if (!persisted) return;
5760
6438
  if (typeof persisted.isOpen === "boolean") this.isOpen = persisted.isOpen;
5761
6439
  if (isValidDockMode(persisted.dockMode)) this.dockMode = persisted.dockMode;
5762
- if (typeof persisted.selectedMenu === "string") {
5763
- const validMenu = this.menuItems.find((item) => item.key === persisted.selectedMenu);
5764
- if (validMenu) this.selectedMenu = validMenu.key;
5765
- }
6440
+ this.restorePersistedMenu(persisted.selectedMenu);
5766
6441
  if (typeof persisted.selectedContext === "string") {
5767
6442
  this.selectedContext = persisted.selectedContext;
5768
6443
  this.pendingSelectedContext = persisted.selectedContext;
@@ -5772,6 +6447,7 @@ ${argsString}</pre
5772
6447
  if (typeof document === "undefined" || typeof window === "undefined") return;
5773
6448
  const persisted = loadInspectorState(INSPECTOR_STORAGE_KEY);
5774
6449
  if (!persisted) return;
6450
+ this.restorePersistedMenu(persisted.selectedMenu);
5775
6451
  const persistedButton = persisted.button;
5776
6452
  if (persistedButton) {
5777
6453
  if (isValidAnchor(persistedButton.anchor)) this.contextState.button.anchor = persistedButton.anchor;
@@ -5790,6 +6466,28 @@ ${argsString}</pre
5790
6466
  this.pendingSelectedContext = persisted.selectedContext;
5791
6467
  }
5792
6468
  }
6469
+ /** Restore a visible legacy leaf, or use Threads for stale state. */
6470
+ restorePersistedMenu(value) {
6471
+ this.selectedMenu = "threads";
6472
+ this.lastSelectedMenuByGroup.threads = "threads";
6473
+ this.pendingPersistedMenu = null;
6474
+ if (!isInspectorMenuKey(value)) return;
6475
+ const validMenu = this.menuItems.find((item) => item.key === value);
6476
+ if (!validMenu) {
6477
+ if (!this.hasResolvedCore) this.pendingPersistedMenu = value;
6478
+ return;
6479
+ }
6480
+ this.selectedMenu = validMenu.key;
6481
+ this.lastSelectedMenuByGroup[this.getGroupForMenu(validMenu.key)] = validMenu.key;
6482
+ }
6483
+ /** Resolve a valid stored leaf after the first Core exposes its sources. */
6484
+ resolvePendingPersistedMenu() {
6485
+ const pendingMenu = this.pendingPersistedMenu;
6486
+ if (!pendingMenu) return;
6487
+ this.pendingPersistedMenu = null;
6488
+ this.restorePersistedMenu(pendingMenu);
6489
+ this.persistState();
6490
+ }
5793
6491
  get activeContext() {
5794
6492
  return this.isOpen ? "window" : "button";
5795
6493
  }
@@ -5855,7 +6553,7 @@ ${argsString}</pre
5855
6553
  },
5856
6554
  isOpen: this.isOpen,
5857
6555
  dockMode: this.dockMode,
5858
- selectedMenu: this.selectedMenu,
6556
+ selectedMenu: this.pendingPersistedMenu ?? this.selectedMenu,
5859
6557
  selectedContext: this.selectedContext
5860
6558
  };
5861
6559
  saveInspectorState(INSPECTOR_STORAGE_KEY, state);
@@ -5878,7 +6576,7 @@ ${argsString}</pre
5878
6576
  if (mode === "docked-left") this.contextState.window.size.width = DOCKED_LEFT_WIDTH;
5879
6577
  this.applyDockStyles();
5880
6578
  } else {
5881
- this.contextState.window.size = { ...DEFAULT_WINDOW_SIZE };
6579
+ this.contextState.window.size = this.clampWindowSize(DEFAULT_WINDOW_SIZE);
5882
6580
  this.centerContext("window");
5883
6581
  }
5884
6582
  this.persistState();
@@ -6037,6 +6735,7 @@ ${argsString}</pre
6037
6735
  type="button"
6038
6736
  aria-label="Dock to left"
6039
6737
  title="Dock Left"
6738
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6040
6739
  @click=${() => this.handleDockClick("docked-left")}
6041
6740
  >
6042
6741
  ${this.renderIcon("PanelLeft")}
@@ -6048,6 +6747,7 @@ ${argsString}</pre
6048
6747
  type="button"
6049
6748
  aria-label="Float window"
6050
6749
  title="Float"
6750
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6051
6751
  @click=${() => this.handleDockClick("floating")}
6052
6752
  >
6053
6753
  ${this.renderIcon("Maximize2")}
@@ -6226,6 +6926,7 @@ ${argsString}</pre
6226
6926
  };
6227
6927
  }
6228
6928
  renderMainContent() {
6929
+ if (this.settingsOpen) return this.renderSettingsPanel();
6229
6930
  if (this.selectedMenu === "ag-ui-events") return this.renderEventsTable();
6230
6931
  if (this.selectedMenu === "agents") return this.renderAgentsView();
6231
6932
  if (this.selectedMenu === "frontend-tools") return this.renderToolsView();
@@ -6233,7 +6934,6 @@ ${argsString}</pre
6233
6934
  if (this.selectedMenu === "agent-context") return this.renderContextView();
6234
6935
  if (this.selectedMenu === "threads") return this.renderThreadsView();
6235
6936
  if (this.selectedMenu === "memories") return this.renderMemoriesView();
6236
- if (this.selectedMenu === "settings") return this.renderSettingsPanel();
6237
6937
  return nothing;
6238
6938
  }
6239
6939
  renderSettingsPanel() {
@@ -6281,6 +6981,94 @@ ${argsString}</pre
6281
6981
  has_unseen_announcement: hadUnseenAnnouncement
6282
6982
  });
6283
6983
  }
6984
+ getVisibleInspectorMetadataAction() {
6985
+ const { threadsFooterAction, lockedAction } = this.inspectorMetadataProjection;
6986
+ if (threadsFooterAction && !this.settingsOpen && this.selectedMenu === "threads") return {
6987
+ action: threadsFooterAction,
6988
+ placement: "threads-footer"
6989
+ };
6990
+ if (lockedAction && !this.settingsOpen && this.selectedMenu === "threads" && !this.areThreadEndpointsAvailable()) return {
6991
+ action: lockedAction,
6992
+ placement: "locked"
6993
+ };
6994
+ }
6995
+ /** Return paired stable navigation keys, or neither for an invalid leaf. */
6996
+ getMetadataTelemetryNavigation() {
6997
+ const selectedLeaf = this.selectedMenu;
6998
+ if (!isInspectorMenuKey(selectedLeaf)) return {};
6999
+ for (const group of INSPECTOR_PRIMARY_NAVIGATION) if (INSPECTOR_GROUPS[group.key].some((menuKey) => menuKey === selectedLeaf)) return {
7000
+ group_key: group.key,
7001
+ leaf_key: selectedLeaf
7002
+ };
7003
+ return {};
7004
+ }
7005
+ /** Build metadata's shared coarse buckets and stable navigation context. */
7006
+ getMetadataTelemetryContext() {
7007
+ return {
7008
+ usage_bucket: this.getThreadsUsageBucket(),
7009
+ expiry_bucket: this.getThreadsExpiryBucket(),
7010
+ ...this.getMetadataTelemetryNavigation()
7011
+ };
7012
+ }
7013
+ trackMetadataModuleIfChanged(props, fingerprint) {
7014
+ if (this.metadataTelemetryFingerprints.get(props.module) === fingerprint) return;
7015
+ this.metadataTelemetryFingerprints.set(props.module, fingerprint);
7016
+ try {
7017
+ trackMetadataModuleViewed(props);
7018
+ } catch {}
7019
+ }
7020
+ maybeTrackInspectorMetadataViews() {
7021
+ if (!this.isOpen || this.runtimeStatus !== CopilotKitCoreRuntimeConnectionStatus.Connected || this.core?.telemetryDisabled) return;
7022
+ const { identity, plan } = this.inspectorMetadataProjection;
7023
+ const license_bucket = this.inspectorMetadataProjection.licenseState;
7024
+ const telemetryContext = this.getMetadataTelemetryContext();
7025
+ const contextFingerprint = [
7026
+ telemetryContext.usage_bucket,
7027
+ telemetryContext.expiry_bucket,
7028
+ telemetryContext.group_key ?? null,
7029
+ telemetryContext.leaf_key ?? null
7030
+ ];
7031
+ if (identity) this.trackMetadataModuleIfChanged({
7032
+ module: "identity",
7033
+ license_bucket,
7034
+ ...telemetryContext
7035
+ }, JSON.stringify([
7036
+ license_bucket,
7037
+ ...contextFingerprint,
7038
+ identity.organizationName,
7039
+ identity.projectName
7040
+ ]));
7041
+ else this.metadataTelemetryFingerprints.delete("identity");
7042
+ if (plan) this.trackMetadataModuleIfChanged({
7043
+ module: "plan",
7044
+ license_bucket,
7045
+ ...telemetryContext
7046
+ }, JSON.stringify([
7047
+ license_bucket,
7048
+ ...contextFingerprint,
7049
+ plan.code,
7050
+ plan.label
7051
+ ]));
7052
+ else this.metadataTelemetryFingerprints.delete("plan");
7053
+ const visibleAction = this.getVisibleInspectorMetadataAction();
7054
+ if (visibleAction) {
7055
+ const { action, placement } = visibleAction;
7056
+ const action_placement = getMetadataActionPlacement(placement);
7057
+ this.trackMetadataModuleIfChanged({
7058
+ module: "action",
7059
+ action_kind: action.kind,
7060
+ license_bucket,
7061
+ ...telemetryContext,
7062
+ action_placement
7063
+ }, JSON.stringify([
7064
+ license_bucket,
7065
+ ...contextFingerprint,
7066
+ action_placement,
7067
+ action.kind,
7068
+ action.url
7069
+ ]));
7070
+ } else this.metadataTelemetryFingerprints.delete("action");
7071
+ }
6284
7072
  trackBannerDismissedOnce(surface) {
6285
7073
  if (this.core?.telemetryDisabled) return;
6286
7074
  const id = this.announcementTimestamp;
@@ -6307,21 +7095,18 @@ ${argsString}</pre
6307
7095
  cta_label: this.announcementCtaLabel ?? void 0
6308
7096
  });
6309
7097
  }
6310
- trackThreadsViewStateOnce(state, threadCount) {
7098
+ trackThreadsViewStateOnce(state) {
6311
7099
  if (this.core?.telemetryDisabled) return;
6312
7100
  const key = `${state}:${this.getThreadServiceStatus()}`;
6313
7101
  if (this.viewedThreadsTelemetryStates.has(key)) return;
6314
7102
  this.viewedThreadsTelemetryStates.add(key);
6315
- const props = this.getThreadsTelemetryProps({ thread_count: threadCount });
7103
+ const props = this.getThreadsTelemetryProps();
6316
7104
  if (state === "locked") trackThreadsLockedViewed(props);
6317
7105
  else if (state === "empty_enabled") trackThreadsEmptyEnabledViewed(props);
6318
7106
  else trackThreadsEnabledViewed(props);
6319
7107
  }
6320
- shouldRenderExampleThreads(displayThreads, threadsErrorMessage) {
6321
- return this.areThreadEndpointsAvailable() && !threadsErrorMessage && displayThreads.length === 0;
6322
- }
6323
- getVisibleThreads(displayThreads, threadsErrorMessage) {
6324
- return this.shouldRenderExampleThreads(displayThreads, threadsErrorMessage) ? THREADS_EXAMPLE_THREADS : displayThreads;
7108
+ shouldRenderExampleThreads(locked, displayThreads, threadsErrorMessage, threadsLoading) {
7109
+ return locked || !threadsErrorMessage && !threadsLoading && displayThreads.length === 0;
6325
7110
  }
6326
7111
  isExampleThreadId(threadId) {
6327
7112
  return THREADS_EXAMPLE_THREADS.some((thread) => thread.id === threadId);
@@ -6338,6 +7123,7 @@ ${argsString}</pre
6338
7123
  agentId: thread.agentId,
6339
7124
  endUserId: "example-user",
6340
7125
  status: "completed",
7126
+ createdAt: thread.createdAt,
6341
7127
  updatedAt: thread.updatedAt
6342
7128
  } : null,
6343
7129
  getMessages: async () => details?.messages ?? [],
@@ -6350,39 +7136,43 @@ ${argsString}</pre
6350
7136
  trackThreadsExampleViewedOnce() {
6351
7137
  if (this.core?.telemetryDisabled) return;
6352
7138
  for (const thread of THREADS_EXAMPLE_THREADS) {
6353
- if (this.viewedExampleThreadIds.has(thread.id)) continue;
6354
- this.viewedExampleThreadIds.add(thread.id);
6355
- trackThreadsExampleViewed(this.getThreadsTelemetryProps({
6356
- example_thread_id: thread.id,
6357
- thread_count: 0
6358
- }));
7139
+ const exampleKind = getExampleKind(thread.id);
7140
+ if (!exampleKind || this.viewedExampleKinds.has(exampleKind)) continue;
7141
+ this.viewedExampleKinds.add(exampleKind);
7142
+ trackThreadsExampleViewed({
7143
+ ...this.getThreadsTelemetryProps(),
7144
+ example_kind: exampleKind
7145
+ });
6359
7146
  }
6360
7147
  }
6361
7148
  trackThreadsExampleSelectedOnce(threadId) {
6362
7149
  if (this.core?.telemetryDisabled) return;
6363
- if (this.selectedExampleThreadIds.has(threadId)) return;
6364
- this.selectedExampleThreadIds.add(threadId);
6365
- trackThreadsExampleSelected(this.getThreadsTelemetryProps({
6366
- example_thread_id: threadId,
6367
- thread_count: 0
6368
- }));
7150
+ const exampleKind = getExampleKind(threadId);
7151
+ if (!exampleKind || this.selectedExampleKinds.has(exampleKind)) return;
7152
+ this.selectedExampleKinds.add(exampleKind);
7153
+ trackThreadsExampleSelected({
7154
+ ...this.getThreadsTelemetryProps(),
7155
+ example_kind: exampleKind
7156
+ });
6369
7157
  }
6370
7158
  getCurrentExampleTourProps() {
6371
- const step = THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0];
6372
- return this.getThreadsTelemetryProps({
6373
- example_thread_id: this.selectedThreadId ?? void 0,
6374
- thread_count: 0,
6375
- tour_step: this.exampleTourStep + 1,
6376
- tour_tab: step?.tab
6377
- });
7159
+ const exampleKind = this.selectedThreadId ? getExampleKind(this.selectedThreadId) : void 0;
7160
+ const tourPair = getExampleTourTelemetryPair(this.exampleTourStep);
7161
+ if (!exampleKind || !tourPair) return void 0;
7162
+ return {
7163
+ ...this.getThreadsTelemetryProps(),
7164
+ example_kind: exampleKind,
7165
+ ...tourPair
7166
+ };
6378
7167
  }
6379
7168
  trackThreadsExampleTourStepViewedOnce() {
6380
7169
  if (this.core?.telemetryDisabled || !this.selectedThreadId) return;
6381
- if (!(THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0])) return;
6382
- const key = `${this.selectedThreadId}:${this.exampleTourStep}`;
7170
+ const props = this.getCurrentExampleTourProps();
7171
+ if (!props) return;
7172
+ const key = `${props.example_kind}:${props.tour_step}`;
6383
7173
  if (this.viewedExampleTourSteps.has(key)) return;
6384
7174
  this.viewedExampleTourSteps.add(key);
6385
- trackThreadsExampleTourStepViewed(this.getCurrentExampleTourProps());
7175
+ trackThreadsExampleTourStepViewed(props);
6386
7176
  }
6387
7177
  syncExampleTourTab() {
6388
7178
  const step = THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0];
@@ -6397,44 +7187,65 @@ ${argsString}</pre
6397
7187
  this.exampleTourStep = 0;
6398
7188
  if (autoStarted) {
6399
7189
  this.exampleTourAutoShown = true;
6400
- if (!this.core?.telemetryDisabled) trackThreadsExampleTourStarted(this.getCurrentExampleTourProps());
6401
- } else if (!this.core?.telemetryDisabled) trackThreadsExampleTourReopened(this.getCurrentExampleTourProps());
7190
+ if (!this.core?.telemetryDisabled) {
7191
+ const props = this.getCurrentExampleTourProps();
7192
+ if (props) trackThreadsExampleTourStarted(props);
7193
+ }
7194
+ } else if (!this.core?.telemetryDisabled) {
7195
+ const props = this.getCurrentExampleTourProps();
7196
+ if (props) trackThreadsExampleTourReopened(props);
7197
+ }
6402
7198
  this.trackThreadsExampleTourStepViewedOnce();
6403
7199
  this.syncExampleTourTab();
6404
7200
  this.requestUpdate();
6405
7201
  }
6406
7202
  setExampleTourStep(nextStep) {
7203
+ const telemetryStepIsValid = getExampleTourTelemetryPair(nextStep) !== void 0;
6407
7204
  this.exampleTourStep = Math.max(0, Math.min(THREADS_EXAMPLE_TOUR_STEPS.length - 1, nextStep));
6408
- this.trackThreadsExampleTourStepViewedOnce();
7205
+ if (telemetryStepIsValid) this.trackThreadsExampleTourStepViewedOnce();
6409
7206
  this.syncExampleTourTab();
6410
7207
  this.requestUpdate();
6411
7208
  }
6412
7209
  dismissExampleTour(method) {
6413
7210
  if (!this.selectedThreadId) return;
6414
- const props = {
6415
- ...this.getCurrentExampleTourProps(),
6416
- dismiss_method: method
6417
- };
6418
7211
  this.exampleTourActive = false;
6419
7212
  this.exampleTourDismissed = true;
6420
7213
  this.writeThreadsExampleTourDismissed();
6421
- if (!this.core?.telemetryDisabled) if (method === "done") trackThreadsExampleTourCompleted(props);
6422
- else trackThreadsExampleTourDismissed(props);
7214
+ if (!this.core?.telemetryDisabled) {
7215
+ const currentProps = this.getCurrentExampleTourProps();
7216
+ if (currentProps) {
7217
+ const props = {
7218
+ ...currentProps,
7219
+ dismiss_method: method
7220
+ };
7221
+ if (method === "done") trackThreadsExampleTourCompleted(props);
7222
+ else trackThreadsExampleTourDismissed(props);
7223
+ }
7224
+ }
6423
7225
  this.requestUpdate();
6424
7226
  }
6425
7227
  handleThreadsThreadSelected(threadId, showingExamples) {
6426
- if (showingExamples && this.selectedThreadId === threadId) {
7228
+ if (showingExamples && this.selectedThreadId === threadId && this.selectedLocalExampleThreadId === threadId) {
6427
7229
  this.selectedThreadId = null;
7230
+ this.selectedRealThreadIsExplicit = false;
7231
+ this.selectedLocalExampleThreadId = null;
6428
7232
  this.exampleTourActive = false;
6429
7233
  this.requestUpdate();
6430
7234
  return;
6431
7235
  }
6432
7236
  this.selectedThreadId = threadId;
6433
7237
  if (showingExamples && this.isExampleThreadId(threadId)) {
7238
+ this.selectedRealThreadIsExplicit = false;
7239
+ this.selectedLocalExampleThreadId = threadId;
6434
7240
  this.trackThreadsExampleSelectedOnce(threadId);
6435
7241
  if (!this.exampleTourDismissed && !this.exampleTourAutoShown) this.startExampleTour(true);
6436
7242
  else this.exampleTourActive = false;
6437
- } else this.exampleTourActive = false;
7243
+ } else {
7244
+ const { displayThreads } = this.getActiveThreadsState();
7245
+ this.selectedRealThreadIsExplicit = displayThreads.some((thread) => thread.id === threadId);
7246
+ this.selectedLocalExampleThreadId = null;
7247
+ this.exampleTourActive = false;
7248
+ }
6438
7249
  this.requestUpdate();
6439
7250
  }
6440
7251
  readThreadsExampleTourDismissed() {
@@ -6453,42 +7264,228 @@ ${argsString}</pre
6453
7264
  window.localStorage.setItem(THREADS_EXAMPLE_TOUR_STORAGE_KEY, JSON.stringify({ dismissed: true }));
6454
7265
  } catch {}
6455
7266
  }
6456
- scheduleThreadsExampleOverviewVideoLoad() {
6457
- if (this.threadsExampleOverviewVideoShouldLoad || this.threadsExampleOverviewVideoLoadTimer !== null || this.threadsExampleOverviewVideoIdleCallbackId !== null || typeof window === "undefined") return;
6458
- if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
6459
- const loadVideo = () => {
7267
+ /** Cancels the one deferred gate without disturbing a newer lifecycle. */
7268
+ cancelThreadsExampleOverviewVideoGate() {
7269
+ if (this.threadsExampleOverviewVideoLoadTimer !== null) {
7270
+ if (typeof window !== "undefined") window.clearTimeout(this.threadsExampleOverviewVideoLoadTimer);
6460
7271
  this.threadsExampleOverviewVideoLoadTimer = null;
7272
+ }
7273
+ if (this.threadsExampleOverviewVideoIdleCallbackId !== null) {
7274
+ if (typeof window !== "undefined" && typeof window.cancelIdleCallback === "function") window.cancelIdleCallback(this.threadsExampleOverviewVideoIdleCallbackId);
6461
7275
  this.threadsExampleOverviewVideoIdleCallbackId = null;
6462
- this.threadsExampleOverviewVideoShouldLoad = true;
6463
- this.requestUpdate();
6464
- };
7276
+ }
7277
+ }
7278
+ /** Returns whether media work still belongs to the mounted lifecycle. */
7279
+ isCurrentThreadsExampleOverviewVideo(video, lifecycleGeneration) {
7280
+ return this.isConnected && video.isConnected && this.threadsExampleOverviewVideoElement === video && this.threadsExampleOverviewVideoLifecycleGeneration === lifecycleGeneration;
7281
+ }
7282
+ /** Invalidates any unresolved play request without exposing another state. */
7283
+ invalidateThreadsExampleOverviewVideoPlay() {
7284
+ this.threadsExampleOverviewVideoPlayAttemptGeneration += 1;
7285
+ this.threadsExampleOverviewVideoPlayPromise = null;
7286
+ }
7287
+ /** Moves a current media lifecycle into its readable failure state. */
7288
+ failThreadsExampleOverviewVideo(video, lifecycleGeneration) {
7289
+ if (!this.isCurrentThreadsExampleOverviewVideo(video, lifecycleGeneration)) return;
7290
+ this.invalidateThreadsExampleOverviewVideoPlay();
7291
+ this.threadsExampleOverviewVideoLoaded = false;
7292
+ this.threadsExampleOverviewVideoState = "failed";
7293
+ this.requestUpdate();
7294
+ }
7295
+ /** Settles one guarded play promise and ignores stale completion. */
7296
+ async settleThreadsExampleOverviewVideoPlay(video, lifecycleGeneration, playAttemptGeneration, playback) {
7297
+ try {
7298
+ await playback;
7299
+ if (this.isCurrentThreadsExampleOverviewVideo(video, lifecycleGeneration) && this.threadsExampleOverviewVideoPlayAttemptGeneration === playAttemptGeneration && this.threadsExampleOverviewVideoState !== "failed") {
7300
+ this.threadsExampleOverviewVideoState = "playing";
7301
+ this.requestUpdate();
7302
+ }
7303
+ } catch {
7304
+ if (this.isCurrentThreadsExampleOverviewVideo(video, lifecycleGeneration) && this.threadsExampleOverviewVideoPlayAttemptGeneration === playAttemptGeneration) this.failThreadsExampleOverviewVideo(video, lifecycleGeneration);
7305
+ } finally {
7306
+ if (this.threadsExampleOverviewVideoPlayAttemptGeneration === playAttemptGeneration) this.threadsExampleOverviewVideoPlayPromise = null;
7307
+ }
7308
+ }
7309
+ /** Starts at most one play request for the current media lifecycle. */
7310
+ playThreadsExampleOverviewVideo(video, lifecycleGeneration) {
7311
+ if (this.threadsExampleOverviewVideoPlayPromise !== null || this.threadsExampleOverviewVideoState === "deferred" || this.threadsExampleOverviewVideoState === "failed" || !this.isCurrentThreadsExampleOverviewVideo(video, lifecycleGeneration)) return;
7312
+ const playAttemptGeneration = this.threadsExampleOverviewVideoPlayAttemptGeneration + 1;
7313
+ this.threadsExampleOverviewVideoPlayAttemptGeneration = playAttemptGeneration;
7314
+ try {
7315
+ const playback = Promise.resolve(video.play());
7316
+ this.threadsExampleOverviewVideoPlayPromise = this.settleThreadsExampleOverviewVideoPlay(video, lifecycleGeneration, playAttemptGeneration, playback);
7317
+ } catch {
7318
+ this.failThreadsExampleOverviewVideo(video, lifecycleGeneration);
7319
+ }
7320
+ }
7321
+ /** Attaches the sole source for a generation and optionally starts playback. */
7322
+ activateThreadsExampleOverviewVideo(video, lifecycleGeneration, play) {
7323
+ if (this.threadsExampleOverviewVideoState !== "deferred" || !this.isCurrentThreadsExampleOverviewVideo(video, lifecycleGeneration)) return;
7324
+ this.threadsExampleOverviewVideoState = "ready";
7325
+ this.threadsExampleOverviewVideoLoaded = false;
7326
+ video.autoplay = !this.threadsExampleOverviewVideoReducedMotion;
7327
+ video.setAttribute("src", THREADS_EXAMPLE_OVERVIEW_VIDEO_URL);
7328
+ this.requestUpdate();
7329
+ if (play) this.playThreadsExampleOverviewVideo(video, lifecycleGeneration);
7330
+ }
7331
+ /** Schedules exactly one idle or timer gate for the current video. */
7332
+ scheduleThreadsExampleOverviewVideoLoad() {
7333
+ const video = this.threadsExampleOverviewVideoElement;
7334
+ if (!video || !this.isConnected || this.threadsExampleOverviewVideoState !== "deferred" || this.threadsExampleOverviewVideoLoadTimer !== null || this.threadsExampleOverviewVideoIdleCallbackId !== null || typeof window === "undefined") return;
7335
+ const lifecycleGeneration = this.threadsExampleOverviewVideoLifecycleGeneration;
6465
7336
  if (typeof window.requestIdleCallback === "function") {
6466
- this.threadsExampleOverviewVideoIdleCallbackId = window.requestIdleCallback(loadVideo, { timeout: 1200 });
7337
+ let idleCallbackId = 0;
7338
+ const loadVideo = () => {
7339
+ if (this.threadsExampleOverviewVideoIdleCallbackId !== idleCallbackId) return;
7340
+ this.threadsExampleOverviewVideoIdleCallbackId = null;
7341
+ this.activateThreadsExampleOverviewVideo(video, lifecycleGeneration, !this.threadsExampleOverviewVideoReducedMotion);
7342
+ };
7343
+ idleCallbackId = window.requestIdleCallback(loadVideo, { timeout: 1200 });
7344
+ this.threadsExampleOverviewVideoIdleCallbackId = idleCallbackId;
6467
7345
  return;
6468
7346
  }
6469
- this.threadsExampleOverviewVideoLoadTimer = window.setTimeout(loadVideo, 450);
7347
+ const loadTimer = window.setTimeout(() => {
7348
+ if (this.threadsExampleOverviewVideoLoadTimer !== loadTimer) return;
7349
+ this.threadsExampleOverviewVideoLoadTimer = null;
7350
+ this.activateThreadsExampleOverviewVideo(video, lifecycleGeneration, !this.threadsExampleOverviewVideoReducedMotion);
7351
+ }, 450);
7352
+ this.threadsExampleOverviewVideoLoadTimer = loadTimer;
7353
+ }
7354
+ /** Attaches one generation-scoped media listener set to the stable video. */
7355
+ bindThreadsExampleOverviewVideo(video) {
7356
+ const lifecycleGeneration = this.threadsExampleOverviewVideoLifecycleGeneration;
7357
+ const listeners = {
7358
+ loadeddata: () => {
7359
+ if (!this.isCurrentThreadsExampleOverviewVideo(video, lifecycleGeneration) || this.threadsExampleOverviewVideoState === "deferred" || this.threadsExampleOverviewVideoState === "failed") return;
7360
+ this.threadsExampleOverviewVideoLoaded = true;
7361
+ this.requestUpdate();
7362
+ },
7363
+ play: () => {
7364
+ if (this.isCurrentThreadsExampleOverviewVideo(video, lifecycleGeneration) && this.threadsExampleOverviewVideoState !== "deferred" && this.threadsExampleOverviewVideoState !== "failed") {
7365
+ this.threadsExampleOverviewVideoState = "playing";
7366
+ this.requestUpdate();
7367
+ }
7368
+ },
7369
+ pause: () => {
7370
+ if (this.isCurrentThreadsExampleOverviewVideo(video, lifecycleGeneration) && this.threadsExampleOverviewVideoState !== "deferred" && this.threadsExampleOverviewVideoState !== "failed") {
7371
+ this.invalidateThreadsExampleOverviewVideoPlay();
7372
+ this.threadsExampleOverviewVideoState = "ready";
7373
+ this.requestUpdate();
7374
+ }
7375
+ },
7376
+ error: () => {
7377
+ this.failThreadsExampleOverviewVideo(video, lifecycleGeneration);
7378
+ }
7379
+ };
7380
+ this.threadsExampleOverviewVideoElement = video;
7381
+ this.threadsExampleOverviewVideoListeners = listeners;
7382
+ video.addEventListener("loadeddata", listeners.loadeddata);
7383
+ video.addEventListener("play", listeners.play);
7384
+ video.addEventListener("pause", listeners.pause);
7385
+ video.addEventListener("error", listeners.error);
7386
+ video.muted = true;
7387
+ video.loop = true;
7388
+ video.playsInline = true;
7389
+ video.autoplay = this.threadsExampleOverviewVideoState !== "deferred" && !this.threadsExampleOverviewVideoReducedMotion;
7390
+ }
7391
+ /** Tears down gates, listeners, playback, and source in cleanup order. */
7392
+ cleanupThreadsExampleOverviewVideo() {
7393
+ const video = this.threadsExampleOverviewVideoElement;
7394
+ if (video) this.threadsExampleOverviewVideoLifecycleGeneration += 1;
7395
+ this.invalidateThreadsExampleOverviewVideoPlay();
7396
+ this.cancelThreadsExampleOverviewVideoGate();
7397
+ this.threadsExampleOverviewVideoPlayOnNextBind = false;
7398
+ const listeners = this.threadsExampleOverviewVideoListeners;
7399
+ if (video && listeners) {
7400
+ video.removeEventListener("loadeddata", listeners.loadeddata);
7401
+ video.removeEventListener("play", listeners.play);
7402
+ video.removeEventListener("pause", listeners.pause);
7403
+ video.removeEventListener("error", listeners.error);
7404
+ }
7405
+ this.threadsExampleOverviewVideoElement = null;
7406
+ this.threadsExampleOverviewVideoListeners = null;
7407
+ if (video) {
7408
+ try {
7409
+ video.pause();
7410
+ } catch {}
7411
+ video.removeAttribute("src");
7412
+ try {
7413
+ video.load();
7414
+ } catch {}
7415
+ }
7416
+ this.threadsExampleOverviewVideoLoaded = false;
7417
+ this.threadsExampleOverviewVideoState = "deferred";
7418
+ }
7419
+ /** Reconciles the rendered media node with its current lifecycle. */
7420
+ syncThreadsExampleOverviewVideo() {
7421
+ const video = this.shadowRoot?.querySelector(".cpk-threads-overview-video");
7422
+ if (!video) {
7423
+ if (this.threadsExampleOverviewVideoElement || this.threadsExampleOverviewVideoState !== "deferred" || this.threadsExampleOverviewVideoLoadTimer !== null || this.threadsExampleOverviewVideoIdleCallbackId !== null || this.threadsExampleOverviewVideoPlayOnNextBind) this.cleanupThreadsExampleOverviewVideo();
7424
+ return;
7425
+ }
7426
+ if (this.threadsExampleOverviewVideoElement !== video) {
7427
+ if (this.threadsExampleOverviewVideoElement) this.cleanupThreadsExampleOverviewVideo();
7428
+ this.bindThreadsExampleOverviewVideo(video);
7429
+ }
7430
+ if (this.threadsExampleOverviewVideoPlayOnNextBind) {
7431
+ this.threadsExampleOverviewVideoPlayOnNextBind = false;
7432
+ this.activateThreadsExampleOverviewVideo(video, this.threadsExampleOverviewVideoLifecycleGeneration, true);
7433
+ return;
7434
+ }
7435
+ this.scheduleThreadsExampleOverviewVideoLoad();
6470
7436
  }
6471
7437
  renderThreadsExampleOverviewVideo() {
6472
- this.scheduleThreadsExampleOverviewVideoLoad();
7438
+ const isPlaying = this.threadsExampleOverviewVideoState === "playing";
7439
+ const sourceIsAttached = this.threadsExampleOverviewVideoState !== "deferred";
7440
+ const video = html`<video
7441
+ class="cpk-threads-overview-video"
7442
+ data-loaded=${this.threadsExampleOverviewVideoLoaded}
7443
+ ?autoplay=${sourceIsAttached && !this.threadsExampleOverviewVideoReducedMotion}
7444
+ .autoplay=${sourceIsAttached && !this.threadsExampleOverviewVideoReducedMotion}
7445
+ loop
7446
+ .loop=${true}
7447
+ muted
7448
+ .muted=${true}
7449
+ playsinline
7450
+ .playsInline=${true}
7451
+ preload="metadata"
7452
+ ></video>`;
6473
7453
  return html`
6474
7454
  <div class="cpk-threads-overview-video-frame" aria-hidden="true">
6475
- ${this.threadsExampleOverviewVideoShouldLoad ? html`
6476
- <video
6477
- class="cpk-threads-overview-video"
6478
- data-ready=${this.threadsExampleOverviewVideoReady}
6479
- src=${THREADS_EXAMPLE_OVERVIEW_VIDEO_URL}
6480
- autoplay
6481
- loop
6482
- muted
6483
- playsinline
6484
- preload="metadata"
6485
- @loadeddata=${this.handleThreadsExampleOverviewVideoLoaded}
6486
- ></video>
6487
- ` : nothing}
7455
+ ${this.threadsExampleOverviewVideoLifecycleGeneration % 2 === 0 ? html`<!-- cpk-video-generation-even -->${video}` : html`<!-- cpk-video-generation-odd -->${video}`}
6488
7456
  </div>
7457
+ <button
7458
+ class="cpk-threads-overview-video-control"
7459
+ type="button"
7460
+ aria-pressed=${isPlaying ? "false" : "true"}
7461
+ @click=${this.handleThreadsExampleOverviewVideoControl}
7462
+ >
7463
+ ${isPlaying ? "Pause demo" : "Play demo"}
7464
+ </button>
7465
+ ${this.threadsExampleOverviewVideoState === "failed" ? html`
7466
+ <p class="cpk-threads-overview-video-fallback" role="status">
7467
+ ${THREADS_EXAMPLE_OVERVIEW_VIDEO_FALLBACK}
7468
+ </p>
7469
+ ` : nothing}
6489
7470
  `;
6490
7471
  }
6491
- renderThreadsExampleOverview() {
7472
+ /** Show a copy result briefly and announce it to assistive technology. */
7473
+ showThreadsSetupPromptCopyState(state, generation) {
7474
+ if (!this.isConnected || generation !== this.threadsSetupPromptCopyGeneration) return;
7475
+ if (this.threadsSetupPromptCopyResetTimeoutId !== null) window.clearTimeout(this.threadsSetupPromptCopyResetTimeoutId);
7476
+ this.threadsSetupPromptCopyState = state;
7477
+ this.requestUpdate();
7478
+ this.threadsSetupPromptCopyResetTimeoutId = window.setTimeout(() => {
7479
+ if (!this.isConnected || generation !== this.threadsSetupPromptCopyGeneration) return;
7480
+ this.threadsSetupPromptCopyState = "idle";
7481
+ this.threadsSetupPromptCopyResetTimeoutId = null;
7482
+ this.requestUpdate();
7483
+ }, 2e3);
7484
+ }
7485
+ renderThreadsExampleOverview(locked) {
7486
+ const lockedCopy = locked ? this.getThreadsLockedCopy() : void 0;
7487
+ const { lockedAction } = this.inspectorMetadataProjection;
7488
+ const onboardingAction = this.getThreadsEmptyOnboardingAction();
6492
7489
  return html`
6493
7490
  <div
6494
7491
  style="
@@ -6511,7 +7508,7 @@ ${argsString}</pre
6511
7508
  color: #010507;
6512
7509
  "
6513
7510
  >
6514
- Threads are persistent, inspectable conversations
7511
+ ${lockedCopy?.heading ?? "Threads are persistent, inspectable conversations"}
6515
7512
  </h2>
6516
7513
  ${this.renderThreadsExampleOverviewVideo()}
6517
7514
  <p
@@ -6522,60 +7519,89 @@ ${argsString}</pre
6522
7519
  color: #57575b;
6523
7520
  "
6524
7521
  >
6525
- Take a tour with the example threads in the sidebar. Then, start
6526
- chatting in your app to create the first real thread.
7522
+ ${lockedCopy?.description ?? "Take a tour with the example threads in the sidebar. Then, start chatting in your app to create the first real thread."}
6527
7523
  </p>
6528
7524
  <div style="display:flex;flex-wrap:wrap;gap:8px;">
6529
- <a
6530
- href=${this.getThreadsDocsUrl()}
6531
- target="_blank"
6532
- rel="noopener"
6533
- style="
6534
- display: inline-flex;
6535
- align-items: center;
6536
- justify-content: center;
6537
- gap: 6px;
6538
- min-height: 34px;
6539
- border-radius: 6px;
6540
- background: #010507;
6541
- padding: 8px 12px;
6542
- font-size: 12px;
6543
- font-weight: 600;
6544
- color: #ffffff;
6545
- text-decoration: none;
6546
- "
6547
- >
6548
- Learn how Threads work
6549
- </a>
6550
- <a
6551
- href=${this.getSelfHostedIntelligenceUrl()}
6552
- target="_blank"
6553
- rel="noopener"
6554
- style="
6555
- display: inline-flex;
6556
- align-items: center;
6557
- justify-content: center;
6558
- gap: 6px;
6559
- min-height: 34px;
6560
- border-radius: 6px;
6561
- border: 1px solid #dbdbe5;
6562
- background: #ffffff;
6563
- padding: 8px 12px;
6564
- font-size: 12px;
6565
- font-weight: 600;
6566
- color: #010507;
6567
- text-decoration: none;
6568
- "
6569
- >
6570
- Explore self-hosted Intelligence
6571
- </a>
7525
+ ${locked ? html`
7526
+ ${this.inspectorMetadataProjection.licenseState === "valid" ? html`
7527
+ <button
7528
+ data-inspector-threads-setup-prompt
7529
+ type="button"
7530
+ aria-label=${this.threadsSetupPromptCopyState === "copied" ? "Setup prompt copied" : this.threadsSetupPromptCopyState === "error" ? "Copy setup prompt failed. Try again" : "Copy setup prompt for your coding agent"}
7531
+ @click=${this.handleThreadsSetupPromptCopy}
7532
+ >
7533
+ ${this.renderIcon(this.threadsSetupPromptCopyState === "copied" ? "Check" : "Copy")}
7534
+ ${this.threadsSetupPromptCopyState === "copied" ? "Copied" : this.threadsSetupPromptCopyState === "error" ? "Copy blocked" : "Copy prompt for your agent"}
7535
+ </button>
7536
+ <a
7537
+ data-inspector-threads-setup-link
7538
+ href=${this.getThreadsRuntimeSetupDocsUrl()}
7539
+ target="_blank"
7540
+ rel="noopener"
7541
+ aria-label="Open setup guide (opens in a new tab)"
7542
+ >
7543
+ Open setup guide
7544
+ </a>
7545
+ <span
7546
+ class="sr-only"
7547
+ data-inspector-threads-setup-copy-status
7548
+ aria-live="polite"
7549
+ >${this.threadsSetupPromptCopyState === "copied" ? "Setup prompt copied." : this.threadsSetupPromptCopyState === "error" ? "Setup prompt copy failed. Open the setup guide and copy it manually." : ""}</span
7550
+ >
7551
+ ` : nothing}
7552
+ ${lockedAction ? this.renderInspectorAction(lockedAction, "locked") : nothing}
7553
+ ` : html`
7554
+ <a
7555
+ href=${this.getThreadsDocsUrl()}
7556
+ target="_blank"
7557
+ rel="noopener"
7558
+ style="
7559
+ display: inline-flex;
7560
+ align-items: center;
7561
+ justify-content: center;
7562
+ gap: 6px;
7563
+ min-height: 34px;
7564
+ border-radius: 6px;
7565
+ background: #010507;
7566
+ padding: 8px 12px;
7567
+ font-size: 12px;
7568
+ font-weight: 600;
7569
+ color: #ffffff;
7570
+ text-decoration: none;
7571
+ "
7572
+ >
7573
+ Learn how Threads work
7574
+ </a>
7575
+ <a
7576
+ href=${onboardingAction.href}
7577
+ target="_blank"
7578
+ rel="noopener"
7579
+ style="
7580
+ display: inline-flex;
7581
+ align-items: center;
7582
+ justify-content: center;
7583
+ gap: 6px;
7584
+ min-height: 34px;
7585
+ border-radius: 6px;
7586
+ border: 1px solid #dbdbe5;
7587
+ background: #ffffff;
7588
+ padding: 8px 12px;
7589
+ font-size: 12px;
7590
+ font-weight: 600;
7591
+ color: #010507;
7592
+ text-decoration: none;
7593
+ "
7594
+ >
7595
+ ${onboardingAction.label}
7596
+ </a>
7597
+ `}
6572
7598
  </div>
6573
7599
  </div>
6574
7600
  </div>
6575
7601
  `;
6576
7602
  }
6577
7603
  renderThreadsExampleTour() {
6578
- if (!this.selectedThreadId || !this.isExampleThreadId(this.selectedThreadId)) return nothing;
7604
+ if (!this.selectedThreadId || this.selectedThreadId !== this.selectedLocalExampleThreadId) return nothing;
6579
7605
  if (!this.exampleTourActive) return html`
6580
7606
  <button
6581
7607
  type="button"
@@ -6626,7 +7652,7 @@ ${argsString}</pre
6626
7652
  font-family: 'Spline Sans Mono', monospace;
6627
7653
  font-size: 10px;
6628
7654
  font-weight: 600;
6629
- color: #189370;
7655
+ color: #087653;
6630
7656
  text-transform: uppercase;
6631
7657
  "
6632
7658
  >
@@ -6657,7 +7683,7 @@ ${argsString}</pre
6657
7683
  >
6658
7684
  <button
6659
7685
  type="button"
6660
- style="border:0;background:transparent;color:#838389;font-size:12px;font-weight:600;cursor:pointer;padding:7px 0;"
7686
+ style="border:0;background:transparent;color:#68686e;font-size:12px;font-weight:600;cursor:pointer;padding:7px 0;"
6661
7687
  @click=${() => this.dismissExampleTour("skip")}
6662
7688
  >
6663
7689
  Skip
@@ -6836,124 +7862,25 @@ ${argsString}</pre
6836
7862
  </div>
6837
7863
  `;
6838
7864
  }
6839
- renderThreadsLockedView() {
6840
- this.trackThreadsViewStateOnce("locked", 0);
6841
- return html`
6842
- <div
6843
- style="
6844
- position: relative;
6845
- height: 100%;
6846
- display: flex;
6847
- align-items: center;
6848
- justify-content: center;
6849
- padding: 32px;
6850
- overflow: hidden;
6851
- background: #ffffff;
6852
- "
6853
- >
6854
- ${this.renderThreadsLockedBackgroundMockup()}
6855
- <div
6856
- aria-hidden="true"
6857
- style="
6858
- position: absolute;
6859
- inset: 0;
6860
- pointer-events: none;
6861
- background:
6862
- 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%);
6863
- "
6864
- ></div>
6865
- <div
6866
- style="
6867
- position: relative;
6868
- z-index: 1;
6869
- max-width: 440px;
6870
- text-align: center;
6871
- color: #57575b;
6872
- "
6873
- >
6874
- <div
6875
- aria-hidden="true"
6876
- style="
6877
- margin: 0 auto 18px;
6878
- display: flex;
6879
- justify-content: center;
6880
- "
6881
- >
6882
- <div
6883
- style="
6884
- display: flex;
6885
- height: 44px;
6886
- width: 44px;
6887
- align-items: center;
6888
- justify-content: center;
6889
- border: 1px solid #dfd6fb;
6890
- border-radius: 8px;
6891
- background: #eee6fe;
6892
- color: #57575b;
6893
- box-shadow: 0 8px 18px rgba(87, 87, 91, 0.14);
6894
- "
6895
- >
6896
- ${this.renderIcon("Lock")}
6897
- </div>
6898
- </div>
6899
- <h2
6900
- style="
6901
- margin: 0 0 8px;
6902
- font-size: 16px;
6903
- line-height: 1.35;
6904
- font-weight: 600;
6905
- color: #010507;
6906
- "
6907
- >
6908
- Enable Intelligence to inspect Threads.
6909
- </h2>
6910
- <p
6911
- style="
6912
- margin: 0 auto 18px;
6913
- max-width: 380px;
6914
- font-size: 13px;
6915
- line-height: 1.55;
6916
- color: #57575b;
6917
- "
6918
- >
6919
- Persist conversations and inspect saved thread history from the
6920
- Inspector.
6921
- </p>
6922
- <div
6923
- style="
6924
- display: flex;
6925
- flex-wrap: wrap;
6926
- justify-content: center;
6927
- gap: 8px;
6928
- "
6929
- >
6930
- <a
6931
- href=${this.getThreadsIntelligenceSignupUrl()}
6932
- target="_blank"
6933
- rel="noopener"
6934
- style="
6935
- display: inline-flex;
6936
- min-height: 34px;
6937
- align-items: center;
6938
- justify-content: center;
6939
- gap: 6px;
6940
- border-radius: 6px;
6941
- border: 1px solid #dbdbe5;
6942
- background: #ffffff;
6943
- padding: 8px 12px;
6944
- font-size: 12px;
6945
- font-weight: 600;
6946
- color: #57575b;
6947
- text-decoration: none;
6948
- "
6949
- @click=${this.handleThreadsIntelligenceSignupClick}
6950
- >
6951
- Sign up for Intelligence
6952
- </a>
6953
- </div>
6954
- </div>
6955
- </div>
6956
- `;
7865
+ getThreadsLockedCopy() {
7866
+ switch (this.inspectorMetadataProjection.licenseState) {
7867
+ case "valid": return {
7868
+ heading: "Finish setting up Rich Threads",
7869
+ description: "Copy this prompt into your coding agent to finish the setup."
7870
+ };
7871
+ case "none": return {
7872
+ heading: "Enable Intelligence to inspect Threads.",
7873
+ description: "Persist conversations and inspect saved thread history from the Inspector."
7874
+ };
7875
+ case "expired": return {
7876
+ heading: "Renew Intelligence to inspect Threads.",
7877
+ description: "Your Intelligence access has expired. Renew it to inspect saved thread history."
7878
+ };
7879
+ case "unknown": return {
7880
+ heading: "Threads are unavailable.",
7881
+ description: "This runtime does not expose Threads for the Inspector."
7882
+ };
7883
+ }
6957
7884
  }
6958
7885
  /**
6959
7886
  * Renders the realtime-connection indicator in the memory-store header.
@@ -6973,7 +7900,7 @@ ${argsString}</pre
6973
7900
  gap: 4px;
6974
7901
  font-size: 10px;
6975
7902
  font-weight: 500;
6976
- color: ${connected ? "#57575b" : "#838389"};
7903
+ color: ${connected ? "#57575b" : "#68686e"};
6977
7904
  "
6978
7905
  >
6979
7906
  <span
@@ -7136,7 +8063,7 @@ ${argsString}</pre
7136
8063
  align-items: center;
7137
8064
  justify-content: center;
7138
8065
  gap: 8px;
7139
- color: #838389;
8066
+ color: #68686e;
7140
8067
  "
7141
8068
  >
7142
8069
  <svg
@@ -7178,7 +8105,7 @@ ${argsString}</pre
7178
8105
  align-items: center;
7179
8106
  justify-content: center;
7180
8107
  gap: 8px;
7181
- color: #838389;
8108
+ color: #68686e;
7182
8109
  "
7183
8110
  >
7184
8111
  <svg
@@ -7270,17 +8197,65 @@ ${argsString}</pre
7270
8197
  ></cpk-memory-list>
7271
8198
  </div>
7272
8199
  </div>
8200
+ `;
8201
+ }
8202
+ /** Renders trusted Threads usage and its independent plan action. */
8203
+ renderThreadsUsageFooter() {
8204
+ const { usage, threadsFooterAction } = this.inspectorMetadataProjection;
8205
+ if (!usage && !threadsFooterAction) return nothing;
8206
+ let countLabel;
8207
+ let progressMax;
8208
+ let progressValue;
8209
+ const capacityState = this.getThreadsCapacityState();
8210
+ if (usage) if (usage.limit.kind === "finite") {
8211
+ countLabel = `${usage.used > usage.limit.value ? `${usage.limit.value}+` : String(usage.used)} / ${usage.limit.value} Threads`;
8212
+ progressMax = usage.limit.value;
8213
+ progressValue = Math.min(usage.used, usage.limit.value);
8214
+ } else if (usage.limit.kind === "unlimited") countLabel = `${usage.used} Threads · Unlimited`;
8215
+ else countLabel = `${usage.used} Threads · Limit unavailable`;
8216
+ return html`
8217
+ <footer
8218
+ class="inspector-threads-footer"
8219
+ data-inspector-threads-footer
8220
+ role="group"
8221
+ aria-label="Threads usage"
8222
+ >
8223
+ ${usage && countLabel ? html`
8224
+ <div class="inspector-threads-usage">
8225
+ <span data-inspector-thread-count>${countLabel}</span>
8226
+ ${progressMax !== void 0 && progressValue !== void 0 ? html`
8227
+ <progress
8228
+ class="inspector-thread-progress"
8229
+ data-inspector-thread-progress
8230
+ data-inspector-thread-capacity=${capacityState}
8231
+ max=${progressMax}
8232
+ value=${progressValue}
8233
+ aria-label=${capacityState === "warning" ? `${countLabel}. Near thread limit.` : capacityState === "critical" ? `${countLabel}. Thread limit reached.` : countLabel}
8234
+ >${countLabel}</progress
8235
+ >
8236
+ ` : nothing}
8237
+ ${usage.expiringSoonCount !== void 0 ? html`
8238
+ <span data-inspector-thread-expiry
8239
+ >${usage.expiringSoonCount} Expiring Soon</span
8240
+ >
8241
+ ` : nothing}
8242
+ </div>
8243
+ ` : nothing}
8244
+ ${threadsFooterAction ? this.renderInspectorAction(threadsFooterAction, "threads-footer", capacityState === "warning" || capacityState === "critical" ? "Upgrade Your Plan" : threadsFooterAction.label) : nothing}
8245
+ </footer>
7273
8246
  `;
7274
8247
  }
7275
8248
  renderThreadsView() {
7276
- if (!this.areThreadEndpointsAvailable()) return this.renderThreadsLockedView();
7277
- const { displayThreads, threadsErrorMessage } = this.getActiveThreadsState();
7278
- const showingExamples = this.shouldRenderExampleThreads(displayThreads, threadsErrorMessage);
7279
- const visibleThreads = this.getVisibleThreads(displayThreads, threadsErrorMessage);
8249
+ const locked = !this.areThreadEndpointsAvailable();
8250
+ const { displayThreads, threadsErrorMessage, threadsLoading } = this.getActiveThreadsState();
8251
+ const loadingWithoutRows = !locked && threadsLoading && !threadsErrorMessage && displayThreads.length === 0;
8252
+ const showingExamples = this.shouldRenderExampleThreads(locked, displayThreads, threadsErrorMessage, threadsLoading);
8253
+ const visibleThreads = !locked && (threadsErrorMessage || loadingWithoutRows) ? [] : showingExamples ? THREADS_EXAMPLE_THREADS : displayThreads;
7280
8254
  if (showingExamples) this.trackThreadsExampleViewedOnce();
7281
8255
  const selectedThread = this.selectedThreadId != null ? visibleThreads.find((t) => t.id === this.selectedThreadId) ?? null : null;
7282
- const selectedThreadIsExample = this.isExampleThreadId(selectedThread?.id);
7283
- if (!threadsErrorMessage) this.trackThreadsViewStateOnce(displayThreads.length === 0 ? "empty_enabled" : "enabled", displayThreads.length);
8256
+ const selectedThreadIsLocalExample = selectedThread !== null && selectedThread.id === this.selectedLocalExampleThreadId;
8257
+ if (locked) this.trackThreadsViewStateOnce("locked");
8258
+ else if (!threadsErrorMessage && (!threadsLoading || displayThreads.length > 0)) this.trackThreadsViewStateOnce(displayThreads.length === 0 ? "empty_enabled" : "enabled");
7284
8259
  return html`
7285
8260
  <div style="display:flex;height:100%;overflow:hidden;flex-direction:column;">
7286
8261
  <div style="display:flex;min-height:0;flex:1;overflow:hidden;">
@@ -7289,14 +8264,16 @@ ${argsString}</pre
7289
8264
  style="width:${this.threadListWidth}px;flex-shrink:0;overflow:hidden;display:flex;flex-direction:column;border-right:1px solid #DBDBE5;"
7290
8265
  >
7291
8266
  <cpk-thread-list
7292
- style="height:100%;"
8267
+ style="min-height:0;flex:1;"
7293
8268
  .threads=${visibleThreads}
7294
8269
  .selectedThreadId=${this.selectedThreadId}
7295
8270
  .errorMessage=${threadsErrorMessage}
8271
+ .suppressEmptyState=${loadingWithoutRows}
7296
8272
  @threadSelected=${(e) => {
7297
8273
  this.handleThreadsThreadSelected(e.detail, showingExamples);
7298
8274
  }}
7299
8275
  ></cpk-thread-list>
8276
+ ${this.renderThreadsUsageFooter()}
7300
8277
  </div>
7301
8278
 
7302
8279
  <!-- Resize divider -->
@@ -7310,19 +8287,53 @@ ${argsString}</pre
7310
8287
 
7311
8288
  <!-- Center + right: thread details or empty state -->
7312
8289
  <div style="flex:1;min-width:0;overflow:hidden;display:flex;position:relative;">
7313
- ${selectedThread ? html`<cpk-thread-details
8290
+ ${!locked && threadsErrorMessage ? html`
8291
+ <div
8292
+ role="alert"
8293
+ style="
8294
+ display: flex;
8295
+ flex: 1;
8296
+ flex-direction: column;
8297
+ align-items: center;
8298
+ justify-content: center;
8299
+ gap: 6px;
8300
+ padding: 24px;
8301
+ color: #c0333a;
8302
+ text-align: center;
8303
+ "
8304
+ >
8305
+ <strong style="font-size:13px;">Failed to load threads</strong>
8306
+ <span style="max-width:440px;font-size:12px;line-height:1.5;"
8307
+ >${threadsErrorMessage}</span
8308
+ >
8309
+ </div>
8310
+ ` : loadingWithoutRows ? html`
8311
+ <div
8312
+ role="status"
8313
+ style="
8314
+ display: flex;
8315
+ flex: 1;
8316
+ align-items: center;
8317
+ justify-content: center;
8318
+ color: #57575b;
8319
+ font-size: 13px;
8320
+ "
8321
+ >
8322
+ Loading threads…
8323
+ </div>
8324
+ ` : selectedThread ? html`<cpk-thread-details
7314
8325
  style="flex:1;min-width:0;"
7315
8326
  .threadId=${selectedThread.id}
7316
8327
  .thread=${selectedThread}
7317
- .provider=${selectedThreadIsExample ? this.getExampleThreadProvider(selectedThread.id) : null}
7318
- .runtimeUrl=${this._core?.runtimeUrl ?? ""}
8328
+ .provider=${selectedThreadIsLocalExample ? this.getExampleThreadProvider(selectedThread.id) : null}
8329
+ .runtimeUrl=${selectedThreadIsLocalExample ? "" : this._core?.runtimeUrl ?? ""}
7319
8330
  .headers=${this._core?.headers ?? {}}
7320
- .threadInspectionAvailable=${selectedThreadIsExample || this._core?.threadEndpoints?.inspect !== false}
8331
+ .threadInspectionAvailable=${selectedThreadIsLocalExample || this.areThreadEndpointsAvailable() && this._core?.threadEndpoints?.inspect !== false}
7321
8332
  .liveMessageVersion=${this.liveMessageVersion.get(selectedThread.id) ?? 0}
7322
8333
  .agentStateInput=${this.getLatestStateForAgent(selectedThread.agentId)}
7323
8334
  .agentEventsInput=${this.agentEvents.get(selectedThread.agentId) ?? []}
7324
8335
  ></cpk-thread-details>
7325
- ${selectedThreadIsExample ? this.renderThreadsExampleTour() : nothing}` : showingExamples ? this.renderThreadsExampleOverview() : html`
8336
+ ${selectedThreadIsLocalExample ? this.renderThreadsExampleTour() : nothing}` : showingExamples ? this.renderThreadsExampleOverview(locked) : html`
7326
8337
  <div
7327
8338
  style="
7328
8339
  flex: 1;
@@ -7331,7 +8342,7 @@ ${argsString}</pre
7331
8342
  align-items: center;
7332
8343
  justify-content: center;
7333
8344
  gap: 8px;
7334
- color: #838389;
8345
+ color: #68686e;
7335
8346
  "
7336
8347
  >
7337
8348
  <svg
@@ -7855,9 +8866,12 @@ ${prettyEvent}</pre
7855
8866
  `;
7856
8867
  }
7857
8868
  handleMenuSelect(key) {
7858
- if (key !== "settings" && !this.menuItems.some((item) => item.key === key)) return;
8869
+ if (!this.menuItems.some((item) => item.key === key)) return;
7859
8870
  const previousMenu = this.selectedMenu;
8871
+ this.pendingPersistedMenu = null;
7860
8872
  this.selectedMenu = key;
8873
+ this.settingsOpen = false;
8874
+ this.lastSelectedMenuByGroup[this.getGroupForMenu(key)] = key;
7861
8875
  if (key === "agents" && this.selectedContext === "all-agents") {
7862
8876
  const agentOptions = this.contextOptions.filter((opt) => opt.key !== "all-agents");
7863
8877
  if (agentOptions.length > 0) {
@@ -7901,6 +8915,7 @@ ${prettyEvent}</pre
7901
8915
  if (this.selectedContext !== key) {
7902
8916
  this.selectedContext = key;
7903
8917
  this.expandedRows.clear();
8918
+ this.autoSelectLatestThread();
7904
8919
  }
7905
8920
  this.contextMenuOpen = false;
7906
8921
  this.persistState();