@copilotkit/web-inspector 1.66.4 → 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 +1436 -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 +1438 -431
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/index.umd.js +1721 -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 -3935
  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 -11849
  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,17 +76,49 @@ 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",
@@ -134,6 +204,15 @@ const THREADS_EXAMPLE_THREADS = [
134
204
  isExample: true
135
205
  }
136
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
+ }
137
216
  const THREADS_EXAMPLE_DETAILS = {
138
217
  "example-realtime-sync": {
139
218
  messages: [{
@@ -277,13 +356,13 @@ const THREADS_EXAMPLE_DETAILS = {
277
356
  const THREADS_EXAMPLE_TOUR_STEPS = [
278
357
  {
279
358
  tab: "timeline",
280
- label: "Timeline",
359
+ label: "Messages",
281
360
  title: "Read the run as a story",
282
361
  body: "The timeline turns messages, tool calls, state changes, and run markers into a scannable debugging trail."
283
362
  },
284
363
  {
285
364
  tab: "raw-events",
286
- label: "Raw AG-UI Events",
365
+ label: "AG-UI Events",
287
366
  title: "Drop into the protocol payloads",
288
367
  body: "Raw events show the exact AG-UI stream behind the timeline when you need to verify ordering or payload shape."
289
368
  },
@@ -294,6 +373,28 @@ const THREADS_EXAMPLE_TOUR_STEPS = [
294
373
  body: "The state tab shows the saved values that make a thread resumable across sessions."
295
374
  }
296
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
+ }
297
398
  function escapeHtml(s) {
298
399
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
299
400
  }
@@ -305,10 +406,10 @@ function highlightedJson(obj) {
305
406
  }
306
407
  const colors = {
307
408
  key: "#5558B2",
308
- str: "#189370",
309
- num: "#996300",
409
+ str: "#087653",
410
+ num: "#8a5900",
310
411
  bool: "#c0333a",
311
- nil: "#838389"
412
+ nil: "#68686e"
312
413
  };
313
414
  const json = JSON.stringify(obj, null, 2);
314
415
  if (!json) return "";
@@ -338,7 +439,7 @@ function eventColors(type) {
338
439
  };
339
440
  if (type.startsWith("TOOL_CALL")) return {
340
441
  bg: "rgba(133,236,206,0.15)",
341
- fg: "#189370"
442
+ fg: "#087653"
342
443
  };
343
444
  if (type.startsWith("STATE")) return {
344
445
  bg: "rgba(190,194,255,0.102)",
@@ -350,11 +451,11 @@ function eventColors(type) {
350
451
  };
351
452
  if (type.startsWith("RUN_") || type.startsWith("STEP_")) return {
352
453
  bg: "rgba(255,172,77,0.2)",
353
- fg: "#996300"
454
+ fg: "#8a5900"
354
455
  };
355
456
  return {
356
457
  bg: "#F7F7F9",
357
- fg: "#838389"
458
+ fg: "#68686e"
358
459
  };
359
460
  }
360
461
  function formatTimestamp(ts) {
@@ -374,6 +475,7 @@ var CpkThreadList = class extends LitElement {
374
475
  this.threads = [];
375
476
  this.selectedThreadId = null;
376
477
  this.errorMessage = null;
478
+ this.suppressEmptyState = false;
377
479
  this._query = "";
378
480
  this.onSearchInput = (event) => {
379
481
  this._query = event.target.value;
@@ -384,6 +486,7 @@ var CpkThreadList = class extends LitElement {
384
486
  threads: { attribute: false },
385
487
  selectedThreadId: { attribute: false },
386
488
  errorMessage: { attribute: false },
489
+ suppressEmptyState: { attribute: false },
387
490
  _query: { state: true }
388
491
  };
389
492
  }
@@ -440,7 +543,18 @@ var CpkThreadList = class extends LitElement {
440
543
 
441
544
  /* ── Thread item ── */
442
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;
443
553
  padding: 11px 13px;
554
+ background: transparent;
555
+ color: inherit;
556
+ font: inherit;
557
+ text-align: left;
444
558
  cursor: pointer;
445
559
  border-bottom: 1px solid #e9e9ef;
446
560
  border-left: 3px solid transparent;
@@ -460,6 +574,13 @@ var CpkThreadList = class extends LitElement {
460
574
  background: #bec2ff33;
461
575
  }
462
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
+
463
584
  .cpk-tl__row1 {
464
585
  display: flex;
465
586
  align-items: center;
@@ -478,7 +599,7 @@ var CpkThreadList = class extends LitElement {
478
599
  }
479
600
 
480
601
  .cpk-tl__name--unnamed {
481
- color: #838389;
602
+ color: #68686e;
482
603
  font-style: italic;
483
604
  font-weight: 400;
484
605
  }
@@ -486,7 +607,7 @@ var CpkThreadList = class extends LitElement {
486
607
  .cpk-tl__time {
487
608
  font-family: "Spline Sans Mono", monospace;
488
609
  font-size: 10px;
489
- color: #838389;
610
+ color: #68686e;
490
611
  flex-shrink: 0;
491
612
  }
492
613
 
@@ -511,14 +632,14 @@ var CpkThreadList = class extends LitElement {
511
632
 
512
633
  .cpk-tl__pill--example {
513
634
  background: rgba(133, 236, 206, 0.22);
514
- color: #189370;
635
+ color: #087653;
515
636
  }
516
637
 
517
638
  /* ── Empty state ── */
518
639
  .cpk-tl__empty {
519
640
  padding: 32px 16px;
520
641
  text-align: center;
521
- color: #838389;
642
+ color: #68686e;
522
643
  font-size: 12px;
523
644
  display: flex;
524
645
  flex-direction: column;
@@ -572,11 +693,13 @@ var CpkThreadList = class extends LitElement {
572
693
  <!-- Thread list -->
573
694
  <div class="cpk-tl__list">
574
695
  ${filtered.map((thread) => html`
575
- <div
696
+ <button
697
+ type="button"
698
+ aria-current=${this.selectedThreadId === thread.id ? "true" : nothing}
576
699
  class="cpk-tl__item ${this.selectedThreadId === thread.id ? "cpk-tl__item--active" : ""}"
577
700
  @click=${() => this.onThreadClick(thread.id)}
578
701
  >
579
- <div class="cpk-tl__row1">
702
+ <span class="cpk-tl__row1">
580
703
  <span
581
704
  class="cpk-tl__name ${!thread.name ? "cpk-tl__name--unnamed" : ""}"
582
705
  >${thread.name ?? "Untitled"}</span
@@ -584,16 +707,16 @@ var CpkThreadList = class extends LitElement {
584
707
  <span class="cpk-tl__time"
585
708
  >${this.relativeTime(thread.updatedAt)}</span
586
709
  >
587
- </div>
588
- <div class="cpk-tl__meta">
710
+ </span>
711
+ <span class="cpk-tl__meta">
589
712
  <span class="cpk-tl__pill">${thread.agentId}</span>
590
713
  ${thread.isExample ? html`
591
714
  <span class="cpk-tl__pill cpk-tl__pill--example">Example</span>
592
715
  ` : nothing}
593
- </div>
594
- </div>
716
+ </span>
717
+ </button>
595
718
  `)}
596
- ${filtered.length === 0 ? html`
719
+ ${filtered.length === 0 && !this.suppressEmptyState ? html`
597
720
  <div class="cpk-tl__empty">
598
721
  ${this.errorMessage ? html`
599
722
  <svg
@@ -688,10 +811,12 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
688
811
  this._messagesAbort = null;
689
812
  this._eventsAbort = null;
690
813
  this._stateAbort = null;
814
+ this._hasConnectedOnce = false;
691
815
  this._dividerResizing = false;
692
816
  this._dividerPointerId = -1;
693
817
  this._dividerStartX = 0;
694
818
  this._dividerStartWidth = 0;
819
+ this.domIdPrefix = `cpk-thread-detail-${CpkThreadInspector.nextDomId++}`;
695
820
  this.onDetailDividerDown = (event) => {
696
821
  this._dividerResizing = true;
697
822
  this._dividerPointerId = event.pointerId;
@@ -746,6 +871,9 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
746
871
  _activatedTabs: { state: true }
747
872
  };
748
873
  }
874
+ static {
875
+ this.nextDomId = 1;
876
+ }
749
877
  static {
750
878
  this.COLLAPSE_THRESHOLD = 800;
751
879
  }
@@ -753,11 +881,11 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
753
881
  this.TAB_LIST = [
754
882
  {
755
883
  id: "timeline",
756
- label: "Timeline"
884
+ label: "Messages"
757
885
  },
758
886
  {
759
887
  id: "raw-events",
760
- label: "Raw AG-UI Events"
888
+ label: "AG-UI Events"
761
889
  },
762
890
  {
763
891
  id: "state",
@@ -800,6 +928,30 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
800
928
  if (id === "state") return this.renderState();
801
929
  return this.renderEvents();
802
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
+ }
803
955
  activateTab(id) {
804
956
  if (this._tab === id) return;
805
957
  const isFirstActivation = !this._activatedTabs.has(id);
@@ -889,7 +1041,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
889
1041
  border-bottom: 2px solid transparent;
890
1042
  cursor: pointer;
891
1043
  background: transparent;
892
- color: #838389;
1044
+ color: #68686e;
893
1045
  transition:
894
1046
  color 0.12s,
895
1047
  border-color 0.12s;
@@ -900,6 +1052,12 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
900
1052
  color: #010507;
901
1053
  }
902
1054
 
1055
+ .cpk-td__tab:focus-visible {
1056
+ outline: 2px solid #5558b2;
1057
+ outline-offset: -3px;
1058
+ border-radius: 4px;
1059
+ }
1060
+
903
1061
  .cpk-td__tab--active {
904
1062
  color: #010507;
905
1063
  border-bottom-color: #bec2ff;
@@ -919,7 +1077,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
919
1077
  border: none;
920
1078
  border-left: 1px solid #dbdbe5;
921
1079
  background: transparent;
922
- color: #838389;
1080
+ color: #68686e;
923
1081
  cursor: pointer;
924
1082
  flex-shrink: 0;
925
1083
  transition:
@@ -988,7 +1146,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
988
1146
  }
989
1147
 
990
1148
  .cpk-td__metadata-label {
991
- color: #838389;
1149
+ color: #68686e;
992
1150
  text-transform: uppercase;
993
1151
  font-size: 9px;
994
1152
  }
@@ -998,6 +1156,18 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
998
1156
  text-overflow: ellipsis;
999
1157
  }
1000
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
+
1001
1171
  /*
1002
1172
  * Each tab's content is wrapped in this panel so the keep-mounted
1003
1173
  * inactive panels can be hidden via display:none without disturbing
@@ -1015,6 +1185,10 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1015
1185
  flex-shrink: 0;
1016
1186
  }
1017
1187
 
1188
+ .cpk-td__panel[hidden] {
1189
+ display: none;
1190
+ }
1191
+
1018
1192
  /* ── Empty state ─────────────────────────────────────────────────── */
1019
1193
  .cpk-td__empty-state {
1020
1194
  flex: 1;
@@ -1023,14 +1197,14 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1023
1197
  align-items: center;
1024
1198
  justify-content: center;
1025
1199
  gap: 8px;
1026
- color: #838389;
1200
+ color: #68686e;
1027
1201
  font-size: 13px;
1028
1202
  padding: 40px 0;
1029
1203
  }
1030
1204
 
1031
1205
  .cpk-td__empty-hint {
1032
1206
  font-size: 11px;
1033
- color: #838389;
1207
+ color: #68686e;
1034
1208
  text-align: center;
1035
1209
  max-width: 220px;
1036
1210
  line-height: 1.5;
@@ -1040,7 +1214,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1040
1214
  .cpk-td__status {
1041
1215
  padding: 16px;
1042
1216
  font-size: 12px;
1043
- color: #838389;
1217
+ color: #68686e;
1044
1218
  text-align: center;
1045
1219
  }
1046
1220
 
@@ -1119,7 +1293,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1119
1293
  font-family: "Spline Sans Mono", monospace;
1120
1294
  font-size: 10px;
1121
1295
  font-weight: 500;
1122
- color: #189370;
1296
+ color: #087653;
1123
1297
  text-transform: uppercase;
1124
1298
  flex: 1;
1125
1299
  }
@@ -1128,15 +1302,15 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1128
1302
  font-family: "Spline Sans Mono", monospace;
1129
1303
  font-size: 9px;
1130
1304
  text-transform: uppercase;
1131
- color: #189370;
1305
+ color: #087653;
1132
1306
  }
1133
1307
 
1134
1308
  .cpk-td__tool-status--pending {
1135
- color: #996300;
1309
+ color: #8a5900;
1136
1310
  }
1137
1311
 
1138
1312
  .cpk-td__tool-chevron {
1139
- color: #838389;
1313
+ color: #68686e;
1140
1314
  font-size: 10px;
1141
1315
  }
1142
1316
 
@@ -1150,7 +1324,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1150
1324
  font-family: "Spline Sans Mono", monospace;
1151
1325
  font-size: 9px;
1152
1326
  font-weight: 500;
1153
- color: #838389;
1327
+ color: #68686e;
1154
1328
  text-transform: uppercase;
1155
1329
  margin-bottom: 4px;
1156
1330
  letter-spacing: 0.3px;
@@ -1182,7 +1356,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1182
1356
  background: rgba(133, 236, 206, 0.15);
1183
1357
  font-family: "Spline Sans Mono", monospace;
1184
1358
  font-size: 10px;
1185
- color: #189370;
1359
+ color: #087653;
1186
1360
  text-transform: uppercase;
1187
1361
  font-weight: 500;
1188
1362
  border-bottom: 1px solid #e9e9ef;
@@ -1204,7 +1378,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1204
1378
  align-items: center;
1205
1379
  gap: 8px;
1206
1380
  padding: 5px 0;
1207
- color: #838389;
1381
+ color: #68686e;
1208
1382
  font-family: "Spline Sans Mono", monospace;
1209
1383
  font-size: 9px;
1210
1384
  text-transform: uppercase;
@@ -1261,7 +1435,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1261
1435
  .cpk-td__timeline-time {
1262
1436
  font-family: "Spline Sans Mono", monospace;
1263
1437
  font-size: 9px;
1264
- color: #838389;
1438
+ color: #68686e;
1265
1439
  flex-shrink: 0;
1266
1440
  }
1267
1441
 
@@ -1439,7 +1613,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1439
1613
  .cpk-td__event-time {
1440
1614
  font-family: "Spline Sans Mono", monospace;
1441
1615
  font-size: 9px;
1442
- color: #838389;
1616
+ color: #68686e;
1443
1617
  }
1444
1618
 
1445
1619
  .cpk-td__event-payload {
@@ -1511,7 +1685,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1511
1685
  font-family: "Spline Sans Mono", monospace;
1512
1686
  font-size: 10px;
1513
1687
  font-weight: 500;
1514
- color: #838389;
1688
+ color: #68686e;
1515
1689
  text-transform: uppercase;
1516
1690
  letter-spacing: 0.6px;
1517
1691
  margin-bottom: 8px;
@@ -1532,7 +1706,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1532
1706
  }
1533
1707
 
1534
1708
  .cpk-tdp__label {
1535
- color: #838389;
1709
+ color: #68686e;
1536
1710
  font-size: 11px;
1537
1711
  white-space: nowrap;
1538
1712
  flex-shrink: 0;
@@ -1561,6 +1735,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1561
1735
  `;
1562
1736
  }
1563
1737
  updated(_changed) {
1738
+ if (!this.isConnected) return;
1564
1739
  const loadKey = this.currentLoadKey();
1565
1740
  if (loadKey !== this._lastLoadKey) {
1566
1741
  this._lastLoadKey = loadKey;
@@ -1583,6 +1758,16 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
1583
1758
  this.fetchMessages(this.threadId, true);
1584
1759
  }
1585
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
+ }
1586
1771
  canFetchMessages() {
1587
1772
  return !!this.provider?.getMessages || !!this.runtimeUrl && this.threadInspectionAvailable;
1588
1773
  }
@@ -2223,12 +2408,22 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2223
2408
  <div class="cpk-td__left">
2224
2409
  <!-- Tab bar -->
2225
2410
  <div class="cpk-td__tabs-header">
2226
- <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
+ >
2227
2416
  ${CpkThreadInspector.TAB_LIST.map((tab) => html`
2228
2417
  <button
2418
+ id=${this.tabDomId(tab.id)}
2419
+ type="button"
2229
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"}
2230
2424
  class="cpk-td__tab ${this._tab === tab.id ? "cpk-td__tab--active" : ""}"
2231
2425
  @click=${() => this.activateTab(tab.id)}
2426
+ @keydown=${(event) => this.handleTabKeyDown(event, tab.id)}
2232
2427
  >
2233
2428
  ${tab.label}
2234
2429
  </button>
@@ -2244,8 +2439,11 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2244
2439
  <div class="cpk-td__status">Loading…</div>
2245
2440
  ` : nothing}
2246
2441
  ${CpkThreadInspector.TAB_LIST.map((tab) => this._activatedTabs.has(tab.id) ? html`<div
2442
+ id=${this.panelDomId(tab.id)}
2247
2443
  class="cpk-td__panel"
2248
- 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}
2249
2447
  >
2250
2448
  ${this.renderTabContent(tab.id)}
2251
2449
  </div>` : nothing)}
@@ -2280,34 +2478,53 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2280
2478
  }
2281
2479
  renderMetadataStrip() {
2282
2480
  const metadata = this.metadata;
2283
- const pills = [
2284
- {
2285
- label: "Thread",
2286
- value: metadata?.id ?? this.threadId
2287
- },
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 [
2288
2490
  {
2289
2491
  label: "Agent",
2290
2492
  value: metadata?.agentId
2291
2493
  },
2292
2494
  {
2293
- label: "End user",
2294
- value: metadata?.endUserId ?? metadata?.createdById
2495
+ label: "Created",
2496
+ value: metadata?.createdAt
2295
2497
  },
2296
2498
  {
2297
- label: "Status",
2298
- value: metadata?.status
2499
+ label: "Updated",
2500
+ value: metadata?.updatedAt
2299
2501
  }
2300
- ].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
+ }
2301
2509
  const bulkControls = this.renderActiveBulkControls();
2302
- if (pills.length === 0 && bulkControls === nothing) return nothing;
2303
2510
  return html`
2304
- <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
+ >
2305
2516
  <div class="cpk-td__metadata-pills">
2306
2517
  ${pills.map((pill) => html`
2307
- <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
+ >
2308
2524
  <span class="cpk-td__metadata-label">${pill.label}</span>
2309
- <span class="cpk-td__metadata-value"
2310
- >${this.shortId(pill.value)}</span
2525
+ <span
2526
+ class="cpk-td__metadata-value ${pill.wrap ? "cpk-td__metadata-value--wrap" : ""}"
2527
+ >${pill.value}</span
2311
2528
  >
2312
2529
  </span>
2313
2530
  `)}
@@ -2402,8 +2619,8 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2402
2619
  <div class="cpk-td__empty-state">
2403
2620
  <span>No timeline events captured</span>
2404
2621
  <span class="cpk-td__empty-hint"
2405
- >Timeline rows are normalized from AG-UI events. Open Raw AG-UI Events or
2406
- 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
2407
2624
  >
2408
2625
  </div>
2409
2626
  `;
@@ -2577,7 +2794,7 @@ var CpkThreadInspector = class CpkThreadInspector extends LitElement {
2577
2794
  <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
2578
2795
  <path
2579
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"
2580
- stroke="#189370"
2797
+ stroke="#087653"
2581
2798
  stroke-width="1.2"
2582
2799
  stroke-linecap="round"
2583
2800
  stroke-linejoin="round"
@@ -2809,7 +3026,7 @@ ${unsafeHTML(highlightedJson(stateValue))}</pre
2809
3026
  <div class="cpk-tdp__row">
2810
3027
  <span class="cpk-tdp__label">ID</span>
2811
3028
  <span class="cpk-tdp__value cpk-tdp__value--wrap"
2812
- >${this.shortId(metadata?.id)}</span
3029
+ >${metadata?.id ?? this.threadId ?? "—"}</span
2813
3030
  >
2814
3031
  </div>
2815
3032
  <div class="cpk-tdp__row">
@@ -3049,7 +3266,7 @@ var CpkMemoryList = class extends LitElement {
3049
3266
  font-family: "Spline Sans Mono", monospace;
3050
3267
  font-size: 9px;
3051
3268
  margin-left: 4px;
3052
- color: #838389;
3269
+ color: #68686e;
3053
3270
  }
3054
3271
 
3055
3272
  /* ── List ── */
@@ -3116,7 +3333,7 @@ var CpkMemoryList = class extends LitElement {
3116
3333
  font-weight: 500;
3117
3334
  white-space: nowrap;
3118
3335
  background: #f0f0f5;
3119
- color: #838389;
3336
+ color: #68686e;
3120
3337
  }
3121
3338
 
3122
3339
  /* Content */
@@ -3138,7 +3355,7 @@ var CpkMemoryList = class extends LitElement {
3138
3355
 
3139
3356
  .cpk-ml__footer-threads {
3140
3357
  font-size: 10px;
3141
- color: #838389;
3358
+ color: #68686e;
3142
3359
  }
3143
3360
 
3144
3361
  .cpk-ml__footer-id {
@@ -3151,7 +3368,7 @@ var CpkMemoryList = class extends LitElement {
3151
3368
  .cpk-ml__empty {
3152
3369
  padding: 32px 16px;
3153
3370
  text-align: center;
3154
- color: #838389;
3371
+ color: #68686e;
3155
3372
  font-size: 12px;
3156
3373
  display: flex;
3157
3374
  flex-direction: column;
@@ -3232,7 +3449,7 @@ var CpkMemoryList = class extends LitElement {
3232
3449
  .cpk-ml__recall-clear {
3233
3450
  font-family: "Plus Jakarta Sans", sans-serif;
3234
3451
  font-size: 10px;
3235
- color: #838389;
3452
+ color: #68686e;
3236
3453
  background: none;
3237
3454
  border: none;
3238
3455
  cursor: pointer;
@@ -3243,7 +3460,7 @@ var CpkMemoryList = class extends LitElement {
3243
3460
  }
3244
3461
  .cpk-ml__recall-msg {
3245
3462
  font-size: 11px;
3246
- color: #838389;
3463
+ color: #68686e;
3247
3464
  line-height: 1.45;
3248
3465
  }
3249
3466
  .cpk-ml__recall-msg--error {
@@ -3267,7 +3484,7 @@ var CpkMemoryList = class extends LitElement {
3267
3484
  /* ── Scope badge variants ── */
3268
3485
  .cpk-ml__scope-badge--user {
3269
3486
  background: #f0f0f5;
3270
- color: #838389;
3487
+ color: #68686e;
3271
3488
  }
3272
3489
  .cpk-ml__scope-badge--project {
3273
3490
  background: #fef3c7;
@@ -3486,6 +3703,8 @@ var WebInspectorElement = class extends LitElement {
3486
3703
  this._recallQuery = "";
3487
3704
  this._recallSeq = 0;
3488
3705
  this.runtimeStatus = null;
3706
+ this.inspectorMetadataProjection = projectInspectorMetadata(void 0, void 0);
3707
+ this.metadataTelemetryFingerprints = /* @__PURE__ */ new Map();
3489
3708
  this.coreProperties = {};
3490
3709
  this.lastCoreError = null;
3491
3710
  this.agentSubscriptions = /* @__PURE__ */ new Map();
@@ -3507,8 +3726,19 @@ var WebInspectorElement = class extends LitElement {
3507
3726
  this.isOpen = false;
3508
3727
  this.draggedDuringInteraction = false;
3509
3728
  this.ignoreNextButtonClick = false;
3510
- 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;
3511
3739
  this.selectedThreadId = null;
3740
+ this.selectedRealThreadIsExplicit = false;
3741
+ this.selectedLocalExampleThreadId = null;
3512
3742
  this.threadListWidth = 290;
3513
3743
  this.threadDividerResizing = false;
3514
3744
  this.threadDividerPointerId = -1;
@@ -3518,7 +3748,10 @@ var WebInspectorElement = class extends LitElement {
3518
3748
  this._threadStoreSubscriptions = /* @__PURE__ */ new Map();
3519
3749
  this._threadsByAgent = /* @__PURE__ */ new Map();
3520
3750
  this._threadsErrorByAgent = /* @__PURE__ */ new Map();
3751
+ this._threadsLoadingByAgent = /* @__PURE__ */ new Map();
3521
3752
  this._ownedThreadStores = /* @__PURE__ */ new Map();
3753
+ this.threadCapabilityEnabled = null;
3754
+ this.threadCapabilityGeneration = 0;
3522
3755
  this.contextMenuOpen = false;
3523
3756
  this.dockMode = "floating";
3524
3757
  this.previousBodyMargins = null;
@@ -3551,18 +3784,28 @@ var WebInspectorElement = class extends LitElement {
3551
3784
  this.pendingBannerViewed = [];
3552
3785
  this.clickedBannerIds = /* @__PURE__ */ new Set();
3553
3786
  this.viewedThreadsTelemetryStates = /* @__PURE__ */ new Set();
3554
- this.viewedExampleThreadIds = /* @__PURE__ */ new Set();
3555
- this.selectedExampleThreadIds = /* @__PURE__ */ new Set();
3787
+ this.viewedExampleKinds = /* @__PURE__ */ new Set();
3788
+ this.selectedExampleKinds = /* @__PURE__ */ new Set();
3556
3789
  this.viewedExampleTourSteps = /* @__PURE__ */ new Set();
3557
3790
  this.exampleThreadProviders = /* @__PURE__ */ new Map();
3558
3791
  this.exampleTourDismissed = false;
3559
3792
  this.exampleTourActive = false;
3560
3793
  this.exampleTourStep = 0;
3561
3794
  this.exampleTourAutoShown = false;
3562
- this.threadsExampleOverviewVideoShouldLoad = false;
3563
- this.threadsExampleOverviewVideoReady = false;
3795
+ this.threadsExampleOverviewVideoState = "deferred";
3796
+ this.threadsExampleOverviewVideoLoaded = false;
3797
+ this.threadsExampleOverviewVideoReducedMotion = false;
3564
3798
  this.threadsExampleOverviewVideoLoadTimer = null;
3565
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;
3566
3809
  this.contextState = {
3567
3810
  button: {
3568
3811
  position: {
@@ -3609,7 +3852,7 @@ var WebInspectorElement = class extends LitElement {
3609
3852
  const target = event.currentTarget;
3610
3853
  const context = target?.dataset.dragContext === "window" ? "window" : "button";
3611
3854
  const eventTarget = event.target;
3612
- if (context === "window" && eventTarget?.closest("button")) return;
3855
+ if (context === "window" && eventTarget?.closest("button, a, nav")) return;
3613
3856
  this.pointerContext = context;
3614
3857
  this.measureContext(context);
3615
3858
  event.preventDefault();
@@ -3754,8 +3997,10 @@ var WebInspectorElement = class extends LitElement {
3754
3997
  this.measureContext("button");
3755
3998
  this.applyAnchorPosition("button");
3756
3999
  this.measureContext("window");
4000
+ this.contextState.window.size = this.clampWindowSize(this.contextState.window.size);
3757
4001
  if (this.hasCustomPosition.window) this.applyAnchorPosition("window");
3758
4002
  else this.centerContext("window");
4003
+ this.requestUpdate();
3759
4004
  this.updateHostTransform();
3760
4005
  };
3761
4006
  this.contextOptions = [{
@@ -3768,26 +4013,32 @@ var WebInspectorElement = class extends LitElement {
3768
4013
  this.expandedTools = /* @__PURE__ */ new Set();
3769
4014
  this.expandedContextItems = /* @__PURE__ */ new Set();
3770
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
+ };
3771
4031
  this.handleTalkToEngineerClick = () => {
3772
4032
  if (this.core?.telemetryDisabled) return;
3773
- trackTalkToEngineerClicked(this.getThreadsTelemetryProps({
3774
- cta: "talk_to_engineer",
3775
- cta_surface: "threads_header"
3776
- }, { includeUrlAttribution: true }));
4033
+ trackTalkToEngineerClicked(this.getThreadsCtaTelemetryProps("talk_to_engineer", "threads_header"));
3777
4034
  };
3778
4035
  this.handleThreadsIntelligenceSignupClick = () => {
3779
4036
  if (this.core?.telemetryDisabled) return;
3780
- trackThreadsIntelligenceSignupClicked(this.getThreadsTelemetryProps({
3781
- cta: "signup",
3782
- cta_surface: "threads_locked"
3783
- }, { includeUrlAttribution: true }));
4037
+ trackThreadsIntelligenceSignupClicked(this.getThreadsCtaTelemetryProps("signup", "threads_locked"));
3784
4038
  };
3785
4039
  this.handleThreadsTalkToEngineerClick = () => {
3786
4040
  if (this.core?.telemetryDisabled) return;
3787
- trackThreadsTalkToEngineerClicked(this.getThreadsTelemetryProps({
3788
- cta: "talk_to_engineer",
3789
- cta_surface: "threads_locked"
3790
- }, { includeUrlAttribution: true }));
4041
+ trackThreadsTalkToEngineerClicked(this.getThreadsCtaTelemetryProps("talk_to_engineer", "threads_locked"));
3791
4042
  };
3792
4043
  this.handleThreadDividerPointerDown = (event) => {
3793
4044
  this.threadDividerResizing = true;
@@ -3809,9 +4060,51 @@ var WebInspectorElement = class extends LitElement {
3809
4060
  if (target.hasPointerCapture(this.threadDividerPointerId)) target.releasePointerCapture(this.threadDividerPointerId);
3810
4061
  this.threadDividerResizing = false;
3811
4062
  };
3812
- this.handleThreadsExampleOverviewVideoLoaded = () => {
3813
- 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";
3814
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
+ }
3815
4108
  };
3816
4109
  this.handleClearEvents = () => {
3817
4110
  if (this.selectedContext === "all-agents") {
@@ -3884,7 +4177,11 @@ var WebInspectorElement = class extends LitElement {
3884
4177
  const oldValue = this._core;
3885
4178
  if (oldValue === value) return;
3886
4179
  this.detachFromCore();
4180
+ const hadResolvedCore = this.hasResolvedCore;
3887
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();
3888
4185
  this.requestUpdate("core", oldValue);
3889
4186
  if (this._core) this.attachToCore(this._core);
3890
4187
  }
@@ -3930,36 +4227,141 @@ var WebInspectorElement = class extends LitElement {
3930
4227
  }
3931
4228
  ];
3932
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
+ }
3933
4267
  getThreadServiceStatus() {
3934
4268
  if (!this._core) return "unknown";
3935
4269
  if (!this._core.threadEndpoints) return "unknown";
3936
4270
  return this._core.threadEndpoints?.list === false ? "unavailable" : "available";
3937
4271
  }
3938
4272
  areThreadEndpointsAvailable() {
3939
- 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();
3940
4297
  }
3941
4298
  getActiveThreadsState() {
3942
4299
  const displayThreads = this.selectedContext === "all-agents" ? this._threads : this._threadsByAgent.get(this.selectedContext) ?? [];
3943
4300
  let threadsErrorMessage = null;
3944
4301
  if (this.selectedContext === "all-agents") threadsErrorMessage = this._threadsErrorByAgent.values().next().value?.message ?? null;
3945
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;
3946
4304
  return {
3947
4305
  displayThreads,
3948
- threadsErrorMessage
4306
+ threadsErrorMessage,
4307
+ threadsLoading
3949
4308
  };
3950
4309
  }
3951
- getThreadsTelemetryProps(extra = {}, options = {}) {
3952
- 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() {
3953
4342
  const threadServiceStatus = this.getThreadServiceStatus();
3954
4343
  return {
3955
- posthog_distinct_id: distinctId ?? void 0,
3956
4344
  intelligence_status: threadServiceStatus === "available" ? "intelligence_enabled" : threadServiceStatus === "unavailable" ? "intelligence_not_enabled" : "unknown",
3957
4345
  thread_service_status: threadServiceStatus,
3958
4346
  license_status: this.core?.licenseStatus ?? void 0,
3959
4347
  runtime_mode: this.core?.runtimeMode ?? void 0,
3960
4348
  runtime_url_type: getRuntimeUrlType(this.core?.runtimeUrl),
3961
- telemetry_disabled: this.core?.telemetryDisabled ?? false,
3962
- ...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
3963
4365
  };
3964
4366
  }
3965
4367
  getMemoriesTelemetryProps() {
@@ -3972,9 +4374,6 @@ var WebInspectorElement = class extends LitElement {
3972
4374
  getIntelligenceSignupUrl() {
3973
4375
  return this.appendRefParam(INTELLIGENCE_SIGNUP_URL, "cpk-inspector");
3974
4376
  }
3975
- getThreadsIntelligenceSignupUrl() {
3976
- return this.appendRefParam(THREADS_INTELLIGENCE_SIGNIN_URL, "cpk-inspector");
3977
- }
3978
4377
  getTalkToEngineerUrl() {
3979
4378
  return this.appendRefParam(TALK_TO_ENGINEER_URL, "cpk-inspector-threads");
3980
4379
  }
@@ -3984,29 +4383,53 @@ var WebInspectorElement = class extends LitElement {
3984
4383
  getThreadsDocsUrl() {
3985
4384
  return this.appendRefParam(THREADS_DOCS_URL, "cpk-inspector-threads");
3986
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
+ }
3987
4392
  getSelfHostedIntelligenceUrl() {
3988
4393
  return this.appendRefParam(SELF_HOSTED_INTELLIGENCE_URL, "cpk-inspector-threads");
3989
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
+ }
3990
4407
  subscribeToThreadStore(agentId, store) {
3991
4408
  if (!this.areThreadEndpointsAvailable()) return;
3992
4409
  if (this._threadStoreSubscriptions.has(agentId)) return;
4410
+ const capabilityGeneration = this.threadCapabilityGeneration;
3993
4411
  const threadsSub = store.select(ɵselectThreads).subscribe((threads) => {
4412
+ if (capabilityGeneration !== this.threadCapabilityGeneration || !this.areThreadEndpointsAvailable()) return;
3994
4413
  this._threadsByAgent.set(agentId, threads);
3995
4414
  this._threads = Array.from(this._threadsByAgent.values()).flat();
3996
4415
  this.autoSelectLatestThread();
3997
4416
  this.requestUpdate();
3998
4417
  });
3999
- const errorSub = store.select(ɵselectThreadsError).subscribe((error) => {
4418
+ const statusSub = store.select(createThreadStoreStatusSelector()).subscribe(({ error, isLoading }) => {
4419
+ if (capabilityGeneration !== this.threadCapabilityGeneration || !this.areThreadEndpointsAvailable()) return;
4000
4420
  if (error) this._threadsErrorByAgent.set(agentId, error);
4001
4421
  else this._threadsErrorByAgent.delete(agentId);
4422
+ this._threadsLoadingByAgent.set(agentId, isLoading);
4002
4423
  this.requestUpdate();
4003
4424
  });
4004
4425
  this._threadStoreSubscriptions.set(agentId, () => {
4005
4426
  threadsSub.unsubscribe();
4006
- errorSub.unsubscribe();
4427
+ statusSub.unsubscribe();
4007
4428
  });
4429
+ if (capabilityGeneration !== this.threadCapabilityGeneration || !this.areThreadEndpointsAvailable()) return;
4008
4430
  const initialState = store.getState();
4009
4431
  this._threadsByAgent.set(agentId, ɵselectThreads(initialState));
4432
+ this._threadsLoadingByAgent.set(agentId, ɵselectThreadsIsLoading(initialState));
4010
4433
  const initialError = ɵselectThreadsError(initialState);
4011
4434
  if (initialError) this._threadsErrorByAgent.set(agentId, initialError);
4012
4435
  else this._threadsErrorByAgent.delete(agentId);
@@ -4014,22 +4437,37 @@ var WebInspectorElement = class extends LitElement {
4014
4437
  this.autoSelectLatestThread();
4015
4438
  }
4016
4439
  autoSelectLatestThread() {
4017
- if (this._threads.length === 0) return;
4018
- 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;
4019
4456
  }
4020
4457
  teardownThreadStoreSubscriptions() {
4021
4458
  for (const unsub of this._threadStoreSubscriptions.values()) unsub();
4022
4459
  this._threadStoreSubscriptions.clear();
4023
4460
  this._threadsByAgent.clear();
4024
4461
  this._threadsErrorByAgent.clear();
4462
+ this._threadsLoadingByAgent.clear();
4025
4463
  this._threads = [];
4026
4464
  }
4027
4465
  ensureOwnedThreadStore(agentId) {
4466
+ if (!this.areThreadEndpointsAvailable()) return;
4028
4467
  if (this._ownedThreadStores.has(agentId)) return;
4029
4468
  if (this.core?.getThreadStore(agentId)) return;
4030
4469
  const core = this.core;
4031
4470
  if (!core?.runtimeUrl) return;
4032
- if (!this.areThreadEndpointsAvailable()) return;
4033
4471
  const store = ɵcreateThreadStore({ fetch: globalThis.fetch });
4034
4472
  store.start();
4035
4473
  store.setContext({
@@ -4043,11 +4481,13 @@ var WebInspectorElement = class extends LitElement {
4043
4481
  core.registerThreadStore(agentId, store);
4044
4482
  }
4045
4483
  refreshOwnedThreadStore(agentId) {
4484
+ if (!this.areThreadEndpointsAvailable()) return;
4046
4485
  const store = this._ownedThreadStores.get(agentId);
4047
4486
  if (!store) return;
4048
4487
  store.refresh();
4049
4488
  }
4050
4489
  updateOwnedThreadStoreHeaders(headers) {
4490
+ if (!this.areThreadEndpointsAvailable()) return;
4051
4491
  const core = this.core;
4052
4492
  if (!core?.runtimeUrl) return;
4053
4493
  for (const [agentId, store] of this._ownedThreadStores) store.setContext({
@@ -4060,32 +4500,62 @@ var WebInspectorElement = class extends LitElement {
4060
4500
  removeOwnedThreadStore(agentId) {
4061
4501
  const store = this._ownedThreadStores.get(agentId);
4062
4502
  if (!store) return;
4063
- store.stop();
4064
- this.core?.unregisterThreadStore(agentId);
4065
4503
  this._ownedThreadStores.delete(agentId);
4504
+ store.stop();
4505
+ if (this.core?.getThreadStore(agentId) === store) this.core.unregisterThreadStore(agentId);
4066
4506
  }
4067
4507
  teardownOwnedThreadStores() {
4068
- 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) {
4069
4511
  store.stop();
4070
- this.core?.unregisterThreadStore(agentId);
4512
+ if (this.core?.getThreadStore(agentId) === store) this.core.unregisterThreadStore(agentId);
4071
4513
  }
4072
- 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);
4073
4539
  }
4074
4540
  attachToCore(core) {
4075
4541
  this.runtimeStatus = core.runtimeConnectionStatus;
4076
4542
  this.coreProperties = core.properties;
4077
4543
  this.lastCoreError = null;
4544
+ const supportsInspectorMetadata = this.coreSupportsInspectorMetadata(core);
4545
+ this.updateInspectorMetadataProjection(this.readCoreInspectorMetadata(core));
4078
4546
  this.coreSubscriber = {
4079
4547
  onRuntimeConnectionStatusChanged: ({ status }) => {
4080
4548
  this.runtimeStatus = status;
4549
+ this.updateInspectorMetadataProjection(this.inspectorMetadataValue);
4550
+ const threadCapabilityWasEnabled = this.threadCapabilityEnabled === true;
4551
+ this.synchronizeThreadCapability();
4081
4552
  if (status === "connected") {
4082
4553
  if (!core.telemetryDisabled) {
4083
4554
  ensureTelemetryDistinctId();
4084
4555
  maybeShowDisclosure();
4085
4556
  }
4086
4557
  this.flushPendingBannerViewed();
4087
- if (this.areThreadEndpointsAvailable()) for (const agentId of this._ownedThreadStores.keys()) this.refreshOwnedThreadStore(agentId);
4088
- else this.teardownOwnedThreadStores();
4558
+ if (threadCapabilityWasEnabled && this.areThreadEndpointsAvailable()) for (const agentId of this._ownedThreadStores.keys()) this.refreshOwnedThreadStore(agentId);
4089
4559
  } else {
4090
4560
  this._threadsByAgent.clear();
4091
4561
  this._threads = [];
@@ -4100,6 +4570,11 @@ var WebInspectorElement = class extends LitElement {
4100
4570
  this.updateOwnedThreadStoreHeaders(headers);
4101
4571
  this.requestUpdate();
4102
4572
  },
4573
+ ...supportsInspectorMetadata ? { onInspectorMetadataChanged: ({ inspectorMetadata }) => {
4574
+ if (this._core !== core) return;
4575
+ this.updateInspectorMetadataProjection(inspectorMetadata);
4576
+ this.requestUpdate();
4577
+ } } : {},
4103
4578
  onError: ({ code, error }) => {
4104
4579
  this.lastCoreError = {
4105
4580
  code,
@@ -4118,18 +4593,25 @@ var WebInspectorElement = class extends LitElement {
4118
4593
  this.requestUpdate();
4119
4594
  },
4120
4595
  onThreadStoreRegistered: ({ agentId, store }) => {
4596
+ if (!this.areThreadEndpointsAvailable()) return;
4121
4597
  this.subscribeToThreadStore(agentId, store);
4122
4598
  this.requestUpdate();
4123
4599
  },
4124
- onThreadStoreUnregistered: ({ agentId }) => {
4600
+ onThreadStoreUnregistered: ({ agentId, prevStore }) => {
4125
4601
  const unsub = this._threadStoreSubscriptions.get(agentId);
4126
4602
  if (unsub) {
4127
4603
  unsub();
4128
4604
  this._threadStoreSubscriptions.delete(agentId);
4129
4605
  }
4606
+ if (this._ownedThreadStores.get(agentId) === prevStore) {
4607
+ this._ownedThreadStores.delete(agentId);
4608
+ prevStore.stop();
4609
+ }
4130
4610
  this._threadsByAgent.delete(agentId);
4131
4611
  this._threadsErrorByAgent.delete(agentId);
4612
+ this._threadsLoadingByAgent.delete(agentId);
4132
4613
  this._threads = Array.from(this._threadsByAgent.values()).flat();
4614
+ this.autoSelectLatestThread();
4133
4615
  this.requestUpdate();
4134
4616
  }
4135
4617
  };
@@ -4143,7 +4625,7 @@ var WebInspectorElement = class extends LitElement {
4143
4625
  this.flushPendingBannerViewed();
4144
4626
  }
4145
4627
  const threadStores = typeof core.getThreadStores === "function" ? core.getThreadStores() : {};
4146
- 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);
4147
4629
  if (core.context) this.contextStore = this.normalizeContextStore(core.context);
4148
4630
  if (this.selectedMenu === "memories") this.ensureMemorySubscription();
4149
4631
  }
@@ -4245,6 +4727,13 @@ var WebInspectorElement = class extends LitElement {
4245
4727
  this.requestUpdate();
4246
4728
  }
4247
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;
4248
4737
  if (this.coreUnsubscribe) {
4249
4738
  this.coreUnsubscribe();
4250
4739
  this.coreUnsubscribe = null;
@@ -4265,6 +4754,9 @@ var WebInspectorElement = class extends LitElement {
4265
4754
  this._recallQuery = "";
4266
4755
  this.coreSubscriber = null;
4267
4756
  this.runtimeStatus = null;
4757
+ this.inspectorMetadataValue = void 0;
4758
+ this.inspectorMetadataProjection = projectInspectorMetadata(void 0, void 0);
4759
+ this.metadataTelemetryFingerprints.clear();
4268
4760
  this.lastCoreError = null;
4269
4761
  this.coreProperties = {};
4270
4762
  this.cachedTools = [];
@@ -4283,6 +4775,7 @@ var WebInspectorElement = class extends LitElement {
4283
4775
  this.eventCounter = 0;
4284
4776
  }
4285
4777
  processAgentsChanged(agents) {
4778
+ this.synchronizeThreadCapability();
4286
4779
  const seenAgentIds = /* @__PURE__ */ new Set();
4287
4780
  for (const agent of Object.values(agents)) {
4288
4781
  if (!agent?.agentId) continue;
@@ -4347,7 +4840,7 @@ var WebInspectorElement = class extends LitElement {
4347
4840
  event: params.event,
4348
4841
  result: "result" in params ? params.result : void 0
4349
4842
  });
4350
- this.refreshOwnedThreadStore(agentId);
4843
+ if (this.areThreadEndpointsAvailable()) this.refreshOwnedThreadStore(agentId);
4351
4844
  },
4352
4845
  onRunErrorEvent: ({ event }) => {
4353
4846
  this.recordAgentEvent(agentId, "RUN_ERROR", event);
@@ -4513,6 +5006,7 @@ var WebInspectorElement = class extends LitElement {
4513
5006
  }
4514
5007
  }
4515
5008
  updateContextOptions(agentIds) {
5009
+ let selectedContextChanged = false;
4516
5010
  const nextOptions = [{
4517
5011
  key: "all-agents",
4518
5012
  label: "All Agents"
@@ -4526,12 +5020,14 @@ var WebInspectorElement = class extends LitElement {
4526
5020
  if ((pendingContext === "all-agents" || agentIds.has(pendingContext)) && (pendingContext === "all-agents" || agentIds.size === 1)) {
4527
5021
  if (this.selectedContext !== pendingContext) {
4528
5022
  this.selectedContext = pendingContext;
5023
+ selectedContextChanged = true;
4529
5024
  this.expandedRows.clear();
4530
5025
  }
4531
5026
  this.pendingSelectedContext = null;
4532
5027
  } else if (agentIds.size > 0) {
4533
5028
  if (this.selectedContext !== "all-agents") {
4534
5029
  this.selectedContext = "all-agents";
5030
+ selectedContextChanged = true;
4535
5031
  this.expandedRows.clear();
4536
5032
  }
4537
5033
  this.pendingSelectedContext = null;
@@ -4542,10 +5038,12 @@ var WebInspectorElement = class extends LitElement {
4542
5038
  if (agentIds.size === 1) nextSelected = Array.from(agentIds)[0];
4543
5039
  if (this.selectedContext !== nextSelected) {
4544
5040
  this.selectedContext = nextSelected;
5041
+ selectedContextChanged = true;
4545
5042
  this.expandedRows.clear();
4546
5043
  this.persistState();
4547
5044
  }
4548
5045
  }
5046
+ if (selectedContextChanged) this.autoSelectLatestThread();
4549
5047
  }
4550
5048
  getEventsForSelectedContext() {
4551
5049
  if (this.selectedContext === "all-agents") return this.flattenedEvents;
@@ -4866,7 +5364,7 @@ ${argsString}</pre
4866
5364
  align-items: center;
4867
5365
  justify-content: center;
4868
5366
  border-radius: 6px;
4869
- color: #838389;
5367
+ color: #68686e;
4870
5368
  cursor: pointer;
4871
5369
  transition:
4872
5370
  background 120ms ease,
@@ -4887,7 +5385,7 @@ ${argsString}</pre
4887
5385
  background: none;
4888
5386
  border: none;
4889
5387
  cursor: pointer;
4890
- color: #838389;
5388
+ color: #68686e;
4891
5389
  width: 28px;
4892
5390
  height: 28px;
4893
5391
  display: flex;
@@ -4936,7 +5434,7 @@ ${argsString}</pre
4936
5434
  height: 24px;
4937
5435
  border-radius: 50%;
4938
5436
  background-color: #f0f0f4;
4939
- color: #838389;
5437
+ color: #68686e;
4940
5438
  flex-shrink: 0;
4941
5439
  transition: transform 0.2s;
4942
5440
  }
@@ -5053,7 +5551,7 @@ ${argsString}</pre
5053
5551
  }
5054
5552
 
5055
5553
  .announcement-content a {
5056
- color: #757cf2;
5554
+ color: #5558b2;
5057
5555
  text-decoration: underline;
5058
5556
  }
5059
5557
 
@@ -5177,7 +5675,7 @@ ${argsString}</pre
5177
5675
  font-family: "Plus Jakarta Sans", system-ui, sans-serif;
5178
5676
  font-size: 12px;
5179
5677
  font-weight: 500;
5180
- color: #757cf2;
5678
+ color: #5558b2;
5181
5679
  cursor: pointer;
5182
5680
  text-align: center;
5183
5681
  }
@@ -5248,14 +5746,14 @@ ${argsString}</pre
5248
5746
  align-items: center;
5249
5747
  }
5250
5748
  .cpk-tab-active .cpk-tab-icon {
5251
- color: #757cf2;
5749
+ color: #5558b2;
5252
5750
  }
5253
5751
  .cpk-tab-inactive {
5254
5752
  background-color: transparent;
5255
5753
  color: #2b2b2b;
5256
5754
  }
5257
5755
  .cpk-tab-inactive .cpk-tab-icon {
5258
- color: #838389;
5756
+ color: #68686e;
5259
5757
  }
5260
5758
  .cpk-tab-inactive:hover {
5261
5759
  background-color: rgba(190, 194, 255, 0.08);
@@ -5288,24 +5786,40 @@ ${argsString}</pre
5288
5786
  width: 100%;
5289
5787
  height: 100%;
5290
5788
  object-fit: cover;
5291
- opacity: 0;
5292
- transition: opacity 220ms ease;
5293
- }
5294
- .cpk-threads-overview-video[data-ready="true"] {
5295
- opacity: 1;
5296
5789
  }
5297
5790
 
5298
- /* ── Header control buttons (dock, close) — first row only ───── */
5299
- .drag-handle > div:first-child button {
5300
- 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;
5301
5795
  }
5302
- .drag-handle > div:first-child button:hover {
5303
- background-color: #f0f0f4 !important;
5304
- 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;
5801
+ }
5802
+ .drag-handle > div[data-inspector-account-strip] button:hover {
5803
+ background-color: rgba(255, 255, 255, 0.12) !important;
5804
+ color: #ffffff !important;
5305
5805
  }
5306
- .drag-handle > div:first-child button:focus-visible {
5806
+ .drag-handle > div[data-inspector-account-strip] button:focus-visible {
5307
5807
  outline-color: #bec2ff !important;
5308
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
+ }
5309
5823
 
5310
5824
  /* ── Agent/context dropdown ──────────────────────────────────── */
5311
5825
  [data-context-dropdown-root="true"] > button {
@@ -5317,7 +5831,7 @@ ${argsString}</pre
5317
5831
  background-color: #f7f7f9 !important;
5318
5832
  }
5319
5833
  [data-context-dropdown-root="true"] > button > span:last-child {
5320
- color: #838389 !important;
5834
+ color: #68686e !important;
5321
5835
  }
5322
5836
  [data-context-dropdown-root="true"] > div {
5323
5837
  border-color: #dbdbe5 !important;
@@ -5327,6 +5841,28 @@ ${argsString}</pre
5327
5841
  [data-context-dropdown-root="true"] > div button:focus {
5328
5842
  background-color: #f7f7f9 !important;
5329
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
+ }
5330
5866
 
5331
5867
  /* ── Status bar (bottom chrome) ──────────────────────────────── */
5332
5868
  .inspector-window > div > div:last-child {
@@ -5336,7 +5872,7 @@ ${argsString}</pre
5336
5872
 
5337
5873
  /* ── Resize handle ───────────────────────────────────────────── */
5338
5874
  .resize-handle {
5339
- color: #838389 !important;
5875
+ color: #68686e !important;
5340
5876
  }
5341
5877
  .resize-handle:hover {
5342
5878
  color: #57575b !important;
@@ -5357,7 +5893,7 @@ ${argsString}</pre
5357
5893
  /* Copy "copied" state: generic green → brand mint */
5358
5894
  button[class*="bg-green-100"] {
5359
5895
  background-color: rgba(133, 236, 206, 0.2) !important;
5360
- color: #189370 !important;
5896
+ color: #087653 !important;
5361
5897
  }
5362
5898
 
5363
5899
  /* ── Agents tab ──────────────────────────────────────────────── */
@@ -5366,14 +5902,14 @@ ${argsString}</pre
5366
5902
  background-color: rgba(190, 194, 255, 0.15) !important;
5367
5903
  }
5368
5904
  span[class*="text-blue-600"] {
5369
- color: #757cf2 !important;
5905
+ color: #5558b2 !important;
5370
5906
  }
5371
5907
  /* Running badge: emerald → mint */
5372
5908
  span[class*="bg-emerald-50"] {
5373
5909
  background-color: rgba(133, 236, 206, 0.15) !important;
5374
5910
  }
5375
5911
  span[class*="text-emerald-700"] {
5376
- color: #189370 !important;
5912
+ color: #087653 !important;
5377
5913
  }
5378
5914
  /* Running status dot */
5379
5915
  span[class*="bg-emerald-500"] {
@@ -5393,7 +5929,7 @@ ${argsString}</pre
5393
5929
  span[class*="bg-green-100"][class*="text-green-800"] {
5394
5930
  background-color: rgba(133, 236, 206, 0.18) !important;
5395
5931
  border: 1px solid rgba(133, 236, 206, 0.4) !important;
5396
- color: #189370 !important;
5932
+ color: #087653 !important;
5397
5933
  }
5398
5934
  /* Tool role badge (amber → orange brand) */
5399
5935
  span[class*="bg-amber-100"][class*="text-amber-800"] {
@@ -5434,13 +5970,17 @@ ${argsString}</pre
5434
5970
  > div:last-child:has(div[class*="bg-emerald-50"]) {
5435
5971
  background-color: rgba(91, 228, 187, 0.08) !important;
5436
5972
  border-top-color: rgba(91, 228, 187, 0.3) !important;
5437
- color: #189370 !important;
5973
+ color: #087653 !important;
5438
5974
  }
5439
5975
  /* Inner badge — slightly more opaque on the mint bg */
5440
5976
  div[class*="bg-emerald-50"][class*="border-emerald-200"] {
5441
5977
  background-color: rgba(91, 228, 187, 0.12) !important;
5442
5978
  border-color: rgba(91, 228, 187, 0.4) !important;
5443
- 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;
5444
5984
  }
5445
5985
  /* Icon bubble inside connected badge → mint tint */
5446
5986
  div[class*="bg-emerald-50"] span[class*="bg-white"] {
@@ -5455,7 +5995,7 @@ ${argsString}</pre
5455
5995
  span[class*="bg-slate-900"],
5456
5996
  div[class*="bg-slate-900"] {
5457
5997
  background-color: #eee6fe !important;
5458
- color: #757cf2 !important;
5998
+ color: #5558b2 !important;
5459
5999
  }
5460
6000
  span[class*="text-slate-800"],
5461
6001
  div[class*="text-slate-800"] {
@@ -5466,6 +6006,7 @@ ${argsString}</pre
5466
6006
  connectedCallback() {
5467
6007
  super.connectedCallback();
5468
6008
  if (typeof window !== "undefined") {
6009
+ this.threadsExampleOverviewVideoReducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
5469
6010
  this.ensureBrandFonts();
5470
6011
  window.addEventListener("resize", this.handleResize);
5471
6012
  window.addEventListener("pointerdown", this.handleGlobalPointerDown);
@@ -5474,6 +6015,7 @@ ${argsString}</pre
5474
6015
  this.tryAutoAttachCore();
5475
6016
  this.ensureAnnouncementLoading();
5476
6017
  }
6018
+ this.requestUpdate();
5477
6019
  }
5478
6020
  ensureBrandFonts() {
5479
6021
  const FONT_LINK_ID = "cpk-inspector-brand-fonts";
@@ -5496,14 +6038,13 @@ ${argsString}</pre
5496
6038
  clearTimeout(this.transitionTimeoutId);
5497
6039
  this.transitionTimeoutId = null;
5498
6040
  }
5499
- if (this.threadsExampleOverviewVideoLoadTimer !== null) {
5500
- window.clearTimeout(this.threadsExampleOverviewVideoLoadTimer);
5501
- this.threadsExampleOverviewVideoLoadTimer = null;
5502
- }
5503
- if (this.threadsExampleOverviewVideoIdleCallbackId !== null && typeof window.cancelIdleCallback === "function") {
5504
- window.cancelIdleCallback(this.threadsExampleOverviewVideoIdleCallbackId);
5505
- this.threadsExampleOverviewVideoIdleCallbackId = null;
6041
+ this.threadsSetupPromptCopyGeneration += 1;
6042
+ if (this.threadsSetupPromptCopyResetTimeoutId !== null) {
6043
+ window.clearTimeout(this.threadsSetupPromptCopyResetTimeoutId);
6044
+ this.threadsSetupPromptCopyResetTimeoutId = null;
5506
6045
  }
6046
+ this.threadsSetupPromptCopyState = "idle";
6047
+ this.cleanupThreadsExampleOverviewVideo();
5507
6048
  this.removeDockStyles(true);
5508
6049
  this.detachFromCore();
5509
6050
  }
@@ -5529,6 +6070,7 @@ ${argsString}</pre
5529
6070
  y: EDGE_MARGIN
5530
6071
  };
5531
6072
  this.hydrateStateFromStorage();
6073
+ this.contextState.window.size = this.clampWindowSize(this.contextState.window.size);
5532
6074
  if (this.selectedMenu === "memories") this.ensureMemorySubscription();
5533
6075
  if (this.isOpen && this.dockMode !== "floating") this.applyDockStyles(true);
5534
6076
  this.applyAnchorPosition("button");
@@ -5540,6 +6082,35 @@ ${argsString}</pre
5540
6082
  render() {
5541
6083
  return this.isOpen ? this.renderWindow() : this.renderButton();
5542
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
+ }
5543
6114
  renderButton() {
5544
6115
  return html`
5545
6116
  <div class="console-button-wrapper">
@@ -5595,24 +6166,79 @@ ${argsString}</pre
5595
6166
  </button>
5596
6167
  ${this.renderAnnouncementPreview()}
5597
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>
5598
6220
  `;
5599
6221
  }
5600
6222
  renderWindow() {
5601
6223
  const windowState = this.contextState.window;
5602
6224
  const isDocked = this.dockMode !== "floating";
5603
6225
  const isTransitioning = this.hasAttribute("data-transitioning");
5604
- const windowStyles = isDocked ? this.getDockedWindowStyles() : {
6226
+ const windowStyles = isDocked ? {
6227
+ ...this.getDockedWindowStyles(),
6228
+ overflowX: "hidden"
6229
+ } : {
5605
6230
  width: `${Math.round(windowState.size.width)}px`,
5606
6231
  height: `${Math.round(windowState.size.height)}px`,
5607
6232
  minWidth: `${MIN_WINDOW_WIDTH}px`,
5608
- minHeight: `${MIN_WINDOW_HEIGHT}px`
6233
+ minHeight: `${MIN_WINDOW_HEIGHT}px`,
6234
+ overflowX: "hidden"
5609
6235
  };
5610
- const hasContextDropdown = this.contextOptions.length > 0;
6236
+ const hasContextDropdown = this.contextOptions.some((option) => option.key !== "all-agents");
5611
6237
  const contextDropdown = hasContextDropdown ? this.renderContextDropdown() : nothing;
5612
6238
  const coreStatus = this.getCoreStatusSummary();
5613
6239
  const agentSelector = hasContextDropdown ? contextDropdown : html`
5614
6240
  <div
5615
- 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"
5616
6242
  >
5617
6243
  <span>${this.renderIcon("Bot")}</span>
5618
6244
  <span class="truncate">No agents available</span>
@@ -5647,32 +6273,41 @@ ${argsString}</pre
5647
6273
  @pointerup=${isDocked ? void 0 : this.handlePointerUp}
5648
6274
  @pointercancel=${isDocked ? void 0 : this.handlePointerCancel}
5649
6275
  >
5650
- <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
+ >
5651
6281
  <div class="flex items-center min-w-0">
5652
6282
  <img
5653
6283
  src=${inspector_logo_default}
5654
6284
  alt="Inspector logo"
5655
- class="h-6 w-auto"
6285
+ class="inspector-account-logo h-6 w-auto"
5656
6286
  loading="lazy"
5657
6287
  />
5658
6288
  </div>
6289
+ ${this.renderInspectorMetadataHeader()}
5659
6290
  <div class="ml-auto flex min-w-0 items-center gap-2">
5660
- <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>
5661
6294
  <div class="flex items-center gap-1">
5662
6295
  ${this.renderDockControls()}
5663
6296
  <button
5664
- 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" : ""}"
5665
6298
  type="button"
5666
6299
  aria-label="Settings"
5667
- aria-pressed=${this.selectedMenu === "settings"}
5668
- @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}
5669
6303
  >
5670
6304
  ${this.renderIcon("Settings")}
5671
6305
  </button>
5672
6306
  <button
5673
- 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"
5674
6308
  type="button"
5675
6309
  aria-label="Close Web Inspector"
6310
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
5676
6311
  @pointerdown=${this.handleClosePointerDown}
5677
6312
  @click=${this.handleCloseClick}
5678
6313
  >
@@ -5681,37 +6316,72 @@ ${argsString}</pre
5681
6316
  </div>
5682
6317
  </div>
5683
6318
  </div>
5684
- <div
5685
- 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;"
5686
6323
  >
5687
- ${this.menuItems.map(({ key, label, icon }) => {
5688
- const isSelected = this.selectedMenu === key;
6324
+ ${INSPECTOR_PRIMARY_NAVIGATION.map(({ key, label, icon }) => {
6325
+ const isSelected = this.selectedGroup === key;
5689
6326
  return html`
5690
6327
  <button
5691
6328
  type="button"
5692
- 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(" ")}
5693
- aria-pressed=${isSelected}
5694
- @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)}
5695
6335
  >
5696
- <span class="cpk-tab-icon">
5697
- ${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)}
5698
6338
  </span>
5699
6339
  <span>${label}</span>
5700
6340
  </button>
5701
6341
  `;
5702
6342
  })}
5703
- ${this.selectedMenu === "threads" ? html`
6343
+ ${this.selectedGroup === "threads" ? html`
5704
6344
  <a
6345
+ data-inspector-thread-cta
5705
6346
  href=${this.getThreadsTalkToEngineerUrl()}
5706
6347
  target="_blank"
5707
- rel="noopener"
5708
- 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}
5709
6351
  @click=${this.handleTalkToEngineerClick}
5710
6352
  >
5711
6353
  Talk to an Engineer
5712
6354
  </a>
5713
6355
  ` : nothing}
5714
- </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}
5715
6385
  </div>
5716
6386
  <div class="flex flex-1 flex-col overflow-hidden">
5717
6387
  <div id="cpk-main-scroll" class="flex-1 overflow-auto">
@@ -5767,10 +6437,7 @@ ${argsString}</pre
5767
6437
  if (!persisted) return;
5768
6438
  if (typeof persisted.isOpen === "boolean") this.isOpen = persisted.isOpen;
5769
6439
  if (isValidDockMode(persisted.dockMode)) this.dockMode = persisted.dockMode;
5770
- if (typeof persisted.selectedMenu === "string") {
5771
- const validMenu = this.menuItems.find((item) => item.key === persisted.selectedMenu);
5772
- if (validMenu) this.selectedMenu = validMenu.key;
5773
- }
6440
+ this.restorePersistedMenu(persisted.selectedMenu);
5774
6441
  if (typeof persisted.selectedContext === "string") {
5775
6442
  this.selectedContext = persisted.selectedContext;
5776
6443
  this.pendingSelectedContext = persisted.selectedContext;
@@ -5780,6 +6447,7 @@ ${argsString}</pre
5780
6447
  if (typeof document === "undefined" || typeof window === "undefined") return;
5781
6448
  const persisted = loadInspectorState(INSPECTOR_STORAGE_KEY);
5782
6449
  if (!persisted) return;
6450
+ this.restorePersistedMenu(persisted.selectedMenu);
5783
6451
  const persistedButton = persisted.button;
5784
6452
  if (persistedButton) {
5785
6453
  if (isValidAnchor(persistedButton.anchor)) this.contextState.button.anchor = persistedButton.anchor;
@@ -5798,6 +6466,28 @@ ${argsString}</pre
5798
6466
  this.pendingSelectedContext = persisted.selectedContext;
5799
6467
  }
5800
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
+ }
5801
6491
  get activeContext() {
5802
6492
  return this.isOpen ? "window" : "button";
5803
6493
  }
@@ -5863,7 +6553,7 @@ ${argsString}</pre
5863
6553
  },
5864
6554
  isOpen: this.isOpen,
5865
6555
  dockMode: this.dockMode,
5866
- selectedMenu: this.selectedMenu,
6556
+ selectedMenu: this.pendingPersistedMenu ?? this.selectedMenu,
5867
6557
  selectedContext: this.selectedContext
5868
6558
  };
5869
6559
  saveInspectorState(INSPECTOR_STORAGE_KEY, state);
@@ -5886,7 +6576,7 @@ ${argsString}</pre
5886
6576
  if (mode === "docked-left") this.contextState.window.size.width = DOCKED_LEFT_WIDTH;
5887
6577
  this.applyDockStyles();
5888
6578
  } else {
5889
- this.contextState.window.size = { ...DEFAULT_WINDOW_SIZE };
6579
+ this.contextState.window.size = this.clampWindowSize(DEFAULT_WINDOW_SIZE);
5890
6580
  this.centerContext("window");
5891
6581
  }
5892
6582
  this.persistState();
@@ -6045,6 +6735,7 @@ ${argsString}</pre
6045
6735
  type="button"
6046
6736
  aria-label="Dock to left"
6047
6737
  title="Dock Left"
6738
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6048
6739
  @click=${() => this.handleDockClick("docked-left")}
6049
6740
  >
6050
6741
  ${this.renderIcon("PanelLeft")}
@@ -6056,6 +6747,7 @@ ${argsString}</pre
6056
6747
  type="button"
6057
6748
  aria-label="Float window"
6058
6749
  title="Float"
6750
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6059
6751
  @click=${() => this.handleDockClick("floating")}
6060
6752
  >
6061
6753
  ${this.renderIcon("Maximize2")}
@@ -6234,6 +6926,7 @@ ${argsString}</pre
6234
6926
  };
6235
6927
  }
6236
6928
  renderMainContent() {
6929
+ if (this.settingsOpen) return this.renderSettingsPanel();
6237
6930
  if (this.selectedMenu === "ag-ui-events") return this.renderEventsTable();
6238
6931
  if (this.selectedMenu === "agents") return this.renderAgentsView();
6239
6932
  if (this.selectedMenu === "frontend-tools") return this.renderToolsView();
@@ -6241,7 +6934,6 @@ ${argsString}</pre
6241
6934
  if (this.selectedMenu === "agent-context") return this.renderContextView();
6242
6935
  if (this.selectedMenu === "threads") return this.renderThreadsView();
6243
6936
  if (this.selectedMenu === "memories") return this.renderMemoriesView();
6244
- if (this.selectedMenu === "settings") return this.renderSettingsPanel();
6245
6937
  return nothing;
6246
6938
  }
6247
6939
  renderSettingsPanel() {
@@ -6289,6 +6981,94 @@ ${argsString}</pre
6289
6981
  has_unseen_announcement: hadUnseenAnnouncement
6290
6982
  });
6291
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
+ }
6292
7072
  trackBannerDismissedOnce(surface) {
6293
7073
  if (this.core?.telemetryDisabled) return;
6294
7074
  const id = this.announcementTimestamp;
@@ -6315,21 +7095,18 @@ ${argsString}</pre
6315
7095
  cta_label: this.announcementCtaLabel ?? void 0
6316
7096
  });
6317
7097
  }
6318
- trackThreadsViewStateOnce(state, threadCount) {
7098
+ trackThreadsViewStateOnce(state) {
6319
7099
  if (this.core?.telemetryDisabled) return;
6320
7100
  const key = `${state}:${this.getThreadServiceStatus()}`;
6321
7101
  if (this.viewedThreadsTelemetryStates.has(key)) return;
6322
7102
  this.viewedThreadsTelemetryStates.add(key);
6323
- const props = this.getThreadsTelemetryProps({ thread_count: threadCount });
7103
+ const props = this.getThreadsTelemetryProps();
6324
7104
  if (state === "locked") trackThreadsLockedViewed(props);
6325
7105
  else if (state === "empty_enabled") trackThreadsEmptyEnabledViewed(props);
6326
7106
  else trackThreadsEnabledViewed(props);
6327
7107
  }
6328
- shouldRenderExampleThreads(displayThreads, threadsErrorMessage) {
6329
- return this.areThreadEndpointsAvailable() && !threadsErrorMessage && displayThreads.length === 0;
6330
- }
6331
- getVisibleThreads(displayThreads, threadsErrorMessage) {
6332
- return this.shouldRenderExampleThreads(displayThreads, threadsErrorMessage) ? THREADS_EXAMPLE_THREADS : displayThreads;
7108
+ shouldRenderExampleThreads(locked, displayThreads, threadsErrorMessage, threadsLoading) {
7109
+ return locked || !threadsErrorMessage && !threadsLoading && displayThreads.length === 0;
6333
7110
  }
6334
7111
  isExampleThreadId(threadId) {
6335
7112
  return THREADS_EXAMPLE_THREADS.some((thread) => thread.id === threadId);
@@ -6346,6 +7123,7 @@ ${argsString}</pre
6346
7123
  agentId: thread.agentId,
6347
7124
  endUserId: "example-user",
6348
7125
  status: "completed",
7126
+ createdAt: thread.createdAt,
6349
7127
  updatedAt: thread.updatedAt
6350
7128
  } : null,
6351
7129
  getMessages: async () => details?.messages ?? [],
@@ -6358,39 +7136,43 @@ ${argsString}</pre
6358
7136
  trackThreadsExampleViewedOnce() {
6359
7137
  if (this.core?.telemetryDisabled) return;
6360
7138
  for (const thread of THREADS_EXAMPLE_THREADS) {
6361
- if (this.viewedExampleThreadIds.has(thread.id)) continue;
6362
- this.viewedExampleThreadIds.add(thread.id);
6363
- trackThreadsExampleViewed(this.getThreadsTelemetryProps({
6364
- example_thread_id: thread.id,
6365
- thread_count: 0
6366
- }));
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
+ });
6367
7146
  }
6368
7147
  }
6369
7148
  trackThreadsExampleSelectedOnce(threadId) {
6370
7149
  if (this.core?.telemetryDisabled) return;
6371
- if (this.selectedExampleThreadIds.has(threadId)) return;
6372
- this.selectedExampleThreadIds.add(threadId);
6373
- trackThreadsExampleSelected(this.getThreadsTelemetryProps({
6374
- example_thread_id: threadId,
6375
- thread_count: 0
6376
- }));
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
+ });
6377
7157
  }
6378
7158
  getCurrentExampleTourProps() {
6379
- const step = THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0];
6380
- return this.getThreadsTelemetryProps({
6381
- example_thread_id: this.selectedThreadId ?? void 0,
6382
- thread_count: 0,
6383
- tour_step: this.exampleTourStep + 1,
6384
- tour_tab: step?.tab
6385
- });
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
+ };
6386
7167
  }
6387
7168
  trackThreadsExampleTourStepViewedOnce() {
6388
7169
  if (this.core?.telemetryDisabled || !this.selectedThreadId) return;
6389
- if (!(THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0])) return;
6390
- const key = `${this.selectedThreadId}:${this.exampleTourStep}`;
7170
+ const props = this.getCurrentExampleTourProps();
7171
+ if (!props) return;
7172
+ const key = `${props.example_kind}:${props.tour_step}`;
6391
7173
  if (this.viewedExampleTourSteps.has(key)) return;
6392
7174
  this.viewedExampleTourSteps.add(key);
6393
- trackThreadsExampleTourStepViewed(this.getCurrentExampleTourProps());
7175
+ trackThreadsExampleTourStepViewed(props);
6394
7176
  }
6395
7177
  syncExampleTourTab() {
6396
7178
  const step = THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0];
@@ -6405,44 +7187,65 @@ ${argsString}</pre
6405
7187
  this.exampleTourStep = 0;
6406
7188
  if (autoStarted) {
6407
7189
  this.exampleTourAutoShown = true;
6408
- if (!this.core?.telemetryDisabled) trackThreadsExampleTourStarted(this.getCurrentExampleTourProps());
6409
- } 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
+ }
6410
7198
  this.trackThreadsExampleTourStepViewedOnce();
6411
7199
  this.syncExampleTourTab();
6412
7200
  this.requestUpdate();
6413
7201
  }
6414
7202
  setExampleTourStep(nextStep) {
7203
+ const telemetryStepIsValid = getExampleTourTelemetryPair(nextStep) !== void 0;
6415
7204
  this.exampleTourStep = Math.max(0, Math.min(THREADS_EXAMPLE_TOUR_STEPS.length - 1, nextStep));
6416
- this.trackThreadsExampleTourStepViewedOnce();
7205
+ if (telemetryStepIsValid) this.trackThreadsExampleTourStepViewedOnce();
6417
7206
  this.syncExampleTourTab();
6418
7207
  this.requestUpdate();
6419
7208
  }
6420
7209
  dismissExampleTour(method) {
6421
7210
  if (!this.selectedThreadId) return;
6422
- const props = {
6423
- ...this.getCurrentExampleTourProps(),
6424
- dismiss_method: method
6425
- };
6426
7211
  this.exampleTourActive = false;
6427
7212
  this.exampleTourDismissed = true;
6428
7213
  this.writeThreadsExampleTourDismissed();
6429
- if (!this.core?.telemetryDisabled) if (method === "done") trackThreadsExampleTourCompleted(props);
6430
- 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
+ }
6431
7225
  this.requestUpdate();
6432
7226
  }
6433
7227
  handleThreadsThreadSelected(threadId, showingExamples) {
6434
- if (showingExamples && this.selectedThreadId === threadId) {
7228
+ if (showingExamples && this.selectedThreadId === threadId && this.selectedLocalExampleThreadId === threadId) {
6435
7229
  this.selectedThreadId = null;
7230
+ this.selectedRealThreadIsExplicit = false;
7231
+ this.selectedLocalExampleThreadId = null;
6436
7232
  this.exampleTourActive = false;
6437
7233
  this.requestUpdate();
6438
7234
  return;
6439
7235
  }
6440
7236
  this.selectedThreadId = threadId;
6441
7237
  if (showingExamples && this.isExampleThreadId(threadId)) {
7238
+ this.selectedRealThreadIsExplicit = false;
7239
+ this.selectedLocalExampleThreadId = threadId;
6442
7240
  this.trackThreadsExampleSelectedOnce(threadId);
6443
7241
  if (!this.exampleTourDismissed && !this.exampleTourAutoShown) this.startExampleTour(true);
6444
7242
  else this.exampleTourActive = false;
6445
- } 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
+ }
6446
7249
  this.requestUpdate();
6447
7250
  }
6448
7251
  readThreadsExampleTourDismissed() {
@@ -6461,42 +7264,228 @@ ${argsString}</pre
6461
7264
  window.localStorage.setItem(THREADS_EXAMPLE_TOUR_STORAGE_KEY, JSON.stringify({ dismissed: true }));
6462
7265
  } catch {}
6463
7266
  }
6464
- scheduleThreadsExampleOverviewVideoLoad() {
6465
- if (this.threadsExampleOverviewVideoShouldLoad || this.threadsExampleOverviewVideoLoadTimer !== null || this.threadsExampleOverviewVideoIdleCallbackId !== null || typeof window === "undefined") return;
6466
- if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
6467
- 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);
6468
7271
  this.threadsExampleOverviewVideoLoadTimer = null;
7272
+ }
7273
+ if (this.threadsExampleOverviewVideoIdleCallbackId !== null) {
7274
+ if (typeof window !== "undefined" && typeof window.cancelIdleCallback === "function") window.cancelIdleCallback(this.threadsExampleOverviewVideoIdleCallbackId);
6469
7275
  this.threadsExampleOverviewVideoIdleCallbackId = null;
6470
- this.threadsExampleOverviewVideoShouldLoad = true;
6471
- this.requestUpdate();
6472
- };
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;
6473
7336
  if (typeof window.requestIdleCallback === "function") {
6474
- 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;
7345
+ return;
7346
+ }
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();
6475
7424
  return;
6476
7425
  }
6477
- this.threadsExampleOverviewVideoLoadTimer = window.setTimeout(loadVideo, 450);
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();
6478
7436
  }
6479
7437
  renderThreadsExampleOverviewVideo() {
6480
- 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>`;
6481
7453
  return html`
6482
7454
  <div class="cpk-threads-overview-video-frame" aria-hidden="true">
6483
- ${this.threadsExampleOverviewVideoShouldLoad ? html`
6484
- <video
6485
- class="cpk-threads-overview-video"
6486
- data-ready=${this.threadsExampleOverviewVideoReady}
6487
- src=${THREADS_EXAMPLE_OVERVIEW_VIDEO_URL}
6488
- autoplay
6489
- loop
6490
- muted
6491
- playsinline
6492
- preload="metadata"
6493
- @loadeddata=${this.handleThreadsExampleOverviewVideoLoaded}
6494
- ></video>
6495
- ` : nothing}
7455
+ ${this.threadsExampleOverviewVideoLifecycleGeneration % 2 === 0 ? html`<!-- cpk-video-generation-even -->${video}` : html`<!-- cpk-video-generation-odd -->${video}`}
6496
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}
6497
7470
  `;
6498
7471
  }
6499
- 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();
6500
7489
  return html`
6501
7490
  <div
6502
7491
  style="
@@ -6519,7 +7508,7 @@ ${argsString}</pre
6519
7508
  color: #010507;
6520
7509
  "
6521
7510
  >
6522
- Threads are persistent, inspectable conversations
7511
+ ${lockedCopy?.heading ?? "Threads are persistent, inspectable conversations"}
6523
7512
  </h2>
6524
7513
  ${this.renderThreadsExampleOverviewVideo()}
6525
7514
  <p
@@ -6530,60 +7519,89 @@ ${argsString}</pre
6530
7519
  color: #57575b;
6531
7520
  "
6532
7521
  >
6533
- Take a tour with the example threads in the sidebar. Then, start
6534
- 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."}
6535
7523
  </p>
6536
7524
  <div style="display:flex;flex-wrap:wrap;gap:8px;">
6537
- <a
6538
- href=${this.getThreadsDocsUrl()}
6539
- target="_blank"
6540
- rel="noopener"
6541
- style="
6542
- display: inline-flex;
6543
- align-items: center;
6544
- justify-content: center;
6545
- gap: 6px;
6546
- min-height: 34px;
6547
- border-radius: 6px;
6548
- background: #010507;
6549
- padding: 8px 12px;
6550
- font-size: 12px;
6551
- font-weight: 600;
6552
- color: #ffffff;
6553
- text-decoration: none;
6554
- "
6555
- >
6556
- Learn how Threads work
6557
- </a>
6558
- <a
6559
- href=${this.getSelfHostedIntelligenceUrl()}
6560
- target="_blank"
6561
- rel="noopener"
6562
- style="
6563
- display: inline-flex;
6564
- align-items: center;
6565
- justify-content: center;
6566
- gap: 6px;
6567
- min-height: 34px;
6568
- border-radius: 6px;
6569
- border: 1px solid #dbdbe5;
6570
- background: #ffffff;
6571
- padding: 8px 12px;
6572
- font-size: 12px;
6573
- font-weight: 600;
6574
- color: #010507;
6575
- text-decoration: none;
6576
- "
6577
- >
6578
- Explore self-hosted Intelligence
6579
- </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
+ `}
6580
7598
  </div>
6581
7599
  </div>
6582
7600
  </div>
6583
7601
  `;
6584
7602
  }
6585
7603
  renderThreadsExampleTour() {
6586
- if (!this.selectedThreadId || !this.isExampleThreadId(this.selectedThreadId)) return nothing;
7604
+ if (!this.selectedThreadId || this.selectedThreadId !== this.selectedLocalExampleThreadId) return nothing;
6587
7605
  if (!this.exampleTourActive) return html`
6588
7606
  <button
6589
7607
  type="button"
@@ -6634,7 +7652,7 @@ ${argsString}</pre
6634
7652
  font-family: 'Spline Sans Mono', monospace;
6635
7653
  font-size: 10px;
6636
7654
  font-weight: 600;
6637
- color: #189370;
7655
+ color: #087653;
6638
7656
  text-transform: uppercase;
6639
7657
  "
6640
7658
  >
@@ -6665,7 +7683,7 @@ ${argsString}</pre
6665
7683
  >
6666
7684
  <button
6667
7685
  type="button"
6668
- 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;"
6669
7687
  @click=${() => this.dismissExampleTour("skip")}
6670
7688
  >
6671
7689
  Skip
@@ -6844,124 +7862,25 @@ ${argsString}</pre
6844
7862
  </div>
6845
7863
  `;
6846
7864
  }
6847
- renderThreadsLockedView() {
6848
- this.trackThreadsViewStateOnce("locked", 0);
6849
- return html`
6850
- <div
6851
- style="
6852
- position: relative;
6853
- height: 100%;
6854
- display: flex;
6855
- align-items: center;
6856
- justify-content: center;
6857
- padding: 32px;
6858
- overflow: hidden;
6859
- background: #ffffff;
6860
- "
6861
- >
6862
- ${this.renderThreadsLockedBackgroundMockup()}
6863
- <div
6864
- aria-hidden="true"
6865
- style="
6866
- position: absolute;
6867
- inset: 0;
6868
- pointer-events: none;
6869
- background:
6870
- 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%);
6871
- "
6872
- ></div>
6873
- <div
6874
- style="
6875
- position: relative;
6876
- z-index: 1;
6877
- max-width: 440px;
6878
- text-align: center;
6879
- color: #57575b;
6880
- "
6881
- >
6882
- <div
6883
- aria-hidden="true"
6884
- style="
6885
- margin: 0 auto 18px;
6886
- display: flex;
6887
- justify-content: center;
6888
- "
6889
- >
6890
- <div
6891
- style="
6892
- display: flex;
6893
- height: 44px;
6894
- width: 44px;
6895
- align-items: center;
6896
- justify-content: center;
6897
- border: 1px solid #dfd6fb;
6898
- border-radius: 8px;
6899
- background: #eee6fe;
6900
- color: #57575b;
6901
- box-shadow: 0 8px 18px rgba(87, 87, 91, 0.14);
6902
- "
6903
- >
6904
- ${this.renderIcon("Lock")}
6905
- </div>
6906
- </div>
6907
- <h2
6908
- style="
6909
- margin: 0 0 8px;
6910
- font-size: 16px;
6911
- line-height: 1.35;
6912
- font-weight: 600;
6913
- color: #010507;
6914
- "
6915
- >
6916
- Enable Intelligence to inspect Threads.
6917
- </h2>
6918
- <p
6919
- style="
6920
- margin: 0 auto 18px;
6921
- max-width: 380px;
6922
- font-size: 13px;
6923
- line-height: 1.55;
6924
- color: #57575b;
6925
- "
6926
- >
6927
- Persist conversations and inspect saved thread history from the
6928
- Inspector.
6929
- </p>
6930
- <div
6931
- style="
6932
- display: flex;
6933
- flex-wrap: wrap;
6934
- justify-content: center;
6935
- gap: 8px;
6936
- "
6937
- >
6938
- <a
6939
- href=${this.getThreadsIntelligenceSignupUrl()}
6940
- target="_blank"
6941
- rel="noopener"
6942
- style="
6943
- display: inline-flex;
6944
- min-height: 34px;
6945
- align-items: center;
6946
- justify-content: center;
6947
- gap: 6px;
6948
- border-radius: 6px;
6949
- border: 1px solid #dbdbe5;
6950
- background: #ffffff;
6951
- padding: 8px 12px;
6952
- font-size: 12px;
6953
- font-weight: 600;
6954
- color: #57575b;
6955
- text-decoration: none;
6956
- "
6957
- @click=${this.handleThreadsIntelligenceSignupClick}
6958
- >
6959
- Sign up for Intelligence
6960
- </a>
6961
- </div>
6962
- </div>
6963
- </div>
6964
- `;
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
+ }
6965
7884
  }
6966
7885
  /**
6967
7886
  * Renders the realtime-connection indicator in the memory-store header.
@@ -6981,7 +7900,7 @@ ${argsString}</pre
6981
7900
  gap: 4px;
6982
7901
  font-size: 10px;
6983
7902
  font-weight: 500;
6984
- color: ${connected ? "#57575b" : "#838389"};
7903
+ color: ${connected ? "#57575b" : "#68686e"};
6985
7904
  "
6986
7905
  >
6987
7906
  <span
@@ -7144,7 +8063,7 @@ ${argsString}</pre
7144
8063
  align-items: center;
7145
8064
  justify-content: center;
7146
8065
  gap: 8px;
7147
- color: #838389;
8066
+ color: #68686e;
7148
8067
  "
7149
8068
  >
7150
8069
  <svg
@@ -7186,7 +8105,7 @@ ${argsString}</pre
7186
8105
  align-items: center;
7187
8106
  justify-content: center;
7188
8107
  gap: 8px;
7189
- color: #838389;
8108
+ color: #68686e;
7190
8109
  "
7191
8110
  >
7192
8111
  <svg
@@ -7278,17 +8197,65 @@ ${argsString}</pre
7278
8197
  ></cpk-memory-list>
7279
8198
  </div>
7280
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>
7281
8246
  `;
7282
8247
  }
7283
8248
  renderThreadsView() {
7284
- if (!this.areThreadEndpointsAvailable()) return this.renderThreadsLockedView();
7285
- const { displayThreads, threadsErrorMessage } = this.getActiveThreadsState();
7286
- const showingExamples = this.shouldRenderExampleThreads(displayThreads, threadsErrorMessage);
7287
- 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;
7288
8254
  if (showingExamples) this.trackThreadsExampleViewedOnce();
7289
8255
  const selectedThread = this.selectedThreadId != null ? visibleThreads.find((t) => t.id === this.selectedThreadId) ?? null : null;
7290
- const selectedThreadIsExample = this.isExampleThreadId(selectedThread?.id);
7291
- 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");
7292
8259
  return html`
7293
8260
  <div style="display:flex;height:100%;overflow:hidden;flex-direction:column;">
7294
8261
  <div style="display:flex;min-height:0;flex:1;overflow:hidden;">
@@ -7297,14 +8264,16 @@ ${argsString}</pre
7297
8264
  style="width:${this.threadListWidth}px;flex-shrink:0;overflow:hidden;display:flex;flex-direction:column;border-right:1px solid #DBDBE5;"
7298
8265
  >
7299
8266
  <cpk-thread-list
7300
- style="height:100%;"
8267
+ style="min-height:0;flex:1;"
7301
8268
  .threads=${visibleThreads}
7302
8269
  .selectedThreadId=${this.selectedThreadId}
7303
8270
  .errorMessage=${threadsErrorMessage}
8271
+ .suppressEmptyState=${loadingWithoutRows}
7304
8272
  @threadSelected=${(e) => {
7305
8273
  this.handleThreadsThreadSelected(e.detail, showingExamples);
7306
8274
  }}
7307
8275
  ></cpk-thread-list>
8276
+ ${this.renderThreadsUsageFooter()}
7308
8277
  </div>
7309
8278
 
7310
8279
  <!-- Resize divider -->
@@ -7318,19 +8287,53 @@ ${argsString}</pre
7318
8287
 
7319
8288
  <!-- Center + right: thread details or empty state -->
7320
8289
  <div style="flex:1;min-width:0;overflow:hidden;display:flex;position:relative;">
7321
- ${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
7322
8325
  style="flex:1;min-width:0;"
7323
8326
  .threadId=${selectedThread.id}
7324
8327
  .thread=${selectedThread}
7325
- .provider=${selectedThreadIsExample ? this.getExampleThreadProvider(selectedThread.id) : null}
7326
- .runtimeUrl=${this._core?.runtimeUrl ?? ""}
8328
+ .provider=${selectedThreadIsLocalExample ? this.getExampleThreadProvider(selectedThread.id) : null}
8329
+ .runtimeUrl=${selectedThreadIsLocalExample ? "" : this._core?.runtimeUrl ?? ""}
7327
8330
  .headers=${this._core?.headers ?? {}}
7328
- .threadInspectionAvailable=${selectedThreadIsExample || this._core?.threadEndpoints?.inspect !== false}
8331
+ .threadInspectionAvailable=${selectedThreadIsLocalExample || this.areThreadEndpointsAvailable() && this._core?.threadEndpoints?.inspect !== false}
7329
8332
  .liveMessageVersion=${this.liveMessageVersion.get(selectedThread.id) ?? 0}
7330
8333
  .agentStateInput=${this.getLatestStateForAgent(selectedThread.agentId)}
7331
8334
  .agentEventsInput=${this.agentEvents.get(selectedThread.agentId) ?? []}
7332
8335
  ></cpk-thread-details>
7333
- ${selectedThreadIsExample ? this.renderThreadsExampleTour() : nothing}` : showingExamples ? this.renderThreadsExampleOverview() : html`
8336
+ ${selectedThreadIsLocalExample ? this.renderThreadsExampleTour() : nothing}` : showingExamples ? this.renderThreadsExampleOverview(locked) : html`
7334
8337
  <div
7335
8338
  style="
7336
8339
  flex: 1;
@@ -7339,7 +8342,7 @@ ${argsString}</pre
7339
8342
  align-items: center;
7340
8343
  justify-content: center;
7341
8344
  gap: 8px;
7342
- color: #838389;
8345
+ color: #68686e;
7343
8346
  "
7344
8347
  >
7345
8348
  <svg
@@ -7863,9 +8866,12 @@ ${prettyEvent}</pre
7863
8866
  `;
7864
8867
  }
7865
8868
  handleMenuSelect(key) {
7866
- if (key !== "settings" && !this.menuItems.some((item) => item.key === key)) return;
8869
+ if (!this.menuItems.some((item) => item.key === key)) return;
7867
8870
  const previousMenu = this.selectedMenu;
8871
+ this.pendingPersistedMenu = null;
7868
8872
  this.selectedMenu = key;
8873
+ this.settingsOpen = false;
8874
+ this.lastSelectedMenuByGroup[this.getGroupForMenu(key)] = key;
7869
8875
  if (key === "agents" && this.selectedContext === "all-agents") {
7870
8876
  const agentOptions = this.contextOptions.filter((opt) => opt.key !== "all-agents");
7871
8877
  if (agentOptions.length > 0) {
@@ -7909,6 +8915,7 @@ ${prettyEvent}</pre
7909
8915
  if (this.selectedContext !== key) {
7910
8916
  this.selectedContext = key;
7911
8917
  this.expandedRows.clear();
8918
+ this.autoSelectLatestThread();
7912
8919
  }
7913
8920
  this.contextMenuOpen = false;
7914
8921
  this.persistState();