@copilotkit/web-inspector 1.66.2 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +92 -13
  2. package/dist/index.cjs +1444 -429
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +118 -8
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.mts +118 -8
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +1446 -431
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/index.umd.js +1729 -533
  11. package/dist/index.umd.js.map +1 -1
  12. package/dist/lib/inspector-metadata.cjs +60 -0
  13. package/dist/lib/inspector-metadata.cjs.map +1 -0
  14. package/dist/lib/inspector-metadata.mjs +60 -0
  15. package/dist/lib/inspector-metadata.mjs.map +1 -0
  16. package/dist/lib/telemetry.cjs +117 -32
  17. package/dist/lib/telemetry.cjs.map +1 -1
  18. package/dist/lib/telemetry.mjs +116 -33
  19. package/dist/lib/telemetry.mjs.map +1 -1
  20. package/dist/lib/thread-selection.cjs +36 -0
  21. package/dist/lib/thread-selection.cjs.map +1 -0
  22. package/dist/lib/thread-selection.mjs +35 -0
  23. package/dist/lib/thread-selection.mjs.map +1 -0
  24. package/dist/package.cjs +1 -1
  25. package/dist/package.mjs +1 -1
  26. package/dist/styles/generated.cjs +1 -1
  27. package/dist/styles/generated.cjs.map +1 -1
  28. package/dist/styles/generated.mjs +1 -1
  29. package/dist/styles/generated.mjs.map +1 -1
  30. package/package.json +9 -4
  31. package/CHANGELOG.md +0 -505
  32. package/dev/css-raw-import.spec.ts +0 -31
  33. package/dev/index.html +0 -115
  34. package/dev/main.ts +0 -165
  35. package/dev/vite.config.ts +0 -30
  36. package/src/__tests__/memory-recall.spec.ts +0 -76
  37. package/src/__tests__/telemetry-egress-guard.spec.ts +0 -41
  38. package/src/__tests__/web-inspector.spec.ts +0 -3911
  39. package/src/assets/inspector-logo-icon.svg +0 -8
  40. package/src/assets/inspector-logo.svg +0 -40
  41. package/src/components.d.ts +0 -20
  42. package/src/index.ts +0 -11839
  43. package/src/lib/__tests__/telemetry.test.ts +0 -557
  44. package/src/lib/context-helpers.ts +0 -166
  45. package/src/lib/persistence.ts +0 -206
  46. package/src/lib/telemetry.ts +0 -474
  47. package/src/lib/testing/telemetry-egress-guard.ts +0 -33
  48. package/src/lib/types.ts +0 -19
  49. package/src/styles/generated.css +0 -2
  50. package/src/styles/tailwind.css +0 -23
  51. package/src/types/css.d.ts +0 -4
  52. package/src/types/svg.d.ts +0 -4
  53. package/tsconfig.json +0 -14
  54. package/tsdown.config.ts +0 -45
  55. package/vitest.config.ts +0 -66
  56. package/vitest.setup.ts +0 -84
package/dist/index.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,21 +77,55 @@ 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",
56
126
  "RUN_ERROR",
127
+ "STEP_STARTED",
128
+ "STEP_FINISHED",
57
129
  "TEXT_MESSAGE_START",
58
130
  "TEXT_MESSAGE_CONTENT",
59
131
  "TEXT_MESSAGE_END",
@@ -133,6 +205,15 @@ const THREADS_EXAMPLE_THREADS = [
133
205
  isExample: true
134
206
  }
135
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
+ }
136
217
  const THREADS_EXAMPLE_DETAILS = {
137
218
  "example-realtime-sync": {
138
219
  messages: [{
@@ -276,13 +357,13 @@ const THREADS_EXAMPLE_DETAILS = {
276
357
  const THREADS_EXAMPLE_TOUR_STEPS = [
277
358
  {
278
359
  tab: "timeline",
279
- label: "Timeline",
360
+ label: "Messages",
280
361
  title: "Read the run as a story",
281
362
  body: "The timeline turns messages, tool calls, state changes, and run markers into a scannable debugging trail."
282
363
  },
283
364
  {
284
365
  tab: "raw-events",
285
- label: "Raw AG-UI Events",
366
+ label: "AG-UI Events",
286
367
  title: "Drop into the protocol payloads",
287
368
  body: "Raw events show the exact AG-UI stream behind the timeline when you need to verify ordering or payload shape."
288
369
  },
@@ -293,6 +374,28 @@ const THREADS_EXAMPLE_TOUR_STEPS = [
293
374
  body: "The state tab shows the saved values that make a thread resumable across sessions."
294
375
  }
295
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
+ }
296
399
  function escapeHtml(s) {
297
400
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
298
401
  }
@@ -304,10 +407,10 @@ function highlightedJson(obj) {
304
407
  }
305
408
  const colors = {
306
409
  key: "#5558B2",
307
- str: "#189370",
308
- num: "#996300",
410
+ str: "#087653",
411
+ num: "#8a5900",
309
412
  bool: "#c0333a",
310
- nil: "#838389"
413
+ nil: "#68686e"
311
414
  };
312
415
  const json = JSON.stringify(obj, null, 2);
313
416
  if (!json) return "";
@@ -337,7 +440,7 @@ function eventColors(type) {
337
440
  };
338
441
  if (type.startsWith("TOOL_CALL")) return {
339
442
  bg: "rgba(133,236,206,0.15)",
340
- fg: "#189370"
443
+ fg: "#087653"
341
444
  };
342
445
  if (type.startsWith("STATE")) return {
343
446
  bg: "rgba(190,194,255,0.102)",
@@ -349,11 +452,11 @@ function eventColors(type) {
349
452
  };
350
453
  if (type.startsWith("RUN_") || type.startsWith("STEP_")) return {
351
454
  bg: "rgba(255,172,77,0.2)",
352
- fg: "#996300"
455
+ fg: "#8a5900"
353
456
  };
354
457
  return {
355
458
  bg: "#F7F7F9",
356
- fg: "#838389"
459
+ fg: "#68686e"
357
460
  };
358
461
  }
359
462
  function formatTimestamp(ts) {
@@ -373,6 +476,7 @@ var CpkThreadList = class extends lit.LitElement {
373
476
  this.threads = [];
374
477
  this.selectedThreadId = null;
375
478
  this.errorMessage = null;
479
+ this.suppressEmptyState = false;
376
480
  this._query = "";
377
481
  this.onSearchInput = (event) => {
378
482
  this._query = event.target.value;
@@ -383,6 +487,7 @@ var CpkThreadList = class extends lit.LitElement {
383
487
  threads: { attribute: false },
384
488
  selectedThreadId: { attribute: false },
385
489
  errorMessage: { attribute: false },
490
+ suppressEmptyState: { attribute: false },
386
491
  _query: { state: true }
387
492
  };
388
493
  }
@@ -439,7 +544,18 @@ var CpkThreadList = class extends lit.LitElement {
439
544
 
440
545
  /* ── Thread item ── */
441
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;
442
554
  padding: 11px 13px;
555
+ background: transparent;
556
+ color: inherit;
557
+ font: inherit;
558
+ text-align: left;
443
559
  cursor: pointer;
444
560
  border-bottom: 1px solid #e9e9ef;
445
561
  border-left: 3px solid transparent;
@@ -459,6 +575,13 @@ var CpkThreadList = class extends lit.LitElement {
459
575
  background: #bec2ff33;
460
576
  }
461
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
+
462
585
  .cpk-tl__row1 {
463
586
  display: flex;
464
587
  align-items: center;
@@ -477,7 +600,7 @@ var CpkThreadList = class extends lit.LitElement {
477
600
  }
478
601
 
479
602
  .cpk-tl__name--unnamed {
480
- color: #838389;
603
+ color: #68686e;
481
604
  font-style: italic;
482
605
  font-weight: 400;
483
606
  }
@@ -485,7 +608,7 @@ var CpkThreadList = class extends lit.LitElement {
485
608
  .cpk-tl__time {
486
609
  font-family: "Spline Sans Mono", monospace;
487
610
  font-size: 10px;
488
- color: #838389;
611
+ color: #68686e;
489
612
  flex-shrink: 0;
490
613
  }
491
614
 
@@ -510,14 +633,14 @@ var CpkThreadList = class extends lit.LitElement {
510
633
 
511
634
  .cpk-tl__pill--example {
512
635
  background: rgba(133, 236, 206, 0.22);
513
- color: #189370;
636
+ color: #087653;
514
637
  }
515
638
 
516
639
  /* ── Empty state ── */
517
640
  .cpk-tl__empty {
518
641
  padding: 32px 16px;
519
642
  text-align: center;
520
- color: #838389;
643
+ color: #68686e;
521
644
  font-size: 12px;
522
645
  display: flex;
523
646
  flex-direction: column;
@@ -571,11 +694,13 @@ var CpkThreadList = class extends lit.LitElement {
571
694
  <!-- Thread list -->
572
695
  <div class="cpk-tl__list">
573
696
  ${filtered.map((thread) => lit.html`
574
- <div
697
+ <button
698
+ type="button"
699
+ aria-current=${this.selectedThreadId === thread.id ? "true" : lit.nothing}
575
700
  class="cpk-tl__item ${this.selectedThreadId === thread.id ? "cpk-tl__item--active" : ""}"
576
701
  @click=${() => this.onThreadClick(thread.id)}
577
702
  >
578
- <div class="cpk-tl__row1">
703
+ <span class="cpk-tl__row1">
579
704
  <span
580
705
  class="cpk-tl__name ${!thread.name ? "cpk-tl__name--unnamed" : ""}"
581
706
  >${thread.name ?? "Untitled"}</span
@@ -583,16 +708,16 @@ var CpkThreadList = class extends lit.LitElement {
583
708
  <span class="cpk-tl__time"
584
709
  >${this.relativeTime(thread.updatedAt)}</span
585
710
  >
586
- </div>
587
- <div class="cpk-tl__meta">
711
+ </span>
712
+ <span class="cpk-tl__meta">
588
713
  <span class="cpk-tl__pill">${thread.agentId}</span>
589
714
  ${thread.isExample ? lit.html`
590
715
  <span class="cpk-tl__pill cpk-tl__pill--example">Example</span>
591
716
  ` : lit.nothing}
592
- </div>
593
- </div>
717
+ </span>
718
+ </button>
594
719
  `)}
595
- ${filtered.length === 0 ? lit.html`
720
+ ${filtered.length === 0 && !this.suppressEmptyState ? lit.html`
596
721
  <div class="cpk-tl__empty">
597
722
  ${this.errorMessage ? lit.html`
598
723
  <svg
@@ -687,10 +812,12 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
687
812
  this._messagesAbort = null;
688
813
  this._eventsAbort = null;
689
814
  this._stateAbort = null;
815
+ this._hasConnectedOnce = false;
690
816
  this._dividerResizing = false;
691
817
  this._dividerPointerId = -1;
692
818
  this._dividerStartX = 0;
693
819
  this._dividerStartWidth = 0;
820
+ this.domIdPrefix = `cpk-thread-detail-${CpkThreadInspector.nextDomId++}`;
694
821
  this.onDetailDividerDown = (event) => {
695
822
  this._dividerResizing = true;
696
823
  this._dividerPointerId = event.pointerId;
@@ -745,6 +872,9 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
745
872
  _activatedTabs: { state: true }
746
873
  };
747
874
  }
875
+ static {
876
+ this.nextDomId = 1;
877
+ }
748
878
  static {
749
879
  this.COLLAPSE_THRESHOLD = 800;
750
880
  }
@@ -752,11 +882,11 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
752
882
  this.TAB_LIST = [
753
883
  {
754
884
  id: "timeline",
755
- label: "Timeline"
885
+ label: "Messages"
756
886
  },
757
887
  {
758
888
  id: "raw-events",
759
- label: "Raw AG-UI Events"
889
+ label: "AG-UI Events"
760
890
  },
761
891
  {
762
892
  id: "state",
@@ -799,6 +929,30 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
799
929
  if (id === "state") return this.renderState();
800
930
  return this.renderEvents();
801
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
+ }
802
956
  activateTab(id) {
803
957
  if (this._tab === id) return;
804
958
  const isFirstActivation = !this._activatedTabs.has(id);
@@ -888,7 +1042,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
888
1042
  border-bottom: 2px solid transparent;
889
1043
  cursor: pointer;
890
1044
  background: transparent;
891
- color: #838389;
1045
+ color: #68686e;
892
1046
  transition:
893
1047
  color 0.12s,
894
1048
  border-color 0.12s;
@@ -899,6 +1053,12 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
899
1053
  color: #010507;
900
1054
  }
901
1055
 
1056
+ .cpk-td__tab:focus-visible {
1057
+ outline: 2px solid #5558b2;
1058
+ outline-offset: -3px;
1059
+ border-radius: 4px;
1060
+ }
1061
+
902
1062
  .cpk-td__tab--active {
903
1063
  color: #010507;
904
1064
  border-bottom-color: #bec2ff;
@@ -918,7 +1078,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
918
1078
  border: none;
919
1079
  border-left: 1px solid #dbdbe5;
920
1080
  background: transparent;
921
- color: #838389;
1081
+ color: #68686e;
922
1082
  cursor: pointer;
923
1083
  flex-shrink: 0;
924
1084
  transition:
@@ -987,7 +1147,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
987
1147
  }
988
1148
 
989
1149
  .cpk-td__metadata-label {
990
- color: #838389;
1150
+ color: #68686e;
991
1151
  text-transform: uppercase;
992
1152
  font-size: 9px;
993
1153
  }
@@ -997,6 +1157,18 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
997
1157
  text-overflow: ellipsis;
998
1158
  }
999
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
+
1000
1172
  /*
1001
1173
  * Each tab's content is wrapped in this panel so the keep-mounted
1002
1174
  * inactive panels can be hidden via display:none without disturbing
@@ -1014,6 +1186,10 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1014
1186
  flex-shrink: 0;
1015
1187
  }
1016
1188
 
1189
+ .cpk-td__panel[hidden] {
1190
+ display: none;
1191
+ }
1192
+
1017
1193
  /* ── Empty state ─────────────────────────────────────────────────── */
1018
1194
  .cpk-td__empty-state {
1019
1195
  flex: 1;
@@ -1022,14 +1198,14 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1022
1198
  align-items: center;
1023
1199
  justify-content: center;
1024
1200
  gap: 8px;
1025
- color: #838389;
1201
+ color: #68686e;
1026
1202
  font-size: 13px;
1027
1203
  padding: 40px 0;
1028
1204
  }
1029
1205
 
1030
1206
  .cpk-td__empty-hint {
1031
1207
  font-size: 11px;
1032
- color: #838389;
1208
+ color: #68686e;
1033
1209
  text-align: center;
1034
1210
  max-width: 220px;
1035
1211
  line-height: 1.5;
@@ -1039,7 +1215,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1039
1215
  .cpk-td__status {
1040
1216
  padding: 16px;
1041
1217
  font-size: 12px;
1042
- color: #838389;
1218
+ color: #68686e;
1043
1219
  text-align: center;
1044
1220
  }
1045
1221
 
@@ -1118,7 +1294,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1118
1294
  font-family: "Spline Sans Mono", monospace;
1119
1295
  font-size: 10px;
1120
1296
  font-weight: 500;
1121
- color: #189370;
1297
+ color: #087653;
1122
1298
  text-transform: uppercase;
1123
1299
  flex: 1;
1124
1300
  }
@@ -1127,15 +1303,15 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1127
1303
  font-family: "Spline Sans Mono", monospace;
1128
1304
  font-size: 9px;
1129
1305
  text-transform: uppercase;
1130
- color: #189370;
1306
+ color: #087653;
1131
1307
  }
1132
1308
 
1133
1309
  .cpk-td__tool-status--pending {
1134
- color: #996300;
1310
+ color: #8a5900;
1135
1311
  }
1136
1312
 
1137
1313
  .cpk-td__tool-chevron {
1138
- color: #838389;
1314
+ color: #68686e;
1139
1315
  font-size: 10px;
1140
1316
  }
1141
1317
 
@@ -1149,7 +1325,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1149
1325
  font-family: "Spline Sans Mono", monospace;
1150
1326
  font-size: 9px;
1151
1327
  font-weight: 500;
1152
- color: #838389;
1328
+ color: #68686e;
1153
1329
  text-transform: uppercase;
1154
1330
  margin-bottom: 4px;
1155
1331
  letter-spacing: 0.3px;
@@ -1181,7 +1357,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1181
1357
  background: rgba(133, 236, 206, 0.15);
1182
1358
  font-family: "Spline Sans Mono", monospace;
1183
1359
  font-size: 10px;
1184
- color: #189370;
1360
+ color: #087653;
1185
1361
  text-transform: uppercase;
1186
1362
  font-weight: 500;
1187
1363
  border-bottom: 1px solid #e9e9ef;
@@ -1203,7 +1379,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1203
1379
  align-items: center;
1204
1380
  gap: 8px;
1205
1381
  padding: 5px 0;
1206
- color: #838389;
1382
+ color: #68686e;
1207
1383
  font-family: "Spline Sans Mono", monospace;
1208
1384
  font-size: 9px;
1209
1385
  text-transform: uppercase;
@@ -1260,7 +1436,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1260
1436
  .cpk-td__timeline-time {
1261
1437
  font-family: "Spline Sans Mono", monospace;
1262
1438
  font-size: 9px;
1263
- color: #838389;
1439
+ color: #68686e;
1264
1440
  flex-shrink: 0;
1265
1441
  }
1266
1442
 
@@ -1438,7 +1614,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1438
1614
  .cpk-td__event-time {
1439
1615
  font-family: "Spline Sans Mono", monospace;
1440
1616
  font-size: 9px;
1441
- color: #838389;
1617
+ color: #68686e;
1442
1618
  }
1443
1619
 
1444
1620
  .cpk-td__event-payload {
@@ -1510,7 +1686,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1510
1686
  font-family: "Spline Sans Mono", monospace;
1511
1687
  font-size: 10px;
1512
1688
  font-weight: 500;
1513
- color: #838389;
1689
+ color: #68686e;
1514
1690
  text-transform: uppercase;
1515
1691
  letter-spacing: 0.6px;
1516
1692
  margin-bottom: 8px;
@@ -1531,7 +1707,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1531
1707
  }
1532
1708
 
1533
1709
  .cpk-tdp__label {
1534
- color: #838389;
1710
+ color: #68686e;
1535
1711
  font-size: 11px;
1536
1712
  white-space: nowrap;
1537
1713
  flex-shrink: 0;
@@ -1560,6 +1736,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1560
1736
  `;
1561
1737
  }
1562
1738
  updated(_changed) {
1739
+ if (!this.isConnected) return;
1563
1740
  const loadKey = this.currentLoadKey();
1564
1741
  if (loadKey !== this._lastLoadKey) {
1565
1742
  this._lastLoadKey = loadKey;
@@ -1582,6 +1759,16 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
1582
1759
  this.fetchMessages(this.threadId, true);
1583
1760
  }
1584
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
+ }
1585
1772
  canFetchMessages() {
1586
1773
  return !!this.provider?.getMessages || !!this.runtimeUrl && this.threadInspectionAvailable;
1587
1774
  }
@@ -2222,12 +2409,22 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
2222
2409
  <div class="cpk-td__left">
2223
2410
  <!-- Tab bar -->
2224
2411
  <div class="cpk-td__tabs-header">
2225
- <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
+ >
2226
2417
  ${CpkThreadInspector.TAB_LIST.map((tab) => lit.html`
2227
2418
  <button
2419
+ id=${this.tabDomId(tab.id)}
2420
+ type="button"
2228
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"}
2229
2425
  class="cpk-td__tab ${this._tab === tab.id ? "cpk-td__tab--active" : ""}"
2230
2426
  @click=${() => this.activateTab(tab.id)}
2427
+ @keydown=${(event) => this.handleTabKeyDown(event, tab.id)}
2231
2428
  >
2232
2429
  ${tab.label}
2233
2430
  </button>
@@ -2243,8 +2440,11 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
2243
2440
  <div class="cpk-td__status">Loading…</div>
2244
2441
  ` : lit.nothing}
2245
2442
  ${CpkThreadInspector.TAB_LIST.map((tab) => this._activatedTabs.has(tab.id) ? lit.html`<div
2443
+ id=${this.panelDomId(tab.id)}
2246
2444
  class="cpk-td__panel"
2247
- 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}
2248
2448
  >
2249
2449
  ${this.renderTabContent(tab.id)}
2250
2450
  </div>` : lit.nothing)}
@@ -2279,34 +2479,53 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
2279
2479
  }
2280
2480
  renderMetadataStrip() {
2281
2481
  const metadata = this.metadata;
2282
- const pills = [
2283
- {
2284
- label: "Thread",
2285
- value: metadata?.id ?? this.threadId
2286
- },
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 [
2287
2491
  {
2288
2492
  label: "Agent",
2289
2493
  value: metadata?.agentId
2290
2494
  },
2291
2495
  {
2292
- label: "End user",
2293
- value: metadata?.endUserId ?? metadata?.createdById
2496
+ label: "Created",
2497
+ value: metadata?.createdAt
2294
2498
  },
2295
2499
  {
2296
- label: "Status",
2297
- value: metadata?.status
2500
+ label: "Updated",
2501
+ value: metadata?.updatedAt
2298
2502
  }
2299
- ].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
+ }
2300
2510
  const bulkControls = this.renderActiveBulkControls();
2301
- if (pills.length === 0 && bulkControls === lit.nothing) return lit.nothing;
2302
2511
  return lit.html`
2303
- <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
+ >
2304
2517
  <div class="cpk-td__metadata-pills">
2305
2518
  ${pills.map((pill) => lit.html`
2306
- <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
+ >
2307
2525
  <span class="cpk-td__metadata-label">${pill.label}</span>
2308
- <span class="cpk-td__metadata-value"
2309
- >${this.shortId(pill.value)}</span
2526
+ <span
2527
+ class="cpk-td__metadata-value ${pill.wrap ? "cpk-td__metadata-value--wrap" : ""}"
2528
+ >${pill.value}</span
2310
2529
  >
2311
2530
  </span>
2312
2531
  `)}
@@ -2401,8 +2620,8 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
2401
2620
  <div class="cpk-td__empty-state">
2402
2621
  <span>No timeline events captured</span>
2403
2622
  <span class="cpk-td__empty-hint"
2404
- >Timeline rows are normalized from AG-UI events. Open Raw AG-UI Events or
2405
- 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
2406
2625
  >
2407
2626
  </div>
2408
2627
  `;
@@ -2576,7 +2795,7 @@ var CpkThreadInspector = class CpkThreadInspector extends lit.LitElement {
2576
2795
  <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
2577
2796
  <path
2578
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"
2579
- stroke="#189370"
2798
+ stroke="#087653"
2580
2799
  stroke-width="1.2"
2581
2800
  stroke-linecap="round"
2582
2801
  stroke-linejoin="round"
@@ -2808,7 +3027,7 @@ ${(0, lit_directives_unsafe_html_js.unsafeHTML)(highlightedJson(stateValue))}</p
2808
3027
  <div class="cpk-tdp__row">
2809
3028
  <span class="cpk-tdp__label">ID</span>
2810
3029
  <span class="cpk-tdp__value cpk-tdp__value--wrap"
2811
- >${this.shortId(metadata?.id)}</span
3030
+ >${metadata?.id ?? this.threadId ?? "—"}</span
2812
3031
  >
2813
3032
  </div>
2814
3033
  <div class="cpk-tdp__row">
@@ -3048,7 +3267,7 @@ var CpkMemoryList = class extends lit.LitElement {
3048
3267
  font-family: "Spline Sans Mono", monospace;
3049
3268
  font-size: 9px;
3050
3269
  margin-left: 4px;
3051
- color: #838389;
3270
+ color: #68686e;
3052
3271
  }
3053
3272
 
3054
3273
  /* ── List ── */
@@ -3115,7 +3334,7 @@ var CpkMemoryList = class extends lit.LitElement {
3115
3334
  font-weight: 500;
3116
3335
  white-space: nowrap;
3117
3336
  background: #f0f0f5;
3118
- color: #838389;
3337
+ color: #68686e;
3119
3338
  }
3120
3339
 
3121
3340
  /* Content */
@@ -3137,7 +3356,7 @@ var CpkMemoryList = class extends lit.LitElement {
3137
3356
 
3138
3357
  .cpk-ml__footer-threads {
3139
3358
  font-size: 10px;
3140
- color: #838389;
3359
+ color: #68686e;
3141
3360
  }
3142
3361
 
3143
3362
  .cpk-ml__footer-id {
@@ -3150,7 +3369,7 @@ var CpkMemoryList = class extends lit.LitElement {
3150
3369
  .cpk-ml__empty {
3151
3370
  padding: 32px 16px;
3152
3371
  text-align: center;
3153
- color: #838389;
3372
+ color: #68686e;
3154
3373
  font-size: 12px;
3155
3374
  display: flex;
3156
3375
  flex-direction: column;
@@ -3231,7 +3450,7 @@ var CpkMemoryList = class extends lit.LitElement {
3231
3450
  .cpk-ml__recall-clear {
3232
3451
  font-family: "Plus Jakarta Sans", sans-serif;
3233
3452
  font-size: 10px;
3234
- color: #838389;
3453
+ color: #68686e;
3235
3454
  background: none;
3236
3455
  border: none;
3237
3456
  cursor: pointer;
@@ -3242,7 +3461,7 @@ var CpkMemoryList = class extends lit.LitElement {
3242
3461
  }
3243
3462
  .cpk-ml__recall-msg {
3244
3463
  font-size: 11px;
3245
- color: #838389;
3464
+ color: #68686e;
3246
3465
  line-height: 1.45;
3247
3466
  }
3248
3467
  .cpk-ml__recall-msg--error {
@@ -3266,7 +3485,7 @@ var CpkMemoryList = class extends lit.LitElement {
3266
3485
  /* ── Scope badge variants ── */
3267
3486
  .cpk-ml__scope-badge--user {
3268
3487
  background: #f0f0f5;
3269
- color: #838389;
3488
+ color: #68686e;
3270
3489
  }
3271
3490
  .cpk-ml__scope-badge--project {
3272
3491
  background: #fef3c7;
@@ -3485,6 +3704,8 @@ var WebInspectorElement = class extends lit.LitElement {
3485
3704
  this._recallQuery = "";
3486
3705
  this._recallSeq = 0;
3487
3706
  this.runtimeStatus = null;
3707
+ this.inspectorMetadataProjection = require_inspector_metadata.projectInspectorMetadata(void 0, void 0);
3708
+ this.metadataTelemetryFingerprints = /* @__PURE__ */ new Map();
3488
3709
  this.coreProperties = {};
3489
3710
  this.lastCoreError = null;
3490
3711
  this.agentSubscriptions = /* @__PURE__ */ new Map();
@@ -3506,8 +3727,19 @@ var WebInspectorElement = class extends lit.LitElement {
3506
3727
  this.isOpen = false;
3507
3728
  this.draggedDuringInteraction = false;
3508
3729
  this.ignoreNextButtonClick = false;
3509
- 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;
3510
3740
  this.selectedThreadId = null;
3741
+ this.selectedRealThreadIsExplicit = false;
3742
+ this.selectedLocalExampleThreadId = null;
3511
3743
  this.threadListWidth = 290;
3512
3744
  this.threadDividerResizing = false;
3513
3745
  this.threadDividerPointerId = -1;
@@ -3517,7 +3749,10 @@ var WebInspectorElement = class extends lit.LitElement {
3517
3749
  this._threadStoreSubscriptions = /* @__PURE__ */ new Map();
3518
3750
  this._threadsByAgent = /* @__PURE__ */ new Map();
3519
3751
  this._threadsErrorByAgent = /* @__PURE__ */ new Map();
3752
+ this._threadsLoadingByAgent = /* @__PURE__ */ new Map();
3520
3753
  this._ownedThreadStores = /* @__PURE__ */ new Map();
3754
+ this.threadCapabilityEnabled = null;
3755
+ this.threadCapabilityGeneration = 0;
3521
3756
  this.contextMenuOpen = false;
3522
3757
  this.dockMode = "floating";
3523
3758
  this.previousBodyMargins = null;
@@ -3550,18 +3785,28 @@ var WebInspectorElement = class extends lit.LitElement {
3550
3785
  this.pendingBannerViewed = [];
3551
3786
  this.clickedBannerIds = /* @__PURE__ */ new Set();
3552
3787
  this.viewedThreadsTelemetryStates = /* @__PURE__ */ new Set();
3553
- this.viewedExampleThreadIds = /* @__PURE__ */ new Set();
3554
- this.selectedExampleThreadIds = /* @__PURE__ */ new Set();
3788
+ this.viewedExampleKinds = /* @__PURE__ */ new Set();
3789
+ this.selectedExampleKinds = /* @__PURE__ */ new Set();
3555
3790
  this.viewedExampleTourSteps = /* @__PURE__ */ new Set();
3556
3791
  this.exampleThreadProviders = /* @__PURE__ */ new Map();
3557
3792
  this.exampleTourDismissed = false;
3558
3793
  this.exampleTourActive = false;
3559
3794
  this.exampleTourStep = 0;
3560
3795
  this.exampleTourAutoShown = false;
3561
- this.threadsExampleOverviewVideoShouldLoad = false;
3562
- this.threadsExampleOverviewVideoReady = false;
3796
+ this.threadsExampleOverviewVideoState = "deferred";
3797
+ this.threadsExampleOverviewVideoLoaded = false;
3798
+ this.threadsExampleOverviewVideoReducedMotion = false;
3563
3799
  this.threadsExampleOverviewVideoLoadTimer = null;
3564
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;
3565
3810
  this.contextState = {
3566
3811
  button: {
3567
3812
  position: {
@@ -3608,7 +3853,7 @@ var WebInspectorElement = class extends lit.LitElement {
3608
3853
  const target = event.currentTarget;
3609
3854
  const context = target?.dataset.dragContext === "window" ? "window" : "button";
3610
3855
  const eventTarget = event.target;
3611
- if (context === "window" && eventTarget?.closest("button")) return;
3856
+ if (context === "window" && eventTarget?.closest("button, a, nav")) return;
3612
3857
  this.pointerContext = context;
3613
3858
  this.measureContext(context);
3614
3859
  event.preventDefault();
@@ -3753,8 +3998,10 @@ var WebInspectorElement = class extends lit.LitElement {
3753
3998
  this.measureContext("button");
3754
3999
  this.applyAnchorPosition("button");
3755
4000
  this.measureContext("window");
4001
+ this.contextState.window.size = this.clampWindowSize(this.contextState.window.size);
3756
4002
  if (this.hasCustomPosition.window) this.applyAnchorPosition("window");
3757
4003
  else this.centerContext("window");
4004
+ this.requestUpdate();
3758
4005
  this.updateHostTransform();
3759
4006
  };
3760
4007
  this.contextOptions = [{
@@ -3767,26 +4014,32 @@ var WebInspectorElement = class extends lit.LitElement {
3767
4014
  this.expandedTools = /* @__PURE__ */ new Set();
3768
4015
  this.expandedContextItems = /* @__PURE__ */ new Set();
3769
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
+ };
3770
4032
  this.handleTalkToEngineerClick = () => {
3771
4033
  if (this.core?.telemetryDisabled) return;
3772
- require_telemetry.trackTalkToEngineerClicked(this.getThreadsTelemetryProps({
3773
- cta: "talk_to_engineer",
3774
- cta_surface: "threads_header"
3775
- }, { includeUrlAttribution: true }));
4034
+ require_telemetry.trackTalkToEngineerClicked(this.getThreadsCtaTelemetryProps("talk_to_engineer", "threads_header"));
3776
4035
  };
3777
4036
  this.handleThreadsIntelligenceSignupClick = () => {
3778
4037
  if (this.core?.telemetryDisabled) return;
3779
- require_telemetry.trackThreadsIntelligenceSignupClicked(this.getThreadsTelemetryProps({
3780
- cta: "signup",
3781
- cta_surface: "threads_locked"
3782
- }, { includeUrlAttribution: true }));
4038
+ require_telemetry.trackThreadsIntelligenceSignupClicked(this.getThreadsCtaTelemetryProps("signup", "threads_locked"));
3783
4039
  };
3784
4040
  this.handleThreadsTalkToEngineerClick = () => {
3785
4041
  if (this.core?.telemetryDisabled) return;
3786
- require_telemetry.trackThreadsTalkToEngineerClicked(this.getThreadsTelemetryProps({
3787
- cta: "talk_to_engineer",
3788
- cta_surface: "threads_locked"
3789
- }, { includeUrlAttribution: true }));
4042
+ require_telemetry.trackThreadsTalkToEngineerClicked(this.getThreadsCtaTelemetryProps("talk_to_engineer", "threads_locked"));
3790
4043
  };
3791
4044
  this.handleThreadDividerPointerDown = (event) => {
3792
4045
  this.threadDividerResizing = true;
@@ -3808,9 +4061,51 @@ var WebInspectorElement = class extends lit.LitElement {
3808
4061
  if (target.hasPointerCapture(this.threadDividerPointerId)) target.releasePointerCapture(this.threadDividerPointerId);
3809
4062
  this.threadDividerResizing = false;
3810
4063
  };
3811
- this.handleThreadsExampleOverviewVideoLoaded = () => {
3812
- 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";
3813
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
+ }
3814
4109
  };
3815
4110
  this.handleClearEvents = () => {
3816
4111
  if (this.selectedContext === "all-agents") {
@@ -3883,7 +4178,11 @@ var WebInspectorElement = class extends lit.LitElement {
3883
4178
  const oldValue = this._core;
3884
4179
  if (oldValue === value) return;
3885
4180
  this.detachFromCore();
4181
+ const hadResolvedCore = this.hasResolvedCore;
3886
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();
3887
4186
  this.requestUpdate("core", oldValue);
3888
4187
  if (this._core) this.attachToCore(this._core);
3889
4188
  }
@@ -3929,36 +4228,141 @@ var WebInspectorElement = class extends lit.LitElement {
3929
4228
  }
3930
4229
  ];
3931
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
+ }
3932
4268
  getThreadServiceStatus() {
3933
4269
  if (!this._core) return "unknown";
3934
4270
  if (!this._core.threadEndpoints) return "unknown";
3935
4271
  return this._core.threadEndpoints?.list === false ? "unavailable" : "available";
3936
4272
  }
3937
4273
  areThreadEndpointsAvailable() {
3938
- 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();
3939
4298
  }
3940
4299
  getActiveThreadsState() {
3941
4300
  const displayThreads = this.selectedContext === "all-agents" ? this._threads : this._threadsByAgent.get(this.selectedContext) ?? [];
3942
4301
  let threadsErrorMessage = null;
3943
4302
  if (this.selectedContext === "all-agents") threadsErrorMessage = this._threadsErrorByAgent.values().next().value?.message ?? null;
3944
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;
3945
4305
  return {
3946
4306
  displayThreads,
3947
- threadsErrorMessage
4307
+ threadsErrorMessage,
4308
+ threadsLoading
3948
4309
  };
3949
4310
  }
3950
- getThreadsTelemetryProps(extra = {}, options = {}) {
3951
- 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() {
3952
4343
  const threadServiceStatus = this.getThreadServiceStatus();
3953
4344
  return {
3954
- posthog_distinct_id: distinctId ?? void 0,
3955
4345
  intelligence_status: threadServiceStatus === "available" ? "intelligence_enabled" : threadServiceStatus === "unavailable" ? "intelligence_not_enabled" : "unknown",
3956
4346
  thread_service_status: threadServiceStatus,
3957
4347
  license_status: this.core?.licenseStatus ?? void 0,
3958
4348
  runtime_mode: this.core?.runtimeMode ?? void 0,
3959
4349
  runtime_url_type: require_telemetry.getRuntimeUrlType(this.core?.runtimeUrl),
3960
- telemetry_disabled: this.core?.telemetryDisabled ?? false,
3961
- ...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
3962
4366
  };
3963
4367
  }
3964
4368
  getMemoriesTelemetryProps() {
@@ -3971,9 +4375,6 @@ var WebInspectorElement = class extends lit.LitElement {
3971
4375
  getIntelligenceSignupUrl() {
3972
4376
  return this.appendRefParam(INTELLIGENCE_SIGNUP_URL, "cpk-inspector");
3973
4377
  }
3974
- getThreadsIntelligenceSignupUrl() {
3975
- return this.appendRefParam(THREADS_INTELLIGENCE_SIGNIN_URL, "cpk-inspector");
3976
- }
3977
4378
  getTalkToEngineerUrl() {
3978
4379
  return this.appendRefParam(TALK_TO_ENGINEER_URL, "cpk-inspector-threads");
3979
4380
  }
@@ -3983,29 +4384,53 @@ var WebInspectorElement = class extends lit.LitElement {
3983
4384
  getThreadsDocsUrl() {
3984
4385
  return this.appendRefParam(THREADS_DOCS_URL, "cpk-inspector-threads");
3985
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
+ }
3986
4393
  getSelfHostedIntelligenceUrl() {
3987
4394
  return this.appendRefParam(SELF_HOSTED_INTELLIGENCE_URL, "cpk-inspector-threads");
3988
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
+ }
3989
4408
  subscribeToThreadStore(agentId, store) {
3990
4409
  if (!this.areThreadEndpointsAvailable()) return;
3991
4410
  if (this._threadStoreSubscriptions.has(agentId)) return;
4411
+ const capabilityGeneration = this.threadCapabilityGeneration;
3992
4412
  const threadsSub = store.select(_copilotkit_core.ɵselectThreads).subscribe((threads) => {
4413
+ if (capabilityGeneration !== this.threadCapabilityGeneration || !this.areThreadEndpointsAvailable()) return;
3993
4414
  this._threadsByAgent.set(agentId, threads);
3994
4415
  this._threads = Array.from(this._threadsByAgent.values()).flat();
3995
4416
  this.autoSelectLatestThread();
3996
4417
  this.requestUpdate();
3997
4418
  });
3998
- 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;
3999
4421
  if (error) this._threadsErrorByAgent.set(agentId, error);
4000
4422
  else this._threadsErrorByAgent.delete(agentId);
4423
+ this._threadsLoadingByAgent.set(agentId, isLoading);
4001
4424
  this.requestUpdate();
4002
4425
  });
4003
4426
  this._threadStoreSubscriptions.set(agentId, () => {
4004
4427
  threadsSub.unsubscribe();
4005
- errorSub.unsubscribe();
4428
+ statusSub.unsubscribe();
4006
4429
  });
4430
+ if (capabilityGeneration !== this.threadCapabilityGeneration || !this.areThreadEndpointsAvailable()) return;
4007
4431
  const initialState = store.getState();
4008
4432
  this._threadsByAgent.set(agentId, (0, _copilotkit_core.ɵselectThreads)(initialState));
4433
+ this._threadsLoadingByAgent.set(agentId, (0, _copilotkit_core.ɵselectThreadsIsLoading)(initialState));
4009
4434
  const initialError = (0, _copilotkit_core.ɵselectThreadsError)(initialState);
4010
4435
  if (initialError) this._threadsErrorByAgent.set(agentId, initialError);
4011
4436
  else this._threadsErrorByAgent.delete(agentId);
@@ -4013,22 +4438,37 @@ var WebInspectorElement = class extends lit.LitElement {
4013
4438
  this.autoSelectLatestThread();
4014
4439
  }
4015
4440
  autoSelectLatestThread() {
4016
- if (this._threads.length === 0) return;
4017
- 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;
4018
4457
  }
4019
4458
  teardownThreadStoreSubscriptions() {
4020
4459
  for (const unsub of this._threadStoreSubscriptions.values()) unsub();
4021
4460
  this._threadStoreSubscriptions.clear();
4022
4461
  this._threadsByAgent.clear();
4023
4462
  this._threadsErrorByAgent.clear();
4463
+ this._threadsLoadingByAgent.clear();
4024
4464
  this._threads = [];
4025
4465
  }
4026
4466
  ensureOwnedThreadStore(agentId) {
4467
+ if (!this.areThreadEndpointsAvailable()) return;
4027
4468
  if (this._ownedThreadStores.has(agentId)) return;
4028
4469
  if (this.core?.getThreadStore(agentId)) return;
4029
4470
  const core = this.core;
4030
4471
  if (!core?.runtimeUrl) return;
4031
- if (!this.areThreadEndpointsAvailable()) return;
4032
4472
  const store = (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch });
4033
4473
  store.start();
4034
4474
  store.setContext({
@@ -4042,11 +4482,13 @@ var WebInspectorElement = class extends lit.LitElement {
4042
4482
  core.registerThreadStore(agentId, store);
4043
4483
  }
4044
4484
  refreshOwnedThreadStore(agentId) {
4485
+ if (!this.areThreadEndpointsAvailable()) return;
4045
4486
  const store = this._ownedThreadStores.get(agentId);
4046
4487
  if (!store) return;
4047
4488
  store.refresh();
4048
4489
  }
4049
4490
  updateOwnedThreadStoreHeaders(headers) {
4491
+ if (!this.areThreadEndpointsAvailable()) return;
4050
4492
  const core = this.core;
4051
4493
  if (!core?.runtimeUrl) return;
4052
4494
  for (const [agentId, store] of this._ownedThreadStores) store.setContext({
@@ -4059,32 +4501,62 @@ var WebInspectorElement = class extends lit.LitElement {
4059
4501
  removeOwnedThreadStore(agentId) {
4060
4502
  const store = this._ownedThreadStores.get(agentId);
4061
4503
  if (!store) return;
4062
- store.stop();
4063
- this.core?.unregisterThreadStore(agentId);
4064
4504
  this._ownedThreadStores.delete(agentId);
4505
+ store.stop();
4506
+ if (this.core?.getThreadStore(agentId) === store) this.core.unregisterThreadStore(agentId);
4065
4507
  }
4066
4508
  teardownOwnedThreadStores() {
4067
- 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) {
4068
4512
  store.stop();
4069
- this.core?.unregisterThreadStore(agentId);
4513
+ if (this.core?.getThreadStore(agentId) === store) this.core.unregisterThreadStore(agentId);
4070
4514
  }
4071
- 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);
4072
4540
  }
4073
4541
  attachToCore(core) {
4074
4542
  this.runtimeStatus = core.runtimeConnectionStatus;
4075
4543
  this.coreProperties = core.properties;
4076
4544
  this.lastCoreError = null;
4545
+ const supportsInspectorMetadata = this.coreSupportsInspectorMetadata(core);
4546
+ this.updateInspectorMetadataProjection(this.readCoreInspectorMetadata(core));
4077
4547
  this.coreSubscriber = {
4078
4548
  onRuntimeConnectionStatusChanged: ({ status }) => {
4079
4549
  this.runtimeStatus = status;
4550
+ this.updateInspectorMetadataProjection(this.inspectorMetadataValue);
4551
+ const threadCapabilityWasEnabled = this.threadCapabilityEnabled === true;
4552
+ this.synchronizeThreadCapability();
4080
4553
  if (status === "connected") {
4081
4554
  if (!core.telemetryDisabled) {
4082
4555
  require_telemetry.ensureTelemetryDistinctId();
4083
4556
  require_telemetry.maybeShowDisclosure();
4084
4557
  }
4085
4558
  this.flushPendingBannerViewed();
4086
- if (this.areThreadEndpointsAvailable()) for (const agentId of this._ownedThreadStores.keys()) this.refreshOwnedThreadStore(agentId);
4087
- else this.teardownOwnedThreadStores();
4559
+ if (threadCapabilityWasEnabled && this.areThreadEndpointsAvailable()) for (const agentId of this._ownedThreadStores.keys()) this.refreshOwnedThreadStore(agentId);
4088
4560
  } else {
4089
4561
  this._threadsByAgent.clear();
4090
4562
  this._threads = [];
@@ -4099,6 +4571,11 @@ var WebInspectorElement = class extends lit.LitElement {
4099
4571
  this.updateOwnedThreadStoreHeaders(headers);
4100
4572
  this.requestUpdate();
4101
4573
  },
4574
+ ...supportsInspectorMetadata ? { onInspectorMetadataChanged: ({ inspectorMetadata }) => {
4575
+ if (this._core !== core) return;
4576
+ this.updateInspectorMetadataProjection(inspectorMetadata);
4577
+ this.requestUpdate();
4578
+ } } : {},
4102
4579
  onError: ({ code, error }) => {
4103
4580
  this.lastCoreError = {
4104
4581
  code,
@@ -4117,18 +4594,25 @@ var WebInspectorElement = class extends lit.LitElement {
4117
4594
  this.requestUpdate();
4118
4595
  },
4119
4596
  onThreadStoreRegistered: ({ agentId, store }) => {
4597
+ if (!this.areThreadEndpointsAvailable()) return;
4120
4598
  this.subscribeToThreadStore(agentId, store);
4121
4599
  this.requestUpdate();
4122
4600
  },
4123
- onThreadStoreUnregistered: ({ agentId }) => {
4601
+ onThreadStoreUnregistered: ({ agentId, prevStore }) => {
4124
4602
  const unsub = this._threadStoreSubscriptions.get(agentId);
4125
4603
  if (unsub) {
4126
4604
  unsub();
4127
4605
  this._threadStoreSubscriptions.delete(agentId);
4128
4606
  }
4607
+ if (this._ownedThreadStores.get(agentId) === prevStore) {
4608
+ this._ownedThreadStores.delete(agentId);
4609
+ prevStore.stop();
4610
+ }
4129
4611
  this._threadsByAgent.delete(agentId);
4130
4612
  this._threadsErrorByAgent.delete(agentId);
4613
+ this._threadsLoadingByAgent.delete(agentId);
4131
4614
  this._threads = Array.from(this._threadsByAgent.values()).flat();
4615
+ this.autoSelectLatestThread();
4132
4616
  this.requestUpdate();
4133
4617
  }
4134
4618
  };
@@ -4142,7 +4626,7 @@ var WebInspectorElement = class extends lit.LitElement {
4142
4626
  this.flushPendingBannerViewed();
4143
4627
  }
4144
4628
  const threadStores = typeof core.getThreadStores === "function" ? core.getThreadStores() : {};
4145
- 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);
4146
4630
  if (core.context) this.contextStore = this.normalizeContextStore(core.context);
4147
4631
  if (this.selectedMenu === "memories") this.ensureMemorySubscription();
4148
4632
  }
@@ -4244,6 +4728,13 @@ var WebInspectorElement = class extends lit.LitElement {
4244
4728
  this.requestUpdate();
4245
4729
  }
4246
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;
4247
4738
  if (this.coreUnsubscribe) {
4248
4739
  this.coreUnsubscribe();
4249
4740
  this.coreUnsubscribe = null;
@@ -4264,6 +4755,9 @@ var WebInspectorElement = class extends lit.LitElement {
4264
4755
  this._recallQuery = "";
4265
4756
  this.coreSubscriber = null;
4266
4757
  this.runtimeStatus = null;
4758
+ this.inspectorMetadataValue = void 0;
4759
+ this.inspectorMetadataProjection = require_inspector_metadata.projectInspectorMetadata(void 0, void 0);
4760
+ this.metadataTelemetryFingerprints.clear();
4267
4761
  this.lastCoreError = null;
4268
4762
  this.coreProperties = {};
4269
4763
  this.cachedTools = [];
@@ -4282,6 +4776,7 @@ var WebInspectorElement = class extends lit.LitElement {
4282
4776
  this.eventCounter = 0;
4283
4777
  }
4284
4778
  processAgentsChanged(agents) {
4779
+ this.synchronizeThreadCapability();
4285
4780
  const seenAgentIds = /* @__PURE__ */ new Set();
4286
4781
  for (const agent of Object.values(agents)) {
4287
4782
  if (!agent?.agentId) continue;
@@ -4346,11 +4841,17 @@ var WebInspectorElement = class extends lit.LitElement {
4346
4841
  event: params.event,
4347
4842
  result: "result" in params ? params.result : void 0
4348
4843
  });
4349
- this.refreshOwnedThreadStore(agentId);
4844
+ if (this.areThreadEndpointsAvailable()) this.refreshOwnedThreadStore(agentId);
4350
4845
  },
4351
4846
  onRunErrorEvent: ({ event }) => {
4352
4847
  this.recordAgentEvent(agentId, "RUN_ERROR", event);
4353
4848
  },
4849
+ onStepStartedEvent: ({ event }) => {
4850
+ this.recordAgentEvent(agentId, "STEP_STARTED", event);
4851
+ },
4852
+ onStepFinishedEvent: ({ event }) => {
4853
+ this.recordAgentEvent(agentId, "STEP_FINISHED", event);
4854
+ },
4354
4855
  onTextMessageStartEvent: ({ event }) => {
4355
4856
  this.recordAgentEvent(agentId, "TEXT_MESSAGE_START", event);
4356
4857
  },
@@ -4506,6 +5007,7 @@ var WebInspectorElement = class extends lit.LitElement {
4506
5007
  }
4507
5008
  }
4508
5009
  updateContextOptions(agentIds) {
5010
+ let selectedContextChanged = false;
4509
5011
  const nextOptions = [{
4510
5012
  key: "all-agents",
4511
5013
  label: "All Agents"
@@ -4519,12 +5021,14 @@ var WebInspectorElement = class extends lit.LitElement {
4519
5021
  if ((pendingContext === "all-agents" || agentIds.has(pendingContext)) && (pendingContext === "all-agents" || agentIds.size === 1)) {
4520
5022
  if (this.selectedContext !== pendingContext) {
4521
5023
  this.selectedContext = pendingContext;
5024
+ selectedContextChanged = true;
4522
5025
  this.expandedRows.clear();
4523
5026
  }
4524
5027
  this.pendingSelectedContext = null;
4525
5028
  } else if (agentIds.size > 0) {
4526
5029
  if (this.selectedContext !== "all-agents") {
4527
5030
  this.selectedContext = "all-agents";
5031
+ selectedContextChanged = true;
4528
5032
  this.expandedRows.clear();
4529
5033
  }
4530
5034
  this.pendingSelectedContext = null;
@@ -4535,10 +5039,12 @@ var WebInspectorElement = class extends lit.LitElement {
4535
5039
  if (agentIds.size === 1) nextSelected = Array.from(agentIds)[0];
4536
5040
  if (this.selectedContext !== nextSelected) {
4537
5041
  this.selectedContext = nextSelected;
5042
+ selectedContextChanged = true;
4538
5043
  this.expandedRows.clear();
4539
5044
  this.persistState();
4540
5045
  }
4541
5046
  }
5047
+ if (selectedContextChanged) this.autoSelectLatestThread();
4542
5048
  }
4543
5049
  getEventsForSelectedContext() {
4544
5050
  if (this.selectedContext === "all-agents") return this.flattenedEvents;
@@ -4859,7 +5365,7 @@ ${argsString}</pre
4859
5365
  align-items: center;
4860
5366
  justify-content: center;
4861
5367
  border-radius: 6px;
4862
- color: #838389;
5368
+ color: #68686e;
4863
5369
  cursor: pointer;
4864
5370
  transition:
4865
5371
  background 120ms ease,
@@ -4880,7 +5386,7 @@ ${argsString}</pre
4880
5386
  background: none;
4881
5387
  border: none;
4882
5388
  cursor: pointer;
4883
- color: #838389;
5389
+ color: #68686e;
4884
5390
  width: 28px;
4885
5391
  height: 28px;
4886
5392
  display: flex;
@@ -4929,7 +5435,7 @@ ${argsString}</pre
4929
5435
  height: 24px;
4930
5436
  border-radius: 50%;
4931
5437
  background-color: #f0f0f4;
4932
- color: #838389;
5438
+ color: #68686e;
4933
5439
  flex-shrink: 0;
4934
5440
  transition: transform 0.2s;
4935
5441
  }
@@ -5046,7 +5552,7 @@ ${argsString}</pre
5046
5552
  }
5047
5553
 
5048
5554
  .announcement-content a {
5049
- color: #757cf2;
5555
+ color: #5558b2;
5050
5556
  text-decoration: underline;
5051
5557
  }
5052
5558
 
@@ -5170,7 +5676,7 @@ ${argsString}</pre
5170
5676
  font-family: "Plus Jakarta Sans", system-ui, sans-serif;
5171
5677
  font-size: 12px;
5172
5678
  font-weight: 500;
5173
- color: #757cf2;
5679
+ color: #5558b2;
5174
5680
  cursor: pointer;
5175
5681
  text-align: center;
5176
5682
  }
@@ -5241,14 +5747,14 @@ ${argsString}</pre
5241
5747
  align-items: center;
5242
5748
  }
5243
5749
  .cpk-tab-active .cpk-tab-icon {
5244
- color: #757cf2;
5750
+ color: #5558b2;
5245
5751
  }
5246
5752
  .cpk-tab-inactive {
5247
5753
  background-color: transparent;
5248
5754
  color: #2b2b2b;
5249
5755
  }
5250
5756
  .cpk-tab-inactive .cpk-tab-icon {
5251
- color: #838389;
5757
+ color: #68686e;
5252
5758
  }
5253
5759
  .cpk-tab-inactive:hover {
5254
5760
  background-color: rgba(190, 194, 255, 0.08);
@@ -5281,24 +5787,40 @@ ${argsString}</pre
5281
5787
  width: 100%;
5282
5788
  height: 100%;
5283
5789
  object-fit: cover;
5284
- opacity: 0;
5285
- transition: opacity 220ms ease;
5286
- }
5287
- .cpk-threads-overview-video[data-ready="true"] {
5288
- opacity: 1;
5289
5790
  }
5290
5791
 
5291
- /* ── Header control buttons (dock, close) — first row only ───── */
5292
- .drag-handle > div:first-child button {
5293
- 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;
5294
5796
  }
5295
- .drag-handle > div:first-child button:hover {
5296
- background-color: #f0f0f4 !important;
5297
- 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;
5298
5802
  }
5299
- .drag-handle > div:first-child button:focus-visible {
5803
+ .drag-handle > div[data-inspector-account-strip] button:hover {
5804
+ background-color: rgba(255, 255, 255, 0.12) !important;
5805
+ color: #ffffff !important;
5806
+ }
5807
+ .drag-handle > div[data-inspector-account-strip] button:focus-visible {
5300
5808
  outline-color: #bec2ff !important;
5301
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
+ }
5302
5824
 
5303
5825
  /* ── Agent/context dropdown ──────────────────────────────────── */
5304
5826
  [data-context-dropdown-root="true"] > button {
@@ -5310,7 +5832,7 @@ ${argsString}</pre
5310
5832
  background-color: #f7f7f9 !important;
5311
5833
  }
5312
5834
  [data-context-dropdown-root="true"] > button > span:last-child {
5313
- color: #838389 !important;
5835
+ color: #68686e !important;
5314
5836
  }
5315
5837
  [data-context-dropdown-root="true"] > div {
5316
5838
  border-color: #dbdbe5 !important;
@@ -5320,6 +5842,28 @@ ${argsString}</pre
5320
5842
  [data-context-dropdown-root="true"] > div button:focus {
5321
5843
  background-color: #f7f7f9 !important;
5322
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
+ }
5323
5867
 
5324
5868
  /* ── Status bar (bottom chrome) ──────────────────────────────── */
5325
5869
  .inspector-window > div > div:last-child {
@@ -5329,7 +5873,7 @@ ${argsString}</pre
5329
5873
 
5330
5874
  /* ── Resize handle ───────────────────────────────────────────── */
5331
5875
  .resize-handle {
5332
- color: #838389 !important;
5876
+ color: #68686e !important;
5333
5877
  }
5334
5878
  .resize-handle:hover {
5335
5879
  color: #57575b !important;
@@ -5350,7 +5894,7 @@ ${argsString}</pre
5350
5894
  /* Copy "copied" state: generic green → brand mint */
5351
5895
  button[class*="bg-green-100"] {
5352
5896
  background-color: rgba(133, 236, 206, 0.2) !important;
5353
- color: #189370 !important;
5897
+ color: #087653 !important;
5354
5898
  }
5355
5899
 
5356
5900
  /* ── Agents tab ──────────────────────────────────────────────── */
@@ -5359,14 +5903,14 @@ ${argsString}</pre
5359
5903
  background-color: rgba(190, 194, 255, 0.15) !important;
5360
5904
  }
5361
5905
  span[class*="text-blue-600"] {
5362
- color: #757cf2 !important;
5906
+ color: #5558b2 !important;
5363
5907
  }
5364
5908
  /* Running badge: emerald → mint */
5365
5909
  span[class*="bg-emerald-50"] {
5366
5910
  background-color: rgba(133, 236, 206, 0.15) !important;
5367
5911
  }
5368
5912
  span[class*="text-emerald-700"] {
5369
- color: #189370 !important;
5913
+ color: #087653 !important;
5370
5914
  }
5371
5915
  /* Running status dot */
5372
5916
  span[class*="bg-emerald-500"] {
@@ -5386,7 +5930,7 @@ ${argsString}</pre
5386
5930
  span[class*="bg-green-100"][class*="text-green-800"] {
5387
5931
  background-color: rgba(133, 236, 206, 0.18) !important;
5388
5932
  border: 1px solid rgba(133, 236, 206, 0.4) !important;
5389
- color: #189370 !important;
5933
+ color: #087653 !important;
5390
5934
  }
5391
5935
  /* Tool role badge (amber → orange brand) */
5392
5936
  span[class*="bg-amber-100"][class*="text-amber-800"] {
@@ -5427,13 +5971,17 @@ ${argsString}</pre
5427
5971
  > div:last-child:has(div[class*="bg-emerald-50"]) {
5428
5972
  background-color: rgba(91, 228, 187, 0.08) !important;
5429
5973
  border-top-color: rgba(91, 228, 187, 0.3) !important;
5430
- color: #189370 !important;
5974
+ color: #087653 !important;
5431
5975
  }
5432
5976
  /* Inner badge — slightly more opaque on the mint bg */
5433
5977
  div[class*="bg-emerald-50"][class*="border-emerald-200"] {
5434
5978
  background-color: rgba(91, 228, 187, 0.12) !important;
5435
5979
  border-color: rgba(91, 228, 187, 0.4) !important;
5436
- 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;
5437
5985
  }
5438
5986
  /* Icon bubble inside connected badge → mint tint */
5439
5987
  div[class*="bg-emerald-50"] span[class*="bg-white"] {
@@ -5448,7 +5996,7 @@ ${argsString}</pre
5448
5996
  span[class*="bg-slate-900"],
5449
5997
  div[class*="bg-slate-900"] {
5450
5998
  background-color: #eee6fe !important;
5451
- color: #757cf2 !important;
5999
+ color: #5558b2 !important;
5452
6000
  }
5453
6001
  span[class*="text-slate-800"],
5454
6002
  div[class*="text-slate-800"] {
@@ -5459,6 +6007,7 @@ ${argsString}</pre
5459
6007
  connectedCallback() {
5460
6008
  super.connectedCallback();
5461
6009
  if (typeof window !== "undefined") {
6010
+ this.threadsExampleOverviewVideoReducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
5462
6011
  this.ensureBrandFonts();
5463
6012
  window.addEventListener("resize", this.handleResize);
5464
6013
  window.addEventListener("pointerdown", this.handleGlobalPointerDown);
@@ -5467,6 +6016,7 @@ ${argsString}</pre
5467
6016
  this.tryAutoAttachCore();
5468
6017
  this.ensureAnnouncementLoading();
5469
6018
  }
6019
+ this.requestUpdate();
5470
6020
  }
5471
6021
  ensureBrandFonts() {
5472
6022
  const FONT_LINK_ID = "cpk-inspector-brand-fonts";
@@ -5489,14 +6039,13 @@ ${argsString}</pre
5489
6039
  clearTimeout(this.transitionTimeoutId);
5490
6040
  this.transitionTimeoutId = null;
5491
6041
  }
5492
- if (this.threadsExampleOverviewVideoLoadTimer !== null) {
5493
- window.clearTimeout(this.threadsExampleOverviewVideoLoadTimer);
5494
- this.threadsExampleOverviewVideoLoadTimer = null;
5495
- }
5496
- if (this.threadsExampleOverviewVideoIdleCallbackId !== null && typeof window.cancelIdleCallback === "function") {
5497
- window.cancelIdleCallback(this.threadsExampleOverviewVideoIdleCallbackId);
5498
- this.threadsExampleOverviewVideoIdleCallbackId = null;
6042
+ this.threadsSetupPromptCopyGeneration += 1;
6043
+ if (this.threadsSetupPromptCopyResetTimeoutId !== null) {
6044
+ window.clearTimeout(this.threadsSetupPromptCopyResetTimeoutId);
6045
+ this.threadsSetupPromptCopyResetTimeoutId = null;
5499
6046
  }
6047
+ this.threadsSetupPromptCopyState = "idle";
6048
+ this.cleanupThreadsExampleOverviewVideo();
5500
6049
  this.removeDockStyles(true);
5501
6050
  this.detachFromCore();
5502
6051
  }
@@ -5522,6 +6071,7 @@ ${argsString}</pre
5522
6071
  y: EDGE_MARGIN
5523
6072
  };
5524
6073
  this.hydrateStateFromStorage();
6074
+ this.contextState.window.size = this.clampWindowSize(this.contextState.window.size);
5525
6075
  if (this.selectedMenu === "memories") this.ensureMemorySubscription();
5526
6076
  if (this.isOpen && this.dockMode !== "floating") this.applyDockStyles(true);
5527
6077
  this.applyAnchorPosition("button");
@@ -5533,6 +6083,35 @@ ${argsString}</pre
5533
6083
  render() {
5534
6084
  return this.isOpen ? this.renderWindow() : this.renderButton();
5535
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
+ }
5536
6115
  renderButton() {
5537
6116
  return lit.html`
5538
6117
  <div class="console-button-wrapper">
@@ -5588,24 +6167,79 @@ ${argsString}</pre
5588
6167
  </button>
5589
6168
  ${this.renderAnnouncementPreview()}
5590
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>
5591
6221
  `;
5592
6222
  }
5593
6223
  renderWindow() {
5594
6224
  const windowState = this.contextState.window;
5595
6225
  const isDocked = this.dockMode !== "floating";
5596
6226
  const isTransitioning = this.hasAttribute("data-transitioning");
5597
- const windowStyles = isDocked ? this.getDockedWindowStyles() : {
6227
+ const windowStyles = isDocked ? {
6228
+ ...this.getDockedWindowStyles(),
6229
+ overflowX: "hidden"
6230
+ } : {
5598
6231
  width: `${Math.round(windowState.size.width)}px`,
5599
6232
  height: `${Math.round(windowState.size.height)}px`,
5600
6233
  minWidth: `${MIN_WINDOW_WIDTH}px`,
5601
- minHeight: `${MIN_WINDOW_HEIGHT}px`
6234
+ minHeight: `${MIN_WINDOW_HEIGHT}px`,
6235
+ overflowX: "hidden"
5602
6236
  };
5603
- const hasContextDropdown = this.contextOptions.length > 0;
6237
+ const hasContextDropdown = this.contextOptions.some((option) => option.key !== "all-agents");
5604
6238
  const contextDropdown = hasContextDropdown ? this.renderContextDropdown() : lit.nothing;
5605
6239
  const coreStatus = this.getCoreStatusSummary();
5606
6240
  const agentSelector = hasContextDropdown ? contextDropdown : lit.html`
5607
6241
  <div
5608
- 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"
5609
6243
  >
5610
6244
  <span>${this.renderIcon("Bot")}</span>
5611
6245
  <span class="truncate">No agents available</span>
@@ -5640,32 +6274,41 @@ ${argsString}</pre
5640
6274
  @pointerup=${isDocked ? void 0 : this.handlePointerUp}
5641
6275
  @pointercancel=${isDocked ? void 0 : this.handlePointerCancel}
5642
6276
  >
5643
- <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
+ >
5644
6282
  <div class="flex items-center min-w-0">
5645
6283
  <img
5646
6284
  src=${require_inspector_logo.default}
5647
6285
  alt="Inspector logo"
5648
- class="h-6 w-auto"
6286
+ class="inspector-account-logo h-6 w-auto"
5649
6287
  loading="lazy"
5650
6288
  />
5651
6289
  </div>
6290
+ ${this.renderInspectorMetadataHeader()}
5652
6291
  <div class="ml-auto flex min-w-0 items-center gap-2">
5653
- <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>
5654
6295
  <div class="flex items-center gap-1">
5655
6296
  ${this.renderDockControls()}
5656
6297
  <button
5657
- 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" : ""}"
5658
6299
  type="button"
5659
6300
  aria-label="Settings"
5660
- aria-pressed=${this.selectedMenu === "settings"}
5661
- @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}
5662
6304
  >
5663
6305
  ${this.renderIcon("Settings")}
5664
6306
  </button>
5665
6307
  <button
5666
- 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"
5667
6309
  type="button"
5668
6310
  aria-label="Close Web Inspector"
6311
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
5669
6312
  @pointerdown=${this.handleClosePointerDown}
5670
6313
  @click=${this.handleCloseClick}
5671
6314
  >
@@ -5674,37 +6317,72 @@ ${argsString}</pre
5674
6317
  </div>
5675
6318
  </div>
5676
6319
  </div>
5677
- <div
5678
- 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;"
5679
6324
  >
5680
- ${this.menuItems.map(({ key, label, icon }) => {
5681
- const isSelected = this.selectedMenu === key;
6325
+ ${INSPECTOR_PRIMARY_NAVIGATION.map(({ key, label, icon }) => {
6326
+ const isSelected = this.selectedGroup === key;
5682
6327
  return lit.html`
5683
6328
  <button
5684
6329
  type="button"
5685
- 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(" ")}
5686
- aria-pressed=${isSelected}
5687
- @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)}
5688
6336
  >
5689
- <span class="cpk-tab-icon">
5690
- ${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)}
5691
6339
  </span>
5692
6340
  <span>${label}</span>
5693
6341
  </button>
5694
6342
  `;
5695
6343
  })}
5696
- ${this.selectedMenu === "threads" ? lit.html`
6344
+ ${this.selectedGroup === "threads" ? lit.html`
5697
6345
  <a
6346
+ data-inspector-thread-cta
5698
6347
  href=${this.getThreadsTalkToEngineerUrl()}
5699
6348
  target="_blank"
5700
- rel="noopener"
5701
- 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}
5702
6352
  @click=${this.handleTalkToEngineerClick}
5703
6353
  >
5704
6354
  Talk to an Engineer
5705
6355
  </a>
5706
6356
  ` : lit.nothing}
5707
- </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}
5708
6386
  </div>
5709
6387
  <div class="flex flex-1 flex-col overflow-hidden">
5710
6388
  <div id="cpk-main-scroll" class="flex-1 overflow-auto">
@@ -5760,10 +6438,7 @@ ${argsString}</pre
5760
6438
  if (!persisted) return;
5761
6439
  if (typeof persisted.isOpen === "boolean") this.isOpen = persisted.isOpen;
5762
6440
  if (require_persistence.isValidDockMode(persisted.dockMode)) this.dockMode = persisted.dockMode;
5763
- if (typeof persisted.selectedMenu === "string") {
5764
- const validMenu = this.menuItems.find((item) => item.key === persisted.selectedMenu);
5765
- if (validMenu) this.selectedMenu = validMenu.key;
5766
- }
6441
+ this.restorePersistedMenu(persisted.selectedMenu);
5767
6442
  if (typeof persisted.selectedContext === "string") {
5768
6443
  this.selectedContext = persisted.selectedContext;
5769
6444
  this.pendingSelectedContext = persisted.selectedContext;
@@ -5773,6 +6448,7 @@ ${argsString}</pre
5773
6448
  if (typeof document === "undefined" || typeof window === "undefined") return;
5774
6449
  const persisted = require_persistence.loadInspectorState(INSPECTOR_STORAGE_KEY);
5775
6450
  if (!persisted) return;
6451
+ this.restorePersistedMenu(persisted.selectedMenu);
5776
6452
  const persistedButton = persisted.button;
5777
6453
  if (persistedButton) {
5778
6454
  if (require_persistence.isValidAnchor(persistedButton.anchor)) this.contextState.button.anchor = persistedButton.anchor;
@@ -5791,6 +6467,28 @@ ${argsString}</pre
5791
6467
  this.pendingSelectedContext = persisted.selectedContext;
5792
6468
  }
5793
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
+ }
5794
6492
  get activeContext() {
5795
6493
  return this.isOpen ? "window" : "button";
5796
6494
  }
@@ -5856,7 +6554,7 @@ ${argsString}</pre
5856
6554
  },
5857
6555
  isOpen: this.isOpen,
5858
6556
  dockMode: this.dockMode,
5859
- selectedMenu: this.selectedMenu,
6557
+ selectedMenu: this.pendingPersistedMenu ?? this.selectedMenu,
5860
6558
  selectedContext: this.selectedContext
5861
6559
  };
5862
6560
  require_persistence.saveInspectorState(INSPECTOR_STORAGE_KEY, state);
@@ -5879,7 +6577,7 @@ ${argsString}</pre
5879
6577
  if (mode === "docked-left") this.contextState.window.size.width = DOCKED_LEFT_WIDTH;
5880
6578
  this.applyDockStyles();
5881
6579
  } else {
5882
- this.contextState.window.size = { ...DEFAULT_WINDOW_SIZE };
6580
+ this.contextState.window.size = this.clampWindowSize(DEFAULT_WINDOW_SIZE);
5883
6581
  this.centerContext("window");
5884
6582
  }
5885
6583
  this.persistState();
@@ -6038,6 +6736,7 @@ ${argsString}</pre
6038
6736
  type="button"
6039
6737
  aria-label="Dock to left"
6040
6738
  title="Dock Left"
6739
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6041
6740
  @click=${() => this.handleDockClick("docked-left")}
6042
6741
  >
6043
6742
  ${this.renderIcon("PanelLeft")}
@@ -6049,6 +6748,7 @@ ${argsString}</pre
6049
6748
  type="button"
6050
6749
  aria-label="Float window"
6051
6750
  title="Float"
6751
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
6052
6752
  @click=${() => this.handleDockClick("floating")}
6053
6753
  >
6054
6754
  ${this.renderIcon("Maximize2")}
@@ -6227,6 +6927,7 @@ ${argsString}</pre
6227
6927
  };
6228
6928
  }
6229
6929
  renderMainContent() {
6930
+ if (this.settingsOpen) return this.renderSettingsPanel();
6230
6931
  if (this.selectedMenu === "ag-ui-events") return this.renderEventsTable();
6231
6932
  if (this.selectedMenu === "agents") return this.renderAgentsView();
6232
6933
  if (this.selectedMenu === "frontend-tools") return this.renderToolsView();
@@ -6234,7 +6935,6 @@ ${argsString}</pre
6234
6935
  if (this.selectedMenu === "agent-context") return this.renderContextView();
6235
6936
  if (this.selectedMenu === "threads") return this.renderThreadsView();
6236
6937
  if (this.selectedMenu === "memories") return this.renderMemoriesView();
6237
- if (this.selectedMenu === "settings") return this.renderSettingsPanel();
6238
6938
  return lit.nothing;
6239
6939
  }
6240
6940
  renderSettingsPanel() {
@@ -6282,6 +6982,94 @@ ${argsString}</pre
6282
6982
  has_unseen_announcement: hadUnseenAnnouncement
6283
6983
  });
6284
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
+ }
6285
7073
  trackBannerDismissedOnce(surface) {
6286
7074
  if (this.core?.telemetryDisabled) return;
6287
7075
  const id = this.announcementTimestamp;
@@ -6308,21 +7096,18 @@ ${argsString}</pre
6308
7096
  cta_label: this.announcementCtaLabel ?? void 0
6309
7097
  });
6310
7098
  }
6311
- trackThreadsViewStateOnce(state, threadCount) {
7099
+ trackThreadsViewStateOnce(state) {
6312
7100
  if (this.core?.telemetryDisabled) return;
6313
7101
  const key = `${state}:${this.getThreadServiceStatus()}`;
6314
7102
  if (this.viewedThreadsTelemetryStates.has(key)) return;
6315
7103
  this.viewedThreadsTelemetryStates.add(key);
6316
- const props = this.getThreadsTelemetryProps({ thread_count: threadCount });
7104
+ const props = this.getThreadsTelemetryProps();
6317
7105
  if (state === "locked") require_telemetry.trackThreadsLockedViewed(props);
6318
7106
  else if (state === "empty_enabled") require_telemetry.trackThreadsEmptyEnabledViewed(props);
6319
7107
  else require_telemetry.trackThreadsEnabledViewed(props);
6320
7108
  }
6321
- shouldRenderExampleThreads(displayThreads, threadsErrorMessage) {
6322
- return this.areThreadEndpointsAvailable() && !threadsErrorMessage && displayThreads.length === 0;
6323
- }
6324
- getVisibleThreads(displayThreads, threadsErrorMessage) {
6325
- return this.shouldRenderExampleThreads(displayThreads, threadsErrorMessage) ? THREADS_EXAMPLE_THREADS : displayThreads;
7109
+ shouldRenderExampleThreads(locked, displayThreads, threadsErrorMessage, threadsLoading) {
7110
+ return locked || !threadsErrorMessage && !threadsLoading && displayThreads.length === 0;
6326
7111
  }
6327
7112
  isExampleThreadId(threadId) {
6328
7113
  return THREADS_EXAMPLE_THREADS.some((thread) => thread.id === threadId);
@@ -6339,6 +7124,7 @@ ${argsString}</pre
6339
7124
  agentId: thread.agentId,
6340
7125
  endUserId: "example-user",
6341
7126
  status: "completed",
7127
+ createdAt: thread.createdAt,
6342
7128
  updatedAt: thread.updatedAt
6343
7129
  } : null,
6344
7130
  getMessages: async () => details?.messages ?? [],
@@ -6351,39 +7137,43 @@ ${argsString}</pre
6351
7137
  trackThreadsExampleViewedOnce() {
6352
7138
  if (this.core?.telemetryDisabled) return;
6353
7139
  for (const thread of THREADS_EXAMPLE_THREADS) {
6354
- if (this.viewedExampleThreadIds.has(thread.id)) continue;
6355
- this.viewedExampleThreadIds.add(thread.id);
6356
- require_telemetry.trackThreadsExampleViewed(this.getThreadsTelemetryProps({
6357
- example_thread_id: thread.id,
6358
- thread_count: 0
6359
- }));
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
+ });
6360
7147
  }
6361
7148
  }
6362
7149
  trackThreadsExampleSelectedOnce(threadId) {
6363
7150
  if (this.core?.telemetryDisabled) return;
6364
- if (this.selectedExampleThreadIds.has(threadId)) return;
6365
- this.selectedExampleThreadIds.add(threadId);
6366
- require_telemetry.trackThreadsExampleSelected(this.getThreadsTelemetryProps({
6367
- example_thread_id: threadId,
6368
- thread_count: 0
6369
- }));
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
+ });
6370
7158
  }
6371
7159
  getCurrentExampleTourProps() {
6372
- const step = THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0];
6373
- return this.getThreadsTelemetryProps({
6374
- example_thread_id: this.selectedThreadId ?? void 0,
6375
- thread_count: 0,
6376
- tour_step: this.exampleTourStep + 1,
6377
- tour_tab: step?.tab
6378
- });
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
+ };
6379
7168
  }
6380
7169
  trackThreadsExampleTourStepViewedOnce() {
6381
7170
  if (this.core?.telemetryDisabled || !this.selectedThreadId) return;
6382
- if (!(THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0])) return;
6383
- const key = `${this.selectedThreadId}:${this.exampleTourStep}`;
7171
+ const props = this.getCurrentExampleTourProps();
7172
+ if (!props) return;
7173
+ const key = `${props.example_kind}:${props.tour_step}`;
6384
7174
  if (this.viewedExampleTourSteps.has(key)) return;
6385
7175
  this.viewedExampleTourSteps.add(key);
6386
- require_telemetry.trackThreadsExampleTourStepViewed(this.getCurrentExampleTourProps());
7176
+ require_telemetry.trackThreadsExampleTourStepViewed(props);
6387
7177
  }
6388
7178
  syncExampleTourTab() {
6389
7179
  const step = THREADS_EXAMPLE_TOUR_STEPS[this.exampleTourStep] ?? THREADS_EXAMPLE_TOUR_STEPS[0];
@@ -6398,44 +7188,65 @@ ${argsString}</pre
6398
7188
  this.exampleTourStep = 0;
6399
7189
  if (autoStarted) {
6400
7190
  this.exampleTourAutoShown = true;
6401
- if (!this.core?.telemetryDisabled) require_telemetry.trackThreadsExampleTourStarted(this.getCurrentExampleTourProps());
6402
- } 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
+ }
6403
7199
  this.trackThreadsExampleTourStepViewedOnce();
6404
7200
  this.syncExampleTourTab();
6405
7201
  this.requestUpdate();
6406
7202
  }
6407
7203
  setExampleTourStep(nextStep) {
7204
+ const telemetryStepIsValid = getExampleTourTelemetryPair(nextStep) !== void 0;
6408
7205
  this.exampleTourStep = Math.max(0, Math.min(THREADS_EXAMPLE_TOUR_STEPS.length - 1, nextStep));
6409
- this.trackThreadsExampleTourStepViewedOnce();
7206
+ if (telemetryStepIsValid) this.trackThreadsExampleTourStepViewedOnce();
6410
7207
  this.syncExampleTourTab();
6411
7208
  this.requestUpdate();
6412
7209
  }
6413
7210
  dismissExampleTour(method) {
6414
7211
  if (!this.selectedThreadId) return;
6415
- const props = {
6416
- ...this.getCurrentExampleTourProps(),
6417
- dismiss_method: method
6418
- };
6419
7212
  this.exampleTourActive = false;
6420
7213
  this.exampleTourDismissed = true;
6421
7214
  this.writeThreadsExampleTourDismissed();
6422
- if (!this.core?.telemetryDisabled) if (method === "done") require_telemetry.trackThreadsExampleTourCompleted(props);
6423
- 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
+ }
6424
7226
  this.requestUpdate();
6425
7227
  }
6426
7228
  handleThreadsThreadSelected(threadId, showingExamples) {
6427
- if (showingExamples && this.selectedThreadId === threadId) {
7229
+ if (showingExamples && this.selectedThreadId === threadId && this.selectedLocalExampleThreadId === threadId) {
6428
7230
  this.selectedThreadId = null;
7231
+ this.selectedRealThreadIsExplicit = false;
7232
+ this.selectedLocalExampleThreadId = null;
6429
7233
  this.exampleTourActive = false;
6430
7234
  this.requestUpdate();
6431
7235
  return;
6432
7236
  }
6433
7237
  this.selectedThreadId = threadId;
6434
7238
  if (showingExamples && this.isExampleThreadId(threadId)) {
7239
+ this.selectedRealThreadIsExplicit = false;
7240
+ this.selectedLocalExampleThreadId = threadId;
6435
7241
  this.trackThreadsExampleSelectedOnce(threadId);
6436
7242
  if (!this.exampleTourDismissed && !this.exampleTourAutoShown) this.startExampleTour(true);
6437
7243
  else this.exampleTourActive = false;
6438
- } 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
+ }
6439
7250
  this.requestUpdate();
6440
7251
  }
6441
7252
  readThreadsExampleTourDismissed() {
@@ -6454,42 +7265,228 @@ ${argsString}</pre
6454
7265
  window.localStorage.setItem(THREADS_EXAMPLE_TOUR_STORAGE_KEY, JSON.stringify({ dismissed: true }));
6455
7266
  } catch {}
6456
7267
  }
6457
- scheduleThreadsExampleOverviewVideoLoad() {
6458
- if (this.threadsExampleOverviewVideoShouldLoad || this.threadsExampleOverviewVideoLoadTimer !== null || this.threadsExampleOverviewVideoIdleCallbackId !== null || typeof window === "undefined") return;
6459
- if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
6460
- 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);
6461
7272
  this.threadsExampleOverviewVideoLoadTimer = null;
7273
+ }
7274
+ if (this.threadsExampleOverviewVideoIdleCallbackId !== null) {
7275
+ if (typeof window !== "undefined" && typeof window.cancelIdleCallback === "function") window.cancelIdleCallback(this.threadsExampleOverviewVideoIdleCallbackId);
6462
7276
  this.threadsExampleOverviewVideoIdleCallbackId = null;
6463
- this.threadsExampleOverviewVideoShouldLoad = true;
6464
- this.requestUpdate();
6465
- };
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;
6466
7337
  if (typeof window.requestIdleCallback === "function") {
6467
- 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;
6468
7346
  return;
6469
7347
  }
6470
- this.threadsExampleOverviewVideoLoadTimer = window.setTimeout(loadVideo, 450);
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();
7425
+ return;
7426
+ }
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();
6471
7437
  }
6472
7438
  renderThreadsExampleOverviewVideo() {
6473
- 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>`;
6474
7454
  return lit.html`
6475
7455
  <div class="cpk-threads-overview-video-frame" aria-hidden="true">
6476
- ${this.threadsExampleOverviewVideoShouldLoad ? lit.html`
6477
- <video
6478
- class="cpk-threads-overview-video"
6479
- data-ready=${this.threadsExampleOverviewVideoReady}
6480
- src=${THREADS_EXAMPLE_OVERVIEW_VIDEO_URL}
6481
- autoplay
6482
- loop
6483
- muted
6484
- playsinline
6485
- preload="metadata"
6486
- @loadeddata=${this.handleThreadsExampleOverviewVideoLoaded}
6487
- ></video>
6488
- ` : lit.nothing}
7456
+ ${this.threadsExampleOverviewVideoLifecycleGeneration % 2 === 0 ? lit.html`<!-- cpk-video-generation-even -->${video}` : lit.html`<!-- cpk-video-generation-odd -->${video}`}
6489
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}
6490
7471
  `;
6491
7472
  }
6492
- 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();
6493
7490
  return lit.html`
6494
7491
  <div
6495
7492
  style="
@@ -6512,7 +7509,7 @@ ${argsString}</pre
6512
7509
  color: #010507;
6513
7510
  "
6514
7511
  >
6515
- Threads are persistent, inspectable conversations
7512
+ ${lockedCopy?.heading ?? "Threads are persistent, inspectable conversations"}
6516
7513
  </h2>
6517
7514
  ${this.renderThreadsExampleOverviewVideo()}
6518
7515
  <p
@@ -6523,60 +7520,89 @@ ${argsString}</pre
6523
7520
  color: #57575b;
6524
7521
  "
6525
7522
  >
6526
- Take a tour with the example threads in the sidebar. Then, start
6527
- 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."}
6528
7524
  </p>
6529
7525
  <div style="display:flex;flex-wrap:wrap;gap:8px;">
6530
- <a
6531
- href=${this.getThreadsDocsUrl()}
6532
- target="_blank"
6533
- rel="noopener"
6534
- style="
6535
- display: inline-flex;
6536
- align-items: center;
6537
- justify-content: center;
6538
- gap: 6px;
6539
- min-height: 34px;
6540
- border-radius: 6px;
6541
- background: #010507;
6542
- padding: 8px 12px;
6543
- font-size: 12px;
6544
- font-weight: 600;
6545
- color: #ffffff;
6546
- text-decoration: none;
6547
- "
6548
- >
6549
- Learn how Threads work
6550
- </a>
6551
- <a
6552
- href=${this.getSelfHostedIntelligenceUrl()}
6553
- target="_blank"
6554
- rel="noopener"
6555
- style="
6556
- display: inline-flex;
6557
- align-items: center;
6558
- justify-content: center;
6559
- gap: 6px;
6560
- min-height: 34px;
6561
- border-radius: 6px;
6562
- border: 1px solid #dbdbe5;
6563
- background: #ffffff;
6564
- padding: 8px 12px;
6565
- font-size: 12px;
6566
- font-weight: 600;
6567
- color: #010507;
6568
- text-decoration: none;
6569
- "
6570
- >
6571
- Explore self-hosted Intelligence
6572
- </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
+ `}
6573
7599
  </div>
6574
7600
  </div>
6575
7601
  </div>
6576
7602
  `;
6577
7603
  }
6578
7604
  renderThreadsExampleTour() {
6579
- if (!this.selectedThreadId || !this.isExampleThreadId(this.selectedThreadId)) return lit.nothing;
7605
+ if (!this.selectedThreadId || this.selectedThreadId !== this.selectedLocalExampleThreadId) return lit.nothing;
6580
7606
  if (!this.exampleTourActive) return lit.html`
6581
7607
  <button
6582
7608
  type="button"
@@ -6627,7 +7653,7 @@ ${argsString}</pre
6627
7653
  font-family: 'Spline Sans Mono', monospace;
6628
7654
  font-size: 10px;
6629
7655
  font-weight: 600;
6630
- color: #189370;
7656
+ color: #087653;
6631
7657
  text-transform: uppercase;
6632
7658
  "
6633
7659
  >
@@ -6658,7 +7684,7 @@ ${argsString}</pre
6658
7684
  >
6659
7685
  <button
6660
7686
  type="button"
6661
- 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;"
6662
7688
  @click=${() => this.dismissExampleTour("skip")}
6663
7689
  >
6664
7690
  Skip
@@ -6837,124 +7863,25 @@ ${argsString}</pre
6837
7863
  </div>
6838
7864
  `;
6839
7865
  }
6840
- renderThreadsLockedView() {
6841
- this.trackThreadsViewStateOnce("locked", 0);
6842
- return lit.html`
6843
- <div
6844
- style="
6845
- position: relative;
6846
- height: 100%;
6847
- display: flex;
6848
- align-items: center;
6849
- justify-content: center;
6850
- padding: 32px;
6851
- overflow: hidden;
6852
- background: #ffffff;
6853
- "
6854
- >
6855
- ${this.renderThreadsLockedBackgroundMockup()}
6856
- <div
6857
- aria-hidden="true"
6858
- style="
6859
- position: absolute;
6860
- inset: 0;
6861
- pointer-events: none;
6862
- background:
6863
- 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%);
6864
- "
6865
- ></div>
6866
- <div
6867
- style="
6868
- position: relative;
6869
- z-index: 1;
6870
- max-width: 440px;
6871
- text-align: center;
6872
- color: #57575b;
6873
- "
6874
- >
6875
- <div
6876
- aria-hidden="true"
6877
- style="
6878
- margin: 0 auto 18px;
6879
- display: flex;
6880
- justify-content: center;
6881
- "
6882
- >
6883
- <div
6884
- style="
6885
- display: flex;
6886
- height: 44px;
6887
- width: 44px;
6888
- align-items: center;
6889
- justify-content: center;
6890
- border: 1px solid #dfd6fb;
6891
- border-radius: 8px;
6892
- background: #eee6fe;
6893
- color: #57575b;
6894
- box-shadow: 0 8px 18px rgba(87, 87, 91, 0.14);
6895
- "
6896
- >
6897
- ${this.renderIcon("Lock")}
6898
- </div>
6899
- </div>
6900
- <h2
6901
- style="
6902
- margin: 0 0 8px;
6903
- font-size: 16px;
6904
- line-height: 1.35;
6905
- font-weight: 600;
6906
- color: #010507;
6907
- "
6908
- >
6909
- Enable Intelligence to inspect Threads.
6910
- </h2>
6911
- <p
6912
- style="
6913
- margin: 0 auto 18px;
6914
- max-width: 380px;
6915
- font-size: 13px;
6916
- line-height: 1.55;
6917
- color: #57575b;
6918
- "
6919
- >
6920
- Persist conversations and inspect saved thread history from the
6921
- Inspector.
6922
- </p>
6923
- <div
6924
- style="
6925
- display: flex;
6926
- flex-wrap: wrap;
6927
- justify-content: center;
6928
- gap: 8px;
6929
- "
6930
- >
6931
- <a
6932
- href=${this.getThreadsIntelligenceSignupUrl()}
6933
- target="_blank"
6934
- rel="noopener"
6935
- style="
6936
- display: inline-flex;
6937
- min-height: 34px;
6938
- align-items: center;
6939
- justify-content: center;
6940
- gap: 6px;
6941
- border-radius: 6px;
6942
- border: 1px solid #dbdbe5;
6943
- background: #ffffff;
6944
- padding: 8px 12px;
6945
- font-size: 12px;
6946
- font-weight: 600;
6947
- color: #57575b;
6948
- text-decoration: none;
6949
- "
6950
- @click=${this.handleThreadsIntelligenceSignupClick}
6951
- >
6952
- Sign up for Intelligence
6953
- </a>
6954
- </div>
6955
- </div>
6956
- </div>
6957
- `;
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
+ }
6958
7885
  }
6959
7886
  /**
6960
7887
  * Renders the realtime-connection indicator in the memory-store header.
@@ -6974,7 +7901,7 @@ ${argsString}</pre
6974
7901
  gap: 4px;
6975
7902
  font-size: 10px;
6976
7903
  font-weight: 500;
6977
- color: ${connected ? "#57575b" : "#838389"};
7904
+ color: ${connected ? "#57575b" : "#68686e"};
6978
7905
  "
6979
7906
  >
6980
7907
  <span
@@ -7137,7 +8064,7 @@ ${argsString}</pre
7137
8064
  align-items: center;
7138
8065
  justify-content: center;
7139
8066
  gap: 8px;
7140
- color: #838389;
8067
+ color: #68686e;
7141
8068
  "
7142
8069
  >
7143
8070
  <svg
@@ -7179,7 +8106,7 @@ ${argsString}</pre
7179
8106
  align-items: center;
7180
8107
  justify-content: center;
7181
8108
  gap: 8px;
7182
- color: #838389;
8109
+ color: #68686e;
7183
8110
  "
7184
8111
  >
7185
8112
  <svg
@@ -7271,17 +8198,65 @@ ${argsString}</pre
7271
8198
  ></cpk-memory-list>
7272
8199
  </div>
7273
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>
7274
8247
  `;
7275
8248
  }
7276
8249
  renderThreadsView() {
7277
- if (!this.areThreadEndpointsAvailable()) return this.renderThreadsLockedView();
7278
- const { displayThreads, threadsErrorMessage } = this.getActiveThreadsState();
7279
- const showingExamples = this.shouldRenderExampleThreads(displayThreads, threadsErrorMessage);
7280
- 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;
7281
8255
  if (showingExamples) this.trackThreadsExampleViewedOnce();
7282
8256
  const selectedThread = this.selectedThreadId != null ? visibleThreads.find((t) => t.id === this.selectedThreadId) ?? null : null;
7283
- const selectedThreadIsExample = this.isExampleThreadId(selectedThread?.id);
7284
- 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");
7285
8260
  return lit.html`
7286
8261
  <div style="display:flex;height:100%;overflow:hidden;flex-direction:column;">
7287
8262
  <div style="display:flex;min-height:0;flex:1;overflow:hidden;">
@@ -7290,14 +8265,16 @@ ${argsString}</pre
7290
8265
  style="width:${this.threadListWidth}px;flex-shrink:0;overflow:hidden;display:flex;flex-direction:column;border-right:1px solid #DBDBE5;"
7291
8266
  >
7292
8267
  <cpk-thread-list
7293
- style="height:100%;"
8268
+ style="min-height:0;flex:1;"
7294
8269
  .threads=${visibleThreads}
7295
8270
  .selectedThreadId=${this.selectedThreadId}
7296
8271
  .errorMessage=${threadsErrorMessage}
8272
+ .suppressEmptyState=${loadingWithoutRows}
7297
8273
  @threadSelected=${(e) => {
7298
8274
  this.handleThreadsThreadSelected(e.detail, showingExamples);
7299
8275
  }}
7300
8276
  ></cpk-thread-list>
8277
+ ${this.renderThreadsUsageFooter()}
7301
8278
  </div>
7302
8279
 
7303
8280
  <!-- Resize divider -->
@@ -7311,19 +8288,53 @@ ${argsString}</pre
7311
8288
 
7312
8289
  <!-- Center + right: thread details or empty state -->
7313
8290
  <div style="flex:1;min-width:0;overflow:hidden;display:flex;position:relative;">
7314
- ${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
7315
8326
  style="flex:1;min-width:0;"
7316
8327
  .threadId=${selectedThread.id}
7317
8328
  .thread=${selectedThread}
7318
- .provider=${selectedThreadIsExample ? this.getExampleThreadProvider(selectedThread.id) : null}
7319
- .runtimeUrl=${this._core?.runtimeUrl ?? ""}
8329
+ .provider=${selectedThreadIsLocalExample ? this.getExampleThreadProvider(selectedThread.id) : null}
8330
+ .runtimeUrl=${selectedThreadIsLocalExample ? "" : this._core?.runtimeUrl ?? ""}
7320
8331
  .headers=${this._core?.headers ?? {}}
7321
- .threadInspectionAvailable=${selectedThreadIsExample || this._core?.threadEndpoints?.inspect !== false}
8332
+ .threadInspectionAvailable=${selectedThreadIsLocalExample || this.areThreadEndpointsAvailable() && this._core?.threadEndpoints?.inspect !== false}
7322
8333
  .liveMessageVersion=${this.liveMessageVersion.get(selectedThread.id) ?? 0}
7323
8334
  .agentStateInput=${this.getLatestStateForAgent(selectedThread.agentId)}
7324
8335
  .agentEventsInput=${this.agentEvents.get(selectedThread.agentId) ?? []}
7325
8336
  ></cpk-thread-details>
7326
- ${selectedThreadIsExample ? this.renderThreadsExampleTour() : lit.nothing}` : showingExamples ? this.renderThreadsExampleOverview() : lit.html`
8337
+ ${selectedThreadIsLocalExample ? this.renderThreadsExampleTour() : lit.nothing}` : showingExamples ? this.renderThreadsExampleOverview(locked) : lit.html`
7327
8338
  <div
7328
8339
  style="
7329
8340
  flex: 1;
@@ -7332,7 +8343,7 @@ ${argsString}</pre
7332
8343
  align-items: center;
7333
8344
  justify-content: center;
7334
8345
  gap: 8px;
7335
- color: #838389;
8346
+ color: #68686e;
7336
8347
  "
7337
8348
  >
7338
8349
  <svg
@@ -7856,9 +8867,12 @@ ${prettyEvent}</pre
7856
8867
  `;
7857
8868
  }
7858
8869
  handleMenuSelect(key) {
7859
- if (key !== "settings" && !this.menuItems.some((item) => item.key === key)) return;
8870
+ if (!this.menuItems.some((item) => item.key === key)) return;
7860
8871
  const previousMenu = this.selectedMenu;
8872
+ this.pendingPersistedMenu = null;
7861
8873
  this.selectedMenu = key;
8874
+ this.settingsOpen = false;
8875
+ this.lastSelectedMenuByGroup[this.getGroupForMenu(key)] = key;
7862
8876
  if (key === "agents" && this.selectedContext === "all-agents") {
7863
8877
  const agentOptions = this.contextOptions.filter((opt) => opt.key !== "all-agents");
7864
8878
  if (agentOptions.length > 0) {
@@ -7902,6 +8916,7 @@ ${prettyEvent}</pre
7902
8916
  if (this.selectedContext !== key) {
7903
8917
  this.selectedContext = key;
7904
8918
  this.expandedRows.clear();
8919
+ this.autoSelectLatestThread();
7905
8920
  }
7906
8921
  this.contextMenuOpen = false;
7907
8922
  this.persistState();