@copilotkit/web-inspector 1.62.0 → 1.62.2-canary.1783457132

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/src/index.ts CHANGED
@@ -13,12 +13,19 @@ import {
13
13
  ɵselectThreads,
14
14
  ɵselectThreadsError,
15
15
  ɵcreateThreadStore,
16
+ ɵselectMemories,
17
+ ɵselectMemoriesIsLoading,
18
+ ɵselectMemoriesError,
19
+ ɵselectMemoriesAvailable,
20
+ ɵselectMemoriesRealtimeStatus,
16
21
  } from "@copilotkit/core";
17
22
  import type {
18
23
  CopilotKitCoreSubscriber,
19
24
  CopilotKitCoreErrorCode,
20
25
  ɵThreadStore,
21
26
  ɵThread,
27
+ Memory,
28
+ MemoryRealtimeStatus,
22
29
  } from "@copilotkit/core";
23
30
  import type { AbstractAgent, AgentSubscriber } from "@ag-ui/client";
24
31
  import type {
@@ -60,10 +67,14 @@ import {
60
67
  trackThreadsEnabledViewed,
61
68
  trackThreadsIntelligenceSignupClicked,
62
69
  trackThreadsLockedViewed,
70
+ trackMemoriesTabClicked,
63
71
  trackThreadsTabClicked,
64
72
  trackThreadsTalkToEngineerClicked,
65
73
  } from "./lib/telemetry.js";
66
- import type { InspectorThreadTelemetryProps } from "./lib/telemetry.js";
74
+ import type {
75
+ InspectorMemoryTelemetryProps,
76
+ InspectorThreadTelemetryProps,
77
+ } from "./lib/telemetry.js";
67
78
 
68
79
  export type { Anchor } from "./lib/types.js";
69
80
 
@@ -78,6 +89,7 @@ type MenuKey =
78
89
  | "frontend-tools"
79
90
  | "agent-context"
80
91
  | "threads"
92
+ | "memories"
81
93
  | "settings";
82
94
 
83
95
  type MenuItem = {
@@ -779,6 +791,8 @@ export class CpkThreadInspector extends LitElement {
779
791
  _stateError: { state: true },
780
792
  _expandedTools: { state: true },
781
793
  _expandedMessages: { state: true },
794
+ _expandedTimelineDetails: { state: true },
795
+ _expandedRawEvents: { state: true },
782
796
  _showDetailPanel: { state: true },
783
797
  _detailPanelWidth: { state: true },
784
798
  _eventsNotAvailable: { state: true },
@@ -816,6 +830,8 @@ export class CpkThreadInspector extends LitElement {
816
830
  private _stateError: string | null = null;
817
831
  private _expandedTools = new Set<string>();
818
832
  private _expandedMessages = new Set<string>();
833
+ private _expandedTimelineDetails = new Set<string>();
834
+ private _expandedRawEvents = new Set<string>();
819
835
  private _showDetailPanel = false;
820
836
  private _detailPanelWidth = 250;
821
837
  /** True when the /events endpoint returned 501 — don't fall back to live data. */
@@ -1102,6 +1118,7 @@ export class CpkThreadInspector extends LitElement {
1102
1118
 
1103
1119
  .cpk-td__metadata-strip {
1104
1120
  display: flex;
1121
+ align-items: center;
1105
1122
  gap: 6px;
1106
1123
  flex-wrap: wrap;
1107
1124
  padding: 10px 16px;
@@ -1110,6 +1127,14 @@ export class CpkThreadInspector extends LitElement {
1110
1127
  flex-shrink: 0;
1111
1128
  }
1112
1129
 
1130
+ .cpk-td__metadata-pills {
1131
+ display: flex;
1132
+ gap: 6px;
1133
+ flex: 1;
1134
+ flex-wrap: wrap;
1135
+ min-width: 0;
1136
+ }
1137
+
1113
1138
  .cpk-td__metadata-pill {
1114
1139
  display: inline-flex;
1115
1140
  align-items: center;
@@ -1404,13 +1429,44 @@ export class CpkThreadInspector extends LitElement {
1404
1429
  }
1405
1430
 
1406
1431
  .cpk-td__timeline-body {
1407
- padding: 9px 10px;
1432
+ margin: 0;
1433
+ padding: 0 10px 9px;
1408
1434
  font-size: 12px;
1409
1435
  line-height: 1.55;
1410
1436
  color: #57575b;
1411
1437
  white-space: pre-wrap;
1412
1438
  word-break: break-word;
1413
- border-top: 1px solid #e9e9ef;
1439
+ }
1440
+
1441
+ .cpk-td__timeline-toolbar {
1442
+ display: flex;
1443
+ gap: 6px;
1444
+ margin-left: auto;
1445
+ }
1446
+
1447
+ .cpk-td__timeline-bulk-toggle {
1448
+ margin: 0;
1449
+ padding: 4px 8px;
1450
+ border: 1px solid #dcdce8;
1451
+ border-radius: 6px;
1452
+ background: #ffffff;
1453
+ color: #36363a;
1454
+ cursor: pointer;
1455
+ font-family: "Inter", sans-serif;
1456
+ font-size: 11px;
1457
+ font-weight: 600;
1458
+ line-height: 1.2;
1459
+ }
1460
+
1461
+ .cpk-td__timeline-bulk-toggle:hover {
1462
+ border-color: rgba(85, 88, 178, 0.38);
1463
+ background: #f7f7ff;
1464
+ color: #010507;
1465
+ }
1466
+
1467
+ .cpk-td__timeline-bulk-toggle:disabled {
1468
+ cursor: not-allowed;
1469
+ opacity: 0.45;
1414
1470
  }
1415
1471
 
1416
1472
  .cpk-td__source-link {
@@ -1431,6 +1487,34 @@ export class CpkThreadInspector extends LitElement {
1431
1487
  color: #010507;
1432
1488
  }
1433
1489
 
1490
+ .cpk-td__timeline-details-toggle {
1491
+ margin: 0;
1492
+ padding: 5px 10px;
1493
+ border: none;
1494
+ background: #ffffff;
1495
+ color: #5558b2;
1496
+ cursor: pointer;
1497
+ display: inline-flex;
1498
+ align-items: center;
1499
+ gap: 5px;
1500
+ font-family: "Inter", sans-serif;
1501
+ font-size: 12px;
1502
+ font-weight: 600;
1503
+ line-height: 1.2;
1504
+ width: 100%;
1505
+ }
1506
+
1507
+ .cpk-td__timeline-details-toggle:hover {
1508
+ background: #f7f7ff;
1509
+ color: #010507;
1510
+ }
1511
+
1512
+ .cpk-td__timeline-details-toggle svg {
1513
+ width: 12px;
1514
+ height: 12px;
1515
+ stroke-width: 2;
1516
+ }
1517
+
1434
1518
  /* ── Generative UI ──────────────────────────────────────────────── */
1435
1519
  @keyframes cpk-genui-enter {
1436
1520
  from {
@@ -1720,6 +1804,8 @@ export class CpkThreadInspector extends LitElement {
1720
1804
  this._liveEventsWithSourceIndexCache = null;
1721
1805
  this._expandedTools = new Set();
1722
1806
  this._expandedMessages = new Set();
1807
+ this._expandedTimelineDetails = new Set();
1808
+ this._expandedRawEvents = new Set();
1723
1809
  this._metadataAbort?.abort();
1724
1810
  this._metadataAbort = null;
1725
1811
  this._messagesAbort?.abort();
@@ -2391,6 +2477,47 @@ export class CpkThreadInspector extends LitElement {
2391
2477
  this._expandedMessages = next;
2392
2478
  }
2393
2479
 
2480
+ private toggleTimelineDetails(id: string): void {
2481
+ const next = new Set(this._expandedTimelineDetails);
2482
+ if (next.has(id)) next.delete(id);
2483
+ else next.add(id);
2484
+ this._expandedTimelineDetails = next;
2485
+ }
2486
+
2487
+ private expandTimelineDetails(ids: string[]): void {
2488
+ this._expandedTimelineDetails = new Set([
2489
+ ...this._expandedTimelineDetails,
2490
+ ...ids,
2491
+ ]);
2492
+ }
2493
+
2494
+ private collapseTimelineDetails(ids: string[]): void {
2495
+ const next = new Set(this._expandedTimelineDetails);
2496
+ for (const id of ids) next.delete(id);
2497
+ this._expandedTimelineDetails = next;
2498
+ }
2499
+
2500
+ private toggleRawEventDetails(id: string): void {
2501
+ const next = new Set(this._expandedRawEvents);
2502
+ if (next.has(id)) next.delete(id);
2503
+ else next.add(id);
2504
+ this._expandedRawEvents = next;
2505
+ }
2506
+
2507
+ private expandRawEventDetails(ids: string[]): void {
2508
+ this._expandedRawEvents = new Set([...this._expandedRawEvents, ...ids]);
2509
+ }
2510
+
2511
+ private collapseRawEventDetails(ids: string[]): void {
2512
+ const next = new Set(this._expandedRawEvents);
2513
+ for (const id of ids) next.delete(id);
2514
+ this._expandedRawEvents = next;
2515
+ }
2516
+
2517
+ private rawEventId(event: ApiAgentEvent): string {
2518
+ return `raw-event-${event.sourceIndex ?? event.timestamp ?? event.type}`;
2519
+ }
2520
+
2394
2521
  private get activeEvents(): ApiAgentEvent[] {
2395
2522
  // When the endpoint explicitly returned 501 we report no events rather
2396
2523
  // than leaking the parent's agent-keyed live events across historical
@@ -2562,25 +2689,95 @@ export class CpkThreadInspector extends LitElement {
2562
2689
  },
2563
2690
  { label: "Status", value: metadata?.status },
2564
2691
  ].filter((pill) => pill.value != null && pill.value !== "");
2692
+ const bulkControls = this.renderActiveBulkControls();
2565
2693
 
2566
- if (pills.length === 0) return nothing;
2694
+ if (pills.length === 0 && bulkControls === nothing) return nothing;
2567
2695
 
2568
2696
  return html`
2569
2697
  <div class="cpk-td__metadata-strip" aria-label="Thread metadata">
2570
- ${pills.map(
2571
- (pill) => html`
2572
- <span class="cpk-td__metadata-pill" title=${pill.value ?? ""}>
2573
- <span class="cpk-td__metadata-label">${pill.label}</span>
2574
- <span class="cpk-td__metadata-value"
2575
- >${this.shortId(pill.value)}</span
2576
- >
2577
- </span>
2578
- `,
2579
- )}
2698
+ <div class="cpk-td__metadata-pills">
2699
+ ${pills.map(
2700
+ (pill) => html`
2701
+ <span class="cpk-td__metadata-pill" title=${pill.value ?? ""}>
2702
+ <span class="cpk-td__metadata-label">${pill.label}</span>
2703
+ <span class="cpk-td__metadata-value"
2704
+ >${this.shortId(pill.value)}</span
2705
+ >
2706
+ </span>
2707
+ `,
2708
+ )}
2709
+ </div>
2710
+ ${bulkControls}
2580
2711
  </div>
2581
2712
  `;
2582
2713
  }
2583
2714
 
2715
+ private renderActiveBulkControls() {
2716
+ if (this._eventsNotAvailable) return nothing;
2717
+ if (this._tab === "raw-events") return this.renderRawEventBulkControls();
2718
+ if (this._tab !== "timeline") return nothing;
2719
+
2720
+ const detailIds = this.timelineItemsForEvents(this.activeEvents)
2721
+ .filter((item) => item.details)
2722
+ .map((item) => item.id);
2723
+ if (detailIds.length <= 1) return nothing;
2724
+
2725
+ const allExpanded = detailIds.every((id) =>
2726
+ this._expandedTimelineDetails.has(id),
2727
+ );
2728
+ const allCollapsed = detailIds.every(
2729
+ (id) => !this._expandedTimelineDetails.has(id),
2730
+ );
2731
+
2732
+ return html`<div class="cpk-td__timeline-toolbar">
2733
+ <button
2734
+ type="button"
2735
+ class="cpk-td__timeline-bulk-toggle"
2736
+ ?disabled=${allExpanded}
2737
+ @click=${() => this.expandTimelineDetails(detailIds)}
2738
+ >
2739
+ Expand all
2740
+ </button>
2741
+ <button
2742
+ type="button"
2743
+ class="cpk-td__timeline-bulk-toggle"
2744
+ ?disabled=${allCollapsed}
2745
+ @click=${() => this.collapseTimelineDetails(detailIds)}
2746
+ >
2747
+ Collapse all
2748
+ </button>
2749
+ </div>`;
2750
+ }
2751
+
2752
+ private renderRawEventBulkControls() {
2753
+ const eventIds = this.activeEvents.map((event) => this.rawEventId(event));
2754
+ if (eventIds.length <= 1) return nothing;
2755
+
2756
+ const allExpanded = eventIds.every((id) => this._expandedRawEvents.has(id));
2757
+ const allCollapsed = eventIds.every(
2758
+ (id) => !this._expandedRawEvents.has(id),
2759
+ );
2760
+
2761
+ return html`<div class="cpk-td__timeline-toolbar">
2762
+ <button
2763
+ type="button"
2764
+ class="cpk-td__timeline-bulk-toggle"
2765
+ ?disabled=${allExpanded}
2766
+ @click=${() => this.expandRawEventDetails(eventIds)}
2767
+ >
2768
+ Expand all
2769
+ </button>
2770
+ <button
2771
+ type="button"
2772
+ class="cpk-td__timeline-bulk-toggle"
2773
+ ?disabled=${allCollapsed}
2774
+ @click=${() => this.collapseRawEventDetails(eventIds)}
2775
+ >
2776
+ Collapse all
2777
+ </button>
2778
+ </div>`;
2779
+ }
2780
+
2584
2781
  private revealSourceEvent(sourceIndex: number): void {
2585
2782
  this._activatedTabs = new Set([...this._activatedTabs, "raw-events"]);
2586
2783
  this._tab = "raw-events";
@@ -2619,7 +2816,10 @@ export class CpkThreadInspector extends LitElement {
2619
2816
  }
2620
2817
 
2621
2818
  const events = this.activeEvents;
2622
- const cachedTimeline = this.getCachedPanelTpl("timeline", [events]);
2819
+ const cachedTimeline = this.getCachedPanelTpl("timeline", [
2820
+ events,
2821
+ this._expandedTimelineDetails,
2822
+ ]);
2623
2823
  if (cachedTimeline) return cachedTimeline;
2624
2824
 
2625
2825
  const timelineItems = this.timelineItemsForEvents(events);
@@ -2637,13 +2837,16 @@ export class CpkThreadInspector extends LitElement {
2637
2837
  `;
2638
2838
  }
2639
2839
 
2640
- return this.cachedPanelTpl("timeline", [events], () => {
2641
- return html`${timelineItems.map((item) => this.renderTimelineItem(item))}`;
2642
- });
2840
+ return this.cachedPanelTpl(
2841
+ "timeline",
2842
+ [events, this._expandedTimelineDetails],
2843
+ () => html`${timelineItems.map((item) => this.renderTimelineItem(item))}`,
2844
+ );
2643
2845
  }
2644
2846
 
2645
2847
  private renderTimelineItem(item: TimelineItem) {
2646
2848
  const isWarning = item.kind === "warning";
2849
+ const detailsExpanded = this._expandedTimelineDetails.has(item.id);
2647
2850
  return html`
2648
2851
  <div
2649
2852
  class="cpk-td__timeline-item ${
@@ -2666,14 +2869,56 @@ export class CpkThreadInspector extends LitElement {
2666
2869
  >${formatTimestamp(item.timestamp)}</span
2667
2870
  >
2668
2871
  </div>
2872
+ ${
2873
+ item.details
2874
+ ? html`<button
2875
+ type="button"
2876
+ class="cpk-td__timeline-details-toggle"
2877
+ aria-expanded=${detailsExpanded ? "true" : "false"}
2878
+ @click=${() => this.toggleTimelineDetails(item.id)}
2879
+ >
2880
+ ${
2881
+ detailsExpanded
2882
+ ? html`
2883
+ <svg
2884
+ aria-hidden="true"
2885
+ viewBox="0 0 24 24"
2886
+ fill="none"
2887
+ stroke="currentColor"
2888
+ stroke-linecap="round"
2889
+ stroke-linejoin="round"
2890
+ >
2891
+ <path d="m6 9 6 6 6-6" />
2892
+ </svg>
2893
+ `
2894
+ : html`
2895
+ <svg
2896
+ aria-hidden="true"
2897
+ viewBox="0 0 24 24"
2898
+ fill="none"
2899
+ stroke="currentColor"
2900
+ stroke-linecap="round"
2901
+ stroke-linejoin="round"
2902
+ >
2903
+ <path d="m9 18 6-6-6-6" />
2904
+ </svg>
2905
+ `
2906
+ }
2907
+ <span>${detailsExpanded ? "Hide details" : "Show details"}</span>
2908
+ </button>`
2909
+ : nothing
2910
+ }
2669
2911
  ${
2670
2912
  item.body
2671
2913
  ? html`<div class="cpk-td__timeline-body">${item.body}</div>`
2672
- : item.details
2673
- ? html`<pre class="cpk-td__timeline-body">
2674
- ${unsafeHTML(highlightedJson(item.details))}</pre
2675
- >`
2676
- : nothing
2914
+ : nothing
2915
+ }
2916
+ ${
2917
+ item.details && detailsExpanded
2918
+ ? html`<pre class="cpk-td__timeline-body">${unsafeHTML(
2919
+ highlightedJson(item.details),
2920
+ )}</pre>`
2921
+ : nothing
2677
2922
  }
2678
2923
  </div>
2679
2924
  `;
@@ -3011,10 +3256,15 @@ ${unsafeHTML(highlightedJson(stateValue))}</pre
3011
3256
  </div>
3012
3257
  `;
3013
3258
  }
3014
- return this.cachedPanelTpl("raw-events", [events], () => {
3015
- return html`${events.map((event) => {
3016
- const { bg, fg } = eventColors(event.type);
3017
- return html`
3259
+ return this.cachedPanelTpl(
3260
+ "raw-events",
3261
+ [events, this._expandedRawEvents],
3262
+ () => {
3263
+ return html`${events.map((event) => {
3264
+ const { bg, fg } = eventColors(event.type);
3265
+ const eventId = this.rawEventId(event);
3266
+ const detailsExpanded = this._expandedRawEvents.has(eventId);
3267
+ return html`
3018
3268
  <div class="cpk-td__event" data-source-index=${event.sourceIndex}>
3019
3269
  <div class="cpk-td__event-header" style="background:${bg}">
3020
3270
  <span class="cpk-td__event-type" style="color:${fg}"
@@ -3024,13 +3274,53 @@ ${unsafeHTML(highlightedJson(stateValue))}</pre
3024
3274
  >${formatTimestamp(event.timestamp)}</span
3025
3275
  >
3026
3276
  </div>
3027
- <pre class="cpk-td__event-payload">
3028
- ${unsafeHTML(highlightedJson(event.rawEvent ?? event))}</pre
3277
+ <button
3278
+ type="button"
3279
+ class="cpk-td__timeline-details-toggle"
3280
+ aria-expanded=${detailsExpanded ? "true" : "false"}
3281
+ @click=${() => this.toggleRawEventDetails(eventId)}
3029
3282
  >
3283
+ ${
3284
+ detailsExpanded
3285
+ ? html`
3286
+ <svg
3287
+ aria-hidden="true"
3288
+ viewBox="0 0 24 24"
3289
+ fill="none"
3290
+ stroke="currentColor"
3291
+ stroke-linecap="round"
3292
+ stroke-linejoin="round"
3293
+ >
3294
+ <path d="m6 9 6 6 6-6" />
3295
+ </svg>
3296
+ `
3297
+ : html`
3298
+ <svg
3299
+ aria-hidden="true"
3300
+ viewBox="0 0 24 24"
3301
+ fill="none"
3302
+ stroke="currentColor"
3303
+ stroke-linecap="round"
3304
+ stroke-linejoin="round"
3305
+ >
3306
+ <path d="m9 18 6-6-6-6" />
3307
+ </svg>
3308
+ `
3309
+ }
3310
+ <span>${detailsExpanded ? "Hide details" : "Show details"}</span>
3311
+ </button>
3312
+ ${
3313
+ detailsExpanded
3314
+ ? html`<pre class="cpk-td__event-payload">${unsafeHTML(
3315
+ highlightedJson(event.rawEvent ?? event),
3316
+ )}</pre>`
3317
+ : nothing
3318
+ }
3030
3319
  </div>
3031
3320
  `;
3032
- })}`;
3033
- });
3321
+ })}`;
3322
+ },
3323
+ );
3034
3324
  }
3035
3325
 
3036
3326
  private renderPanelToggle() {
@@ -3103,38 +3393,400 @@ ${unsafeHTML(highlightedJson(event.rawEvent ?? event))}</pre
3103
3393
  >
3104
3394
  </div>
3105
3395
 
3106
- <div class="cpk-tdp__divider"></div>
3396
+ <div class="cpk-tdp__divider"></div>
3397
+
3398
+ <!-- Timestamps -->
3399
+ <div class="cpk-tdp__section-title">Timestamps</div>
3400
+ <div class="cpk-tdp__row">
3401
+ <span class="cpk-tdp__label">Created</span>
3402
+ <span class="cpk-tdp__value">${this.fmtTime(metadata?.createdAt)}</span>
3403
+ </div>
3404
+ <div class="cpk-tdp__row">
3405
+ <span class="cpk-tdp__label">Updated</span>
3406
+ <span class="cpk-tdp__value">${this.fmtTime(metadata?.updatedAt)}</span>
3407
+ </div>
3408
+ <div class="cpk-tdp__row">
3409
+ <span class="cpk-tdp__label">Duration</span>
3410
+ <span class="cpk-tdp__value">${this.duration}</span>
3411
+ </div>
3412
+
3413
+ <div class="cpk-tdp__divider"></div>
3414
+
3415
+ <!-- Activity -->
3416
+ <div class="cpk-tdp__section-title">Activity</div>
3417
+ <div class="cpk-tdp__row">
3418
+ <span class="cpk-tdp__label">Messages</span>
3419
+ <span class="cpk-tdp__value">${counts.messages}</span>
3420
+ </div>
3421
+ <div class="cpk-tdp__row">
3422
+ <span class="cpk-tdp__label">Tool calls</span>
3423
+ <span class="cpk-tdp__value">${counts.toolCalls}</span>
3424
+ </div>
3425
+ <div class="cpk-tdp__row">
3426
+ <span class="cpk-tdp__label">AG-UI events</span>
3427
+ <span class="cpk-tdp__value">${this.activeEvents.length}</span>
3428
+ </div>
3429
+ `;
3430
+ }
3431
+ }
3432
+
3433
+ // ─── cpk-memory-list ─────────────────────────────────────────────────────────
3434
+
3435
+ /** Memory kind values including the "all" sentinel used by the filter UI. */
3436
+ type MemoryKindFilter = "all" | "topical" | "episodic" | "operational";
3437
+
3438
+ class CpkMemoryList extends LitElement {
3439
+ static properties = {
3440
+ memories: { attribute: false },
3441
+ search: { state: true },
3442
+ kind: { state: true },
3443
+ };
3444
+
3445
+ /** Ordered (newest-first) list of memories supplied by the parent. */
3446
+ memories: Memory[] = [];
3447
+ private search = "";
3448
+ private kind: MemoryKindFilter = "all";
3449
+
3450
+ static styles = css`
3451
+ @import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600&family=Spline+Sans+Mono:wght@400;500&display=swap");
3452
+
3453
+ :host {
3454
+ display: flex;
3455
+ flex-direction: column;
3456
+ height: 100%;
3457
+ overflow: hidden;
3458
+ }
3459
+
3460
+ .cpk-ml {
3461
+ font-family: "Plus Jakarta Sans", sans-serif;
3462
+ display: flex;
3463
+ flex-direction: column;
3464
+ height: 100%;
3465
+ overflow: hidden;
3466
+ background: #f7f7f9;
3467
+ }
3468
+
3469
+ /* ── Search ── */
3470
+ .cpk-ml__search {
3471
+ padding: 10px 12px;
3472
+ border-bottom: 1px solid #dbdbe5;
3473
+ flex-shrink: 0;
3474
+ }
3475
+
3476
+ .cpk-ml__search-input {
3477
+ width: 100%;
3478
+ box-sizing: border-box;
3479
+ font-family: "Plus Jakarta Sans", sans-serif;
3480
+ font-size: 12px;
3481
+ padding: 7px 10px;
3482
+ border-radius: 6px;
3483
+ border: 1px solid #dbdbe5;
3484
+ background: #ffffff;
3485
+ color: #010507;
3486
+ outline: none;
3487
+ transition: border-color 0.15s;
3488
+ }
3489
+
3490
+ .cpk-ml__search-input:focus {
3491
+ border-color: #bec2ff;
3492
+ }
3493
+
3494
+ /* ── Kind filter ── */
3495
+ .cpk-ml__filter {
3496
+ display: flex;
3497
+ gap: 4px;
3498
+ padding: 8px 12px;
3499
+ border-bottom: 1px solid #dbdbe5;
3500
+ flex-shrink: 0;
3501
+ flex-wrap: wrap;
3502
+ }
3503
+
3504
+ .cpk-ml__filter-seg {
3505
+ font-family: "Plus Jakarta Sans", sans-serif;
3506
+ font-size: 11px;
3507
+ font-weight: 500;
3508
+ padding: 3px 9px;
3509
+ border-radius: 5px;
3510
+ border: 1px solid #dbdbe5;
3511
+ background: #ffffff;
3512
+ color: #57575b;
3513
+ cursor: pointer;
3514
+ transition:
3515
+ background 0.1s,
3516
+ border-color 0.1s,
3517
+ color 0.1s;
3518
+ user-select: none;
3519
+ }
3520
+
3521
+ .cpk-ml__filter-seg:hover {
3522
+ background: #f0f0f5;
3523
+ }
3524
+
3525
+ .cpk-ml__filter-seg--active {
3526
+ background: #bec2ff1a;
3527
+ border-color: #bec2ff;
3528
+ color: #010507;
3529
+ }
3530
+
3531
+ .cpk-ml__filter-count {
3532
+ font-family: "Spline Sans Mono", monospace;
3533
+ font-size: 9px;
3534
+ margin-left: 4px;
3535
+ color: #838389;
3536
+ }
3537
+
3538
+ /* ── List ── */
3539
+ .cpk-ml__list {
3540
+ flex: 1;
3541
+ overflow-y: auto;
3542
+ padding: 8px 12px;
3543
+ display: flex;
3544
+ flex-direction: column;
3545
+ gap: 8px;
3546
+ }
3547
+
3548
+ /* ── Card ── */
3549
+ .cpk-ml__card {
3550
+ background: #ffffff;
3551
+ border: 1px solid #e9e9ef;
3552
+ border-radius: 8px;
3553
+ padding: 10px 12px;
3554
+ display: flex;
3555
+ flex-direction: column;
3556
+ gap: 6px;
3557
+ }
3558
+
3559
+ .cpk-ml__card-badges {
3560
+ display: flex;
3561
+ gap: 6px;
3562
+ align-items: center;
3563
+ flex-wrap: wrap;
3564
+ }
3565
+
3566
+ /* Kind badge — color per kind */
3567
+ .cpk-ml__kind-badge {
3568
+ font-family: "Spline Sans Mono", monospace;
3569
+ font-size: 9px;
3570
+ padding: 1px 7px;
3571
+ border-radius: 4px;
3572
+ text-transform: uppercase;
3573
+ font-weight: 500;
3574
+ white-space: nowrap;
3575
+ }
3576
+
3577
+ .cpk-ml__kind-badge--topical {
3578
+ background: #eee6fe;
3579
+ color: #57575b;
3580
+ }
3581
+
3582
+ .cpk-ml__kind-badge--episodic {
3583
+ background: #e6f4fe;
3584
+ color: #2d5f80;
3585
+ }
3586
+
3587
+ .cpk-ml__kind-badge--operational {
3588
+ background: #e6feee;
3589
+ color: #2d6645;
3590
+ }
3591
+
3592
+ /* Scope badge */
3593
+ .cpk-ml__scope-badge {
3594
+ font-family: "Spline Sans Mono", monospace;
3595
+ font-size: 9px;
3596
+ padding: 1px 7px;
3597
+ border-radius: 4px;
3598
+ text-transform: uppercase;
3599
+ font-weight: 500;
3600
+ white-space: nowrap;
3601
+ background: #f0f0f5;
3602
+ color: #838389;
3603
+ }
3604
+
3605
+ /* Content */
3606
+ .cpk-ml__content {
3607
+ font-size: 12px;
3608
+ color: #010507;
3609
+ line-height: 1.5;
3610
+ word-break: break-word;
3611
+ }
3612
+
3613
+ /* Footer */
3614
+ .cpk-ml__footer {
3615
+ display: flex;
3616
+ align-items: center;
3617
+ justify-content: space-between;
3618
+ gap: 8px;
3619
+ margin-top: 2px;
3620
+ }
3621
+
3622
+ .cpk-ml__footer-threads {
3623
+ font-size: 10px;
3624
+ color: #838389;
3625
+ }
3626
+
3627
+ .cpk-ml__footer-id {
3628
+ font-family: "Spline Sans Mono", monospace;
3629
+ font-size: 9px;
3630
+ color: #c0c0c8;
3631
+ }
3632
+
3633
+ /* ── Empty state ── */
3634
+ .cpk-ml__empty {
3635
+ padding: 32px 16px;
3636
+ text-align: center;
3637
+ color: #838389;
3638
+ font-size: 12px;
3639
+ display: flex;
3640
+ flex-direction: column;
3641
+ align-items: center;
3642
+ gap: 8px;
3643
+ }
3644
+
3645
+ .cpk-ml__empty-icon {
3646
+ color: #c0c0c8;
3647
+ }
3648
+ `;
3649
+
3650
+ /** Memories that pass the current text search (before kind filter). */
3651
+ private get searchFiltered(): Memory[] {
3652
+ const q = this.search.trim().toLowerCase();
3653
+ if (!q) return this.memories;
3654
+ return this.memories.filter((m) => m.content.toLowerCase().includes(q));
3655
+ }
3656
+
3657
+ /** Memories that pass both search and kind filter. */
3658
+ private get filtered(): Memory[] {
3659
+ const searched = this.searchFiltered;
3660
+ if (this.kind === "all") return searched;
3661
+ return searched.filter((m) => m.kind === this.kind);
3662
+ }
3663
+
3664
+ /** Count of search-filtered memories for a given kind (for segment labels). */
3665
+ private countForKind(kind: Exclude<MemoryKindFilter, "all">): number {
3666
+ return this.searchFiltered.filter((m) => m.kind === kind).length;
3667
+ }
3668
+
3669
+ private onSearchInput = (event: Event): void => {
3670
+ this.search = (event.target as HTMLInputElement).value;
3671
+ };
3672
+
3673
+ private onKindClick = (event: Event): void => {
3674
+ const seg = (event.target as HTMLElement).closest("[data-kind]");
3675
+ if (!seg) return;
3676
+ const k = (seg as HTMLElement).dataset["kind"] as MemoryKindFilter;
3677
+ this.kind = k;
3678
+ };
3679
+
3680
+ /** Truncate an id to first-4…last-4 characters. */
3681
+ private shortId(id: string): string {
3682
+ if (id.length <= 12) return id;
3683
+ return `${id.slice(0, 4)}…${id.slice(-4)}`;
3684
+ }
3685
+
3686
+ private renderKindBadge(kind: string): TemplateResult {
3687
+ return html`<span class="cpk-ml__kind-badge cpk-ml__kind-badge--${kind}"
3688
+ >${kind}</span
3689
+ >`;
3690
+ }
3691
+
3692
+ private renderEmpty(): TemplateResult {
3693
+ const q = this.search.trim();
3694
+ if (this.memories.length === 0) {
3695
+ return html`
3696
+ <div class="cpk-ml__empty">
3697
+ <svg
3698
+ width="24"
3699
+ height="24"
3700
+ viewBox="0 0 24 24"
3701
+ fill="none"
3702
+ stroke="currentColor"
3703
+ stroke-width="1.5"
3704
+ stroke-linecap="round"
3705
+ stroke-linejoin="round"
3706
+ class="cpk-ml__empty-icon"
3707
+ >
3708
+ <ellipse cx="12" cy="5" rx="9" ry="3" />
3709
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
3710
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
3711
+ </svg>
3712
+ No memories yet — tell the agent a durable fact and watch it appear.
3713
+ </div>
3714
+ `;
3715
+ }
3716
+ if (q) {
3717
+ return html`
3718
+ <div class="cpk-ml__empty">
3719
+ No memories match &ldquo;${q}&rdquo;.
3720
+ </div>
3721
+ `;
3722
+ }
3723
+ return html`
3724
+ <div class="cpk-ml__empty">No ${this.kind} memories yet.</div>
3725
+ `;
3726
+ }
3727
+
3728
+ render() {
3729
+ const filtered = this.filtered;
3730
+ const kinds: Array<Exclude<MemoryKindFilter, "all">> = [
3731
+ "topical",
3732
+ "episodic",
3733
+ "operational",
3734
+ ];
3107
3735
 
3108
- <!-- Timestamps -->
3109
- <div class="cpk-tdp__section-title">Timestamps</div>
3110
- <div class="cpk-tdp__row">
3111
- <span class="cpk-tdp__label">Created</span>
3112
- <span class="cpk-tdp__value">${this.fmtTime(metadata?.createdAt)}</span>
3113
- </div>
3114
- <div class="cpk-tdp__row">
3115
- <span class="cpk-tdp__label">Updated</span>
3116
- <span class="cpk-tdp__value">${this.fmtTime(metadata?.updatedAt)}</span>
3117
- </div>
3118
- <div class="cpk-tdp__row">
3119
- <span class="cpk-tdp__label">Duration</span>
3120
- <span class="cpk-tdp__value">${this.duration}</span>
3121
- </div>
3736
+ return html`
3737
+ <div class="cpk-ml">
3738
+ <!-- Search -->
3739
+ <div class="cpk-ml__search">
3740
+ <input
3741
+ type="text"
3742
+ placeholder="Search memories…"
3743
+ .value=${this.search}
3744
+ @input=${this.onSearchInput}
3745
+ class="cpk-ml__search-input"
3746
+ />
3747
+ </div>
3122
3748
 
3123
- <div class="cpk-tdp__divider"></div>
3749
+ <!-- Kind filter -->
3750
+ <div class="cpk-ml__filter" @click=${this.onKindClick}>
3751
+ <button
3752
+ class="cpk-ml__filter-seg ${this.kind === "all" ? "cpk-ml__filter-seg--active" : ""}"
3753
+ data-kind="all"
3754
+ >
3755
+ All<span class="cpk-ml__filter-count">${this.searchFiltered.length}</span>
3756
+ </button>
3757
+ ${kinds.map(
3758
+ (k) => html`
3759
+ <button
3760
+ class="cpk-ml__filter-seg ${this.kind === k ? "cpk-ml__filter-seg--active" : ""}"
3761
+ data-kind="${k}"
3762
+ >
3763
+ ${k}<span class="cpk-ml__filter-count">${this.countForKind(k)}</span>
3764
+ </button>
3765
+ `,
3766
+ )}
3767
+ </div>
3124
3768
 
3125
- <!-- Activity -->
3126
- <div class="cpk-tdp__section-title">Activity</div>
3127
- <div class="cpk-tdp__row">
3128
- <span class="cpk-tdp__label">Messages</span>
3129
- <span class="cpk-tdp__value">${counts.messages}</span>
3130
- </div>
3131
- <div class="cpk-tdp__row">
3132
- <span class="cpk-tdp__label">Tool calls</span>
3133
- <span class="cpk-tdp__value">${counts.toolCalls}</span>
3134
- </div>
3135
- <div class="cpk-tdp__row">
3136
- <span class="cpk-tdp__label">AG-UI events</span>
3137
- <span class="cpk-tdp__value">${this.activeEvents.length}</span>
3769
+ <!-- Memory list -->
3770
+ <div class="cpk-ml__list">
3771
+ ${filtered.map(
3772
+ (m) => html`
3773
+ <div class="cpk-ml__card">
3774
+ <div class="cpk-ml__card-badges">
3775
+ ${this.renderKindBadge(m.kind)}
3776
+ <span class="cpk-ml__scope-badge">${m.scope}</span>
3777
+ </div>
3778
+ <div class="cpk-ml__content">${m.content}</div>
3779
+ <div class="cpk-ml__footer">
3780
+ <span class="cpk-ml__footer-threads"
3781
+ >${m.sourceThreadIds.length} source thread${m.sourceThreadIds.length === 1 ? "" : "s"}</span
3782
+ >
3783
+ <span class="cpk-ml__footer-id">${this.shortId(m.id)}</span>
3784
+ </div>
3785
+ </div>
3786
+ `,
3787
+ )}
3788
+ ${filtered.length === 0 ? this.renderEmpty() : nothing}
3789
+ </div>
3138
3790
  </div>
3139
3791
  `;
3140
3792
  }
@@ -3154,6 +3806,9 @@ if (!customElements.get(THREAD_INSPECTOR_TAG)) {
3154
3806
  if (!customElements.get("cpk-thread-details")) {
3155
3807
  customElements.define("cpk-thread-details", ɵCpkThreadDetails);
3156
3808
  }
3809
+ if (!customElements.get("cpk-memory-list")) {
3810
+ customElements.define("cpk-memory-list", CpkMemoryList);
3811
+ }
3157
3812
 
3158
3813
  export class WebInspectorElement extends LitElement {
3159
3814
  static properties = {
@@ -3164,6 +3819,25 @@ export class WebInspectorElement extends LitElement {
3164
3819
  private _core: CopilotKitCore | null = null;
3165
3820
  private coreSubscriber: CopilotKitCoreSubscriber | null = null;
3166
3821
  private coreUnsubscribe: (() => void) | null = null;
3822
+ private _memories: Memory[] = [];
3823
+ private _memoriesLoading = false;
3824
+ private _memoriesError: Error | null = null;
3825
+ private _memoriesAvailable = true;
3826
+ // Realtime-connection health, independent of `_memoriesAvailable` (the REST
3827
+ // list route). Drives the "live" indicator: only "connected" shows "live".
3828
+ private _memoriesRealtimeStatus: MemoryRealtimeStatus = "connecting";
3829
+ private _memoryUnsub: (() => void) | null = null;
3830
+ // Lazy-subscription guard. The memory store is created + started + opens
3831
+ // realtime the first time `core.getMemoryStore()` is called, so we defer
3832
+ // that call until the user actually activates the Memories tab (see
3833
+ // `ensureMemorySubscription`). This flag prevents a repeated tab click from
3834
+ // double-subscribing; `detachFromCore` resets it so a later attach + tab
3835
+ // activation re-subscribes cleanly.
3836
+ private _memorySubscribed = false;
3837
+ // True when the attached core predates `getMemoryStore` (older @copilotkit
3838
+ // SDK). Distinct from `_memoriesAvailable` (memory not enabled on an
3839
+ // otherwise-current deployment) so the teaser can show upgrade-the-SDK copy.
3840
+ private _memoryStoreUnsupported = false;
3167
3841
  private runtimeStatus: CopilotKitCoreRuntimeConnectionStatus | null = null;
3168
3842
  private coreProperties: Readonly<Record<string, unknown>> = {};
3169
3843
  private lastCoreError: {
@@ -3341,6 +4015,7 @@ export class WebInspectorElement extends LitElement {
3341
4015
  label: "Threads",
3342
4016
  icon: "MessageSquare" as LucideIconName,
3343
4017
  },
4018
+ { key: "memories", label: "Learning", icon: "Brain" as LucideIconName },
3344
4019
  ];
3345
4020
  }
3346
4021
 
@@ -3382,6 +4057,17 @@ export class WebInspectorElement extends LitElement {
3382
4057
  };
3383
4058
  }
3384
4059
 
4060
+ private getMemoriesTelemetryProps(): InspectorMemoryTelemetryProps {
4061
+ const distinctId = !this.core?.telemetryDisabled
4062
+ ? getTelemetryDistinctIdForUrl()
4063
+ : null;
4064
+ return {
4065
+ posthog_distinct_id: distinctId ?? undefined,
4066
+ memory_count: this._memories.length,
4067
+ available: this._memoriesAvailable,
4068
+ };
4069
+ }
4070
+
3385
4071
  private getIntelligenceSignupUrl(): string {
3386
4072
  return this.appendRefParam(INTELLIGENCE_SIGNUP_URL, "cpk-inspector");
3387
4073
  }
@@ -3608,6 +4294,91 @@ export class WebInspectorElement extends LitElement {
3608
4294
  if (core.context) {
3609
4295
  this.contextStore = this.normalizeContextStore(core.context);
3610
4296
  }
4297
+
4298
+ // NOTE: the memory store is intentionally NOT touched here. Calling
4299
+ // `core.getMemoryStore()` lazily creates + `.start()`s the store and opens
4300
+ // a realtime connection, so merely attaching the inspector would spin up a
4301
+ // memory store + realtime even in apps that never use memory. Instead, the
4302
+ // store is created + subscribed on first Memories-tab activation via
4303
+ // `ensureMemorySubscription` (user-initiated, acceptable). Attaching the
4304
+ // inspector creates nothing.
4305
+ //
4306
+ // Exception: if the Memories tab is ALREADY the active tab when core is
4307
+ // wired (e.g. core attaches after `firstUpdated` restored a persisted
4308
+ // `selectedMenu: "memories"`), subscribe now so the live store + realtime
4309
+ // status paint instead of the stuck defaults. This preserves INSP-1 (no
4310
+ // unconditional subscribe on attach) because it is gated on the active tab,
4311
+ // and is safe to call when already subscribed — `ensureMemorySubscription`
4312
+ // early-returns on `_memorySubscribed`.
4313
+ if (this.selectedMenu === "memories") {
4314
+ this.ensureMemorySubscription();
4315
+ }
4316
+ }
4317
+
4318
+ /**
4319
+ * Lazily subscribes to the singleton memory store the first time the user
4320
+ * activates the Memories tab. This is deferred out of `attachToCore` because
4321
+ * `core.getMemoryStore()` is what creates + starts the store and opens
4322
+ * realtime — doing it on attach would start memory for apps that never use
4323
+ * it. Idempotent: repeated tab activations are guarded by
4324
+ * `_memorySubscribed`. On an older @copilotkit/core without `getMemoryStore`,
4325
+ * records the unsupported state so the teaser can guide an SDK upgrade.
4326
+ */
4327
+ private ensureMemorySubscription(): void {
4328
+ if (this._memorySubscribed) {
4329
+ return;
4330
+ }
4331
+ const core = this._core;
4332
+ if (!core) {
4333
+ return;
4334
+ }
4335
+
4336
+ // Guard like getThreadStores: older @copilotkit/core has no getMemoryStore.
4337
+ // When absent, flag the unsupported state so the teaser shows upgrade copy
4338
+ // instead of throwing a TypeError that would break the entire inspector.
4339
+ if (typeof core.getMemoryStore !== "function") {
4340
+ this._memoryStoreUnsupported = true;
4341
+ this._memoriesAvailable = false;
4342
+ this.requestUpdate();
4343
+ return;
4344
+ }
4345
+
4346
+ this._memorySubscribed = true;
4347
+ this._memoryStoreUnsupported = false;
4348
+
4349
+ // First touch of getMemoryStore() — creates + starts the store, opens realtime.
4350
+ const memoryStore = core.getMemoryStore();
4351
+ const ms = memoryStore.getState();
4352
+ this._memories = ɵselectMemories(ms);
4353
+ this._memoriesLoading = ɵselectMemoriesIsLoading(ms);
4354
+ this._memoriesError = ɵselectMemoriesError(ms);
4355
+ this._memoriesAvailable = ɵselectMemoriesAvailable(ms);
4356
+ this._memoriesRealtimeStatus = ɵselectMemoriesRealtimeStatus(ms);
4357
+ const memSubs = [
4358
+ memoryStore.select(ɵselectMemories).subscribe((v) => {
4359
+ this._memories = v;
4360
+ this.requestUpdate();
4361
+ }),
4362
+ memoryStore.select(ɵselectMemoriesIsLoading).subscribe((v) => {
4363
+ this._memoriesLoading = v;
4364
+ this.requestUpdate();
4365
+ }),
4366
+ memoryStore.select(ɵselectMemoriesError).subscribe((v) => {
4367
+ this._memoriesError = v;
4368
+ this.requestUpdate();
4369
+ }),
4370
+ memoryStore.select(ɵselectMemoriesAvailable).subscribe((v) => {
4371
+ this._memoriesAvailable = v;
4372
+ this.requestUpdate();
4373
+ }),
4374
+ // Group E — realtime connection health.
4375
+ memoryStore.select(ɵselectMemoriesRealtimeStatus).subscribe((v) => {
4376
+ this._memoriesRealtimeStatus = v;
4377
+ this.requestUpdate();
4378
+ }),
4379
+ ];
4380
+ this._memoryUnsub = () => memSubs.forEach((s) => s.unsubscribe());
4381
+ this.requestUpdate();
3611
4382
  }
3612
4383
 
3613
4384
  private detachFromCore(): void {
@@ -3615,6 +4386,17 @@ export class WebInspectorElement extends LitElement {
3615
4386
  this.coreUnsubscribe();
3616
4387
  this.coreUnsubscribe = null;
3617
4388
  }
4389
+ this._memoryUnsub?.();
4390
+ this._memoryUnsub = null;
4391
+ this._memories = [];
4392
+ this._memoriesLoading = false;
4393
+ this._memoriesError = null;
4394
+ this._memoriesAvailable = true;
4395
+ this._memoriesRealtimeStatus = "connecting";
4396
+ // Reset the lazy-subscription guards so a later attach + Memories-tab
4397
+ // activation re-subscribes (and re-evaluates SDK support) cleanly.
4398
+ this._memorySubscribed = false;
4399
+ this._memoryStoreUnsupported = false;
3618
4400
  this.coreSubscriber = null;
3619
4401
  this.runtimeStatus = null;
3620
4402
  this.lastCoreError = null;
@@ -5161,6 +5943,18 @@ ${argsString}</pre
5161
5943
 
5162
5944
  this.hydrateStateFromStorage();
5163
5945
 
5946
+ // `hydrateStateFromStorage` may have restored `selectedMenu: "memories"`.
5947
+ // The memory subscription is normally created on a Memories-tab CLICK via
5948
+ // `handleMenuSelect`, which never fires when the tab boots already active —
5949
+ // leaving the realtime indicator stuck on the default "connecting" and the
5950
+ // list empty until the user toggles tabs. Subscribe now when the Memories
5951
+ // tab is the active tab. Gated on the active tab to preserve INSP-1 (no
5952
+ // unconditional subscribe), and safe if core is not yet attached or already
5953
+ // subscribed — `ensureMemorySubscription` early-returns in both cases.
5954
+ if (this.selectedMenu === "memories") {
5955
+ this.ensureMemorySubscription();
5956
+ }
5957
+
5164
5958
  // Apply docking styles if open and docked (skip transition on initial load)
5165
5959
  if (this.isOpen && this.dockMode !== "floating") {
5166
5960
  this.applyDockStyles(true);
@@ -6618,6 +7412,10 @@ ${argsString}</pre
6618
7412
  return this.renderThreadsView();
6619
7413
  }
6620
7414
 
7415
+ if (this.selectedMenu === "memories") {
7416
+ return this.renderMemoriesView();
7417
+ }
7418
+
6621
7419
  if (this.selectedMenu === "settings") {
6622
7420
  return this.renderSettingsPanel();
6623
7421
  }
@@ -7065,6 +7863,346 @@ ${argsString}</pre
7065
7863
  `;
7066
7864
  }
7067
7865
 
7866
+ /**
7867
+ * Renders the realtime-connection indicator in the memory-store header.
7868
+ * Only `"connected"` shows the live (green-dot) state; `"connecting"` shows a
7869
+ * muted amber "reconnecting" and `"unavailable"` a muted grey "offline", so
7870
+ * the indicator never claims "live" over a frozen snapshot once the realtime
7871
+ * socket has permanently given up.
7872
+ */
7873
+ private renderMemoryRealtimeIndicator() {
7874
+ const status = this._memoriesRealtimeStatus;
7875
+ const connected = status === "connected";
7876
+ const dotColor = connected
7877
+ ? "#22c55e"
7878
+ : status === "connecting"
7879
+ ? "#f59e0b"
7880
+ : "#9ca3af";
7881
+ const label =
7882
+ status === "connected"
7883
+ ? "live"
7884
+ : status === "connecting"
7885
+ ? "reconnecting"
7886
+ : "offline";
7887
+ return html`
7888
+ <span
7889
+ style="
7890
+ display: inline-flex;
7891
+ align-items: center;
7892
+ gap: 4px;
7893
+ font-size: 10px;
7894
+ font-weight: 500;
7895
+ color: ${connected ? "#57575b" : "#838389"};
7896
+ "
7897
+ >
7898
+ <span
7899
+ style="
7900
+ display: inline-block;
7901
+ width: 6px;
7902
+ height: 6px;
7903
+ border-radius: 50%;
7904
+ background: ${dotColor};
7905
+ "
7906
+ ></span>
7907
+ ${label}
7908
+ </span>
7909
+ `;
7910
+ }
7911
+
7912
+ private renderMemoriesView() {
7913
+ // 1. Locked teaser — intelligence not configured or memories not available.
7914
+ if (!this.core?.intelligence || !this._memoriesAvailable) {
7915
+ return html`
7916
+ <div
7917
+ style="
7918
+ position: relative;
7919
+ height: 100%;
7920
+ display: flex;
7921
+ align-items: center;
7922
+ justify-content: center;
7923
+ padding: 32px;
7924
+ overflow: hidden;
7925
+ background: #ffffff;
7926
+ "
7927
+ >
7928
+ ${this.renderThreadsLockedBackgroundMockup()}
7929
+ <div
7930
+ aria-hidden="true"
7931
+ style="
7932
+ position: absolute;
7933
+ inset: 0;
7934
+ pointer-events: none;
7935
+ background:
7936
+ radial-gradient(circle at center, rgba(255,255,255,0.9) 0, rgba(255,255,255,0.78) 24%, rgba(255,255,255,0.34) 48%, rgba(255,255,255,0.56) 100%);
7937
+ "
7938
+ ></div>
7939
+ <div
7940
+ style="
7941
+ position: relative;
7942
+ z-index: 1;
7943
+ max-width: 440px;
7944
+ text-align: center;
7945
+ color: #57575b;
7946
+ "
7947
+ >
7948
+ <div
7949
+ aria-hidden="true"
7950
+ style="
7951
+ margin: 0 auto 18px;
7952
+ display: flex;
7953
+ justify-content: center;
7954
+ "
7955
+ >
7956
+ <div
7957
+ style="
7958
+ display: flex;
7959
+ height: 44px;
7960
+ width: 44px;
7961
+ align-items: center;
7962
+ justify-content: center;
7963
+ border: 1px solid #dfd6fb;
7964
+ border-radius: 8px;
7965
+ background: #eee6fe;
7966
+ color: #57575b;
7967
+ box-shadow: 0 8px 18px rgba(87, 87, 91, 0.14);
7968
+ "
7969
+ >
7970
+ ${this.renderIcon("Lock")}
7971
+ </div>
7972
+ </div>
7973
+ <h2
7974
+ style="
7975
+ margin: 0 0 8px;
7976
+ font-size: 16px;
7977
+ line-height: 1.35;
7978
+ font-weight: 600;
7979
+ color: #010507;
7980
+ "
7981
+ >
7982
+ Long-term memory
7983
+ </h2>
7984
+ <p
7985
+ style="
7986
+ margin: 0 auto 18px;
7987
+ max-width: 380px;
7988
+ font-size: 13px;
7989
+ line-height: 1.55;
7990
+ color: #57575b;
7991
+ "
7992
+ >
7993
+ ${
7994
+ this._memoryStoreUnsupported
7995
+ ? "Long-term memory isn't available in this version of the @copilotkit SDK. Upgrade @copilotkit/core (and @copilotkit/react) to a version that supports memory."
7996
+ : "Long-term memory isn't enabled on this deployment."
7997
+ }
7998
+ </p>
7999
+ <div
8000
+ style="
8001
+ display: flex;
8002
+ flex-wrap: wrap;
8003
+ justify-content: center;
8004
+ gap: 8px;
8005
+ "
8006
+ >
8007
+ <a
8008
+ href=${this.getTalkToEngineerUrl()}
8009
+ target="_blank"
8010
+ rel="noopener"
8011
+ style="
8012
+ display: inline-flex;
8013
+ min-height: 34px;
8014
+ align-items: center;
8015
+ justify-content: center;
8016
+ gap: 6px;
8017
+ border-radius: 6px;
8018
+ background: #010507;
8019
+ padding: 8px 12px;
8020
+ font-size: 12px;
8021
+ font-weight: 600;
8022
+ color: #ffffff;
8023
+ text-decoration: none;
8024
+ "
8025
+ @click=${this.handleThreadsTalkToEngineerClick}
8026
+ >
8027
+ Talk to an Engineer
8028
+ </a>
8029
+ <a
8030
+ href=${this.getIntelligenceSignupUrl()}
8031
+ target="_blank"
8032
+ rel="noopener"
8033
+ style="
8034
+ display: inline-flex;
8035
+ min-height: 34px;
8036
+ align-items: center;
8037
+ justify-content: center;
8038
+ gap: 6px;
8039
+ border-radius: 6px;
8040
+ border: 1px solid #dbdbe5;
8041
+ background: #ffffff;
8042
+ padding: 8px 12px;
8043
+ font-size: 12px;
8044
+ font-weight: 600;
8045
+ color: #57575b;
8046
+ text-decoration: none;
8047
+ "
8048
+ @click=${this.handleThreadsIntelligenceSignupClick}
8049
+ >
8050
+ Sign up for Intelligence
8051
+ </a>
8052
+ </div>
8053
+ </div>
8054
+ </div>
8055
+ `;
8056
+ }
8057
+
8058
+ // 2. Full-screen error — only for a snapshot-LOAD failure (no memories
8059
+ // loaded). A mutation failure that arrives while memories are already on
8060
+ // screen must NOT blank the list; it is surfaced inline below (step 4).
8061
+ if (this._memoriesError && this._memories.length === 0) {
8062
+ return html`
8063
+ <div
8064
+ style="
8065
+ display: flex;
8066
+ height: 100%;
8067
+ flex-direction: column;
8068
+ align-items: center;
8069
+ justify-content: center;
8070
+ gap: 8px;
8071
+ color: #838389;
8072
+ "
8073
+ >
8074
+ <svg
8075
+ width="24"
8076
+ height="24"
8077
+ viewBox="0 0 24 24"
8078
+ fill="none"
8079
+ stroke="#c0333a"
8080
+ stroke-width="1.5"
8081
+ stroke-linecap="round"
8082
+ stroke-linejoin="round"
8083
+ >
8084
+ <circle cx="12" cy="12" r="10" />
8085
+ <line x1="12" y1="8" x2="12" y2="12" />
8086
+ <line x1="12" y1="16" x2="12.01" y2="16" />
8087
+ </svg>
8088
+ <span style="font-size: 13px; color: #c0333a;">
8089
+ Failed to load memories
8090
+ </span>
8091
+ <span
8092
+ style="
8093
+ max-width: 320px;
8094
+ text-align: center;
8095
+ font-size: 11px;
8096
+ line-height: 1.5;
8097
+ color: #c0333a;
8098
+ "
8099
+ >
8100
+ ${this._memoriesError.message}
8101
+ </span>
8102
+ </div>
8103
+ `;
8104
+ }
8105
+
8106
+ // 3. Initial loading placeholder (no memories yet to show behind it).
8107
+ if (this._memoriesLoading && this._memories.length === 0) {
8108
+ return html`
8109
+ <div
8110
+ style="
8111
+ display: flex;
8112
+ height: 100%;
8113
+ flex-direction: column;
8114
+ align-items: center;
8115
+ justify-content: center;
8116
+ gap: 8px;
8117
+ color: #838389;
8118
+ "
8119
+ >
8120
+ <svg
8121
+ width="24"
8122
+ height="24"
8123
+ viewBox="0 0 24 24"
8124
+ fill="none"
8125
+ stroke="#c0c0c8"
8126
+ stroke-width="1.5"
8127
+ stroke-linecap="round"
8128
+ stroke-linejoin="round"
8129
+ >
8130
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
8131
+ </svg>
8132
+ <span style="font-size: 13px">Loading memories…</span>
8133
+ </div>
8134
+ `;
8135
+ }
8136
+
8137
+ // 4. Content — header + memory list.
8138
+ return html`
8139
+ <div style="display:flex;height:100%;overflow:hidden;flex-direction:column;">
8140
+ <div class="cpk-section-header" style="display:flex;align-items:center;justify-content:space-between;">
8141
+ <h4>Learning</h4>
8142
+ <div style="display:flex;align-items:center;gap:6px;">
8143
+ ${this.renderMemoryRealtimeIndicator()}
8144
+ <span
8145
+ style="
8146
+ font-size: 11px;
8147
+ font-weight: 500;
8148
+ color: #57575b;
8149
+ background: rgba(0,0,0,0.07);
8150
+ border-radius: 9999px;
8151
+ padding: 1px 7px;
8152
+ "
8153
+ >
8154
+ ${this._memories.length}
8155
+ </span>
8156
+ </div>
8157
+ </div>
8158
+ ${
8159
+ this._memoriesError
8160
+ ? html`
8161
+ <div
8162
+ role="alert"
8163
+ style="
8164
+ display: flex;
8165
+ align-items: flex-start;
8166
+ gap: 8px;
8167
+ flex-shrink: 0;
8168
+ border-bottom: 1px solid #f1c7c9;
8169
+ background: #fdf3f3;
8170
+ padding: 8px 12px;
8171
+ color: #c0333a;
8172
+ font-size: 12px;
8173
+ line-height: 1.45;
8174
+ "
8175
+ >
8176
+ <svg
8177
+ width="16"
8178
+ height="16"
8179
+ viewBox="0 0 24 24"
8180
+ fill="none"
8181
+ stroke="#c0333a"
8182
+ stroke-width="1.5"
8183
+ stroke-linecap="round"
8184
+ stroke-linejoin="round"
8185
+ style="flex-shrink:0;margin-top:1px;"
8186
+ >
8187
+ <circle cx="12" cy="12" r="10" />
8188
+ <line x1="12" y1="8" x2="12" y2="12" />
8189
+ <line x1="12" y1="16" x2="12.01" y2="16" />
8190
+ </svg>
8191
+ <span>Action failed: ${this._memoriesError.message}</span>
8192
+ </div>
8193
+ `
8194
+ : nothing
8195
+ }
8196
+ <div style="flex:1;min-height:0;overflow:hidden;">
8197
+ <cpk-memory-list
8198
+ style="height:100%;"
8199
+ .memories=${this._memories}
8200
+ ></cpk-memory-list>
8201
+ </div>
8202
+ </div>
8203
+ `;
8204
+ }
8205
+
7068
8206
  private renderThreadsView() {
7069
8207
  if (!this.areThreadEndpointsAvailable()) {
7070
8208
  return this.renderThreadsLockedView();
@@ -7884,6 +9022,16 @@ ${prettyEvent}</pre
7884
9022
  this.autoSelectLatestThread();
7885
9023
  }
7886
9024
 
9025
+ if (key === "memories") {
9026
+ // Lazily create + subscribe to the memory store on first activation. This
9027
+ // is the only place that touches getMemoryStore(), so the store/realtime
9028
+ // are never started just by attaching the inspector.
9029
+ this.ensureMemorySubscription();
9030
+ if (previousMenu !== "memories" && !this.core?.telemetryDisabled) {
9031
+ trackMemoriesTabClicked(this.getMemoriesTelemetryProps());
9032
+ }
9033
+ }
9034
+
7887
9035
  if (key === "ag-ui-events" || key === "agents") {
7888
9036
  requestAnimationFrame(() => {
7889
9037
  const scroller = this.shadowRoot?.getElementById("cpk-main-scroll");