@copilotkit/web-inspector 1.56.4 → 1.56.5-canary.1777664617

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4,11 +4,11 @@ import inspector_logo_icon_default from "./assets/inspector-logo-icon.mjs";
4
4
  import { applyAnchorPosition, centerContext, clampSize, constrainToViewport, keepPositionWithinViewport, updateAnchorFromPosition, updateSizeFromElement } from "./lib/context-helpers.mjs";
5
5
  import { isValidAnchor, isValidDockMode, isValidPosition, isValidSize, loadInspectorState, saveInspectorState } from "./lib/persistence.mjs";
6
6
  import { LitElement, css, html, nothing, unsafeCSS } from "lit";
7
+ import { marked } from "marked";
7
8
  import { styleMap } from "lit/directives/style-map.js";
8
9
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
9
- import { marked } from "marked";
10
10
  import { icons } from "lucide";
11
- import { CopilotKitCoreRuntimeConnectionStatus } from "@copilotkit/core";
11
+ import { CopilotKitCoreRuntimeConnectionStatus, ɵcreateThreadStore, ɵselectThreads, ɵselectThreadsError } from "@copilotkit/core";
12
12
 
13
13
  //#region src/index.ts
14
14
  const WEB_INSPECTOR_TAG = "cpk-web-inspector";
@@ -26,7 +26,7 @@ const DEFAULT_BUTTON_SIZE = {
26
26
  };
27
27
  const DEFAULT_WINDOW_SIZE = {
28
28
  width: 840,
29
- height: 560
29
+ height: 700
30
30
  };
31
31
  const DOCKED_LEFT_WIDTH = 500;
32
32
  const MAX_AGENT_EVENTS = 200;
@@ -52,11 +52,1765 @@ const AGENT_EVENT_TYPES = [
52
52
  "REASONING_MESSAGE_CONTENT",
53
53
  "REASONING_MESSAGE_END",
54
54
  "REASONING_END",
55
- "REASONING_ENCRYPTED_VALUE"
55
+ "REASONING_ENCRYPTED_VALUE",
56
+ "ACTIVITY_SNAPSHOT",
57
+ "ACTIVITY_DELTA"
56
58
  ];
57
- var WebInspectorElement = class extends LitElement {
59
+ function escapeHtml(s) {
60
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
61
+ }
62
+ const highlightedJsonCache = /* @__PURE__ */ new WeakMap();
63
+ function highlightedJson(obj) {
64
+ if (typeof obj === "object" && obj !== null) {
65
+ const cached = highlightedJsonCache.get(obj);
66
+ if (cached !== void 0) return cached;
67
+ }
68
+ const colors = {
69
+ key: "#5558B2",
70
+ str: "#189370",
71
+ num: "#996300",
72
+ bool: "#c0333a",
73
+ nil: "#838389"
74
+ };
75
+ const json = JSON.stringify(obj, null, 2);
76
+ if (!json) return "";
77
+ const parts = [];
78
+ let lastIndex = 0;
79
+ const re = /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*"(?:\s*:)?|\b(?:true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g;
80
+ let match;
81
+ while ((match = re.exec(json)) !== null) {
82
+ parts.push(escapeHtml(json.slice(lastIndex, match.index)));
83
+ const m = match[0];
84
+ let color = colors.num;
85
+ if (m.startsWith("\"")) color = m.trimEnd().endsWith(":") ? colors.key : colors.str;
86
+ else if (m === "true" || m === "false") color = colors.bool;
87
+ else if (m === "null") color = colors.nil;
88
+ parts.push(`<span style="color:${color}">${escapeHtml(m)}</span>`);
89
+ lastIndex = match.index + m.length;
90
+ }
91
+ parts.push(escapeHtml(json.slice(lastIndex)));
92
+ const result = parts.join("");
93
+ if (typeof obj === "object" && obj !== null) highlightedJsonCache.set(obj, result);
94
+ return result;
95
+ }
96
+ function eventColors(type) {
97
+ if (type.startsWith("TEXT_MESSAGE")) return {
98
+ bg: "#EEE6FE",
99
+ fg: "#57575B"
100
+ };
101
+ if (type.startsWith("TOOL_CALL")) return {
102
+ bg: "rgba(133,236,206,0.15)",
103
+ fg: "#189370"
104
+ };
105
+ if (type.startsWith("STATE")) return {
106
+ bg: "rgba(190,194,255,0.102)",
107
+ fg: "#5558B2"
108
+ };
109
+ if (type.startsWith("RUN_") || type.startsWith("STEP_")) return {
110
+ bg: "rgba(255,172,77,0.2)",
111
+ fg: "#996300"
112
+ };
113
+ if (type === "ERROR") return {
114
+ bg: "rgba(250,95,103,0.13)",
115
+ fg: "#c0333a"
116
+ };
117
+ return {
118
+ bg: "#F7F7F9",
119
+ fg: "#838389"
120
+ };
121
+ }
122
+ function formatTimestamp(ts) {
123
+ const date = typeof ts === "number" ? new Date(ts) : new Date(ts);
124
+ if (Number.isNaN(date.getTime())) return "";
125
+ const ms = date.getMilliseconds().toString().padStart(3, "0");
126
+ return date.toLocaleTimeString("en-US", {
127
+ hour: "2-digit",
128
+ minute: "2-digit",
129
+ second: "2-digit",
130
+ hour12: false
131
+ }) + "." + ms;
132
+ }
133
+ var CpkThreadList = class extends LitElement {
58
134
  constructor(..._args) {
59
135
  super(..._args);
136
+ this.threads = [];
137
+ this.selectedThreadId = null;
138
+ this.errorMessage = null;
139
+ this._query = "";
140
+ this.onSearchInput = (event) => {
141
+ this._query = event.target.value;
142
+ };
143
+ }
144
+ static {
145
+ this.properties = {
146
+ threads: { attribute: false },
147
+ selectedThreadId: { attribute: false },
148
+ errorMessage: { attribute: false },
149
+ _query: { state: true }
150
+ };
151
+ }
152
+ static {
153
+ this.styles = css`
154
+ @import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600&family=Spline+Sans+Mono:wght@400;500&display=swap");
155
+
156
+ :host {
157
+ display: flex;
158
+ flex-direction: column;
159
+ height: 100%;
160
+ overflow: hidden;
161
+ }
162
+
163
+ .cpk-tl {
164
+ font-family: "Plus Jakarta Sans", sans-serif;
165
+ display: flex;
166
+ flex-direction: column;
167
+ height: 100%;
168
+ overflow: hidden;
169
+ background: #f7f7f9;
170
+ }
171
+
172
+ /* ── Search ── */
173
+ .cpk-tl__search {
174
+ padding: 10px 12px;
175
+ border-bottom: 1px solid #dbdbe5;
176
+ flex-shrink: 0;
177
+ }
178
+
179
+ .cpk-tl__search-input {
180
+ width: 100%;
181
+ box-sizing: border-box;
182
+ font-family: "Plus Jakarta Sans", sans-serif;
183
+ font-size: 12px;
184
+ padding: 7px 10px;
185
+ border-radius: 6px;
186
+ border: 1px solid #dbdbe5;
187
+ background: #ffffff;
188
+ color: #010507;
189
+ outline: none;
190
+ transition: border-color 0.15s;
191
+ }
192
+
193
+ .cpk-tl__search-input:focus {
194
+ border-color: #bec2ff;
195
+ }
196
+
197
+ /* ── List ── */
198
+ .cpk-tl__list {
199
+ flex: 1;
200
+ overflow-y: auto;
201
+ }
202
+
203
+ /* ── Thread item ── */
204
+ .cpk-tl__item {
205
+ padding: 11px 13px;
206
+ cursor: pointer;
207
+ border-bottom: 1px solid #e9e9ef;
208
+ border-left: 3px solid transparent;
209
+ transition: background 0.1s;
210
+ }
211
+
212
+ .cpk-tl__item:hover {
213
+ background: #ffffff;
214
+ }
215
+
216
+ .cpk-tl__item--active {
217
+ background: #bec2ff1a;
218
+ border-left-color: #bec2ff;
219
+ }
220
+
221
+ .cpk-tl__item--active:hover {
222
+ background: #bec2ff33;
223
+ }
224
+
225
+ .cpk-tl__row1 {
226
+ display: flex;
227
+ align-items: center;
228
+ gap: 8px;
229
+ margin-bottom: 3px;
230
+ }
231
+
232
+ .cpk-tl__name {
233
+ font-size: 12px;
234
+ font-weight: 500;
235
+ color: #010507;
236
+ flex: 1;
237
+ overflow: hidden;
238
+ text-overflow: ellipsis;
239
+ white-space: nowrap;
240
+ }
241
+
242
+ .cpk-tl__name--unnamed {
243
+ color: #838389;
244
+ font-style: italic;
245
+ font-weight: 400;
246
+ }
247
+
248
+ .cpk-tl__time {
249
+ font-family: "Spline Sans Mono", monospace;
250
+ font-size: 10px;
251
+ color: #838389;
252
+ flex-shrink: 0;
253
+ }
254
+
255
+ .cpk-tl__meta {
256
+ display: flex;
257
+ gap: 6px;
258
+ align-items: center;
259
+ flex-wrap: wrap;
260
+ }
261
+
262
+ .cpk-tl__pill {
263
+ font-family: "Spline Sans Mono", monospace;
264
+ font-size: 9px;
265
+ padding: 1px 7px;
266
+ border-radius: 4px;
267
+ text-transform: uppercase;
268
+ font-weight: 500;
269
+ white-space: nowrap;
270
+ background: #eee6fe;
271
+ color: #57575b;
272
+ }
273
+
274
+ /* ── Empty state ── */
275
+ .cpk-tl__empty {
276
+ padding: 32px 16px;
277
+ text-align: center;
278
+ color: #838389;
279
+ font-size: 12px;
280
+ display: flex;
281
+ flex-direction: column;
282
+ align-items: center;
283
+ gap: 8px;
284
+ }
285
+
286
+ .cpk-tl__empty-icon {
287
+ color: #c0c0c8;
288
+ }
289
+ `;
290
+ }
291
+ relativeTime(dateStr) {
292
+ const date = new Date(dateStr);
293
+ const diffMs = Date.now() - date.getTime();
294
+ const diffSec = Math.floor(diffMs / 1e3);
295
+ if (diffSec < 60) return `${diffSec}s ago`;
296
+ const diffMin = Math.floor(diffSec / 60);
297
+ if (diffMin < 60) return `${diffMin}m ago`;
298
+ const diffH = Math.floor(diffMin / 60);
299
+ if (diffH < 24) return `${diffH}h ago`;
300
+ return `${Math.floor(diffH / 24)}d ago`;
301
+ }
302
+ get filtered() {
303
+ const q = this._query.toLowerCase();
304
+ if (!q) return this.threads;
305
+ return this.threads.filter((t) => (t.name?.toLowerCase().includes(q) ?? false) || t.agentId.toLowerCase().includes(q) || t.id.toLowerCase().includes(q));
306
+ }
307
+ onThreadClick(threadId) {
308
+ this.dispatchEvent(new CustomEvent("threadSelected", {
309
+ detail: threadId,
310
+ bubbles: true,
311
+ composed: true
312
+ }));
313
+ }
314
+ render() {
315
+ const filtered = this.filtered;
316
+ return html`
317
+ <div class="cpk-tl">
318
+ <!-- Search -->
319
+ <div class="cpk-tl__search">
320
+ <input
321
+ type="text"
322
+ placeholder="Search threads…"
323
+ .value=${this._query}
324
+ @input=${this.onSearchInput}
325
+ class="cpk-tl__search-input"
326
+ />
327
+ </div>
328
+
329
+ <!-- Thread list -->
330
+ <div class="cpk-tl__list">
331
+ ${filtered.map((thread) => html`
332
+ <div
333
+ class="cpk-tl__item ${this.selectedThreadId === thread.id ? "cpk-tl__item--active" : ""}"
334
+ @click=${() => this.onThreadClick(thread.id)}
335
+ >
336
+ <div class="cpk-tl__row1">
337
+ <span
338
+ class="cpk-tl__name ${!thread.name ? "cpk-tl__name--unnamed" : ""}"
339
+ >${thread.name ?? "Untitled"}</span
340
+ >
341
+ <span class="cpk-tl__time"
342
+ >${this.relativeTime(thread.updatedAt)}</span
343
+ >
344
+ </div>
345
+ <div class="cpk-tl__meta">
346
+ <span class="cpk-tl__pill">${thread.agentId}</span>
347
+ </div>
348
+ </div>
349
+ `)}
350
+ ${filtered.length === 0 ? html`
351
+ <div class="cpk-tl__empty">
352
+ ${this.errorMessage ? html`
353
+ <svg
354
+ width="24"
355
+ height="24"
356
+ viewBox="0 0 24 24"
357
+ fill="none"
358
+ stroke="currentColor"
359
+ stroke-width="1.5"
360
+ stroke-linecap="round"
361
+ stroke-linejoin="round"
362
+ class="cpk-tl__empty-icon"
363
+ >
364
+ <circle cx="12" cy="12" r="10" />
365
+ <line x1="12" y1="8" x2="12" y2="12" />
366
+ <line x1="12" y1="16" x2="12.01" y2="16" />
367
+ </svg>
368
+ <div>
369
+ Failed to load threads
370
+ <div style="font-size:11px;margin-top:4px;color:#c0333a;">
371
+ ${this.errorMessage}
372
+ </div>
373
+ </div>
374
+ ` : this.threads.length === 0 ? html`
375
+ <svg
376
+ width="24"
377
+ height="24"
378
+ viewBox="0 0 24 24"
379
+ fill="none"
380
+ stroke="currentColor"
381
+ stroke-width="1.5"
382
+ stroke-linecap="round"
383
+ stroke-linejoin="round"
384
+ class="cpk-tl__empty-icon"
385
+ >
386
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
387
+ </svg>
388
+ No threads yet
389
+ ` : html`
390
+ No threads match your search.
391
+ `}
392
+ </div>
393
+ ` : nothing}
394
+ </div>
395
+ </div>
396
+ `;
397
+ }
398
+ };
399
+ var ɵCpkThreadDetails = class ɵCpkThreadDetails extends LitElement {
400
+ constructor(..._args2) {
401
+ super(..._args2);
402
+ this.threadId = null;
403
+ this.thread = null;
404
+ this.runtimeUrl = "";
405
+ this.headers = {};
406
+ this.agentStateInput = null;
407
+ this.agentEventsInput = [];
408
+ this.liveMessageVersion = 0;
409
+ this._tab = "conversation";
410
+ this._conversation = [];
411
+ this._fetchedEvents = null;
412
+ this._fetchedState = null;
413
+ this._loadingMessages = false;
414
+ this._loadingEvents = false;
415
+ this._loadingState = false;
416
+ this._messagesError = null;
417
+ this._eventsError = null;
418
+ this._stateError = null;
419
+ this._expandedTools = /* @__PURE__ */ new Set();
420
+ this._expandedMessages = /* @__PURE__ */ new Set();
421
+ this._showDetailPanel = false;
422
+ this._detailPanelWidth = 250;
423
+ this._eventsNotAvailable = false;
424
+ this._stateNotAvailable = false;
425
+ this._panelInitializing = false;
426
+ this._activatedTabs = new Set(["conversation"]);
427
+ this._panelTplCache = /* @__PURE__ */ new Map();
428
+ this._eventsFetched = false;
429
+ this._stateFetched = false;
430
+ this._lastFetchedThreadId = null;
431
+ this._lastSeenLiveMessageVersion = 0;
432
+ this._messagesAbort = null;
433
+ this._eventsAbort = null;
434
+ this._stateAbort = null;
435
+ this._dividerResizing = false;
436
+ this._dividerPointerId = -1;
437
+ this._dividerStartX = 0;
438
+ this._dividerStartWidth = 0;
439
+ this.onDetailDividerDown = (event) => {
440
+ this._dividerResizing = true;
441
+ this._dividerPointerId = event.pointerId;
442
+ this._dividerStartX = event.clientX;
443
+ this._dividerStartWidth = this._detailPanelWidth;
444
+ event.currentTarget.setPointerCapture(event.pointerId);
445
+ event.preventDefault();
446
+ };
447
+ this.onDetailDividerMove = (event) => {
448
+ if (!this._dividerResizing || this._dividerPointerId !== event.pointerId) return;
449
+ const delta = this._dividerStartX - event.clientX;
450
+ this._detailPanelWidth = Math.max(160, Math.min(400, this._dividerStartWidth + delta));
451
+ };
452
+ this.onDetailDividerUp = (event) => {
453
+ if (this._dividerPointerId !== event.pointerId) return;
454
+ const target = event.currentTarget;
455
+ if (target.hasPointerCapture(this._dividerPointerId)) target.releasePointerCapture(this._dividerPointerId);
456
+ this._dividerResizing = false;
457
+ };
458
+ }
459
+ static {
460
+ this.properties = {
461
+ threadId: { attribute: false },
462
+ thread: { attribute: false },
463
+ runtimeUrl: { attribute: false },
464
+ headers: { attribute: false },
465
+ agentStateInput: { attribute: false },
466
+ agentEventsInput: { attribute: false },
467
+ liveMessageVersion: { attribute: false },
468
+ _tab: { state: true },
469
+ _conversation: { state: true },
470
+ _fetchedEvents: { state: true },
471
+ _fetchedState: { state: true },
472
+ _loadingMessages: { state: true },
473
+ _loadingEvents: { state: true },
474
+ _loadingState: { state: true },
475
+ _messagesError: { state: true },
476
+ _eventsError: { state: true },
477
+ _stateError: { state: true },
478
+ _expandedTools: { state: true },
479
+ _expandedMessages: { state: true },
480
+ _showDetailPanel: { state: true },
481
+ _detailPanelWidth: { state: true },
482
+ _eventsNotAvailable: { state: true },
483
+ _stateNotAvailable: { state: true },
484
+ _panelInitializing: { state: true },
485
+ _activatedTabs: { state: true }
486
+ };
487
+ }
488
+ static {
489
+ this.COLLAPSE_THRESHOLD = 800;
490
+ }
491
+ static {
492
+ this.TAB_LIST = [
493
+ {
494
+ id: "conversation",
495
+ label: "Conversation"
496
+ },
497
+ {
498
+ id: "agent-state",
499
+ label: "Agent State"
500
+ },
501
+ {
502
+ id: "ag-ui-events",
503
+ label: "AG-UI Events"
504
+ }
505
+ ];
506
+ }
507
+ renderTabContent(id) {
508
+ if (id === "conversation") return this.renderConversation();
509
+ if (id === "agent-state") return this.renderState();
510
+ return this.renderEvents();
511
+ }
512
+ activateTab(id) {
513
+ if (this._tab === id) return;
514
+ const isFirstActivation = !this._activatedTabs.has(id);
515
+ this._tab = id;
516
+ if (isFirstActivation) {
517
+ this._panelInitializing = true;
518
+ requestAnimationFrame(() => {
519
+ this._activatedTabs = new Set([...this._activatedTabs, id]);
520
+ this._panelInitializing = false;
521
+ });
522
+ }
523
+ this.maybeFetchTabData(id);
524
+ }
525
+ maybeFetchTabData(id) {
526
+ if (!this.threadId) return;
527
+ if (id === "ag-ui-events" && !this._eventsFetched) {
528
+ this._eventsFetched = true;
529
+ this.fetchEvents(this.threadId);
530
+ } else if (id === "agent-state" && !this._stateFetched) {
531
+ this._stateFetched = true;
532
+ this.fetchState(this.threadId);
533
+ }
534
+ }
535
+ static {
536
+ this.styles = css`
537
+ @import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600&family=Spline+Sans+Mono:wght@400;500&display=swap");
538
+
539
+ /* ── Root ────────────────────────────────────────────────────────── */
540
+ :host {
541
+ display: flex;
542
+ flex-direction: row;
543
+ overflow: hidden;
544
+ }
545
+
546
+ .cpk-td {
547
+ font-family: "Plus Jakarta Sans", sans-serif;
548
+ font-size: 13px;
549
+ display: flex;
550
+ flex-direction: row;
551
+ width: 100%;
552
+ height: 100%;
553
+ overflow: hidden;
554
+ background: #ffffff;
555
+ }
556
+
557
+ /* ── Left area ───────────────────────────────────────────────────── */
558
+ .cpk-td__left {
559
+ flex: 1;
560
+ min-width: 0;
561
+ display: flex;
562
+ flex-direction: column;
563
+ overflow: hidden;
564
+ }
565
+
566
+ /* ── Tab bar header ──────────────────────────────────────────────── */
567
+ .cpk-td__tabs-header {
568
+ /* No top/right padding so tabs and toggle sit flush against the
569
+ top and right edges of the inspector. */
570
+ padding: 0 0 0 12px;
571
+ border-bottom: 1px solid #dbdbe5;
572
+ flex-shrink: 0;
573
+ display: flex;
574
+ align-items: stretch;
575
+ }
576
+
577
+ .cpk-td__tab-group {
578
+ display: flex;
579
+ gap: 0;
580
+ margin-bottom: -1px;
581
+ /* Allow the tab list to shrink rather than pushing the panel-toggle
582
+ button past the right edge of the inspector when horizontal space
583
+ gets tight (the drawer being open eats noticeably into width). */
584
+ min-width: 0;
585
+ flex-shrink: 1;
586
+ overflow: hidden;
587
+ }
588
+
589
+ .cpk-td__tab {
590
+ font-family: "Plus Jakarta Sans", sans-serif;
591
+ font-size: 11px;
592
+ font-weight: 500;
593
+ padding: 10px 12px;
594
+ border: none;
595
+ border-bottom: 2px solid transparent;
596
+ cursor: pointer;
597
+ background: transparent;
598
+ color: #838389;
599
+ transition:
600
+ color 0.12s,
601
+ border-color 0.12s;
602
+ white-space: nowrap;
603
+ }
604
+
605
+ .cpk-td__tab:hover {
606
+ color: #010507;
607
+ }
608
+
609
+ .cpk-td__tab--active {
610
+ color: #010507;
611
+ border-bottom-color: #bec2ff;
612
+ }
613
+
614
+ /* Toggle is a separate control, not a tab — so it does NOT use the
615
+ tabs' bottom-border active indicator. Instead, a subtle filled
616
+ state communicates "the drawer is open," and a vertical separator
617
+ on the left visually divorces it from the tab group. */
618
+ .cpk-td__panel-toggle {
619
+ margin-left: auto;
620
+ align-self: stretch;
621
+ display: flex;
622
+ align-items: center;
623
+ justify-content: center;
624
+ padding: 0 12px;
625
+ border: none;
626
+ border-left: 1px solid #dbdbe5;
627
+ background: transparent;
628
+ color: #838389;
629
+ cursor: pointer;
630
+ flex-shrink: 0;
631
+ transition:
632
+ color 0.12s,
633
+ background 0.12s;
634
+ }
635
+ .cpk-td__panel-toggle:hover {
636
+ color: #010507;
637
+ background: #f4f4f9;
638
+ }
639
+ .cpk-td__panel-toggle--active {
640
+ color: #5558b2;
641
+ background: #eee6fe;
642
+ }
643
+ .cpk-td__panel-toggle--active:hover {
644
+ background: #e4d8fc;
645
+ }
646
+
647
+ /* ── Scrollable content ──────────────────────────────────────────── */
648
+ .cpk-td__content {
649
+ flex: 1;
650
+ overflow-y: auto;
651
+ padding: 16px;
652
+ display: flex;
653
+ flex-direction: column;
654
+ gap: 8px;
655
+ }
656
+
657
+ /* Pin direct children so expanded tool bodies don't get flex-shrunk. */
658
+ .cpk-td__content > * {
659
+ flex-shrink: 0;
660
+ }
661
+
662
+ /*
663
+ * Each tab's content is wrapped in this panel so the keep-mounted
664
+ * inactive panels can be hidden via display:none without disturbing
665
+ * the gap between visible siblings. The flex column + gap gives each
666
+ * conversation item / event row breathing room (the cpk-td__content
667
+ * rule above no longer reaches them now that they are nested inside
668
+ * the per-panel wrapper).
669
+ */
670
+ .cpk-td__panel {
671
+ display: flex;
672
+ flex-direction: column;
673
+ gap: 12px;
674
+ }
675
+ .cpk-td__panel > * {
676
+ flex-shrink: 0;
677
+ }
678
+
679
+ /* ── Empty state ─────────────────────────────────────────────────── */
680
+ .cpk-td__empty-state {
681
+ flex: 1;
682
+ display: flex;
683
+ flex-direction: column;
684
+ align-items: center;
685
+ justify-content: center;
686
+ gap: 8px;
687
+ color: #838389;
688
+ font-size: 13px;
689
+ padding: 40px 0;
690
+ }
691
+
692
+ .cpk-td__empty-hint {
693
+ font-size: 11px;
694
+ color: #838389;
695
+ text-align: center;
696
+ max-width: 220px;
697
+ line-height: 1.5;
698
+ }
699
+
700
+ /* ── Status messages ─────────────────────────────────────────────── */
701
+ .cpk-td__status {
702
+ padding: 16px;
703
+ font-size: 12px;
704
+ color: #838389;
705
+ text-align: center;
706
+ }
707
+
708
+ .cpk-td__status--error {
709
+ color: #c0333a;
710
+ }
711
+
712
+ /* ── Conversation bubbles ────────────────────────────────────────── */
713
+ .cpk-td__bubble {
714
+ display: flex;
715
+ margin-bottom: 2px;
716
+ }
717
+
718
+ .cpk-td__bubble--user {
719
+ justify-content: flex-end;
720
+ }
721
+
722
+ .cpk-td__bubble--assistant {
723
+ justify-content: flex-start;
724
+ }
725
+
726
+ .cpk-td__bubble-inner {
727
+ padding: 9px 14px;
728
+ max-width: 75%;
729
+ font-size: 13px;
730
+ line-height: 1.55;
731
+ }
732
+
733
+ .cpk-td__bubble-inner--user {
734
+ background: #eee6fe;
735
+ color: #57575b;
736
+ border-radius: 10px 10px 3px 10px;
737
+ }
738
+
739
+ .cpk-td__show-more {
740
+ display: inline-block;
741
+ margin-top: 4px;
742
+ font-size: 11px;
743
+ font-weight: 500;
744
+ color: #57575b;
745
+ cursor: pointer;
746
+ text-decoration: underline;
747
+ text-underline-offset: 2px;
748
+ }
749
+
750
+ .cpk-td__bubble-inner--assistant {
751
+ background: #f7f7f9;
752
+ color: #010507;
753
+ border-radius: 10px 10px 10px 3px;
754
+ border: 1px solid #e9e9ef;
755
+ }
756
+
757
+ /* ── Tool call blocks ────────────────────────────────────────────── */
758
+ .cpk-td__tool-block {
759
+ border: 1px solid #e9e9ef;
760
+ border-radius: 6px;
761
+ overflow: hidden;
762
+ }
763
+
764
+ .cpk-td__tool-header {
765
+ display: flex;
766
+ align-items: center;
767
+ gap: 6px;
768
+ padding: 6px 10px;
769
+ background: rgba(133, 236, 206, 0.15);
770
+ cursor: pointer;
771
+ font-size: 11px;
772
+ user-select: none;
773
+ }
774
+
775
+ .cpk-td__tool-header:hover {
776
+ background: rgba(133, 236, 206, 0.22);
777
+ }
778
+
779
+ .cpk-td__tool-name {
780
+ font-family: "Spline Sans Mono", monospace;
781
+ font-size: 10px;
782
+ font-weight: 500;
783
+ color: #189370;
784
+ text-transform: uppercase;
785
+ flex: 1;
786
+ }
787
+
788
+ .cpk-td__tool-status {
789
+ font-family: "Spline Sans Mono", monospace;
790
+ font-size: 9px;
791
+ text-transform: uppercase;
792
+ color: #189370;
793
+ }
794
+
795
+ .cpk-td__tool-status--pending {
796
+ color: #996300;
797
+ }
798
+
799
+ .cpk-td__tool-chevron {
800
+ color: #838389;
801
+ font-size: 10px;
802
+ }
803
+
804
+ .cpk-td__tool-body {
805
+ padding: 8px 10px;
806
+ border-top: 1px solid #e9e9ef;
807
+ background: #ffffff;
808
+ }
809
+
810
+ .cpk-td__tool-section-label {
811
+ font-family: "Spline Sans Mono", monospace;
812
+ font-size: 9px;
813
+ font-weight: 500;
814
+ color: #838389;
815
+ text-transform: uppercase;
816
+ margin-bottom: 4px;
817
+ letter-spacing: 0.3px;
818
+ }
819
+
820
+ .cpk-td__tool-pre {
821
+ margin: 0;
822
+ font-family: "Spline Sans Mono", monospace;
823
+ font-size: 10px;
824
+ background: #f7f7f9;
825
+ padding: 6px 8px;
826
+ border-radius: 4px;
827
+ overflow-x: auto;
828
+ white-space: pre-wrap;
829
+ word-break: break-all;
830
+ color: #010507;
831
+ line-height: 1.6;
832
+ }
833
+
834
+ /* ── Tool call group ─────────────────────────────────────────────── */
835
+ .cpk-td__tool-group {
836
+ border: 1px solid #e9e9ef;
837
+ border-radius: 6px;
838
+ overflow: hidden;
839
+ }
840
+
841
+ .cpk-td__tool-group-header {
842
+ padding: 5px 10px;
843
+ background: rgba(133, 236, 206, 0.15);
844
+ font-family: "Spline Sans Mono", monospace;
845
+ font-size: 10px;
846
+ color: #189370;
847
+ text-transform: uppercase;
848
+ font-weight: 500;
849
+ border-bottom: 1px solid #e9e9ef;
850
+ }
851
+
852
+ .cpk-td__tool-group .cpk-td__tool-block {
853
+ border: none;
854
+ border-bottom: 1px solid #e9e9ef;
855
+ border-radius: 0;
856
+ }
857
+
858
+ .cpk-td__tool-group .cpk-td__tool-block:last-child {
859
+ border-bottom: none;
860
+ }
861
+
862
+ /* ── Inline chips (reasoning / state update) ─────────────────────── */
863
+ .cpk-td__inline-chip {
864
+ display: flex;
865
+ align-items: center;
866
+ gap: 8px;
867
+ padding: 5px 0;
868
+ color: #838389;
869
+ font-family: "Spline Sans Mono", monospace;
870
+ font-size: 9px;
871
+ text-transform: uppercase;
872
+ }
873
+
874
+ .cpk-td__inline-chip::before,
875
+ .cpk-td__inline-chip::after {
876
+ content: "";
877
+ flex: 1;
878
+ height: 1px;
879
+ background: #e9e9ef;
880
+ }
881
+
882
+ /* ── Generative UI ──────────────────────────────────────────────── */
883
+ @keyframes cpk-genui-enter {
884
+ from {
885
+ opacity: 0;
886
+ transform: translateY(8px);
887
+ }
888
+ to {
889
+ opacity: 1;
890
+ transform: translateY(0);
891
+ }
892
+ }
893
+
894
+ .cpk-td__genui {
895
+ display: flex;
896
+ flex-direction: column;
897
+ gap: 6px;
898
+ padding: 4px 16px 8px;
899
+ animation: cpk-genui-enter 0.25s cubic-bezier(0.16, 1, 0.3, 1) both;
900
+ }
901
+
902
+ .cpk-td__genui-badge {
903
+ display: inline-flex;
904
+ align-items: center;
905
+ gap: 4px;
906
+ padding: 2px 8px;
907
+ border-radius: 4px;
908
+ background: #eee6fe;
909
+ color: #57575b;
910
+ font-size: 10px;
911
+ font-weight: 600;
912
+ align-self: flex-start;
913
+ }
914
+
915
+ .cpk-td__genui-card {
916
+ overflow: hidden;
917
+ border-radius: 12px;
918
+ border: 1px solid #e2e8f0;
919
+ background: #fff;
920
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.08);
921
+ }
922
+
923
+ .cpk-td__genui-placeholder {
924
+ padding: 8px 12px;
925
+ border-radius: 8px;
926
+ border: 1px solid #ede9fe;
927
+ background: #f5f3ff;
928
+ color: #7c3aed;
929
+ font-size: 11px;
930
+ }
931
+
932
+ /* ── AG-UI Events ────────────────────────────────────────────────── */
933
+ .cpk-td__event {
934
+ flex-shrink: 0;
935
+ border: 1px solid #e9e9ef;
936
+ border-radius: 6px;
937
+ overflow: hidden;
938
+ /*
939
+ * content-visibility: auto lets the browser skip layout + paint for
940
+ * off-screen events while keeping them in the DOM (so scroll size
941
+ * stays correct). Without this, switching back to AG-UI Events on a
942
+ * thread with hundreds of events triggers a full layout pass over
943
+ * every event row, which on Martha's intelligence-backed example
944
+ * shows up as a multi-second freeze each time the panel becomes
945
+ * visible. The intrinsic-size hint avoids the visible jump as the
946
+ * browser swaps in real heights when items scroll into view.
947
+ */
948
+ content-visibility: auto;
949
+ contain-intrinsic-size: 0 80px;
950
+ }
951
+
952
+ .cpk-td__event-header {
953
+ display: flex;
954
+ justify-content: space-between;
955
+ align-items: center;
956
+ padding: 5px 10px;
957
+ }
958
+
959
+ .cpk-td__event-type {
960
+ font-family: "Spline Sans Mono", monospace;
961
+ font-size: 9px;
962
+ font-weight: 500;
963
+ text-transform: uppercase;
964
+ }
965
+
966
+ .cpk-td__event-time {
967
+ font-family: "Spline Sans Mono", monospace;
968
+ font-size: 9px;
969
+ color: #838389;
970
+ }
971
+
972
+ .cpk-td__event-payload {
973
+ margin: 0;
974
+ font-family: "Spline Sans Mono", monospace;
975
+ font-size: 10px;
976
+ line-height: 1.6;
977
+ white-space: pre-wrap;
978
+ word-break: break-all;
979
+ color: #57575b;
980
+ padding: 8px 10px;
981
+ border-top: 1px solid #e9e9ef;
982
+ }
983
+
984
+ /* ── JSON block (agent state) ────────────────────────────────────── */
985
+ .cpk-td__json-block {
986
+ margin: 0;
987
+ font-family: "Spline Sans Mono", monospace;
988
+ font-size: 11px;
989
+ line-height: 1.8;
990
+ white-space: pre-wrap;
991
+ word-break: break-all;
992
+ color: #57575b;
993
+ }
994
+
995
+ /* ── Resize divider ──────────────────────────────────────────────── */
996
+ /* Floats over the drawer's left edge so the toggle and the drawer
997
+ touch directly without a 4px flex-gap between them. The hit zone
998
+ is wider than its visual hint to make it easy to grab. */
999
+ .cpk-td__detail-divider {
1000
+ position: absolute;
1001
+ top: 0;
1002
+ bottom: 0;
1003
+ left: -3px;
1004
+ width: 7px;
1005
+ cursor: col-resize;
1006
+ background: transparent;
1007
+ z-index: 5;
1008
+ }
1009
+
1010
+ .cpk-td__detail-divider:hover {
1011
+ background: rgba(190, 194, 255, 0.3);
1012
+ }
1013
+
1014
+ /* ── Right detail panel ──────────────────────────────────────────── */
1015
+ .cpk-td__detail {
1016
+ flex-shrink: 0;
1017
+ overflow: hidden;
1018
+ background: #f7f7f9;
1019
+ display: flex;
1020
+ flex-direction: column;
1021
+ gap: 0;
1022
+ padding: 0;
1023
+ box-sizing: border-box;
1024
+ position: relative;
1025
+ /* Slide open/closed via width + padding transition. When closed,
1026
+ width and padding are 0 so the drawer fully collapses. */
1027
+ transition:
1028
+ width 220ms cubic-bezier(0.4, 0, 0.2, 1),
1029
+ padding 220ms cubic-bezier(0.4, 0, 0.2, 1);
1030
+ }
1031
+
1032
+ .cpk-td__detail[data-open="true"] {
1033
+ overflow-y: auto;
1034
+ padding: 16px;
1035
+ }
1036
+
1037
+ .cpk-tdp__section-title {
1038
+ font-family: "Spline Sans Mono", monospace;
1039
+ font-size: 10px;
1040
+ font-weight: 500;
1041
+ color: #838389;
1042
+ text-transform: uppercase;
1043
+ letter-spacing: 0.6px;
1044
+ margin-bottom: 8px;
1045
+ }
1046
+
1047
+ .cpk-tdp__divider {
1048
+ height: 1px;
1049
+ background: #dbdbe5;
1050
+ margin: 14px 0;
1051
+ }
1052
+
1053
+ .cpk-tdp__row {
1054
+ display: flex;
1055
+ justify-content: space-between;
1056
+ align-items: flex-start;
1057
+ padding: 3px 0;
1058
+ gap: 8px;
1059
+ }
1060
+
1061
+ .cpk-tdp__label {
1062
+ color: #838389;
1063
+ font-size: 11px;
1064
+ white-space: nowrap;
1065
+ flex-shrink: 0;
1066
+ }
1067
+
1068
+ .cpk-tdp__value {
1069
+ color: #010507;
1070
+ font-family: "Spline Sans Mono", monospace;
1071
+ font-size: 11px;
1072
+ text-align: right;
1073
+ min-width: 0;
1074
+ }
1075
+
1076
+ .cpk-tdp__value--truncate {
1077
+ overflow: hidden;
1078
+ text-overflow: ellipsis;
1079
+ white-space: nowrap;
1080
+ max-width: 130px;
1081
+ }
1082
+
1083
+ .cpk-tdp__value--wrap {
1084
+ white-space: normal;
1085
+ word-break: break-all;
1086
+ text-align: right;
1087
+ }
1088
+ `;
1089
+ }
1090
+ updated(_changed) {
1091
+ if (this.threadId !== this._lastFetchedThreadId) {
1092
+ this._lastFetchedThreadId = this.threadId;
1093
+ this._lastSeenLiveMessageVersion = this.liveMessageVersion;
1094
+ this._tab = "conversation";
1095
+ this._activatedTabs = new Set(["conversation"]);
1096
+ this._panelTplCache = /* @__PURE__ */ new Map();
1097
+ this._expandedTools = /* @__PURE__ */ new Set();
1098
+ this._expandedMessages = /* @__PURE__ */ new Set();
1099
+ this._messagesAbort?.abort();
1100
+ this._messagesAbort = null;
1101
+ this._eventsAbort?.abort();
1102
+ this._eventsAbort = null;
1103
+ this._stateAbort?.abort();
1104
+ this._stateAbort = null;
1105
+ this._eventsFetched = false;
1106
+ this._stateFetched = false;
1107
+ this._fetchedEvents = null;
1108
+ this._fetchedState = null;
1109
+ if (this.threadId) this.fetchMessages(this.threadId);
1110
+ else this._conversation = [];
1111
+ } else if (this.threadId && this.liveMessageVersion !== this._lastSeenLiveMessageVersion) {
1112
+ this._lastSeenLiveMessageVersion = this.liveMessageVersion;
1113
+ this._messagesAbort?.abort();
1114
+ this._messagesAbort = null;
1115
+ this.fetchMessages(this.threadId, true);
1116
+ }
1117
+ }
1118
+ /**
1119
+ * Fetch the canonical conversation for `threadId` from the runtime.
1120
+ *
1121
+ * `silent` is true for live re-fetches triggered by `liveMessageVersion`
1122
+ * bumps during streaming. In that mode we never toggle the loading state
1123
+ * (which would flash "Loading messages…" between every message) and we
1124
+ * keep the previous conversation on transient errors instead of blanking
1125
+ * it. Initial threadId-change fetches use the default (`silent=false`)
1126
+ * so users see an explicit loading indicator on first load.
1127
+ */
1128
+ async fetchMessages(threadId, silent = false) {
1129
+ if (!this.runtimeUrl) {
1130
+ if (!silent) this._conversation = [];
1131
+ return;
1132
+ }
1133
+ const controller = new AbortController();
1134
+ this._messagesAbort = controller;
1135
+ if (!silent) {
1136
+ this._loadingMessages = true;
1137
+ this._messagesError = null;
1138
+ }
1139
+ try {
1140
+ const res = await fetch(`${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/messages`, {
1141
+ headers: { ...this.headers },
1142
+ signal: controller.signal
1143
+ });
1144
+ if (controller.signal.aborted || this.threadId !== threadId) return;
1145
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1146
+ const data = await res.json();
1147
+ if (controller.signal.aborted || this.threadId !== threadId) return;
1148
+ this._conversation = this.mapMessages(data.messages);
1149
+ } catch (err) {
1150
+ if (err instanceof Error && err.name === "AbortError") return;
1151
+ if (!silent) {
1152
+ this._messagesError = err instanceof Error ? err.message : "Failed to load messages";
1153
+ this._conversation = [];
1154
+ }
1155
+ } finally {
1156
+ if (!silent && !controller.signal.aborted) this._loadingMessages = false;
1157
+ }
1158
+ }
1159
+ async fetchEvents(threadId) {
1160
+ this._eventsNotAvailable = false;
1161
+ if (!this.runtimeUrl) {
1162
+ this._fetchedEvents = null;
1163
+ return;
1164
+ }
1165
+ const controller = new AbortController();
1166
+ this._eventsAbort = controller;
1167
+ this._loadingEvents = true;
1168
+ this._eventsError = null;
1169
+ try {
1170
+ const res = await fetch(`${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/events`, {
1171
+ headers: { ...this.headers },
1172
+ signal: controller.signal
1173
+ });
1174
+ if (controller.signal.aborted || this.threadId !== threadId) return;
1175
+ if (res.status === 501) {
1176
+ this._eventsNotAvailable = true;
1177
+ this._fetchedEvents = null;
1178
+ return;
1179
+ }
1180
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1181
+ const data = await res.json();
1182
+ if (controller.signal.aborted || this.threadId !== threadId) return;
1183
+ this._fetchedEvents = this.mapApiEvents(data.events);
1184
+ } catch (err) {
1185
+ if (err instanceof Error && err.name === "AbortError") return;
1186
+ if (this.threadId !== threadId) return;
1187
+ this._eventsError = err instanceof Error ? err.message : "Failed to load events";
1188
+ this._fetchedEvents = [];
1189
+ } finally {
1190
+ if (!controller.signal.aborted && this.threadId === threadId) this._loadingEvents = false;
1191
+ }
1192
+ }
1193
+ async fetchState(threadId) {
1194
+ this._stateNotAvailable = false;
1195
+ if (!this.runtimeUrl) {
1196
+ this._fetchedState = null;
1197
+ return;
1198
+ }
1199
+ const controller = new AbortController();
1200
+ this._stateAbort = controller;
1201
+ this._loadingState = true;
1202
+ this._stateError = null;
1203
+ try {
1204
+ const res = await fetch(`${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/state`, {
1205
+ headers: { ...this.headers },
1206
+ signal: controller.signal
1207
+ });
1208
+ if (controller.signal.aborted || this.threadId !== threadId) return;
1209
+ if (res.status === 501) {
1210
+ this._stateNotAvailable = true;
1211
+ this._fetchedState = null;
1212
+ return;
1213
+ }
1214
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1215
+ const data = await res.json();
1216
+ if (controller.signal.aborted || this.threadId !== threadId) return;
1217
+ this._fetchedState = data.state ?? null;
1218
+ } catch (err) {
1219
+ if (err instanceof Error && err.name === "AbortError") return;
1220
+ if (this.threadId !== threadId) return;
1221
+ this._stateError = err instanceof Error ? err.message : "Failed to load state";
1222
+ this._fetchedState = null;
1223
+ } finally {
1224
+ if (!controller.signal.aborted && this.threadId === threadId) this._loadingState = false;
1225
+ }
1226
+ }
1227
+ mapMessages(messages) {
1228
+ const items = [];
1229
+ const toolCallMap = /* @__PURE__ */ new Map();
1230
+ for (const msg of messages) if (msg.role === "user" && msg.content) items.push({
1231
+ id: msg.id,
1232
+ type: "user",
1233
+ content: msg.content,
1234
+ createdAt: ""
1235
+ });
1236
+ else if (msg.role === "assistant") {
1237
+ if (msg.toolCalls?.length) for (const tc of msg.toolCalls) {
1238
+ let args = {};
1239
+ try {
1240
+ args = JSON.parse(tc.args);
1241
+ } catch (err) {
1242
+ console.error("[CopilotKit Inspector] Failed to parse tool-call arguments", {
1243
+ toolCallId: tc.id,
1244
+ raw: tc.args,
1245
+ error: err
1246
+ });
1247
+ args = {
1248
+ __parseError: true,
1249
+ __raw: tc.args
1250
+ };
1251
+ }
1252
+ const item = {
1253
+ id: tc.id,
1254
+ type: "tool_call",
1255
+ toolName: tc.name,
1256
+ toolCallId: tc.id,
1257
+ arguments: args,
1258
+ result: null,
1259
+ createdAt: ""
1260
+ };
1261
+ toolCallMap.set(tc.id, item);
1262
+ items.push(item);
1263
+ }
1264
+ if (msg.content) items.push({
1265
+ id: msg.id,
1266
+ type: "assistant",
1267
+ content: msg.content,
1268
+ createdAt: ""
1269
+ });
1270
+ } else if (msg.role === "activity") items.push({
1271
+ id: msg.id,
1272
+ type: "generative-ui",
1273
+ activityType: msg.activityType ?? "unknown",
1274
+ createdAt: ""
1275
+ });
1276
+ else if (msg.role === "tool" && msg.toolCallId) {
1277
+ const tc = toolCallMap.get(msg.toolCallId);
1278
+ if (tc) try {
1279
+ tc.result = JSON.parse(msg.content ?? "{}");
1280
+ } catch (err) {
1281
+ console.error("[CopilotKit Inspector] Failed to parse tool-call result content", {
1282
+ toolCallId: msg.toolCallId,
1283
+ raw: msg.content,
1284
+ error: err
1285
+ });
1286
+ tc.result = {
1287
+ __parseError: true,
1288
+ __raw: msg.content ?? null
1289
+ };
1290
+ }
1291
+ }
1292
+ return items;
1293
+ }
1294
+ mapApiEvents(events) {
1295
+ return events.map((event) => {
1296
+ const { type, timestamp, ...rest } = event;
1297
+ return {
1298
+ type: typeof type === "string" ? type : "UNKNOWN",
1299
+ timestamp: typeof timestamp === "string" || typeof timestamp === "number" ? timestamp : Date.now(),
1300
+ payload: rest
1301
+ };
1302
+ });
1303
+ }
1304
+ get renderItems() {
1305
+ const items = this._conversation;
1306
+ const result = [];
1307
+ const seen = /* @__PURE__ */ new Set();
1308
+ for (const item of items) {
1309
+ if (item.type === "agent_responded") continue;
1310
+ if (item.type !== "tool_call" || !item.groupId) {
1311
+ result.push(item);
1312
+ continue;
1313
+ }
1314
+ if (seen.has(item.groupId)) continue;
1315
+ seen.add(item.groupId);
1316
+ const group = {
1317
+ type: "tool_call_group",
1318
+ id: item.groupId,
1319
+ items: items.filter((i) => i.type === "tool_call" && i.groupId === item.groupId)
1320
+ };
1321
+ result.push(group);
1322
+ }
1323
+ return result;
1324
+ }
1325
+ get activityCounts() {
1326
+ let messages = 0;
1327
+ let toolCalls = 0;
1328
+ let generativeUi = 0;
1329
+ for (const item of this._conversation) {
1330
+ if (item.type === "user" || item.type === "assistant") messages++;
1331
+ if (item.type === "tool_call") toolCalls++;
1332
+ if (item.type === "generative-ui") generativeUi++;
1333
+ }
1334
+ return {
1335
+ messages,
1336
+ toolCalls,
1337
+ generativeUi
1338
+ };
1339
+ }
1340
+ get duration() {
1341
+ const t = this.thread;
1342
+ if (!t?.createdAt || !t?.updatedAt) return "—";
1343
+ const ms = new Date(t.updatedAt).getTime() - new Date(t.createdAt).getTime();
1344
+ if (ms < 0) return "—";
1345
+ if (ms < 1e3) return `${ms}ms`;
1346
+ const s = Math.floor(ms / 1e3);
1347
+ if (s < 60) return `${s}s`;
1348
+ return `${Math.floor(s / 60)}m ${s % 60}s`;
1349
+ }
1350
+ toggleToolExpand(id) {
1351
+ const next = new Set(this._expandedTools);
1352
+ if (next.has(id)) next.delete(id);
1353
+ else next.add(id);
1354
+ this._expandedTools = next;
1355
+ }
1356
+ toggleMessageExpand(id) {
1357
+ const next = new Set(this._expandedMessages);
1358
+ if (next.has(id)) next.delete(id);
1359
+ else next.add(id);
1360
+ this._expandedMessages = next;
1361
+ }
1362
+ get activeEvents() {
1363
+ if (this._eventsNotAvailable) return [];
1364
+ return this._fetchedEvents ?? this.agentEventsInput ?? [];
1365
+ }
1366
+ get activeState() {
1367
+ if (this._stateNotAvailable) return null;
1368
+ return this._fetchedState ?? this.agentStateInput ?? null;
1369
+ }
1370
+ hasRenderableState() {
1371
+ const s = this.activeState;
1372
+ return !!s && typeof s === "object" && Object.keys(s).length > 0;
1373
+ }
1374
+ shortId(id) {
1375
+ if (!id) return "—";
1376
+ return id.length > 20 ? id.slice(0, 8) + "…" : id;
1377
+ }
1378
+ fmtTime(dateStr) {
1379
+ if (!dateStr) return "—";
1380
+ const d = new Date(dateStr);
1381
+ if (Number.isNaN(d.getTime())) return "—";
1382
+ return d.toLocaleTimeString("en-US", {
1383
+ hour: "2-digit",
1384
+ minute: "2-digit",
1385
+ second: "2-digit",
1386
+ hour12: false
1387
+ });
1388
+ }
1389
+ render() {
1390
+ return html`
1391
+ <div class="cpk-td">
1392
+ <!-- ── Left area: tabs + content ─────────────────────────────────── -->
1393
+ <div class="cpk-td__left">
1394
+ <!-- Tab bar -->
1395
+ <div class="cpk-td__tabs-header">
1396
+ <div class="cpk-td__tab-group" role="tablist">
1397
+ ${ɵCpkThreadDetails.TAB_LIST.map((tab) => html`
1398
+ <button
1399
+ role="tab"
1400
+ class="cpk-td__tab ${this._tab === tab.id ? "cpk-td__tab--active" : ""}"
1401
+ @click=${() => this.activateTab(tab.id)}
1402
+ >
1403
+ ${tab.label}
1404
+ </button>
1405
+ `)}
1406
+ </div>
1407
+ ${this.renderPanelToggle()}
1408
+ </div>
1409
+
1410
+ <!-- Scrollable content -->
1411
+ <div class="cpk-td__content">
1412
+ ${this._panelInitializing ? html`
1413
+ <div class="cpk-td__status">Loading…</div>
1414
+ ` : nothing}
1415
+ ${ɵCpkThreadDetails.TAB_LIST.map((tab) => this._activatedTabs.has(tab.id) ? html`<div
1416
+ class="cpk-td__panel"
1417
+ style=${this._tab === tab.id && !this._panelInitializing ? "" : "display:none"}
1418
+ >
1419
+ ${this.renderTabContent(tab.id)}
1420
+ </div>` : nothing)}
1421
+ </div>
1422
+ </div>
1423
+
1424
+ <!--
1425
+ Drawer always rendered so width animates between 0 and its
1426
+ target. Divider lives INSIDE the drawer and is absolutely
1427
+ positioned over its left edge so the toggle (rightmost of the
1428
+ tab row) and the drawer touch with no flex-gap between them.
1429
+ -->
1430
+ <div
1431
+ class="cpk-td__detail"
1432
+ data-open=${this._showDetailPanel ? "true" : "false"}
1433
+ style="width:${this._showDetailPanel ? this._detailPanelWidth : 0}px"
1434
+ aria-hidden=${this._showDetailPanel ? "false" : "true"}
1435
+ >
1436
+ ${this._showDetailPanel ? html`
1437
+ <div
1438
+ class="cpk-td__detail-divider"
1439
+ @pointerdown=${this.onDetailDividerDown}
1440
+ @pointermove=${this.onDetailDividerMove}
1441
+ @pointerup=${this.onDetailDividerUp}
1442
+ @pointercancel=${this.onDetailDividerUp}
1443
+ ></div>
1444
+ ` : nothing}
1445
+ ${this.renderDetailPanel()}
1446
+ </div>
1447
+ </div>
1448
+ `;
1449
+ }
1450
+ renderConversation() {
1451
+ if (this._loadingMessages) return html`
1452
+ <div class="cpk-td__status">Loading messages…</div>
1453
+ `;
1454
+ if (this._messagesError) return html`<div class="cpk-td__status cpk-td__status--error">
1455
+ ${this._messagesError}
1456
+ </div>`;
1457
+ if (this._conversation.length === 0) return html`
1458
+ <div class="cpk-td__empty-state">
1459
+ <svg
1460
+ width="28"
1461
+ height="28"
1462
+ viewBox="0 0 24 24"
1463
+ fill="none"
1464
+ stroke="currentColor"
1465
+ stroke-width="1.5"
1466
+ stroke-linecap="round"
1467
+ stroke-linejoin="round"
1468
+ >
1469
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
1470
+ </svg>
1471
+ <span>No messages yet</span>
1472
+ </div>
1473
+ `;
1474
+ return this.cachedPanelTpl("conversation", [
1475
+ this._conversation,
1476
+ this._expandedTools,
1477
+ this._expandedMessages
1478
+ ], () => {
1479
+ return html`${this.renderItems.map((item) => this.renderRenderItem(item))}`;
1480
+ });
1481
+ }
1482
+ /**
1483
+ * Memoize the rendered TemplateResult for `slot` keyed by tuple
1484
+ * element-wise reference equality. The hot path for tab switches: when
1485
+ * the underlying data hasn't changed, return the previously built
1486
+ * TemplateResult so Lit's diff short-circuits. Each panel's `key` is
1487
+ * the tuple of inputs the template reads — pass everything the template
1488
+ * depends on, or the cache will return stale output when those inputs
1489
+ * change without the listed key flipping.
1490
+ */
1491
+ cachedPanelTpl(slot, key, build) {
1492
+ const cached = this._panelTplCache.get(slot);
1493
+ if (cached && cached.key.length === key.length && cached.key.every((v, i) => v === key[i])) return cached.tpl;
1494
+ const tpl = build();
1495
+ this._panelTplCache.set(slot, {
1496
+ key,
1497
+ tpl
1498
+ });
1499
+ return tpl;
1500
+ }
1501
+ renderRenderItem(item) {
1502
+ switch (item.type) {
1503
+ case "user":
1504
+ case "assistant": return this.renderBubble(item);
1505
+ case "tool_call": return this.renderToolBlock(item);
1506
+ case "tool_call_group": return this.renderToolGroup(item);
1507
+ case "reasoning": return html`<div class="cpk-td__inline-chip">
1508
+ <span>Reasoned for ${item.duration}</span>
1509
+ </div>`;
1510
+ case "state_update": return html`
1511
+ <div class="cpk-td__inline-chip">
1512
+ <span>Updated agent state</span>
1513
+ </div>
1514
+ `;
1515
+ case "generative-ui": return this.renderGenerativeUI(item);
1516
+ case "agent_responded": return nothing;
1517
+ }
1518
+ }
1519
+ renderBubble(item) {
1520
+ const isUser = item.type === "user";
1521
+ const threshold = ɵCpkThreadDetails.COLLAPSE_THRESHOLD;
1522
+ const expanded = this._expandedMessages.has(item.id);
1523
+ const tooLong = item.content.length > threshold;
1524
+ const shown = tooLong && !expanded ? item.content.slice(0, threshold) + "…" : item.content;
1525
+ return html`
1526
+ <div
1527
+ class="cpk-td__bubble ${isUser ? "cpk-td__bubble--user" : "cpk-td__bubble--assistant"}"
1528
+ >
1529
+ <div
1530
+ class="cpk-td__bubble-inner ${isUser ? "cpk-td__bubble-inner--user" : "cpk-td__bubble-inner--assistant"}"
1531
+ >
1532
+ ${shown}
1533
+ ${tooLong ? html`<span
1534
+ class="cpk-td__show-more"
1535
+ @click=${() => this.toggleMessageExpand(item.id)}
1536
+ >${expanded ? "Show less" : "Show more"}</span
1537
+ >` : nothing}
1538
+ </div>
1539
+ </div>
1540
+ `;
1541
+ }
1542
+ renderToolBlock(item) {
1543
+ const expanded = this._expandedTools.has(item.id);
1544
+ return html`
1545
+ <div class="cpk-td__tool-block">
1546
+ <div
1547
+ class="cpk-td__tool-header"
1548
+ @click=${() => this.toggleToolExpand(item.id)}
1549
+ >
1550
+ <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
1551
+ <path
1552
+ 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"
1553
+ stroke="#189370"
1554
+ stroke-width="1.2"
1555
+ stroke-linecap="round"
1556
+ stroke-linejoin="round"
1557
+ />
1558
+ </svg>
1559
+ <span class="cpk-td__tool-name">${item.toolName}</span>
1560
+ ${item.result || Object.keys(item.arguments).length > 0 ? html`
1561
+ <span class="cpk-td__tool-status">DONE</span>
1562
+ ` : html`
1563
+ <span class="cpk-td__tool-status cpk-td__tool-status--pending">PENDING</span>
1564
+ `}
1565
+ <span class="cpk-td__tool-chevron">${expanded ? "▾" : "▸"}</span>
1566
+ </div>
1567
+ ${expanded ? html`
1568
+ <div class="cpk-td__tool-body">
1569
+ <div class="cpk-td__tool-section-label">Arguments</div>
1570
+ <pre class="cpk-td__tool-pre">
1571
+ ${unsafeHTML(highlightedJson(item.arguments))}</pre
1572
+ >
1573
+ ${item.result ? html`
1574
+ <div
1575
+ class="cpk-td__tool-section-label"
1576
+ style="margin-top:8px"
1577
+ >
1578
+ Result
1579
+ </div>
1580
+ <pre class="cpk-td__tool-pre">
1581
+ ${unsafeHTML(highlightedJson(item.result))}</pre
1582
+ >
1583
+ ` : nothing}
1584
+ </div>
1585
+ ` : nothing}
1586
+ </div>
1587
+ `;
1588
+ }
1589
+ renderToolGroup(group) {
1590
+ return html`
1591
+ <div class="cpk-td__tool-group">
1592
+ <div class="cpk-td__tool-group-header">
1593
+ ${group.items.length} tool call${group.items.length !== 1 ? "s" : ""}
1594
+ </div>
1595
+ ${group.items.map((tc) => this.renderToolBlock(tc))}
1596
+ </div>
1597
+ `;
1598
+ }
1599
+ renderGenerativeUI(item) {
1600
+ return html`
1601
+ <div class="cpk-td__genui">
1602
+ <div class="cpk-td__genui-badge">
1603
+ <svg width="9" height="9" viewBox="0 0 24 24" fill="currentColor">
1604
+ <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
1605
+ </svg>
1606
+ Generative UI
1607
+ </div>
1608
+ <div class="cpk-td__genui-placeholder">
1609
+ ${item.activityType} — rendered in chat
1610
+ </div>
1611
+ </div>
1612
+ `;
1613
+ }
1614
+ renderState() {
1615
+ if (this._loadingState) return html`
1616
+ <div class="cpk-td__status">Loading state…</div>
1617
+ `;
1618
+ if (this._stateError) return html`<div class="cpk-td__status cpk-td__status--error">
1619
+ ${this._stateError}
1620
+ </div>`;
1621
+ if (this._stateNotAvailable) return html`
1622
+ <div class="cpk-td__empty-state">
1623
+ <svg
1624
+ width="28"
1625
+ height="28"
1626
+ viewBox="0 0 24 24"
1627
+ fill="none"
1628
+ stroke="currentColor"
1629
+ stroke-width="1.5"
1630
+ stroke-linecap="round"
1631
+ stroke-linejoin="round"
1632
+ >
1633
+ <ellipse cx="12" cy="5" rx="9" ry="3" />
1634
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
1635
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
1636
+ </svg>
1637
+ <span>State history not available</span>
1638
+ <span class="cpk-td__empty-hint"
1639
+ >This runtime doesn't yet expose per-thread agent state. Available when
1640
+ running against the in-memory runner.</span
1641
+ >
1642
+ </div>
1643
+ `;
1644
+ if (!this.hasRenderableState()) return html`
1645
+ <div class="cpk-td__empty-state">
1646
+ <svg
1647
+ width="28"
1648
+ height="28"
1649
+ viewBox="0 0 24 24"
1650
+ fill="none"
1651
+ stroke="currentColor"
1652
+ stroke-width="1.5"
1653
+ stroke-linecap="round"
1654
+ stroke-linejoin="round"
1655
+ >
1656
+ <ellipse cx="12" cy="5" rx="9" ry="3" />
1657
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
1658
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
1659
+ </svg>
1660
+ <span>No state captured</span>
1661
+ <span class="cpk-td__empty-hint"
1662
+ >Emitted live from STATE_SNAPSHOT events.</span
1663
+ >
1664
+ </div>
1665
+ `;
1666
+ const stateValue = this.activeState;
1667
+ return this.cachedPanelTpl("agent-state", [stateValue], () => {
1668
+ return html`<pre class="cpk-td__json-block">
1669
+ ${unsafeHTML(highlightedJson(stateValue))}</pre
1670
+ >`;
1671
+ });
1672
+ }
1673
+ renderEvents() {
1674
+ if (this._loadingEvents) return html`
1675
+ <div class="cpk-td__status">Loading events…</div>
1676
+ `;
1677
+ if (this._eventsError) return html`<div class="cpk-td__status cpk-td__status--error">
1678
+ ${this._eventsError}
1679
+ </div>`;
1680
+ if (this._eventsNotAvailable) return html`
1681
+ <div class="cpk-td__empty-state">
1682
+ <span>Event history not available</span>
1683
+ <span class="cpk-td__empty-hint"
1684
+ >This runtime doesn't yet expose per-thread AG-UI events. Available when
1685
+ running against the in-memory runner.</span
1686
+ >
1687
+ </div>
1688
+ `;
1689
+ const events = this.activeEvents;
1690
+ if (events.length === 0) return html`
1691
+ <div class="cpk-td__empty-state">
1692
+ <span>No events captured</span>
1693
+ <span class="cpk-td__empty-hint"
1694
+ >Events are recorded live. Run the agent to see them here.</span
1695
+ >
1696
+ </div>
1697
+ `;
1698
+ return this.cachedPanelTpl("ag-ui-events", [events], () => {
1699
+ return html`${events.map((event) => {
1700
+ const { bg, fg } = eventColors(event.type);
1701
+ return html`
1702
+ <div class="cpk-td__event">
1703
+ <div class="cpk-td__event-header" style="background:${bg}">
1704
+ <span class="cpk-td__event-type" style="color:${fg}"
1705
+ >${event.type}</span
1706
+ >
1707
+ <span class="cpk-td__event-time"
1708
+ >${formatTimestamp(event.timestamp)}</span
1709
+ >
1710
+ </div>
1711
+ <pre class="cpk-td__event-payload">
1712
+ ${unsafeHTML(highlightedJson(event.payload))}</pre
1713
+ >
1714
+ </div>
1715
+ `;
1716
+ })}`;
1717
+ });
1718
+ }
1719
+ renderPanelToggle() {
1720
+ return html`
1721
+ <button
1722
+ class="cpk-td__panel-toggle ${this._showDetailPanel ? "cpk-td__panel-toggle--active" : ""}"
1723
+ @click=${() => {
1724
+ this._showDetailPanel = !this._showDetailPanel;
1725
+ }}
1726
+ title="Toggle thread details"
1727
+ type="button"
1728
+ >
1729
+ <svg
1730
+ width="14"
1731
+ height="14"
1732
+ viewBox="0 0 24 24"
1733
+ fill="none"
1734
+ stroke="currentColor"
1735
+ stroke-width="2"
1736
+ stroke-linecap="round"
1737
+ stroke-linejoin="round"
1738
+ >
1739
+ <rect x="3" y="3" width="18" height="18" rx="2" />
1740
+ <line x1="15" y1="3" x2="15" y2="21" />
1741
+ </svg>
1742
+ </button>
1743
+ `;
1744
+ }
1745
+ renderDetailPanel() {
1746
+ const counts = this.activityCounts;
1747
+ return html`
1748
+ <!-- Thread -->
1749
+ <div class="cpk-tdp__section-title">Thread</div>
1750
+ <div class="cpk-tdp__row">
1751
+ <span class="cpk-tdp__label">ID</span>
1752
+ <span class="cpk-tdp__value cpk-tdp__value--wrap"
1753
+ >${this.shortId(this.thread?.id)}</span
1754
+ >
1755
+ </div>
1756
+ <div class="cpk-tdp__row">
1757
+ <span class="cpk-tdp__label">Name</span>
1758
+ <span class="cpk-tdp__value">${this.thread?.name ?? "—"}</span>
1759
+ </div>
1760
+ <div class="cpk-tdp__row">
1761
+ <span class="cpk-tdp__label">Agent</span>
1762
+ <span class="cpk-tdp__value cpk-tdp__value--truncate"
1763
+ >${this.thread?.agentId ?? "—"}</span
1764
+ >
1765
+ </div>
1766
+ <div class="cpk-tdp__row">
1767
+ <span class="cpk-tdp__label">Created by</span>
1768
+ <span class="cpk-tdp__value cpk-tdp__value--truncate"
1769
+ >${this.thread?.createdById ?? "—"}</span
1770
+ >
1771
+ </div>
1772
+
1773
+ <div class="cpk-tdp__divider"></div>
1774
+
1775
+ <!-- Timestamps -->
1776
+ <div class="cpk-tdp__section-title">Timestamps</div>
1777
+ <div class="cpk-tdp__row">
1778
+ <span class="cpk-tdp__label">Created</span>
1779
+ <span class="cpk-tdp__value">${this.fmtTime(this.thread?.createdAt)}</span>
1780
+ </div>
1781
+ <div class="cpk-tdp__row">
1782
+ <span class="cpk-tdp__label">Updated</span>
1783
+ <span class="cpk-tdp__value">${this.fmtTime(this.thread?.updatedAt)}</span>
1784
+ </div>
1785
+ <div class="cpk-tdp__row">
1786
+ <span class="cpk-tdp__label">Duration</span>
1787
+ <span class="cpk-tdp__value">${this.duration}</span>
1788
+ </div>
1789
+
1790
+ <div class="cpk-tdp__divider"></div>
1791
+
1792
+ <!-- Activity -->
1793
+ <div class="cpk-tdp__section-title">Activity</div>
1794
+ <div class="cpk-tdp__row">
1795
+ <span class="cpk-tdp__label">Messages</span>
1796
+ <span class="cpk-tdp__value">${counts.messages}</span>
1797
+ </div>
1798
+ <div class="cpk-tdp__row">
1799
+ <span class="cpk-tdp__label">Tool calls</span>
1800
+ <span class="cpk-tdp__value">${counts.toolCalls}</span>
1801
+ </div>
1802
+ <div class="cpk-tdp__row">
1803
+ <span class="cpk-tdp__label">AG-UI events</span>
1804
+ <span class="cpk-tdp__value">${this.activeEvents.length}</span>
1805
+ </div>
1806
+ `;
1807
+ }
1808
+ };
1809
+ if (!customElements.get("cpk-thread-list")) customElements.define("cpk-thread-list", CpkThreadList);
1810
+ if (!customElements.get("cpk-thread-details")) customElements.define("cpk-thread-details", ɵCpkThreadDetails);
1811
+ var WebInspectorElement = class WebInspectorElement extends LitElement {
1812
+ constructor(..._args3) {
1813
+ super(..._args3);
60
1814
  this._core = null;
61
1815
  this.coreSubscriber = null;
62
1816
  this.coreUnsubscribe = null;
@@ -66,6 +1820,8 @@ var WebInspectorElement = class extends LitElement {
66
1820
  this.agentSubscriptions = /* @__PURE__ */ new Map();
67
1821
  this.agentEvents = /* @__PURE__ */ new Map();
68
1822
  this.agentMessages = /* @__PURE__ */ new Map();
1823
+ this.agentRunThreadId = /* @__PURE__ */ new Map();
1824
+ this.liveMessageVersion = /* @__PURE__ */ new Map();
69
1825
  this.agentStates = /* @__PURE__ */ new Map();
70
1826
  this.flattenedEvents = [];
71
1827
  this.eventCounter = 0;
@@ -82,6 +1838,17 @@ var WebInspectorElement = class extends LitElement {
82
1838
  this.draggedDuringInteraction = false;
83
1839
  this.ignoreNextButtonClick = false;
84
1840
  this.selectedMenu = "ag-ui-events";
1841
+ this.selectedThreadId = null;
1842
+ this.threadListWidth = 290;
1843
+ this.threadDividerResizing = false;
1844
+ this.threadDividerPointerId = -1;
1845
+ this.threadDividerStartX = 0;
1846
+ this.threadDividerStartWidth = 0;
1847
+ this._threads = [];
1848
+ this._threadStoreSubscriptions = /* @__PURE__ */ new Map();
1849
+ this._threadsByAgent = /* @__PURE__ */ new Map();
1850
+ this._threadsErrorByAgent = /* @__PURE__ */ new Map();
1851
+ this._ownedThreadStores = /* @__PURE__ */ new Map();
85
1852
  this.contextMenuOpen = false;
86
1853
  this.dockMode = "floating";
87
1854
  this.previousBodyMargins = null;
@@ -94,15 +1861,26 @@ var WebInspectorElement = class extends LitElement {
94
1861
  this.toolSignature = "";
95
1862
  this.eventFilterText = "";
96
1863
  this.eventTypeFilter = "all";
97
- this.announcementMarkdown = null;
1864
+ this.evtColWidths = [
1865
+ 100,
1866
+ 80,
1867
+ 150
1868
+ ];
1869
+ this._evtColResize = null;
1870
+ this._threadsUnlocked = false;
1871
+ this._threadsUnlocking = false;
1872
+ this._threadsGateError = null;
1873
+ this._threadsGateCodeInvalid = false;
1874
+ this._threadsGateInvalidTimer = null;
1875
+ this._threadsUnlockingTimer = null;
98
1876
  this.announcementHtml = null;
99
1877
  this.announcementTimestamp = null;
100
1878
  this.announcementPreviewText = null;
101
1879
  this.hasUnseenAnnouncement = false;
102
1880
  this.announcementLoaded = false;
103
- this.announcementLoadError = null;
104
1881
  this.announcementPromise = null;
105
1882
  this.showAnnouncementPreview = true;
1883
+ this.announcementExpanded = false;
106
1884
  this.contextState = {
107
1885
  button: {
108
1886
  position: {
@@ -143,28 +1921,7 @@ var WebInspectorElement = class extends LitElement {
143
1921
  this.resizeStart = null;
144
1922
  this.resizeInitialSize = null;
145
1923
  this.isResizing = false;
146
- this.menuItems = [
147
- {
148
- key: "ag-ui-events",
149
- label: "AG-UI Events",
150
- icon: "Zap"
151
- },
152
- {
153
- key: "agents",
154
- label: "Agent",
155
- icon: "Bot"
156
- },
157
- {
158
- key: "frontend-tools",
159
- label: "Frontend Tools",
160
- icon: "Hammer"
161
- },
162
- {
163
- key: "agent-context",
164
- label: "Context",
165
- icon: "FileText"
166
- }
167
- ];
1924
+ this.customTabIcons = { threads: `<svg class="h-3.5 w-3.5" width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.04167 15C8.29167 15 7.65972 14.7431 7.14583 14.2292C6.63194 13.7153 6.375 13.0972 6.375 12.375C6.375 11.3194 6.80208 10.3646 7.65625 9.51042C8.51042 8.65625 9.57639 8.125 10.8542 7.91667C10.8125 7.41667 10.6875 7.03819 10.4792 6.78125C10.2708 6.52431 9.98611 6.39583 9.625 6.39583C9.20833 6.39583 8.75694 6.56944 8.27083 6.91667C7.78472 7.26389 7.20833 7.83333 6.54167 8.625C5.45833 9.91667 4.66319 10.7569 4.15625 11.1458C3.64931 11.5347 3.10417 11.7292 2.52083 11.7292C1.8125 11.7292 1.21528 11.4653 0.729167 10.9375C0.243056 10.4097 0 9.77083 0 9.02083C0 8.27083 0.163194 7.50347 0.489583 6.71875C0.815972 5.93403 1.36806 4.99306 2.14583 3.89583C2.40972 3.53472 2.60417 3.22917 2.72917 2.97917C2.85417 2.72917 2.91667 2.52778 2.91667 2.375C2.91667 2.27778 2.89931 2.20486 2.86458 2.15625C2.82986 2.10764 2.77778 2.08333 2.70833 2.08333C2.56944 2.08333 2.39583 2.17014 2.1875 2.34375C1.97917 2.51736 1.73611 2.78472 1.45833 3.14583L0 1.66667C0.444444 1.125 0.895833 0.711806 1.35417 0.427083C1.8125 0.142361 2.26389 0 2.70833 0C3.34722 0 3.88889 0.222222 4.33333 0.666667C4.77778 1.11111 5 1.66667 5 2.33333C5 2.73611 4.89583 3.18056 4.6875 3.66667C4.47917 4.15278 4.13194 4.73611 3.64583 5.41667C3.11806 6.16667 2.72569 6.82639 2.46875 7.39583C2.21181 7.96528 2.08333 8.46528 2.08333 8.89583C2.08333 9.13194 2.12153 9.31597 2.19792 9.44792C2.27431 9.57986 2.38194 9.64583 2.52083 9.64583C2.65972 9.64583 2.78125 9.60764 2.88542 9.53125C2.98958 9.45486 3.18056 9.27083 3.45833 8.97917C3.63889 8.78472 3.85417 8.54514 4.10417 8.26042C4.35417 7.97569 4.65972 7.625 5.02083 7.20833C5.89583 6.16667 6.6875 5.42361 7.39583 4.97917C8.10417 4.53472 8.84722 4.3125 9.625 4.3125C10.5556 4.3125 11.3194 4.625 11.9167 5.25C12.5139 5.875 12.8542 6.72917 12.9375 7.8125H15V9.89583H12.9375C12.8264 11.4514 12.4201 12.691 11.7188 13.6146C11.0174 14.5382 10.125 15 9.04167 15ZM9.08333 12.9167C9.52778 12.9167 9.90278 12.6632 10.2083 12.1562C10.5139 11.6493 10.7222 10.9444 10.8333 10.0417C10.1944 10.1944 9.63889 10.4965 9.16667 10.9479C8.69444 11.3993 8.45833 11.8472 8.45833 12.2917C8.45833 12.4861 8.51389 12.6389 8.625 12.75C8.73611 12.8611 8.88889 12.9167 9.08333 12.9167Z" fill="currentColor"/></svg>` };
168
1925
  this.handlePointerDown = (event) => {
169
1926
  if (this.dockMode !== "floating" && this.isOpen) return;
170
1927
  const target = event.currentTarget;
@@ -329,6 +2086,26 @@ var WebInspectorElement = class extends LitElement {
329
2086
  this.expandedTools = /* @__PURE__ */ new Set();
330
2087
  this.expandedContextItems = /* @__PURE__ */ new Set();
331
2088
  this.copiedContextItems = /* @__PURE__ */ new Set();
2089
+ this.handleThreadDividerPointerDown = (event) => {
2090
+ this.threadDividerResizing = true;
2091
+ this.threadDividerPointerId = event.pointerId;
2092
+ this.threadDividerStartX = event.clientX;
2093
+ this.threadDividerStartWidth = this.threadListWidth;
2094
+ event.currentTarget.setPointerCapture(event.pointerId);
2095
+ event.preventDefault();
2096
+ };
2097
+ this.handleThreadDividerPointerMove = (event) => {
2098
+ if (!this.threadDividerResizing || this.threadDividerPointerId !== event.pointerId) return;
2099
+ const delta = event.clientX - this.threadDividerStartX;
2100
+ this.threadListWidth = Math.max(180, Math.min(480, this.threadDividerStartWidth + delta));
2101
+ this.requestUpdate();
2102
+ };
2103
+ this.handleThreadDividerPointerUp = (event) => {
2104
+ if (this.threadDividerPointerId !== event.pointerId) return;
2105
+ const target = event.currentTarget;
2106
+ if (target.hasPointerCapture(this.threadDividerPointerId)) target.releasePointerCapture(this.threadDividerPointerId);
2107
+ this.threadDividerResizing = false;
2108
+ };
332
2109
  this.handleClearEvents = () => {
333
2110
  if (this.selectedContext === "all-agents") {
334
2111
  this.agentEvents.clear();
@@ -374,6 +2151,106 @@ var WebInspectorElement = class extends LitElement {
374
2151
  this.requestUpdate("core", oldValue);
375
2152
  if (this._core) this.attachToCore(this._core);
376
2153
  }
2154
+ get menuItems() {
2155
+ return [
2156
+ {
2157
+ key: "ag-ui-events",
2158
+ label: "AG-UI Events",
2159
+ icon: "Zap"
2160
+ },
2161
+ {
2162
+ key: "agents",
2163
+ label: "Agent",
2164
+ icon: "Bot"
2165
+ },
2166
+ ...(this._core?.tools?.length ?? 0) > 0 ? [{
2167
+ key: "frontend-tools",
2168
+ label: "Frontend Tools",
2169
+ icon: "Hammer"
2170
+ }] : [],
2171
+ {
2172
+ key: "agent-context",
2173
+ label: "Context",
2174
+ icon: "FileText"
2175
+ },
2176
+ {
2177
+ key: "threads",
2178
+ label: "Threads",
2179
+ icon: "MessageSquare"
2180
+ }
2181
+ ];
2182
+ }
2183
+ subscribeToThreadStore(agentId, store) {
2184
+ if (this._threadStoreSubscriptions.has(agentId)) return;
2185
+ const threadsSub = store.select(ɵselectThreads).subscribe((threads) => {
2186
+ this._threadsByAgent.set(agentId, threads);
2187
+ this._threads = Array.from(this._threadsByAgent.values()).flat();
2188
+ this.autoSelectLatestThread();
2189
+ this.requestUpdate();
2190
+ });
2191
+ const errorSub = store.select(ɵselectThreadsError).subscribe((error) => {
2192
+ if (error) this._threadsErrorByAgent.set(agentId, error);
2193
+ else this._threadsErrorByAgent.delete(agentId);
2194
+ this.requestUpdate();
2195
+ });
2196
+ this._threadStoreSubscriptions.set(agentId, () => {
2197
+ threadsSub.unsubscribe();
2198
+ errorSub.unsubscribe();
2199
+ });
2200
+ const initialState = store.getState();
2201
+ this._threadsByAgent.set(agentId, ɵselectThreads(initialState));
2202
+ const initialError = ɵselectThreadsError(initialState);
2203
+ if (initialError) this._threadsErrorByAgent.set(agentId, initialError);
2204
+ else this._threadsErrorByAgent.delete(agentId);
2205
+ this._threads = Array.from(this._threadsByAgent.values()).flat();
2206
+ this.autoSelectLatestThread();
2207
+ }
2208
+ autoSelectLatestThread() {
2209
+ if (this._threads.length === 0) return;
2210
+ if (!(this.selectedThreadId != null && this._threads.some((t) => t.id === this.selectedThreadId))) this.selectedThreadId = this._threads[0].id;
2211
+ }
2212
+ teardownThreadStoreSubscriptions() {
2213
+ for (const unsub of this._threadStoreSubscriptions.values()) unsub();
2214
+ this._threadStoreSubscriptions.clear();
2215
+ this._threadsByAgent.clear();
2216
+ this._threadsErrorByAgent.clear();
2217
+ this._threads = [];
2218
+ }
2219
+ ensureOwnedThreadStore(agentId) {
2220
+ if (this._ownedThreadStores.has(agentId)) return;
2221
+ if (this.core?.getThreadStore(agentId)) return;
2222
+ const core = this.core;
2223
+ if (!core?.runtimeUrl) return;
2224
+ const store = ɵcreateThreadStore({ fetch: globalThis.fetch });
2225
+ store.start();
2226
+ store.setContext({
2227
+ runtimeUrl: core.runtimeUrl,
2228
+ headers: {},
2229
+ agentId
2230
+ });
2231
+ this._ownedThreadStores.set(agentId, store);
2232
+ this.subscribeToThreadStore(agentId, store);
2233
+ core.registerThreadStore(agentId, store);
2234
+ }
2235
+ refreshOwnedThreadStore(agentId) {
2236
+ const store = this._ownedThreadStores.get(agentId);
2237
+ if (!store) return;
2238
+ store.refresh();
2239
+ }
2240
+ removeOwnedThreadStore(agentId) {
2241
+ const store = this._ownedThreadStores.get(agentId);
2242
+ if (!store) return;
2243
+ store.stop();
2244
+ this.core?.unregisterThreadStore(agentId);
2245
+ this._ownedThreadStores.delete(agentId);
2246
+ }
2247
+ teardownOwnedThreadStores() {
2248
+ for (const [agentId, store] of this._ownedThreadStores) {
2249
+ store.stop();
2250
+ this.core?.unregisterThreadStore(agentId);
2251
+ }
2252
+ this._ownedThreadStores.clear();
2253
+ }
377
2254
  attachToCore(core) {
378
2255
  this.runtimeStatus = core.runtimeConnectionStatus;
379
2256
  this.coreProperties = core.properties;
@@ -381,6 +2258,11 @@ var WebInspectorElement = class extends LitElement {
381
2258
  this.coreSubscriber = {
382
2259
  onRuntimeConnectionStatusChanged: ({ status }) => {
383
2260
  this.runtimeStatus = status;
2261
+ if (status === "connected") for (const agentId of this._ownedThreadStores.keys()) this.refreshOwnedThreadStore(agentId);
2262
+ else {
2263
+ this._threadsByAgent.clear();
2264
+ this._threads = [];
2265
+ }
384
2266
  this.requestUpdate();
385
2267
  },
386
2268
  onPropertiesChanged: ({ properties }) => {
@@ -397,16 +2279,36 @@ var WebInspectorElement = class extends LitElement {
397
2279
  onAgentsChanged: ({ agents }) => {
398
2280
  this.processAgentsChanged(agents);
399
2281
  },
400
- onAgentRunStarted: ({ agent }) => {
401
- if (agent?.agentId) this.subscribeToAgent(agent);
402
- },
403
2282
  onContextChanged: ({ context }) => {
404
2283
  this.contextStore = this.normalizeContextStore(context);
405
2284
  this.requestUpdate();
2285
+ },
2286
+ onThreadStoreRegistered: ({ agentId, store }) => {
2287
+ this.subscribeToThreadStore(agentId, store);
2288
+ this.requestUpdate();
2289
+ },
2290
+ onThreadStoreUnregistered: ({ agentId }) => {
2291
+ const unsub = this._threadStoreSubscriptions.get(agentId);
2292
+ if (unsub) {
2293
+ unsub();
2294
+ this._threadStoreSubscriptions.delete(agentId);
2295
+ }
2296
+ this._threadsByAgent.delete(agentId);
2297
+ this._threadsErrorByAgent.delete(agentId);
2298
+ this._threads = Array.from(this._threadsByAgent.values()).flat();
2299
+ this.requestUpdate();
2300
+ },
2301
+ onAgentRunStarted: ({ agent }) => {
2302
+ this.subscribeToAgent(agent);
2303
+ const runThreadId = agent.threadId;
2304
+ if (agent.agentId && runThreadId) this.agentRunThreadId.set(agent.agentId, runThreadId);
2305
+ this.requestUpdate();
406
2306
  }
407
2307
  };
408
2308
  this.coreUnsubscribe = core.subscribe(this.coreSubscriber).unsubscribe;
409
2309
  this.processAgentsChanged(core.agents);
2310
+ const threadStores = typeof core.getThreadStores === "function" ? core.getThreadStores() : {};
2311
+ for (const [agentId, store] of Object.entries(threadStores)) this.subscribeToThreadStore(agentId, store);
410
2312
  if (core.context) this.contextStore = this.normalizeContextStore(core.context);
411
2313
  }
412
2314
  detachFromCore() {
@@ -421,6 +2323,8 @@ var WebInspectorElement = class extends LitElement {
421
2323
  this.cachedTools = [];
422
2324
  this.toolSignature = "";
423
2325
  this.teardownAgentSubscriptions();
2326
+ this.teardownThreadStoreSubscriptions();
2327
+ this.teardownOwnedThreadStores();
424
2328
  }
425
2329
  teardownAgentSubscriptions() {
426
2330
  for (const unsubscribe of this.agentSubscriptions.values()) unsubscribe();
@@ -437,6 +2341,7 @@ var WebInspectorElement = class extends LitElement {
437
2341
  if (!agent?.agentId) continue;
438
2342
  seenAgentIds.add(agent.agentId);
439
2343
  this.subscribeToAgent(agent);
2344
+ this.ensureOwnedThreadStore(agent.agentId);
440
2345
  }
441
2346
  for (const agentId of Array.from(this.agentSubscriptions.keys())) if (!seenAgentIds.has(agentId)) {
442
2347
  this.unsubscribeFromAgent(agentId);
@@ -495,6 +2400,7 @@ var WebInspectorElement = class extends LitElement {
495
2400
  event,
496
2401
  result
497
2402
  });
2403
+ this.refreshOwnedThreadStore(agentId);
498
2404
  },
499
2405
  onRunErrorEvent: ({ event }) => {
500
2406
  this.recordAgentEvent(agentId, "RUN_ERROR", event);
@@ -582,6 +2488,14 @@ var WebInspectorElement = class extends LitElement {
582
2488
  },
583
2489
  onReasoningEncryptedValueEvent: ({ event }) => {
584
2490
  this.recordAgentEvent(agentId, "REASONING_ENCRYPTED_VALUE", event);
2491
+ },
2492
+ onActivitySnapshotEvent: ({ event }) => {
2493
+ this.recordAgentEvent(agentId, "ACTIVITY_SNAPSHOT", event);
2494
+ this.syncAgentMessages(agent);
2495
+ },
2496
+ onActivityDeltaEvent: ({ event }) => {
2497
+ this.recordAgentEvent(agentId, "ACTIVITY_DELTA", event);
2498
+ this.syncAgentMessages(agent);
585
2499
  }
586
2500
  });
587
2501
  this.agentSubscriptions.set(agentId, unsubscribe);
@@ -596,6 +2510,15 @@ var WebInspectorElement = class extends LitElement {
596
2510
  this.agentSubscriptions.delete(agentId);
597
2511
  }
598
2512
  }
2513
+ mapMessagesToConversation(messages) {
2514
+ if (!messages) return null;
2515
+ return messages.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "activity").map((m, i) => ({
2516
+ id: m.id ?? `msg-${i}`,
2517
+ type: m.role === "user" ? "user" : m.role === "activity" ? "generative-ui" : "assistant",
2518
+ content: m.role === "activity" ? m.activityType ?? "unknown" : m.contentText,
2519
+ createdAt: ""
2520
+ }));
2521
+ }
599
2522
  recordAgentEvent(agentId, type, payload) {
600
2523
  const eventId = `${agentId}:${++this.eventCounter}`;
601
2524
  const normalizedPayload = this.normalizeEventPayload(type, payload);
@@ -618,6 +2541,8 @@ var WebInspectorElement = class extends LitElement {
618
2541
  const messages = this.normalizeAgentMessages(agent.messages);
619
2542
  if (messages) this.agentMessages.set(agent.agentId, messages);
620
2543
  else this.agentMessages.delete(agent.agentId);
2544
+ const runThreadId = this.agentRunThreadId.get(agent.agentId);
2545
+ if (runThreadId) this.liveMessageVersion.set(runThreadId, (this.liveMessageVersion.get(runThreadId) ?? 0) + 1);
621
2546
  this.requestUpdate();
622
2547
  } catch (error) {
623
2548
  console.error(`[CopilotKit Inspector] Failed to sync messages for agent "${agent.agentId}":`, error);
@@ -645,18 +2570,23 @@ var WebInspectorElement = class extends LitElement {
645
2570
  if (this.contextOptions.length !== nextOptions.length || this.contextOptions.some((option, index) => option.key !== nextOptions[index]?.key)) this.contextOptions = nextOptions;
646
2571
  const pendingContext = this.pendingSelectedContext;
647
2572
  if (pendingContext) {
648
- if (pendingContext === "all-agents" || agentIds.has(pendingContext)) {
2573
+ if ((pendingContext === "all-agents" || agentIds.has(pendingContext)) && (pendingContext === "all-agents" || agentIds.size === 1)) {
649
2574
  if (this.selectedContext !== pendingContext) {
650
2575
  this.selectedContext = pendingContext;
651
2576
  this.expandedRows.clear();
652
2577
  }
653
2578
  this.pendingSelectedContext = null;
654
- } else if (agentIds.size > 0) this.pendingSelectedContext = null;
2579
+ } else if (agentIds.size > 0) {
2580
+ if (this.selectedContext !== "all-agents") {
2581
+ this.selectedContext = "all-agents";
2582
+ this.expandedRows.clear();
2583
+ }
2584
+ this.pendingSelectedContext = null;
2585
+ }
655
2586
  }
656
2587
  if (!nextOptions.some((option) => option.key === this.selectedContext) && this.pendingSelectedContext === null) {
657
2588
  let nextSelected = "all-agents";
658
- if (agentIds.has("default")) nextSelected = "default";
659
- else if (agentIds.size > 0) nextSelected = Array.from(agentIds).sort((a, b) => a.localeCompare(b))[0];
2589
+ if (agentIds.size === 1) nextSelected = Array.from(agentIds)[0];
660
2590
  if (this.selectedContext !== nextSelected) {
661
2591
  this.selectedContext = nextSelected;
662
2592
  this.expandedRows.clear();
@@ -831,6 +2761,7 @@ ${argsString}</pre
831
2761
  z-index: 2147483646;
832
2762
  display: block;
833
2763
  will-change: transform;
2764
+ font-family: "Plus Jakarta Sans", system-ui, sans-serif;
834
2765
  }
835
2766
 
836
2767
  :host([data-transitioning="true"]) {
@@ -886,13 +2817,14 @@ ${argsString}</pre
886
2817
  left: 50%;
887
2818
  transform: translateX(-50%) translateY(-4px);
888
2819
  white-space: nowrap;
889
- background: rgba(17, 24, 39, 0.95);
2820
+ background: rgba(1, 5, 7, 0.95);
890
2821
  color: white;
891
2822
  padding: 4px 8px;
892
2823
  border-radius: 6px;
893
2824
  font-size: 10px;
2825
+ font-family: "Plus Jakarta Sans", system-ui, sans-serif;
894
2826
  line-height: 1.2;
895
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
2827
+ box-shadow: 0 4px 10px rgba(1, 5, 7, 0.18);
896
2828
  opacity: 0;
897
2829
  pointer-events: none;
898
2830
  transition:
@@ -913,18 +2845,19 @@ ${argsString}</pre
913
2845
  min-width: 300px;
914
2846
  max-width: 300px;
915
2847
  background: white;
916
- color: #111827;
2848
+ color: #010507;
917
2849
  font-size: 13px;
2850
+ font-family: "Plus Jakarta Sans", system-ui, sans-serif;
918
2851
  line-height: 1.4;
919
2852
  border-radius: 12px;
920
- box-shadow: 0 12px 28px rgba(15, 23, 42, 0.22);
2853
+ box-shadow: 0 12px 28px rgba(1, 5, 7, 0.12);
921
2854
  padding: 10px 12px;
922
2855
  display: inline-flex;
923
2856
  align-items: flex-start;
924
2857
  gap: 8px;
925
2858
  z-index: 4500;
926
2859
  animation: fade-slide-in 160ms ease;
927
- border: 1px solid rgba(148, 163, 184, 0.35);
2860
+ border: 1px solid rgba(219, 219, 229, 0.4);
928
2861
  white-space: normal;
929
2862
  word-break: break-word;
930
2863
  text-align: left;
@@ -945,7 +2878,7 @@ ${argsString}</pre
945
2878
  width: 10px;
946
2879
  height: 10px;
947
2880
  background: white;
948
- border: 1px solid rgba(148, 163, 184, 0.35);
2881
+ border: 1px solid rgba(219, 219, 229, 0.4);
949
2882
  transform: rotate(45deg);
950
2883
  top: 50%;
951
2884
  margin-top: -5px;
@@ -954,35 +2887,130 @@ ${argsString}</pre
954
2887
 
955
2888
  .announcement-preview[data-side="left"] .announcement-preview__arrow {
956
2889
  right: -5px;
957
- box-shadow: 6px -6px 10px rgba(15, 23, 42, 0.12);
2890
+ box-shadow: 6px -6px 10px rgba(1, 5, 7, 0.08);
958
2891
  }
959
2892
 
960
2893
  .announcement-preview[data-side="right"] .announcement-preview__arrow {
961
2894
  left: -5px;
962
- box-shadow: -6px 6px 10px rgba(15, 23, 42, 0.12);
2895
+ box-shadow: -6px 6px 10px rgba(1, 5, 7, 0.08);
963
2896
  }
964
2897
 
965
2898
  .announcement-dismiss {
966
- color: #6b7280;
967
- font-size: 12px;
968
- padding: 2px 8px;
969
- border-radius: 8px;
970
- border: 1px solid rgba(148, 163, 184, 0.5);
971
- background: rgba(248, 250, 252, 0.9);
2899
+ background: none;
2900
+ border: none;
2901
+ cursor: pointer;
2902
+ color: #838389;
2903
+ width: 28px;
2904
+ height: 28px;
2905
+ display: flex;
2906
+ align-items: center;
2907
+ justify-content: center;
2908
+ border-radius: 6px;
2909
+ padding: 0;
972
2910
  transition:
973
2911
  background 120ms ease,
974
2912
  color 120ms ease;
975
2913
  }
976
2914
 
977
- .announcement-dismiss:hover {
978
- background: rgba(241, 245, 249, 1);
979
- color: #111827;
2915
+ .announcement-dismiss:hover {
2916
+ background: rgba(0, 0, 0, 0.06);
2917
+ color: #010507;
2918
+ }
2919
+
2920
+ /* ── Agent tab section cards ─────────────────────────────────────── */
2921
+ .cpk-section-card {
2922
+ border-radius: 8px;
2923
+ background: #ffffff;
2924
+ overflow: hidden;
2925
+ }
2926
+
2927
+ /* ── Agent icon bubble ───────────────────────────────────────────── */
2928
+ .cpk-agent-icon {
2929
+ background-color: #f0f0f4 !important;
2930
+ color: #57575b !important;
2931
+ }
2932
+
2933
+ /* ── Agent stat cards ────────────────────────────────────────────── */
2934
+ .cpk-stat-card {
2935
+ background-color: #ffffff !important;
2936
+ border: 1px solid #dbdbe5 !important;
2937
+ }
2938
+ button.cpk-stat-card:hover {
2939
+ background-color: #f7f7f9 !important;
2940
+ }
2941
+
2942
+ /* ── Circle chevron (Frontend Tools + Context) ──────────────────── */
2943
+ .cpk-chevron-circle {
2944
+ display: inline-flex;
2945
+ align-items: center;
2946
+ justify-content: center;
2947
+ width: 24px;
2948
+ height: 24px;
2949
+ border-radius: 50%;
2950
+ background-color: #f0f0f4;
2951
+ color: #838389;
2952
+ flex-shrink: 0;
2953
+ transition: transform 0.2s;
2954
+ }
2955
+ .cpk-chevron-circle svg {
2956
+ width: 14px !important;
2957
+ height: 14px !important;
2958
+ }
2959
+ .cpk-chevron-circle--open {
2960
+ transform: rotate(180deg);
2961
+ }
2962
+
2963
+ /* ── Inline copy button ─────────────────────────────────────────── */
2964
+ .cpk-copy-btn {
2965
+ font-size: 10px;
2966
+ font-weight: 500;
2967
+ color: #57575b;
2968
+ background: #ffffff;
2969
+ border: 1px solid #dbdbe5;
2970
+ cursor: pointer;
2971
+ padding: 2px 8px;
2972
+ border-radius: 4px;
2973
+ flex-shrink: 0;
2974
+ transition:
2975
+ background-color 0.15s,
2976
+ border-color 0.15s;
2977
+ }
2978
+ .cpk-copy-btn:hover {
2979
+ background-color: #f0f0f4;
2980
+ border-color: #afafb7;
2981
+ }
2982
+
2983
+ .cpk-section-header {
2984
+ background: #e8edf5;
2985
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
2986
+ padding: 10px 16px;
2987
+ }
2988
+ .cpk-section-header h4 {
2989
+ font-size: 11px;
2990
+ font-weight: 600;
2991
+ color: #181c1f;
2992
+ margin: 0;
2993
+ }
2994
+
2995
+ /* Inputs/selects inside the lavender header need an explicit white bg */
2996
+ .cpk-section-header input,
2997
+ .cpk-section-header select {
2998
+ background-color: #ffffff !important;
2999
+ box-shadow: none !important;
3000
+ }
3001
+ .cpk-section-header select {
3002
+ padding-right: 24px !important;
3003
+ }
3004
+ /* Events table column headers */
3005
+ table thead th {
3006
+ font-weight: 600 !important;
980
3007
  }
981
3008
 
982
3009
  .announcement-content {
983
- color: #111827;
984
- font-size: 14px;
985
- line-height: 1.6;
3010
+ color: #010507;
3011
+ font-size: 12px;
3012
+ font-family: "Plus Jakarta Sans", system-ui, sans-serif;
3013
+ line-height: 1.5;
986
3014
  }
987
3015
 
988
3016
  .announcement-content h1,
@@ -993,42 +3021,322 @@ ${argsString}</pre
993
3021
  }
994
3022
 
995
3023
  .announcement-content h1 {
996
- font-size: 1.1rem;
3024
+ font-size: 0.75rem;
997
3025
  }
998
-
999
3026
  .announcement-content h2 {
1000
- font-size: 1rem;
3027
+ font-size: 0.8rem;
1001
3028
  }
1002
-
1003
3029
  .announcement-content h3 {
1004
- font-size: 0.95rem;
3030
+ font-size: 0.75rem;
1005
3031
  }
1006
3032
 
1007
3033
  .announcement-content p {
1008
- margin: 0.25rem 0;
3034
+ margin: 0.2rem 0;
1009
3035
  }
1010
3036
 
1011
3037
  .announcement-content ul {
1012
3038
  list-style: disc;
1013
3039
  padding-left: 1.25rem;
1014
- margin: 0.3rem 0;
3040
+ margin: 0.2rem 0;
1015
3041
  }
1016
3042
 
1017
3043
  .announcement-content ol {
1018
3044
  list-style: decimal;
1019
3045
  padding-left: 1.25rem;
1020
- margin: 0.3rem 0;
3046
+ margin: 0.2rem 0;
1021
3047
  }
1022
3048
 
1023
3049
  .announcement-content a {
1024
- color: #0f766e;
3050
+ color: #757cf2;
1025
3051
  text-decoration: underline;
1026
3052
  }
3053
+
3054
+ .announcement-body {
3055
+ position: relative;
3056
+ overflow: hidden;
3057
+ transition: max-height 0.25s ease;
3058
+ }
3059
+ .announcement-body--collapsed {
3060
+ max-height: 72px;
3061
+ }
3062
+ .announcement-body--expanded {
3063
+ max-height: 2000px;
3064
+ }
3065
+ .announcement-fade {
3066
+ position: absolute;
3067
+ bottom: 0;
3068
+ left: 0;
3069
+ right: 0;
3070
+ height: 48px;
3071
+ background: linear-gradient(to bottom, transparent, #ffffff);
3072
+ pointer-events: none;
3073
+ }
3074
+ .announcement-toggle {
3075
+ display: block;
3076
+ width: 100%;
3077
+ margin-top: 6px;
3078
+ padding: 0;
3079
+ background: none;
3080
+ border: none;
3081
+ font-family: "Plus Jakarta Sans", system-ui, sans-serif;
3082
+ font-size: 12px;
3083
+ font-weight: 500;
3084
+ color: #757cf2;
3085
+ cursor: pointer;
3086
+ text-align: center;
3087
+ }
3088
+ .announcement-toggle:hover {
3089
+ color: #6430ab;
3090
+ }
3091
+
3092
+ /* ── Brand typography ────────────────────────────────────────── */
3093
+ /* Override Tailwind font-mono stack → Spline Sans Mono */
3094
+ .font-mono,
3095
+ pre,
3096
+ code {
3097
+ font-family: "Spline Sans Mono", ui-monospace, "Cascadia Code", monospace;
3098
+ }
3099
+
3100
+ /* ── Floating button ─────────────────────────────────────────── */
3101
+ .console-button {
3102
+ background-color: rgba(1, 5, 7, 0.95) !important;
3103
+ border-color: rgba(190, 194, 255, 0.25) !important;
3104
+ box-shadow:
3105
+ 0 0 0 1px rgba(190, 194, 255, 0.15),
3106
+ 0 4px 14px rgba(1, 5, 7, 0.28) !important;
3107
+ }
3108
+ .console-button:hover {
3109
+ background-color: rgba(1, 5, 7, 1) !important;
3110
+ border-color: rgba(190, 194, 255, 0.45) !important;
3111
+ }
3112
+ .console-button:focus-visible {
3113
+ outline-color: #bec2ff !important;
3114
+ }
3115
+
3116
+ /* ── Inspector window ────────────────────────────────────────── */
3117
+ .inspector-window {
3118
+ border-color: #dbdbe5 !important;
3119
+ box-shadow:
3120
+ 0 8px 32px rgba(1, 5, 7, 0.1),
3121
+ 0 2px 8px rgba(1, 5, 7, 0.06) !important;
3122
+ }
3123
+
3124
+ /* ── Header drag area ────────────────────────────────────────── */
3125
+ .drag-handle {
3126
+ border-bottom-color: #dbdbe5 !important;
3127
+ /* Subtle pale lavender gradient — brand "light, spacious" surface */
3128
+ background: linear-gradient(180deg, #f4f4fd 0%, #ffffff 100%) !important;
3129
+ }
3130
+
3131
+ /* Tab strip row: soft off-white, separated from content */
3132
+ .drag-handle > div:last-child {
3133
+ border-top-color: #e2e2ea !important;
3134
+ background-color: #fafafc !important;
3135
+ }
3136
+
3137
+ /* ── Tab buttons ─────────────────────────────────────────────── */
3138
+ /*
3139
+ * Named classes owned by this component — no Tailwind conflict.
3140
+ * Active: brand surface/surfaceContainerActive (lilac tint) +
3141
+ * border/borderActionEnabled underline.
3142
+ * Dark fill is for primary action buttons only, not nav tabs.
3143
+ */
3144
+ .cpk-tab-active {
3145
+ background-color: rgba(190, 194, 255, 0.18);
3146
+ color: #010507;
3147
+ font-weight: 600;
3148
+ }
3149
+ .cpk-tab-active .cpk-tab-icon {
3150
+ color: #757cf2;
3151
+ }
3152
+ .cpk-tab-inactive {
3153
+ background-color: transparent;
3154
+ color: #2b2b2b;
3155
+ }
3156
+ .cpk-tab-inactive .cpk-tab-icon {
3157
+ color: #838389;
3158
+ }
3159
+ .cpk-tab-inactive:hover {
3160
+ background-color: rgba(190, 194, 255, 0.08);
3161
+ color: #010507;
3162
+ cursor: pointer;
3163
+ }
3164
+ .cpk-tab-active {
3165
+ cursor: pointer;
3166
+ }
3167
+
3168
+ /* ── Header control buttons (dock, close) — first row only ───── */
3169
+ .drag-handle > div:first-child button {
3170
+ color: #838389 !important;
3171
+ }
3172
+ .drag-handle > div:first-child button:hover {
3173
+ background-color: #f0f0f4 !important;
3174
+ color: #57575b !important;
3175
+ }
3176
+ .drag-handle > div:first-child button:focus-visible {
3177
+ outline-color: #bec2ff !important;
3178
+ }
3179
+
3180
+ /* ── Agent/context dropdown ──────────────────────────────────── */
3181
+ [data-context-dropdown-root="true"] > button {
3182
+ border-color: #dbdbe5 !important;
3183
+ color: #010507 !important;
3184
+ }
3185
+ [data-context-dropdown-root="true"] > button:hover {
3186
+ border-color: #bec2ff !important;
3187
+ background-color: #f7f7f9 !important;
3188
+ }
3189
+ [data-context-dropdown-root="true"] > button > span:last-child {
3190
+ color: #838389 !important;
3191
+ }
3192
+ [data-context-dropdown-root="true"] > div {
3193
+ border-color: #dbdbe5 !important;
3194
+ box-shadow: 0 4px 12px rgba(1, 5, 7, 0.08) !important;
3195
+ }
3196
+ [data-context-dropdown-root="true"] > div button:hover,
3197
+ [data-context-dropdown-root="true"] > div button:focus {
3198
+ background-color: #f7f7f9 !important;
3199
+ }
3200
+
3201
+ /* ── Status bar (bottom chrome) ──────────────────────────────── */
3202
+ .inspector-window > div > div:last-child {
3203
+ border-top-color: #dbdbe5 !important;
3204
+ background-color: #f7f7f9 !important;
3205
+ }
3206
+
3207
+ /* ── Resize handle ───────────────────────────────────────────── */
3208
+ .resize-handle {
3209
+ color: #838389 !important;
3210
+ }
3211
+ .resize-handle:hover {
3212
+ color: #57575b !important;
3213
+ }
3214
+
3215
+ /* ── AG-UI Events tab ────────────────────────────────────────── */
3216
+ /* Row hover: replace blue tint with brand lilac */
3217
+ tr:hover td {
3218
+ background-color: rgba(190, 194, 255, 0.08) !important;
3219
+ }
3220
+ /* Reset/dark action button */
3221
+ button[class*="bg-gray-900"] {
3222
+ background-color: #010507 !important;
3223
+ }
3224
+ button[class*="bg-gray-800"] {
3225
+ background-color: #2b2b2b !important;
3226
+ }
3227
+ /* Copy "copied" state: generic green → brand mint */
3228
+ button[class*="bg-green-100"] {
3229
+ background-color: rgba(133, 236, 206, 0.2) !important;
3230
+ color: #189370 !important;
3231
+ }
3232
+
3233
+ /* ── Agents tab ──────────────────────────────────────────────── */
3234
+ /* Agent icon bubble: blue → lilac */
3235
+ span[class*="bg-blue-100"]:not([class*="text-blue-800"]) {
3236
+ background-color: rgba(190, 194, 255, 0.15) !important;
3237
+ }
3238
+ span[class*="text-blue-600"] {
3239
+ color: #757cf2 !important;
3240
+ }
3241
+ /* Running badge: emerald → mint */
3242
+ span[class*="bg-emerald-50"] {
3243
+ background-color: rgba(133, 236, 206, 0.15) !important;
3244
+ }
3245
+ span[class*="text-emerald-700"] {
3246
+ color: #189370 !important;
3247
+ }
3248
+ /* Running status dot */
3249
+ span[class*="bg-emerald-500"] {
3250
+ background-color: #85ecce !important;
3251
+ }
3252
+ /* Idle dot */
3253
+ span[class*="bg-gray-400"] {
3254
+ background-color: #afafb7 !important;
3255
+ }
3256
+ /* User role badge (blue → lilac) */
3257
+ span[class*="bg-blue-100"][class*="text-blue-800"] {
3258
+ background-color: rgba(190, 194, 255, 0.22) !important;
3259
+ border: 1px solid rgba(190, 194, 255, 0.45) !important;
3260
+ color: #57575b !important;
3261
+ }
3262
+ /* Assistant role badge (green → mint) */
3263
+ span[class*="bg-green-100"][class*="text-green-800"] {
3264
+ background-color: rgba(133, 236, 206, 0.18) !important;
3265
+ border: 1px solid rgba(133, 236, 206, 0.4) !important;
3266
+ color: #189370 !important;
3267
+ }
3268
+ /* Tool role badge (amber → orange brand) */
3269
+ span[class*="bg-amber-100"][class*="text-amber-800"] {
3270
+ background-color: rgba(255, 172, 77, 0.15) !important;
3271
+ color: #57575b !important;
3272
+ }
3273
+
3274
+ /* ── Frontend Tools tab ──────────────────────────────────────── */
3275
+ /* Handler badge (blue → lilac) */
3276
+ span[class*="bg-blue-50"][class*="text-blue-700"] {
3277
+ background-color: rgba(190, 194, 255, 0.12) !important;
3278
+ border-color: rgba(190, 194, 255, 0.3) !important;
3279
+ color: #010507 !important;
3280
+ }
3281
+ /* Renderer badge (purple → lilac-adjacent) */
3282
+ span[class*="bg-purple-50"][class*="text-purple-700"] {
3283
+ background-color: rgba(190, 194, 255, 0.12) !important;
3284
+ border-color: rgba(190, 194, 255, 0.3) !important;
3285
+ color: #57575b !important;
3286
+ }
3287
+ /* Required badge (rose → brand red) */
3288
+ span[class*="bg-rose-50"][class*="text-rose-700"] {
3289
+ background-color: rgba(250, 95, 103, 0.1) !important;
3290
+ border-color: rgba(250, 95, 103, 0.25) !important;
3291
+ color: #fa5f67 !important;
3292
+ }
3293
+ /* Code/default value blocks */
3294
+ code[class*="bg-gray-100"],
3295
+ span[class*="bg-gray-100"] {
3296
+ background-color: #f0f0f4 !important;
3297
+ }
3298
+
3299
+ /* ── Connected status bar: match threads header mint (#5BE4BB) ──── */
3300
+ /* Outer strip bg + top border + text when connected badge is present */
3301
+ .inspector-window
3302
+ > div
3303
+ > div:last-child
3304
+ > div:last-child:has(div[class*="bg-emerald-50"]) {
3305
+ background-color: rgba(91, 228, 187, 0.08) !important;
3306
+ border-top-color: rgba(91, 228, 187, 0.3) !important;
3307
+ color: #189370 !important;
3308
+ }
3309
+ /* Inner badge — slightly more opaque on the mint bg */
3310
+ div[class*="bg-emerald-50"][class*="border-emerald-200"] {
3311
+ background-color: rgba(91, 228, 187, 0.12) !important;
3312
+ border-color: rgba(91, 228, 187, 0.4) !important;
3313
+ color: #189370 !important;
3314
+ }
3315
+ /* Icon bubble inside connected badge → mint tint */
3316
+ div[class*="bg-emerald-50"] span[class*="bg-white"] {
3317
+ background-color: rgba(91, 228, 187, 0.3) !important;
3318
+ }
3319
+
3320
+ /* ── Announcement panel ──────────────────────────────────────── */
3321
+ div[class*="border-slate-200"][class*="bg-white"] {
3322
+ border-color: #dbdbe5 !important;
3323
+ }
3324
+ /* Announcement icon bubble: black → brand light lavender + lilac icon */
3325
+ span[class*="bg-slate-900"],
3326
+ div[class*="bg-slate-900"] {
3327
+ background-color: #eee6fe !important;
3328
+ color: #757cf2 !important;
3329
+ }
3330
+ span[class*="text-slate-800"],
3331
+ div[class*="text-slate-800"] {
3332
+ color: #010507 !important;
3333
+ }
1027
3334
  `];
1028
3335
  }
1029
3336
  connectedCallback() {
1030
3337
  super.connectedCallback();
1031
3338
  if (typeof window !== "undefined") {
3339
+ this.ensureBrandFonts();
1032
3340
  window.addEventListener("resize", this.handleResize);
1033
3341
  window.addEventListener("pointerdown", this.handleGlobalPointerDown);
1034
3342
  this.hydrateStateFromStorageEarly();
@@ -1036,6 +3344,15 @@ ${argsString}</pre
1036
3344
  this.ensureAnnouncementLoading();
1037
3345
  }
1038
3346
  }
3347
+ ensureBrandFonts() {
3348
+ const FONT_LINK_ID = "cpk-inspector-brand-fonts";
3349
+ if (document.getElementById(FONT_LINK_ID)) return;
3350
+ const link = document.createElement("link");
3351
+ link.id = FONT_LINK_ID;
3352
+ link.rel = "stylesheet";
3353
+ link.href = "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600&family=Spline+Sans+Mono:wght@600&display=swap";
3354
+ document.head.appendChild(link);
3355
+ }
1039
3356
  disconnectedCallback() {
1040
3357
  super.disconnectedCallback();
1041
3358
  if (typeof window !== "undefined") {
@@ -1113,7 +3430,7 @@ ${argsString}</pre
1113
3430
  "focus-visible:outline",
1114
3431
  "focus-visible:outline-2",
1115
3432
  "focus-visible:outline-offset-2",
1116
- "focus-visible:outline-rose-500",
3433
+ "focus-visible:outline-[#BEC2FF]",
1117
3434
  "touch-none",
1118
3435
  "select-none",
1119
3436
  this.isDragging ? "cursor-grabbing" : "cursor-grab"
@@ -1213,6 +3530,7 @@ ${argsString}</pre
1213
3530
  </div>
1214
3531
  </div>
1215
3532
  </div>
3533
+ ${this.renderAnnouncementBanner()}
1216
3534
  <div
1217
3535
  class="flex flex-wrap items-center gap-2 border-t border-gray-100 px-3 py-2 text-xs"
1218
3536
  >
@@ -1221,14 +3539,12 @@ ${argsString}</pre
1221
3539
  return html`
1222
3540
  <button
1223
3541
  type="button"
1224
- 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 ? "bg-gray-900 text-white shadow-sm" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"].join(" ")}
3542
+ 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(" ")}
1225
3543
  aria-pressed=${isSelected}
1226
3544
  @click=${() => this.handleMenuSelect(key)}
1227
3545
  >
1228
- <span
1229
- class="text-gray-400 ${isSelected ? "text-white" : ""}"
1230
- >
1231
- ${this.renderIcon(icon)}
3546
+ <span class="cpk-tab-icon">
3547
+ ${key in this.customTabIcons ? unsafeHTML(this.customTabIcons[key]) : this.renderIcon(icon)}
1232
3548
  </span>
1233
3549
  <span>${label}</span>
1234
3550
  </button>
@@ -1237,8 +3553,7 @@ ${argsString}</pre
1237
3553
  </div>
1238
3554
  </div>
1239
3555
  <div class="flex flex-1 flex-col overflow-hidden">
1240
- <div class="flex-1 overflow-auto">
1241
- ${this.renderAnnouncementPanel()}
3556
+ <div id="cpk-main-scroll" class="flex-1 overflow-auto">
1242
3557
  ${this.renderCoreWarningBanner()} ${this.renderMainContent()}
1243
3558
  <slot></slot>
1244
3559
  </div>
@@ -1286,6 +3601,7 @@ ${argsString}</pre
1286
3601
  }
1287
3602
  hydrateStateFromStorageEarly() {
1288
3603
  if (typeof document === "undefined" || typeof window === "undefined") return;
3604
+ if (document.cookie.includes("cpk_threads_access=1")) this._threadsUnlocked = true;
1289
3605
  const persisted = loadInspectorState(INSPECTOR_STORAGE_KEY);
1290
3606
  if (!persisted) return;
1291
3607
  if (typeof persisted.isOpen === "boolean") this.isOpen = persisted.isOpen;
@@ -1682,7 +3998,8 @@ ${argsString}</pre
1682
3998
  role,
1683
3999
  contentText,
1684
4000
  contentRaw: raw.content !== void 0 ? this.sanitizeForLogging(raw.content) : void 0,
1685
- toolCalls
4001
+ toolCalls,
4002
+ activityType: typeof raw.activityType === "string" ? raw.activityType : void 0
1686
4003
  };
1687
4004
  }
1688
4005
  normalizeAgentMessages(messages) {
@@ -1701,9 +4018,6 @@ ${argsString}</pre
1701
4018
  } else normalized[key] = { value: entry };
1702
4019
  return normalized;
1703
4020
  }
1704
- getSelectedMenu() {
1705
- return this.menuItems.find((item) => item.key === this.selectedMenu) ?? this.menuItems[0];
1706
- }
1707
4021
  renderCoreWarningBanner() {
1708
4022
  if (this._core) return nothing;
1709
4023
  return html`
@@ -1760,27 +4074,456 @@ ${argsString}</pre
1760
4074
  if (this.selectedMenu === "agents") return this.renderAgentsView();
1761
4075
  if (this.selectedMenu === "frontend-tools") return this.renderToolsView();
1762
4076
  if (this.selectedMenu === "agent-context") return this.renderContextView();
4077
+ if (this.selectedMenu === "threads") return this.renderThreadsView();
1763
4078
  return nothing;
1764
4079
  }
4080
+ renderThreadsGate() {
4081
+ return html`
4082
+ <div style="
4083
+ position:relative;
4084
+ display:flex;
4085
+ flex-direction:column;
4086
+ align-items:center;
4087
+ justify-content:center;
4088
+ padding:40px 24px;
4089
+ min-height:100%;
4090
+ text-align:center;
4091
+ background:linear-gradient(135deg,#f5f4ff 0%,#ede9fe 100%);
4092
+ overflow:hidden;
4093
+ ">
4094
+ <!-- Blurred ellipses from Figma/storybook -->
4095
+ <div style="position:absolute;width:570px;height:570px;border-radius:50%;top:-80px;left:-120px;opacity:0.25;background:#757CF2;filter:blur(120px);pointer-events:none;"></div>
4096
+ <div style="position:absolute;width:570px;height:570px;border-radius:50%;bottom:-100px;right:-80px;opacity:0.2;background:#FFAC4D;filter:blur(120px);pointer-events:none;"></div>
4097
+ <div style="position:absolute;width:400px;height:400px;border-radius:50%;bottom:20px;left:-60px;opacity:0.15;background:#FFAC4D;filter:blur(100px);pointer-events:none;"></div>
4098
+
4099
+ ${this._threadsUnlocking ? this._renderUnlockingCard() : this._renderEarlyAccessCard()}
4100
+ </div>
4101
+ `;
4102
+ }
4103
+ static {
4104
+ this.THREADS_REQUEST_URL = "https://r3x69.share-na2.hsforms.com/2uiZg8EkiT7a_KykeXV1ajQ";
4105
+ }
4106
+ _renderEarlyAccessCard() {
4107
+ const invalid = this._threadsGateCodeInvalid;
4108
+ return html`
4109
+ <div
4110
+ style="
4111
+ position:relative;
4112
+ z-index:1;
4113
+ background:#ffffff;
4114
+ border:1px solid #E5E5EA;
4115
+ border-radius:20px;
4116
+ box-shadow:0 16px 48px rgba(1,5,7,0.12),0 2px 6px rgba(1,5,7,0.05);
4117
+ padding:28px;
4118
+ width:400px;
4119
+ max-width:100%;
4120
+ display:flex;
4121
+ flex-direction:column;
4122
+ gap:18px;
4123
+ text-align:left;
4124
+ font-family:'Plus Jakarta Sans', system-ui, sans-serif;
4125
+ "
4126
+ >
4127
+ <!-- Kicker pill -->
4128
+ <div>
4129
+ <span
4130
+ style="
4131
+ display:inline-flex;
4132
+ align-items:center;
4133
+ gap:4px;
4134
+ padding:4px 10px;
4135
+ border-radius:999px;
4136
+ background:#F3F3FC;
4137
+ color:#757CF2;
4138
+ font-family:'Spline Sans Mono', ui-monospace, monospace;
4139
+ font-size:10px;
4140
+ font-weight:500;
4141
+ letter-spacing:0.08em;
4142
+ text-transform:uppercase;
4143
+ "
4144
+ >Early Access</span
4145
+ >
4146
+ </div>
4147
+
4148
+ <!-- Title + description -->
4149
+ <div style="display:flex;flex-direction:column;gap:8px;">
4150
+ <h2
4151
+ style="
4152
+ font-family:'Plus Jakarta Sans', system-ui, sans-serif;
4153
+ font-size:24px;
4154
+ font-weight:700;
4155
+ color:#010507;
4156
+ line-height:1.2;
4157
+ letter-spacing:-0.015em;
4158
+ margin:0;
4159
+ "
4160
+ >
4161
+ <span
4162
+ style="
4163
+ background:linear-gradient(90deg, #757CF2 0%, #5AE4BB 100%);
4164
+ -webkit-background-clip:text;
4165
+ background-clip:text;
4166
+ color:transparent;
4167
+ -webkit-text-fill-color:transparent;
4168
+ "
4169
+ >Threads</span
4170
+ >
4171
+ are in private beta
4172
+ </h2>
4173
+ <p
4174
+ style="
4175
+ font-size:14px;
4176
+ font-weight:500;
4177
+ color:#5C5C66;
4178
+ line-height:1.55;
4179
+ margin:0;
4180
+ "
4181
+ >
4182
+ Spin up separate conversations with your agent, one per task, bug,
4183
+ or feature, and jump back into any of them without losing context.
4184
+ </p>
4185
+ </div>
4186
+
4187
+ <!-- Bullets -->
4188
+ <div
4189
+ style="display:flex;flex-direction:column;gap:8px;padding:4px 0;"
4190
+ >
4191
+ ${[
4192
+ "One agent, many conversations",
4193
+ "Persistent history across sessions",
4194
+ "Jump between threads in a click"
4195
+ ].map((label) => html`
4196
+ <div style="display:flex;align-items:center;gap:10px;">
4197
+ <svg
4198
+ width="14"
4199
+ height="14"
4200
+ viewBox="0 0 24 24"
4201
+ fill="none"
4202
+ stroke="#010507"
4203
+ stroke-width="2.5"
4204
+ stroke-linecap="round"
4205
+ stroke-linejoin="round"
4206
+ style="flex-shrink:0;"
4207
+ >
4208
+ <polyline points="20 6 9 17 4 12"></polyline>
4209
+ </svg>
4210
+ <span style="font-size:13px;font-weight:500;color:#010507;"
4211
+ >${label}</span
4212
+ >
4213
+ </div>
4214
+ `)}
4215
+ </div>
4216
+
4217
+ <!-- Primary CTA: dark MonoPillButton with adjacent arrow circle -->
4218
+ <div>
4219
+ <a
4220
+ href=${WebInspectorElement.THREADS_REQUEST_URL}
4221
+ target="_blank"
4222
+ rel="noopener noreferrer"
4223
+ style="
4224
+ display:inline-flex;
4225
+ align-items:center;
4226
+ gap:8px;
4227
+ text-decoration:none;
4228
+ cursor:pointer;
4229
+ "
4230
+ >
4231
+ <span
4232
+ style="
4233
+ display:inline-flex;
4234
+ align-items:center;
4235
+ justify-content:center;
4236
+ background:#010507;
4237
+ color:#ffffff;
4238
+ font-family:'Spline Sans Mono', ui-monospace, monospace;
4239
+ font-size:13px;
4240
+ font-weight:500;
4241
+ letter-spacing:0.06em;
4242
+ text-transform:uppercase;
4243
+ padding:14px 22px;
4244
+ border-radius:999px;
4245
+ box-shadow:0 4px 12px rgba(1,5,7,0.18);
4246
+ "
4247
+ >Request Early Access</span
4248
+ >
4249
+ <span
4250
+ style="
4251
+ display:inline-flex;
4252
+ align-items:center;
4253
+ justify-content:center;
4254
+ width:36px;
4255
+ height:36px;
4256
+ border-radius:999px;
4257
+ background:#010507;
4258
+ color:#ffffff;
4259
+ box-shadow:0 4px 12px rgba(1,5,7,0.18);
4260
+ "
4261
+ >
4262
+ <svg
4263
+ width="14"
4264
+ height="14"
4265
+ viewBox="0 0 24 24"
4266
+ fill="none"
4267
+ stroke="currentColor"
4268
+ stroke-width="2"
4269
+ stroke-linecap="round"
4270
+ stroke-linejoin="round"
4271
+ >
4272
+ <line x1="5" y1="12" x2="19" y2="12"></line>
4273
+ <polyline points="12 5 19 12 12 19"></polyline>
4274
+ </svg>
4275
+ </span>
4276
+ </a>
4277
+ </div>
4278
+
4279
+ <!-- Divider + invite-code section -->
4280
+ <div
4281
+ style="
4282
+ display:flex;
4283
+ flex-direction:column;
4284
+ gap:8px;
4285
+ padding-top:14px;
4286
+ border-top:1px dashed #E5E5EA;
4287
+ "
4288
+ >
4289
+ <span style="font-size:12px;font-weight:500;color:#8A8A94;"
4290
+ >Have an invite code?</span
4291
+ >
4292
+ <div style="display:flex;gap:8px;">
4293
+ <div
4294
+ style="
4295
+ flex:1;
4296
+ background:#ffffff;
4297
+ border:1px solid ${invalid ? "#FA5F67" : "#E5E5EA"};
4298
+ border-radius:10px;
4299
+ padding:2px 4px 2px 12px;
4300
+ transition:border-color 150ms ease;
4301
+ "
4302
+ >
4303
+ <input
4304
+ id="cpk-gate-input"
4305
+ type="text"
4306
+ placeholder="Enter access code"
4307
+ style="
4308
+ width:100%;
4309
+ padding:10px 0;
4310
+ font-family:'Plus Jakarta Sans', system-ui, sans-serif;
4311
+ font-size:13px;
4312
+ font-weight:500;
4313
+ color:#010507;
4314
+ background:transparent;
4315
+ border:none;
4316
+ outline:none;
4317
+ "
4318
+ @keydown=${(e) => {
4319
+ if (e.key === "Enter") this._submitThreadsCode(e.currentTarget.value);
4320
+ }}
4321
+ />
4322
+ </div>
4323
+ <button
4324
+ style="
4325
+ background:#010507;
4326
+ color:#ffffff;
4327
+ border:none;
4328
+ border-radius:10px;
4329
+ padding:0 16px;
4330
+ font-family:'Spline Sans Mono', ui-monospace, monospace;
4331
+ font-size:11px;
4332
+ font-weight:500;
4333
+ letter-spacing:0.06em;
4334
+ text-transform:uppercase;
4335
+ cursor:pointer;
4336
+ white-space:nowrap;
4337
+ "
4338
+ @click=${() => {
4339
+ const input = this.shadowRoot?.getElementById("cpk-gate-input");
4340
+ if (input) this._submitThreadsCode(input.value);
4341
+ }}
4342
+ >
4343
+ Unlock
4344
+ </button>
4345
+ </div>
4346
+ ${invalid ? html`
4347
+ <div style="font-size: 11px; font-weight: 500; color: #fa5f67">
4348
+ That code isn't valid. Double-check your invite email.
4349
+ </div>
4350
+ ` : nothing}
4351
+ </div>
4352
+ </div>
4353
+ `;
4354
+ }
4355
+ _renderUnlockingCard() {
4356
+ return html`
4357
+ <div
4358
+ style="
4359
+ position: relative;
4360
+ z-index: 1;
4361
+ background: #ffffff;
4362
+ border: 1px solid #e5e5ea;
4363
+ border-radius: 20px;
4364
+ box-shadow:
4365
+ 0 16px 48px rgba(1, 5, 7, 0.12),
4366
+ 0 2px 6px rgba(1, 5, 7, 0.05);
4367
+ padding: 32px;
4368
+ width: 340px;
4369
+ max-width: 100%;
4370
+ display: flex;
4371
+ flex-direction: column;
4372
+ align-items: center;
4373
+ gap: 16px;
4374
+ text-align: center;
4375
+ font-family: &quot;Plus Jakarta Sans&quot;, system-ui, sans-serif;
4376
+ "
4377
+ >
4378
+ <div
4379
+ style="
4380
+ width: 56px;
4381
+ height: 56px;
4382
+ border-radius: 999px;
4383
+ background: linear-gradient(135deg, #bec2ff 0%, #85ecce 100%);
4384
+ display: flex;
4385
+ align-items: center;
4386
+ justify-content: center;
4387
+ "
4388
+ >
4389
+ <svg
4390
+ width="24"
4391
+ height="24"
4392
+ viewBox="0 0 24 24"
4393
+ fill="none"
4394
+ stroke="#010507"
4395
+ stroke-width="2.5"
4396
+ stroke-linecap="round"
4397
+ stroke-linejoin="round"
4398
+ >
4399
+ <polyline points="20 6 9 17 4 12"></polyline>
4400
+ </svg>
4401
+ </div>
4402
+ <div style="font-size: 18px; font-weight: 700; color: #010507">
4403
+ Welcome to Threads
4404
+ </div>
4405
+ <div style="font-size: 13px; color: #5c5c66; line-height: 1.5">
4406
+ Loading your conversations…
4407
+ </div>
4408
+ </div>
4409
+ `;
4410
+ }
4411
+ _submitThreadsCode(value) {
4412
+ if (value.trim().toLowerCase() === "earlyaccess") {
4413
+ document.cookie = "cpk_threads_access=1; path=/; max-age=31536000; SameSite=Lax";
4414
+ this._threadsGateError = null;
4415
+ this._threadsGateCodeInvalid = false;
4416
+ this._threadsUnlocking = true;
4417
+ if (this._threadsUnlockingTimer !== null) clearTimeout(this._threadsUnlockingTimer);
4418
+ this._threadsUnlockingTimer = setTimeout(() => {
4419
+ this._threadsUnlocking = false;
4420
+ this._threadsUnlocked = true;
4421
+ this._threadsUnlockingTimer = null;
4422
+ this.requestUpdate();
4423
+ }, 2e3);
4424
+ } else {
4425
+ this._threadsGateCodeInvalid = true;
4426
+ this._threadsGateError = null;
4427
+ if (this._threadsGateInvalidTimer !== null) clearTimeout(this._threadsGateInvalidTimer);
4428
+ this._threadsGateInvalidTimer = setTimeout(() => {
4429
+ this._threadsGateCodeInvalid = false;
4430
+ this._threadsGateInvalidTimer = null;
4431
+ this.requestUpdate();
4432
+ }, 1600);
4433
+ }
4434
+ this.requestUpdate();
4435
+ }
4436
+ renderThreadsView() {
4437
+ if (!this._threadsUnlocked) return this.renderThreadsGate();
4438
+ const displayThreads = this.selectedContext === "all-agents" ? this._threads : this._threadsByAgent.get(this.selectedContext) ?? [];
4439
+ let threadsErrorMessage = null;
4440
+ if (this.selectedContext === "all-agents") threadsErrorMessage = this._threadsErrorByAgent.values().next().value?.message ?? null;
4441
+ else threadsErrorMessage = this._threadsErrorByAgent.get(this.selectedContext)?.message ?? null;
4442
+ const selectedThread = this.selectedThreadId != null ? displayThreads.find((t) => t.id === this.selectedThreadId) ?? null : null;
4443
+ return html`
4444
+ <div style="display:flex;height:100%;overflow:hidden;">
4445
+ <!-- Left sidebar: thread list -->
4446
+ <div
4447
+ style="width:${this.threadListWidth}px;flex-shrink:0;overflow:hidden;display:flex;flex-direction:column;border-right:1px solid #DBDBE5;"
4448
+ >
4449
+ <cpk-thread-list
4450
+ style="height:100%;"
4451
+ .threads=${displayThreads}
4452
+ .selectedThreadId=${this.selectedThreadId}
4453
+ .errorMessage=${threadsErrorMessage}
4454
+ @threadSelected=${(e) => {
4455
+ this.selectedThreadId = e.detail;
4456
+ this.requestUpdate();
4457
+ }}
4458
+ ></cpk-thread-list>
4459
+ </div>
4460
+
4461
+ <!-- Resize divider -->
4462
+ <div
4463
+ style="width:4px;flex-shrink:0;cursor:col-resize;background:transparent;position:relative;z-index:1;"
4464
+ @pointerdown=${this.handleThreadDividerPointerDown}
4465
+ @pointermove=${this.handleThreadDividerPointerMove}
4466
+ @pointerup=${this.handleThreadDividerPointerUp}
4467
+ @pointercancel=${this.handleThreadDividerPointerUp}
4468
+ ></div>
4469
+
4470
+ <!-- Center + right: thread details or empty state -->
4471
+ <div style="flex:1;min-width:0;overflow:hidden;display:flex;">
4472
+ ${this.selectedThreadId ? html`<cpk-thread-details
4473
+ style="flex:1;min-width:0;"
4474
+ .threadId=${this.selectedThreadId}
4475
+ .thread=${selectedThread}
4476
+ .runtimeUrl=${this._core?.runtimeUrl ?? ""}
4477
+ .headers=${this._core?.headers ?? {}}
4478
+ .liveMessageVersion=${this.selectedThreadId ? this.liveMessageVersion.get(this.selectedThreadId) ?? 0 : 0}
4479
+ .agentStateInput=${selectedThread ? this.getLatestStateForAgent(selectedThread.agentId) : null}
4480
+ .agentEventsInput=${selectedThread ? this.agentEvents.get(selectedThread.agentId) ?? [] : []}
4481
+ ></cpk-thread-details>` : html`
4482
+ <div
4483
+ style="
4484
+ flex: 1;
4485
+ display: flex;
4486
+ flex-direction: column;
4487
+ align-items: center;
4488
+ justify-content: center;
4489
+ gap: 8px;
4490
+ color: #838389;
4491
+ "
4492
+ >
4493
+ <svg
4494
+ width="32"
4495
+ height="32"
4496
+ viewBox="0 0 24 24"
4497
+ fill="none"
4498
+ stroke="#c0c0c8"
4499
+ stroke-width="1.5"
4500
+ stroke-linecap="round"
4501
+ stroke-linejoin="round"
4502
+ >
4503
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
4504
+ </svg>
4505
+ <span style="font-size: 13px">${displayThreads.length === 0 ? "No threads yet" : "Select a thread to inspect"}</span>
4506
+ </div>
4507
+ `}
4508
+ </div>
4509
+ </div>
4510
+ `;
4511
+ }
1765
4512
  renderEventsTable() {
1766
4513
  const events = this.getEventsForSelectedContext();
1767
4514
  const filteredEvents = this.filterEvents(events);
1768
4515
  const selectedLabel = this.selectedContext === "all-agents" ? "all agents" : `agent ${this.selectedContext}`;
1769
4516
  if (events.length === 0) return html`
1770
4517
  <div
1771
- class="flex h-full items-center justify-center px-4 py-8 text-center"
4518
+ class="flex h-full flex-col items-center justify-center gap-2 px-4 py-10 text-center"
1772
4519
  >
1773
- <div class="max-w-md">
1774
- <div
1775
- class="mb-3 flex justify-center text-gray-300 [&>svg]:!h-8 [&>svg]:!w-8"
1776
- >
1777
- ${this.renderIcon("Zap")}
1778
- </div>
1779
- <p class="text-sm text-gray-600">No events yet</p>
1780
- <p class="mt-2 text-xs text-gray-500">
1781
- Trigger an agent run to see live activity.
1782
- </p>
4520
+ <div class="text-gray-300 [&>svg]:!h-8 [&>svg]:!w-8">
4521
+ ${this.renderIcon("Zap")}
1783
4522
  </div>
4523
+ <span class="text-sm text-gray-600">No events yet</span>
4524
+ <span class="max-w-[240px] text-xs leading-snug text-gray-400"
4525
+ >Events are recorded live. Run the agent to see them here.</span
4526
+ >
1784
4527
  </div>
1785
4528
  `;
1786
4529
  if (filteredEvents.length === 0) return html`
@@ -1877,23 +4620,32 @@ ${argsString}</pre
1877
4620
  </div>
1878
4621
  <div class="relative h-full w-full overflow-y-auto overflow-x-hidden">
1879
4622
  <table class="w-full table-fixed border-collapse text-xs box-border">
4623
+ <colgroup>
4624
+ <col style="width:${this.evtColWidths[0]}px">
4625
+ <col style="width:${this.evtColWidths[1]}px">
4626
+ <col style="width:${this.evtColWidths[2]}px">
4627
+ <col>
4628
+ </colgroup>
1880
4629
  <thead class="sticky top-0 z-10">
1881
4630
  <tr class="bg-white">
4631
+ ${[
4632
+ "Agent",
4633
+ "Time",
4634
+ "Event Type"
4635
+ ].map((label, col) => html`
1882
4636
  <th
1883
4637
  class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900"
4638
+ style="position:relative;overflow:hidden;"
1884
4639
  >
1885
- Agent
1886
- </th>
1887
- <th
1888
- class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900"
1889
- >
1890
- Time
1891
- </th>
1892
- <th
1893
- class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900"
1894
- >
1895
- Event Type
1896
- </th>
4640
+ ${label}
4641
+ <div
4642
+ style="position:absolute;top:0;right:0;width:5px;height:100%;cursor:col-resize;user-select:none;background:transparent;"
4643
+ @pointerdown=${(e) => this._onEvtColResizeStart(e, col)}
4644
+ @pointermove=${(e) => this._onEvtColResizeMove(e)}
4645
+ @pointerup=${() => this._onEvtColResizeEnd()}
4646
+ @pointercancel=${() => this._onEvtColResizeEnd()}
4647
+ ></div>
4648
+ </th>`)}
1897
4649
  <th
1898
4650
  class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900"
1899
4651
  >
@@ -1981,6 +4733,25 @@ ${prettyEvent}</pre
1981
4733
  this.eventTypeFilter = "all";
1982
4734
  this.requestUpdate();
1983
4735
  }
4736
+ _onEvtColResizeStart(e, col) {
4737
+ e.preventDefault();
4738
+ e.stopPropagation();
4739
+ e.currentTarget.setPointerCapture(e.pointerId);
4740
+ this._evtColResize = {
4741
+ col,
4742
+ startX: e.clientX,
4743
+ startW: this.evtColWidths[col] ?? 0
4744
+ };
4745
+ }
4746
+ _onEvtColResizeMove(e) {
4747
+ if (!this._evtColResize) return;
4748
+ const { col, startX, startW } = this._evtColResize;
4749
+ this.evtColWidths = this.evtColWidths.map((w, i) => i === col ? Math.max(40, startW + (e.clientX - startX)) : w);
4750
+ this.requestUpdate();
4751
+ }
4752
+ _onEvtColResizeEnd() {
4753
+ this._evtColResize = null;
4754
+ }
1984
4755
  exportEvents(events) {
1985
4756
  try {
1986
4757
  const payload = JSON.stringify(events, null, 2);
@@ -2025,7 +4796,7 @@ ${prettyEvent}</pre
2025
4796
  <div class="flex items-start justify-between mb-4">
2026
4797
  <div class="flex items-center gap-3">
2027
4798
  <div
2028
- class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 text-blue-600"
4799
+ class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 text-blue-600 cpk-agent-icon"
2029
4800
  >
2030
4801
  ${this.renderIcon("Bot")}
2031
4802
  </div>
@@ -2053,7 +4824,7 @@ ${prettyEvent}</pre
2053
4824
  <div class="grid grid-cols-2 gap-4 md:grid-cols-4">
2054
4825
  <button
2055
4826
  type="button"
2056
- class="rounded-md bg-gray-50 px-3 py-2 text-left transition hover:bg-gray-100 cursor-pointer overflow-hidden"
4827
+ class="rounded-md bg-gray-50 px-3 py-2 text-left transition hover:bg-gray-100 cursor-pointer overflow-hidden cpk-stat-card"
2057
4828
  @click=${() => this.handleMenuSelect("ag-ui-events")}
2058
4829
  title="View all events in AG-UI Events"
2059
4830
  >
@@ -2064,7 +4835,9 @@ ${prettyEvent}</pre
2064
4835
  ${stats.totalEvents}
2065
4836
  </div>
2066
4837
  </button>
2067
- <div class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden">
4838
+ <div
4839
+ class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden cpk-stat-card"
4840
+ >
2068
4841
  <div class="truncate whitespace-nowrap text-xs text-gray-600">
2069
4842
  Messages
2070
4843
  </div>
@@ -2072,7 +4845,9 @@ ${prettyEvent}</pre
2072
4845
  ${stats.messages}
2073
4846
  </div>
2074
4847
  </div>
2075
- <div class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden">
4848
+ <div
4849
+ class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden cpk-stat-card"
4850
+ >
2076
4851
  <div class="truncate whitespace-nowrap text-xs text-gray-600">
2077
4852
  Tool Calls
2078
4853
  </div>
@@ -2080,7 +4855,9 @@ ${prettyEvent}</pre
2080
4855
  ${stats.toolCalls}
2081
4856
  </div>
2082
4857
  </div>
2083
- <div class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden">
4858
+ <div
4859
+ class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden cpk-stat-card"
4860
+ >
2084
4861
  <div class="truncate whitespace-nowrap text-xs text-gray-600">
2085
4862
  Errors
2086
4863
  </div>
@@ -2092,9 +4869,9 @@ ${prettyEvent}</pre
2092
4869
  </div>
2093
4870
 
2094
4871
  <!-- Current State Section -->
2095
- <div class="rounded-lg border border-gray-200 bg-white">
2096
- <div class="border-b border-gray-200 px-4 py-3">
2097
- <h4 class="text-sm font-semibold text-gray-900">Current State</h4>
4872
+ <div class="cpk-section-card">
4873
+ <div class="cpk-section-header">
4874
+ <h4>Current State</h4>
2098
4875
  </div>
2099
4876
  <div class="overflow-auto p-4">
2100
4877
  ${this.hasRenderableState(state) ? html`
@@ -2103,7 +4880,7 @@ ${prettyEvent}</pre
2103
4880
  ><code>${this.formatStateForDisplay(state)}</code></pre>
2104
4881
  ` : html`
2105
4882
  <div
2106
- class="flex h-40 items-center justify-center text-xs text-gray-500"
4883
+ class="flex h-12 items-center justify-center text-xs text-gray-500"
2107
4884
  >
2108
4885
  <div class="flex items-center gap-2 text-gray-500">
2109
4886
  <span class="text-lg text-gray-400"
@@ -2117,30 +4894,18 @@ ${prettyEvent}</pre
2117
4894
  </div>
2118
4895
 
2119
4896
  <!-- Current Messages Section -->
2120
- <div class="rounded-lg border border-gray-200 bg-white">
2121
- <div class="border-b border-gray-200 px-4 py-3">
2122
- <h4 class="text-sm font-semibold text-gray-900">
2123
- Current Messages
2124
- </h4>
4897
+ <div class="cpk-section-card">
4898
+ <div class="cpk-section-header">
4899
+ <h4>Current Messages</h4>
2125
4900
  </div>
2126
4901
  <div class="overflow-auto">
2127
4902
  ${messages && messages.length > 0 ? html`
2128
- <table class="w-full text-xs">
2129
- <thead class="bg-gray-50">
2130
- <tr>
2131
- <th
2132
- class="px-4 py-2 text-left font-medium text-gray-700"
2133
- >
2134
- Role
2135
- </th>
2136
- <th
2137
- class="px-4 py-2 text-left font-medium text-gray-700"
2138
- >
2139
- Content
2140
- </th>
2141
- </tr>
2142
- </thead>
2143
- <tbody class="divide-y divide-gray-200">
4903
+ <div class="w-full text-xs">
4904
+ <div class="flex bg-gray-50">
4905
+ <div class="w-40 shrink-0 px-4 py-2 font-medium text-gray-700">Role</div>
4906
+ <div class="flex-1 px-4 py-2 font-medium text-gray-700">Content</div>
4907
+ </div>
4908
+ <div class="divide-y divide-gray-200">
2144
4909
  ${messages.map((msg) => {
2145
4910
  const role = msg.role || "unknown";
2146
4911
  const roleColors = {
@@ -2155,34 +4920,32 @@ ${prettyEvent}</pre
2155
4920
  const hasContent = rawContent.trim().length > 0;
2156
4921
  const contentFallback = toolCalls.length > 0 ? "Invoked tool call" : "—";
2157
4922
  return html`
2158
- <tr>
2159
- <td class="px-4 py-2 align-top">
4923
+ <div class="flex items-start">
4924
+ <div class="w-40 shrink-0 px-4 py-2">
2160
4925
  <span
2161
4926
  class="inline-flex rounded px-2 py-0.5 text-[10px] font-medium ${roleColors[role] || roleColors.unknown}"
2162
4927
  >
2163
4928
  ${role}
2164
4929
  </span>
2165
- </td>
2166
- <td class="px-4 py-2">
4930
+ </div>
4931
+ <div class="flex-1 px-4 py-2">
2167
4932
  ${hasContent ? html`<div
2168
- class="max-w-2xl whitespace-pre-wrap break-words text-gray-700"
4933
+ class="whitespace-pre-line break-words text-gray-700"
2169
4934
  >
2170
4935
  ${rawContent}
2171
- </div>` : html`<div
2172
- class="text-xs italic text-gray-400"
2173
- >
4936
+ </div>` : html`<div class="italic text-gray-400">
2174
4937
  ${contentFallback}
2175
4938
  </div>`}
2176
4939
  ${role === "assistant" && toolCalls.length > 0 ? this.renderToolCallDetails(toolCalls) : nothing}
2177
- </td>
2178
- </tr>
4940
+ </div>
4941
+ </div>
2179
4942
  `;
2180
4943
  })}
2181
- </tbody>
2182
- </table>
4944
+ </div>
4945
+ </div>
2183
4946
  ` : html`
2184
4947
  <div
2185
- class="flex h-40 items-center justify-center text-xs text-gray-500"
4948
+ class="flex h-12 items-center justify-center text-xs text-gray-500"
2186
4949
  >
2187
4950
  <div class="flex items-center gap-2 text-gray-500">
2188
4951
  <span class="text-lg text-gray-400"
@@ -2243,14 +5006,29 @@ ${prettyEvent}</pre
2243
5006
  }
2244
5007
  handleMenuSelect(key) {
2245
5008
  if (!this.menuItems.some((item) => item.key === key)) return;
5009
+ const previousMenu = this.selectedMenu;
2246
5010
  this.selectedMenu = key;
2247
5011
  if (key === "agents" && this.selectedContext === "all-agents") {
2248
5012
  const agentOptions = this.contextOptions.filter((opt) => opt.key !== "all-agents");
2249
5013
  if (agentOptions.length > 0) {
2250
- const defaultAgent = agentOptions.find((opt) => opt.key === "default");
2251
- this.selectedContext = defaultAgent ? defaultAgent.key : agentOptions[0].key;
5014
+ const mostRecent = agentOptions.reduce((best, opt) => {
5015
+ const ts = this.getAgentStats(opt.key).lastActivity ?? -1;
5016
+ return best === null || ts > best.ts ? {
5017
+ key: opt.key,
5018
+ ts
5019
+ } : best;
5020
+ }, null);
5021
+ this.selectedContext = mostRecent ? mostRecent.key : agentOptions[0].key;
2252
5022
  }
2253
5023
  }
5024
+ if (previousMenu === "agents" && key !== "agents") {
5025
+ if (this.contextOptions.filter((opt) => opt.key !== "all-agents").length > 1) this.selectedContext = "all-agents";
5026
+ }
5027
+ if (key === "threads") this.autoSelectLatestThread();
5028
+ if (key === "ag-ui-events" || key === "agents") requestAnimationFrame(() => {
5029
+ const scroller = this.shadowRoot?.getElementById("cpk-main-scroll");
5030
+ if (scroller) scroller.scrollTop = 0;
5031
+ });
2254
5032
  this.contextMenuOpen = false;
2255
5033
  this.persistState();
2256
5034
  this.requestUpdate();
@@ -2638,9 +5416,19 @@ ${prettyEvent}</pre
2638
5416
  <div class="mb-3">
2639
5417
  <h5 class="mb-1 text-xs font-semibold text-gray-700">ID</h5>
2640
5418
  <code
2641
- class="block rounded bg-white border border-gray-200 px-2 py-1 text-[10px] font-mono text-gray-600"
5419
+ class="font-mono text-xs font-medium text-gray-800 flex-1 truncate min-w-0"
2642
5420
  >${id}</code
2643
5421
  >
5422
+ <button
5423
+ type="button"
5424
+ class="cpk-copy-btn"
5425
+ @click=${(e) => {
5426
+ e.stopPropagation();
5427
+ this.copyContextValue(id, `${id}:id`);
5428
+ }}
5429
+ >
5430
+ ${this.copiedContextItems.has(`${id}:id`) ? "✓" : "Copy"}
5431
+ </button>
2644
5432
  </div>
2645
5433
  ${hasValue ? html`
2646
5434
  <div class="mb-2 flex items-center justify-between gap-2">
@@ -2648,8 +5436,8 @@ ${prettyEvent}</pre
2648
5436
  Value
2649
5437
  </h5>
2650
5438
  <button
2651
- class="flex items-center gap-1 rounded-md border border-gray-200 bg-white px-2 py-1 text-[10px] font-medium text-gray-700 transition hover:bg-gray-50"
2652
5439
  type="button"
5440
+ class="cpk-copy-btn"
2653
5441
  @click=${(e) => {
2654
5442
  e.stopPropagation();
2655
5443
  this.copyContextValue(context.value, id);
@@ -2658,13 +5446,6 @@ ${prettyEvent}</pre
2658
5446
  ${this.copiedContextItems.has(id) ? "Copied" : "Copy JSON"}
2659
5447
  </button>
2660
5448
  </div>
2661
- <div
2662
- class="rounded-md border border-gray-200 bg-white p-3"
2663
- >
2664
- <pre
2665
- class="overflow-auto text-xs text-gray-800 max-h-96"
2666
- ><code>${this.formatContextValue(context.value)}</code></pre>
2667
- </div>
2668
5449
  ` : html`
2669
5450
  <div class="flex items-center justify-center py-4 text-xs text-gray-500">
2670
5451
  <span>No value available</span>
@@ -2677,7 +5458,7 @@ ${prettyEvent}</pre
2677
5458
  }
2678
5459
  getContextValuePreview(value) {
2679
5460
  if (value === void 0 || value === null) return "—";
2680
- if (typeof value === "string") return value.length > 50 ? `${value.substring(0, 50)}...` : value;
5461
+ if (typeof value === "string") return value.length > 50 ? `${value.slice(0, 50)}...` : value;
2681
5462
  if (typeof value === "number" || typeof value === "boolean") return String(value);
2682
5463
  if (Array.isArray(value)) return `Array(${value.length})`;
2683
5464
  if (typeof value === "object") {
@@ -2727,50 +5508,25 @@ ${prettyEvent}</pre
2727
5508
  else this.expandedRows.add(eventId);
2728
5509
  this.requestUpdate();
2729
5510
  }
2730
- renderAnnouncementPanel() {
2731
- if (!this.isOpen) return nothing;
2732
- this.ensureAnnouncementLoading();
5511
+ renderAnnouncementBanner() {
2733
5512
  if (!this.hasUnseenAnnouncement) return nothing;
2734
- if (!this.announcementLoaded && !this.announcementMarkdown) return html`<div
2735
- class="mx-4 my-3 rounded-xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-800 shadow-[0_12px_30px_rgba(15,23,42,0.12)]"
2736
- >
2737
- <div class="flex items-center gap-2 font-semibold">
2738
- <span
2739
- class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-slate-900 text-white shadow-sm"
2740
- >
2741
- ${this.renderIcon("Megaphone")}
2742
- </span>
2743
- <span>Loading latest announcement…</span>
2744
- </div>
2745
- </div>`;
2746
- if (this.announcementLoadError) return html`<div
2747
- class="mx-4 my-3 rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-900 shadow-[0_12px_30px_rgba(15,23,42,0.12)]"
5513
+ if (!this.announcementLoaded && !this.announcementHtml) return html`<div
5514
+ class="flex items-center gap-2 px-4 py-3 text-sm font-semibold text-slate-800"
2748
5515
  >
2749
- <div class="flex items-center gap-2 font-semibold">
2750
- <span
2751
- class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-rose-600 text-white shadow-sm"
2752
- >
2753
- ${this.renderIcon("Megaphone")}
2754
- </span>
2755
- <span>Announcement unavailable</span>
2756
- </div>
2757
- <p class="mt-2 text-xs text-rose-800">
2758
- We couldn’t load the latest notice. Please try opening the inspector
2759
- again.
2760
- </p>
5516
+ <span
5517
+ class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-slate-900 text-white shadow-sm"
5518
+ >
5519
+ ${this.renderIcon("Megaphone")}
5520
+ </span>
5521
+ <span>Loading latest announcement…</span>
2761
5522
  </div>`;
2762
- if (!this.announcementMarkdown) return nothing;
2763
- const content = this.announcementHtml ? unsafeHTML(this.announcementHtml) : html`<pre class="whitespace-pre-wrap text-sm text-gray-900">
2764
- ${this.announcementMarkdown}</pre
2765
- >`;
2766
- return html`<div
2767
- class="mx-4 my-3 rounded-xl border border-slate-200 bg-white px-4 py-4 shadow-[0_12px_30px_rgba(15,23,42,0.12)]"
2768
- >
5523
+ if (!this.announcementHtml) return nothing;
5524
+ return html`<div class="mx-4 mb-3 rounded-xl border border-slate-200 bg-white px-4 py-3">
2769
5525
  <div
2770
- class="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-900"
5526
+ class="mb-2 flex items-center gap-2 text-xs font-semibold text-slate-900"
2771
5527
  >
2772
5528
  <span
2773
- class="inline-flex h-7 w-7 items-center justify-center rounded-md bg-slate-900 text-white shadow-sm"
5529
+ class="inline-flex h-5 w-5 items-center justify-center rounded-md bg-slate-900 text-white shadow-sm"
2774
5530
  >
2775
5531
  ${this.renderIcon("Megaphone")}
2776
5532
  </span>
@@ -2781,12 +5537,27 @@ ${this.announcementMarkdown}</pre
2781
5537
  @click=${this.handleDismissAnnouncement}
2782
5538
  aria-label="Dismiss announcement"
2783
5539
  >
2784
- Dismiss
5540
+ ${this.renderIcon("X")}
2785
5541
  </button>
2786
5542
  </div>
2787
- <div class="announcement-content text-sm leading-relaxed text-gray-900">
2788
- ${content}
5543
+ <div class="announcement-body ${this.announcementExpanded ? "announcement-body--expanded" : "announcement-body--collapsed"}">
5544
+ <div class="announcement-content">
5545
+ ${unsafeHTML(this.announcementHtml)}
5546
+ </div>
5547
+ ${!this.announcementExpanded ? html`
5548
+ <div class="announcement-fade"></div>
5549
+ ` : nothing}
2789
5550
  </div>
5551
+ <button
5552
+ class="announcement-toggle"
5553
+ type="button"
5554
+ @click=${() => {
5555
+ this.announcementExpanded = !this.announcementExpanded;
5556
+ this.requestUpdate();
5557
+ }}
5558
+ >
5559
+ ${this.announcementExpanded ? "Show less ↑" : "Show more ↓"}
5560
+ </button>
2790
5561
  </div>`;
2791
5562
  }
2792
5563
  ensureAnnouncementLoading() {
@@ -2821,14 +5592,13 @@ ${this.announcementMarkdown}</pre
2821
5592
  const storedTimestamp = this.loadStoredAnnouncementTimestamp();
2822
5593
  this.announcementTimestamp = timestamp;
2823
5594
  this.announcementPreviewText = previewText ?? "";
2824
- this.announcementMarkdown = markdown;
2825
5595
  this.hasUnseenAnnouncement = (!storedTimestamp || storedTimestamp !== timestamp) && !!this.announcementPreviewText;
2826
5596
  this.showAnnouncementPreview = this.hasUnseenAnnouncement;
2827
5597
  this.announcementHtml = await this.convertMarkdownToHtml(markdown);
2828
5598
  this.announcementLoaded = true;
2829
5599
  this.requestUpdate();
2830
5600
  } catch (error) {
2831
- this.announcementLoadError = error;
5601
+ console.warn("[CopilotKit Inspector] Failed to load announcement", error);
2832
5602
  this.announcementLoaded = true;
2833
5603
  this.requestUpdate();
2834
5604
  }
@@ -2888,5 +5658,5 @@ function defineWebInspector() {
2888
5658
  defineWebInspector();
2889
5659
 
2890
5660
  //#endregion
2891
- export { WEB_INSPECTOR_TAG, WebInspectorElement, defineWebInspector };
5661
+ export { WEB_INSPECTOR_TAG, WebInspectorElement, defineWebInspector, ɵCpkThreadDetails };
2892
5662
  //# sourceMappingURL=index.mjs.map