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