@kodaris/krubble-app-components 1.0.75 → 1.0.77

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/chatbot.js CHANGED
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { LitElement, html, css, nothing } from 'lit';
8
8
  import { customElement, property, state, query } from 'lit/decorators.js';
9
9
  import { classMap } from 'lit/directives/class-map.js';
10
+ import { styleMap } from 'lit/directives/style-map.js';
10
11
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
11
12
  import { repeat } from 'lit/directives/repeat.js';
12
13
  import { marked } from 'marked';
@@ -71,6 +72,10 @@ let KRChatbot = class KRChatbot extends LitElement {
71
72
  this._dragStartTop = 0;
72
73
  this._resizeStartW = 0;
73
74
  this._resizeStartH = 0;
75
+ /** Cursor X at debug-drawer resize start. */
76
+ this._debugResizeStartX = 0;
77
+ /** Debug-drawer width at resize start. */
78
+ this._debugResizeStartW = 0;
74
79
  /** Counter for depth-aware dragenter/dragleave tracking. */
75
80
  this._dragDepth = 0;
76
81
  /** Monotonic id source for pending attachments. */
@@ -143,6 +148,39 @@ let KRChatbot = class KRChatbot extends LitElement {
143
148
  this._inputValue = '';
144
149
  this._isStreaming = false;
145
150
  this._error = '';
151
+ /**
152
+ * User-facing context warning text (`CONVERSATION_TOKEN_WARNING`). Shown as a
153
+ * dismissible banner in all variants. Carries only friendly guidance, no numbers.
154
+ */
155
+ this._contextWarning = '';
156
+ /**
157
+ * True once the conversation hit the context-window limit
158
+ * (`CONVERSATION_LIMIT_REACHED`, or `contextExceeded` on load). Drives a
159
+ * blocking "start a new conversation" notice. User-facing, all variants.
160
+ */
161
+ this._contextLimitReached = false;
162
+ /** Server-provided limit text (`CONVERSATION_LIMIT_REACHED`), if any. */
163
+ this._contextLimitMessage = '';
164
+ /**
165
+ * INTERNAL-ONLY latest telemetry snapshot (last-value-wins). Populated from
166
+ * `TOKEN_USAGE` events during streaming and hydrated from the conversation
167
+ * DTO on load. Only surfaced in the `inline` variant's debug panel.
168
+ */
169
+ this._telemetry = null;
170
+ /**
171
+ * INTERNAL-ONLY per-iteration telemetry log for the debug panel's verbose
172
+ * table. Append-only within a turn; reset at the start of each send. Each row
173
+ * may be augmented with its `CACHE_CHECKPOINT` info.
174
+ */
175
+ this._iterationLog = [];
176
+ /** Latest cache-checkpoint info keyed by iteration, for the verbose table. */
177
+ this._cacheCheckpoints = {};
178
+ /** Whether the internal debug panel is expanded (off by default). */
179
+ this._debugPanelOpen = false;
180
+ /** Current width of the debug drawer in px (user-resizable, persisted). */
181
+ this._debugDrawerWidth = 430;
182
+ /** True while the user is dragging the debug drawer's resize handle. */
183
+ this._isDebugResizing = false;
146
184
  /** Images staged in the composer, before the message is sent. */
147
185
  this._pendingInternalFiles = [];
148
186
  /** True while a file is being dragged over the panel (shows the drop overlay). */
@@ -226,6 +264,25 @@ let KRChatbot = class KRChatbot extends LitElement {
226
264
  document.removeEventListener('mouseup', this._handleMouseUp);
227
265
  this._saveState();
228
266
  };
267
+ this._handleDebugResizeMouseMove = (e) => {
268
+ if (!this._isDebugResizing) {
269
+ return;
270
+ }
271
+ // Handle is on the drawer's left edge; dragging left (negative dx) widens it.
272
+ const dx = e.clientX - this._debugResizeStartX;
273
+ const minW = 300;
274
+ // Never let the chat column shrink below 300px — cap the drawer at the
275
+ // container width minus that reserved chat width.
276
+ const minChatWidth = 300;
277
+ const maxW = Math.max(minW, this.offsetWidth - minChatWidth);
278
+ this._debugDrawerWidth = Math.max(minW, Math.min(this._debugResizeStartW - dx, maxW));
279
+ };
280
+ this._handleDebugResizeMouseUp = () => {
281
+ this._isDebugResizing = false;
282
+ document.removeEventListener('mousemove', this._handleDebugResizeMouseMove);
283
+ document.removeEventListener('mouseup', this._handleDebugResizeMouseUp);
284
+ this._saveState();
285
+ };
229
286
  }
230
287
  // --- Lifecycle ---
231
288
  connectedCallback() {
@@ -247,10 +304,10 @@ let KRChatbot = class KRChatbot extends LitElement {
247
304
  }
248
305
  }).catch(() => { });
249
306
  // Inline mode fills its container and is always open — the floating widget's
250
- // toggle/drag/resize state (including any persisted layout) doesn't apply.
307
+ // toggle/drag/resize state doesn't apply, but the composer message and debug
308
+ // drawer state (restored inside _restoreState) do.
251
309
  if (this.variant === 'inline') {
252
310
  this.opened = true;
253
- return;
254
311
  }
255
312
  this._restoreState();
256
313
  }
@@ -440,7 +497,12 @@ let KRChatbot = class KRChatbot extends LitElement {
440
497
  }
441
498
  _saveState() {
442
499
  try {
443
- const state = { opened: String(this.opened) };
500
+ const state = {
501
+ opened: String(this.opened),
502
+ message: this._inputValue,
503
+ debugPanelOpen: String(this._debugPanelOpen),
504
+ debugDrawerWidth: String(this._debugDrawerWidth),
505
+ };
444
506
  if (this._panelEl) {
445
507
  const s = this._panelEl.style;
446
508
  if (s.left) {
@@ -470,7 +532,17 @@ let KRChatbot = class KRChatbot extends LitElement {
470
532
  return;
471
533
  }
472
534
  const state = JSON.parse(raw);
473
- if (state.opened === 'true') {
535
+ // Draft text and the debug drawer are persisted for all variants.
536
+ if (typeof state.message === 'string') {
537
+ this._inputValue = state.message;
538
+ }
539
+ this._debugPanelOpen = state.debugPanelOpen === 'true';
540
+ const w = Number(state.debugDrawerWidth);
541
+ if (w >= 300) {
542
+ this._debugDrawerWidth = w;
543
+ }
544
+ // The floating widget's open/layout state doesn't apply to inline mode.
545
+ if (this.variant !== 'inline' && state.opened === 'true') {
474
546
  this.opened = true;
475
547
  // Apply layout after first render
476
548
  this.updateComplete.then(() => this._applyStoredLayout());
@@ -505,6 +577,7 @@ let KRChatbot = class KRChatbot extends LitElement {
505
577
  }
506
578
  _handleInputChange(e) {
507
579
  this._inputValue = e.target.value;
580
+ this._saveState();
508
581
  // Auto-resize textarea
509
582
  const textarea = e.target;
510
583
  textarea.style.height = 'auto';
@@ -587,15 +660,26 @@ let KRChatbot = class KRChatbot extends LitElement {
587
660
  this._skillRequestId++;
588
661
  const requestId = this._skillRequestId;
589
662
  const trimmed = query.trim();
590
- // multi_match with an empty string matches nothing, so an empty query falls
591
- // back to match_all to show everything.
663
+ // An empty query matches everything; otherwise substring-match the query
664
+ // against name/code/description. We use case-insensitive wildcard queries on
665
+ // the `.raw` keyword subfields so "ord" matches anywhere in "Create Order"
666
+ // (a plain match/multi_match only matches whole tokens). Escape the wildcard
667
+ // specials so user input is treated literally, then wrap with `*...*`.
592
668
  let must = { match_all: {} };
593
669
  if (trimmed) {
670
+ const pattern = `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`;
671
+ const value = {
672
+ value: pattern,
673
+ case_insensitive: true,
674
+ };
594
675
  must = {
595
- multi_match: {
596
- query: trimmed,
597
- fields: ['name^3', 'description^2', 'instructions'],
598
- type: 'best_fields',
676
+ bool: {
677
+ should: [
678
+ { wildcard: { 'name.raw': value } },
679
+ { wildcard: { 'code.raw': value } },
680
+ { wildcard: { 'description.raw': value } },
681
+ ],
682
+ minimum_should_match: 1,
599
683
  },
600
684
  };
601
685
  }
@@ -638,6 +722,7 @@ let KRChatbot = class KRChatbot extends LitElement {
638
722
  /** Insert `/code ` into the composer and focus it, ready for the message. */
639
723
  _handleSkillSelect(skill) {
640
724
  this._inputValue = `/${skill.code} `;
725
+ this._saveState();
641
726
  this._skillMenuOpened = false;
642
727
  this._skillBrowseOpened = false;
643
728
  this._inputEl?.focus();
@@ -810,6 +895,7 @@ let KRChatbot = class KRChatbot extends LitElement {
810
895
  };
811
896
  this.messages = [...this.messages, userMessage];
812
897
  this._inputValue = '';
898
+ this._saveState();
813
899
  this._error = '';
814
900
  this._userHasScrolledUp = false;
815
901
  // Clear the composer and release its preview object URLs.
@@ -919,6 +1005,14 @@ let KRChatbot = class KRChatbot extends LitElement {
919
1005
  this._error = '';
920
1006
  this._inputValue = '';
921
1007
  this._userHasScrolledUp = false;
1008
+ // Reset conversation-scoped context + telemetry state for the new convo.
1009
+ this._contextWarning = '';
1010
+ this._contextLimitReached = false;
1011
+ this._contextLimitMessage = '';
1012
+ this._telemetry = null;
1013
+ this._iterationLog = [];
1014
+ this._cacheCheckpoints = {};
1015
+ this._saveState();
922
1016
  });
923
1017
  }
924
1018
  _handleMessagesScroll(e) {
@@ -934,6 +1028,22 @@ let KRChatbot = class KRChatbot extends LitElement {
934
1028
  _handleErrorDismiss() {
935
1029
  this._error = '';
936
1030
  }
1031
+ _handleContextWarningDismiss() {
1032
+ this._contextWarning = '';
1033
+ }
1034
+ _handleToggleDebugPanel() {
1035
+ this._debugPanelOpen = !this._debugPanelOpen;
1036
+ this._saveState();
1037
+ }
1038
+ _handleDebugResizeMouseDown(e) {
1039
+ this._isDebugResizing = true;
1040
+ this._debugResizeStartX = e.clientX;
1041
+ this._debugResizeStartW = this._debugDrawerWidth;
1042
+ e.preventDefault();
1043
+ e.stopPropagation();
1044
+ document.addEventListener('mousemove', this._handleDebugResizeMouseMove);
1045
+ document.addEventListener('mouseup', this._handleDebugResizeMouseUp);
1046
+ }
937
1047
  _readStreamChunk() {
938
1048
  if (!this._reader) {
939
1049
  return;
@@ -997,6 +1107,47 @@ let KRChatbot = class KRChatbot extends LitElement {
997
1107
  else if (data.type === 'ERROR') {
998
1108
  this._handleStreamError(String(data.message || 'Unknown error'));
999
1109
  }
1110
+ else if (data.type === 'CONVERSATION_TOKEN_WARNING') {
1111
+ // User-facing: friendly "conversation is getting long" guidance.
1112
+ // Text only, never sent to the model — render verbatim as a banner.
1113
+ this._contextWarning = String(data.message || '');
1114
+ }
1115
+ else if (data.type === 'CONVERSATION_LIMIT_REACHED') {
1116
+ // User-facing hard stop. Carries finished:true (handled below).
1117
+ // Clear any soft warning so we don't double-message the user.
1118
+ this._contextLimitReached = true;
1119
+ this._contextLimitMessage = String(data.message || '');
1120
+ this._contextWarning = '';
1121
+ }
1122
+ else if (data.type === 'TOKEN_USAGE') {
1123
+ // INTERNAL-ONLY debug telemetry. Update the panel snapshot and the
1124
+ // per-iteration log; never render as a chat bubble.
1125
+ if (data.data) {
1126
+ const usage = data.data;
1127
+ this._telemetry = usage;
1128
+ // A new turn starts at iteration 1 — clear the prior turn's log
1129
+ // only now (not at send), so it stays visible until fresh data
1130
+ // actually arrives rather than blanking out on submit.
1131
+ if (usage.iteration === 1) {
1132
+ this._iterationLog = [usage];
1133
+ this._cacheCheckpoints = {};
1134
+ }
1135
+ else {
1136
+ this._iterationLog = [...this._iterationLog, usage];
1137
+ }
1138
+ }
1139
+ }
1140
+ else if (data.type === 'CACHE_CHECKPOINT') {
1141
+ // INTERNAL-ONLY debug telemetry. Records when/under which mode the
1142
+ // history checkpoint was placed, keyed by iteration for the table.
1143
+ if (data.data) {
1144
+ const checkpoint = data.data;
1145
+ this._cacheCheckpoints = {
1146
+ ...this._cacheCheckpoints,
1147
+ [checkpoint.iteration]: checkpoint,
1148
+ };
1149
+ }
1150
+ }
1000
1151
  if (data.finished) {
1001
1152
  this._isStreaming = false;
1002
1153
  this._reader = null;
@@ -1140,6 +1291,158 @@ let KRChatbot = class KRChatbot extends LitElement {
1140
1291
  }
1141
1292
  return content;
1142
1293
  }
1294
+ /** Compact integer formatting for the debug panel (e.g. 88928 → "88,928"). */
1295
+ _fmtTokens(n) {
1296
+ if (n == null || isNaN(n)) {
1297
+ return '—';
1298
+ }
1299
+ return n.toLocaleString('en-US');
1300
+ }
1301
+ /** Approximate USD formatting for the debug panel (e.g. 0.1434 → "~$0.1434"). */
1302
+ _fmtCost(n) {
1303
+ if (n == null || isNaN(n)) {
1304
+ return '—';
1305
+ }
1306
+ return '~$' + n.toFixed(4);
1307
+ }
1308
+ /**
1309
+ * INTERNAL-ONLY debug telemetry panel. Rendered only in the inline variant
1310
+ * (guarded by the caller) and only when the user has toggled it open. Shows
1311
+ * three scopes from the latest TOKEN_USAGE event — context gauge, this turn,
1312
+ * lifetime — plus a verbose per-iteration table. Never customer-facing.
1313
+ */
1314
+ _renderDebugPanel() {
1315
+ return html `
1316
+ <div
1317
+ class=${classMap({
1318
+ 'debug-drawer': true,
1319
+ 'debug-drawer--open': this._debugPanelOpen,
1320
+ 'debug-drawer--resizing': this._isDebugResizing,
1321
+ })}
1322
+ style=${styleMap({ width: `${this._debugDrawerWidth}px` })}
1323
+ >
1324
+ <div
1325
+ class="debug-drawer__resize"
1326
+ @mousedown=${this._handleDebugResizeMouseDown}
1327
+ ></div>
1328
+ <div class="debug-drawer__header">
1329
+ <span class="debug-drawer__title">Debug</span>
1330
+ <button
1331
+ class="header-action"
1332
+ @click=${this._handleToggleDebugPanel}
1333
+ title="Close debug panel"
1334
+ >
1335
+ <svg
1336
+ xmlns="http://www.w3.org/2000/svg"
1337
+ fill="none"
1338
+ viewBox="0 0 24 24"
1339
+ stroke-width="2"
1340
+ stroke="currentColor"
1341
+ class="header-action-icon"
1342
+ >
1343
+ <path
1344
+ stroke-linecap="round"
1345
+ stroke-linejoin="round"
1346
+ d="M6 18 18 6M6 6l12 12"
1347
+ />
1348
+ </svg>
1349
+ </button>
1350
+ </div>
1351
+ <div class="debug-drawer__body">
1352
+ ${this._telemetry ? html `
1353
+ <div class="debug-section">
1354
+ <div class="debug-section__label">Context</div>
1355
+ <div class="debug-gauge">
1356
+ <div class="debug-gauge__bar">
1357
+ <div
1358
+ class="debug-gauge__fill"
1359
+ style=${styleMap({ width: `${Math.min(100, Math.max(0, this._telemetry.contextUsedPercent || 0))}%` })}
1360
+ ></div>
1361
+ </div>
1362
+ <span class="debug-gauge__text">
1363
+ ${this._fmtTokens(this._telemetry.currentContextTokens)} / ${this._fmtTokens(this._telemetry.contextWindowTokens)}
1364
+ · ${this._telemetry.contextUsedPercent ?? 0}%
1365
+ </span>
1366
+ </div>
1367
+ </div>
1368
+
1369
+ <div class="debug-section">
1370
+ <div class="debug-section__label">This turn</div>
1371
+ <div class="debug-metrics">
1372
+ <span class="debug-metrics__key">Input</span>
1373
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.turnInputTokens)}</span>
1374
+ <span class="debug-metrics__key">Output</span>
1375
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.turnOutputTokens)}</span>
1376
+ <span class="debug-metrics__key">Cache read</span>
1377
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.turnCacheReadTokens)}</span>
1378
+ <span class="debug-metrics__key">Hit rate</span>
1379
+ <span class="debug-metrics__val">${this._telemetry.turnHitRate ?? '—'}%</span>
1380
+ <span class="debug-metrics__key">Cost</span>
1381
+ <span class="debug-metrics__val debug-cost">${this._fmtCost(this._telemetry.turnCostUsd)}</span>
1382
+ </div>
1383
+ </div>
1384
+
1385
+ <div class="debug-section">
1386
+ <div class="debug-section__label">Lifetime</div>
1387
+ <div class="debug-metrics">
1388
+ <span class="debug-metrics__key">Input</span>
1389
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationInputTokens)}</span>
1390
+ <span class="debug-metrics__key">Output</span>
1391
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationOutputTokens)}</span>
1392
+ <span class="debug-metrics__key">Cache read</span>
1393
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationCacheReadTokens)}</span>
1394
+ <span class="debug-metrics__key">Cache write</span>
1395
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationCacheWriteTokens)}</span>
1396
+ <span class="debug-metrics__key">Cost so far</span>
1397
+ <span class="debug-metrics__val debug-cost">${this._fmtCost(this._telemetry.conversationCostUsd)}</span>
1398
+ </div>
1399
+ </div>
1400
+ ` : html `
1401
+ <div class="debug-empty">No telemetry yet — send a message to populate.</div>
1402
+ `}
1403
+
1404
+ ${this._iterationLog.length ? html `
1405
+ <div class="debug-section">
1406
+ <div class="debug-section__label">Iterations (this turn)</div>
1407
+ <div class="debug-iterations-scroll">
1408
+ <table class="debug-iterations">
1409
+ <thead>
1410
+ <tr>
1411
+ <th>#</th>
1412
+ <th>In</th>
1413
+ <th>Out</th>
1414
+ <th>Cache R</th>
1415
+ <th>Cache W</th>
1416
+ <th>Hit</th>
1417
+ <th>Cost</th>
1418
+ <th>Ckpt</th>
1419
+ </tr>
1420
+ </thead>
1421
+ <tbody>
1422
+ ${this._iterationLog.map((it) => {
1423
+ const ckpt = this._cacheCheckpoints[it.iteration];
1424
+ return html `
1425
+ <tr>
1426
+ <td>${it.iteration}</td>
1427
+ <td>${this._fmtTokens(it.iterationInput)}</td>
1428
+ <td>${this._fmtTokens(it.iterationOutput)}</td>
1429
+ <td>${this._fmtTokens(it.iterationCacheRead)}</td>
1430
+ <td>${this._fmtTokens(it.iterationCacheWrite)}</td>
1431
+ <td>${it.iterationHitRate ?? '—'}%</td>
1432
+ <td class="debug-cost">${this._fmtCost(it.iterationCostUsd)}</td>
1433
+ <td>${ckpt ? ckpt.mode : '—'}</td>
1434
+ </tr>
1435
+ `;
1436
+ })}
1437
+ </tbody>
1438
+ </table>
1439
+ </div>
1440
+ </div>
1441
+ ` : nothing}
1442
+ </div>
1443
+ </div>
1444
+ `;
1445
+ }
1143
1446
  render() {
1144
1447
  return html `
1145
1448
  ${(this.hideToggle || this.variant === 'inline') ? nothing : html `
@@ -1169,7 +1472,9 @@ let KRChatbot = class KRChatbot extends LitElement {
1169
1472
  class=${classMap({
1170
1473
  'panel': true,
1171
1474
  'panel--opened': this.opened,
1475
+ 'panel--resizing-debug': this._isDebugResizing,
1172
1476
  })}
1477
+ style=${styleMap(this.variant === 'inline' && this._debugPanelOpen ? { paddingRight: `${this._debugDrawerWidth}px` } : {})}
1173
1478
  @dragenter=${this._handleDragEnter}
1174
1479
  @dragover=${this._handleDragOver}
1175
1480
  @dragleave=${this._handleDragLeave}
@@ -1242,6 +1547,31 @@ let KRChatbot = class KRChatbot extends LitElement {
1242
1547
  </div>
1243
1548
  </div>
1244
1549
  <div class="header-actions">
1550
+ ${this.variant === 'inline' ? html `
1551
+ <button
1552
+ class=${classMap({
1553
+ 'header-action': true,
1554
+ 'header-action--active': this._debugPanelOpen,
1555
+ })}
1556
+ @click=${this._handleToggleDebugPanel}
1557
+ title="Debug telemetry"
1558
+ >
1559
+ <svg
1560
+ xmlns="http://www.w3.org/2000/svg"
1561
+ fill="none"
1562
+ viewBox="0 0 24 24"
1563
+ stroke-width="1.5"
1564
+ stroke="currentColor"
1565
+ class="header-action-icon"
1566
+ >
1567
+ <path
1568
+ stroke-linecap="round"
1569
+ stroke-linejoin="round"
1570
+ d="M3.75 3v11.25A2.25 2.25 0 0 0 6 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0 1 18 16.5h-2.25m-7.5 0h7.5m-7.5 0-1 3m8.5-3 1 3m0 0 .5 1.5m-.5-1.5h-9.5m0 0-.5 1.5M9 11.25v1.5M12 9v3.75m3-6v6"
1571
+ />
1572
+ </svg>
1573
+ </button>
1574
+ ` : nothing}
1245
1575
  ${this.messages.length > 0 ? html `
1246
1576
  <button
1247
1577
  class="header-action"
@@ -1406,6 +1736,37 @@ let KRChatbot = class KRChatbot extends LitElement {
1406
1736
  </div>
1407
1737
  ` : nothing}
1408
1738
 
1739
+ ${this._contextLimitReached ? html `
1740
+ <div class="context-limit" role="alert">
1741
+ ${this._contextLimitMessage
1742
+ || 'This conversation has reached its limit. Please start a new one to continue.'}
1743
+ </div>
1744
+ ` : this._contextWarning ? html `
1745
+ <div class="context-warning" role="status">
1746
+ <span>${this._contextWarning}</span>
1747
+ <button
1748
+ class="context-warning-dismiss"
1749
+ title="Dismiss"
1750
+ @click=${this._handleContextWarningDismiss}
1751
+ >
1752
+ <svg
1753
+ xmlns="http://www.w3.org/2000/svg"
1754
+ fill="none"
1755
+ viewBox="0 0 24 24"
1756
+ stroke-width="2"
1757
+ stroke="currentColor"
1758
+ class="context-warning-dismiss-icon"
1759
+ >
1760
+ <path
1761
+ stroke-linecap="round"
1762
+ stroke-linejoin="round"
1763
+ d="M6 18 18 6M6 6l12 12"
1764
+ />
1765
+ </svg>
1766
+ </button>
1767
+ </div>
1768
+ ` : nothing}
1769
+
1409
1770
  <div class="footer">
1410
1771
  ${this._pendingInternalFiles.length ? html `
1411
1772
  <div class="composer-attachments">
@@ -1505,6 +1866,7 @@ let KRChatbot = class KRChatbot extends LitElement {
1505
1866
  </div>
1506
1867
  </div>
1507
1868
  ${this._skillBrowseOpened ? this._renderSkillBrowse() : nothing}
1869
+ ${this.variant === 'inline' ? this._renderDebugPanel() : nothing}
1508
1870
  </div>
1509
1871
  `;
1510
1872
  }
@@ -1686,6 +2048,11 @@ KRChatbot.styles = css `
1686
2048
  color: #000000;
1687
2049
  }
1688
2050
 
2051
+ .header-action--active {
2052
+ background: rgba(0, 0, 0, 0.08);
2053
+ color: #000000;
2054
+ }
2055
+
1689
2056
  .header-action-icon {
1690
2057
  width: 20px;
1691
2058
  height: 20px;
@@ -1715,19 +2082,6 @@ KRChatbot.styles = css `
1715
2082
  margin-right: auto;
1716
2083
  }
1717
2084
 
1718
- .messages::-webkit-scrollbar {
1719
- width: 4px;
1720
- }
1721
-
1722
- .messages::-webkit-scrollbar-track {
1723
- background: transparent;
1724
- }
1725
-
1726
- .messages::-webkit-scrollbar-thumb {
1727
- background: rgba(0, 0, 0, 0.08);
1728
- border-radius: 2px;
1729
- }
1730
-
1731
2085
  .empty {
1732
2086
  flex: 1;
1733
2087
  display: flex;
@@ -1966,6 +2320,238 @@ KRChatbot.styles = css `
1966
2320
  height: 14px;
1967
2321
  }
1968
2322
 
2323
+ /* Context warning banner (user-facing, dismissible, non-blocking) */
2324
+ .context-warning {
2325
+ padding: 8px 16px;
2326
+ background: #fffbeb;
2327
+ color: #92400e;
2328
+ font-size: 13px;
2329
+ display: flex;
2330
+ align-items: center;
2331
+ justify-content: space-between;
2332
+ gap: 8px;
2333
+ flex-shrink: 0;
2334
+ }
2335
+
2336
+ .context-warning-dismiss {
2337
+ background: none;
2338
+ border: none;
2339
+ color: #92400e;
2340
+ cursor: pointer;
2341
+ padding: 2px;
2342
+ display: flex;
2343
+ align-items: center;
2344
+ flex-shrink: 0;
2345
+ }
2346
+
2347
+ .context-warning-dismiss-icon {
2348
+ width: 14px;
2349
+ height: 14px;
2350
+ }
2351
+
2352
+ /* Context-limit notice (user-facing, blocking — steers to a new conversation) */
2353
+ .context-limit {
2354
+ padding: 10px 16px;
2355
+ background: var(--kr-chatbot-error-bg);
2356
+ color: var(--kr-chatbot-error-text);
2357
+ font-size: 13px;
2358
+ line-height: 1.5;
2359
+ flex-shrink: 0;
2360
+ }
2361
+
2362
+ /*
2363
+ * INTERNAL-ONLY debug telemetry drawer. A layout drawer docked to the right
2364
+ * of the inline chat — the chat column shrinks to make room; it does not
2365
+ * overlay. Closed by default (width 0), animates open. Not customer-facing —
2366
+ * token counts, cache internals, and approximate cost estimates for internal
2367
+ * debugging. Styled to match the chatbot's light theme (Inter, design tokens).
2368
+ */
2369
+ /*
2370
+ * The chat column reserves space for the drawer by animating padding-right,
2371
+ * so opening the drawer shrinks the chat (header/messages/composer) rather
2372
+ * than overlaying it. Only applies in inline mode.
2373
+ */
2374
+ :host([variant='inline']) .panel {
2375
+ transition: padding-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2376
+ }
2377
+
2378
+ /* Track the cursor 1:1 while dragging the drawer's resize handle. */
2379
+ :host([variant='inline']) .panel--resizing-debug {
2380
+ transition: none;
2381
+ }
2382
+
2383
+ .debug-drawer {
2384
+ position: absolute;
2385
+ top: 0;
2386
+ right: 0;
2387
+ bottom: 0;
2388
+ background: var(--kr-chatbot-bg);
2389
+ display: flex;
2390
+ flex-direction: column;
2391
+ transform: translateX(100%);
2392
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2393
+ color: #000000;
2394
+ z-index: 5;
2395
+ }
2396
+
2397
+ /* While dragging the resize handle, suppress the slide transition so the
2398
+ drawer tracks the cursor 1:1. */
2399
+ .debug-drawer--resizing {
2400
+ transition: none;
2401
+ }
2402
+
2403
+ .debug-drawer--open {
2404
+ transform: translateX(0);
2405
+ box-shadow: -4px 0 20px rgba(10, 26, 46, 0.07);
2406
+ }
2407
+
2408
+ /* Left-edge grab handle for resizing the drawer. */
2409
+ .debug-drawer__resize {
2410
+ position: absolute;
2411
+ top: 0;
2412
+ left: 0;
2413
+ bottom: 0;
2414
+ width: 10px;
2415
+ cursor: ew-resize;
2416
+ z-index: 10;
2417
+ }
2418
+
2419
+ .debug-drawer__header {
2420
+ display: flex;
2421
+ align-items: center;
2422
+ justify-content: space-between;
2423
+ padding: 0 12px 0 20px;
2424
+ height: 64px;
2425
+ flex-shrink: 0;
2426
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
2427
+ }
2428
+
2429
+ .debug-drawer__title {
2430
+ font-size: 14px;
2431
+ font-weight: 600;
2432
+ color: #000000;
2433
+ }
2434
+
2435
+ .debug-drawer__body {
2436
+ flex: 1;
2437
+ overflow-y: auto;
2438
+ overflow-x: hidden;
2439
+ padding: 4px 0;
2440
+ }
2441
+
2442
+ .debug-section {
2443
+ padding: 14px 20px;
2444
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
2445
+ }
2446
+
2447
+ .debug-section:last-child {
2448
+ border-bottom: none;
2449
+ }
2450
+
2451
+ .debug-section__label {
2452
+ text-transform: uppercase;
2453
+ letter-spacing: 0.06em;
2454
+ color: #000000;
2455
+ font-size: 11px;
2456
+ font-weight: 600;
2457
+ margin-bottom: 10px;
2458
+ }
2459
+
2460
+ /* Context-fullness gauge */
2461
+ .debug-gauge {
2462
+ display: flex;
2463
+ align-items: center;
2464
+ gap: 10px;
2465
+ }
2466
+
2467
+ .debug-gauge__bar {
2468
+ flex: 1;
2469
+ height: 8px;
2470
+ border-radius: 4px;
2471
+ background: rgb(244 244 244 / 100%);
2472
+ overflow: hidden;
2473
+ }
2474
+
2475
+ .debug-gauge__fill {
2476
+ height: 100%;
2477
+ border-radius: 4px;
2478
+ background: var(--kr-chatbot-primary);
2479
+ transition: width 0.3s ease;
2480
+ }
2481
+
2482
+ .debug-gauge__text {
2483
+ color: #000000;
2484
+ font-size: 12px;
2485
+ white-space: nowrap;
2486
+ font-variant-numeric: tabular-nums;
2487
+ }
2488
+
2489
+ /* Metric rows */
2490
+ .debug-metrics {
2491
+ display: grid;
2492
+ grid-template-columns: auto 1fr;
2493
+ column-gap: 12px;
2494
+ row-gap: 7px;
2495
+ font-size: 13px;
2496
+ }
2497
+
2498
+ .debug-metrics__key {
2499
+ color: #000000;
2500
+ }
2501
+
2502
+ .debug-metrics__val {
2503
+ color: #000000;
2504
+ text-align: right;
2505
+ font-variant-numeric: tabular-nums;
2506
+ }
2507
+
2508
+ .debug-cost {
2509
+ font-weight: 600;
2510
+ }
2511
+
2512
+ /* Per-iteration table (verbose). The wrapper scrolls horizontally on its own
2513
+ so a wide table never forces the whole drawer to scroll sideways. */
2514
+ .debug-iterations-scroll {
2515
+ overflow-x: auto;
2516
+ max-width: 100%;
2517
+ }
2518
+
2519
+ .debug-iterations {
2520
+ width: 100%;
2521
+ border-collapse: collapse;
2522
+ font-size: 12px;
2523
+ font-variant-numeric: tabular-nums;
2524
+ }
2525
+
2526
+ .debug-iterations th,
2527
+ .debug-iterations td {
2528
+ padding: 5px 6px;
2529
+ text-align: right;
2530
+ white-space: nowrap;
2531
+ }
2532
+
2533
+ .debug-iterations th {
2534
+ color: #000000;
2535
+ font-weight: 600;
2536
+ border-bottom: 1px solid rgba(0, 0, 0, 0.10);
2537
+ }
2538
+
2539
+ .debug-iterations tbody tr:not(:last-child) td {
2540
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
2541
+ }
2542
+
2543
+ .debug-iterations td:first-child,
2544
+ .debug-iterations th:first-child {
2545
+ text-align: left;
2546
+ }
2547
+
2548
+ .debug-empty {
2549
+ color: #000000;
2550
+ padding: 20px;
2551
+ font-size: 13px;
2552
+ text-align: center;
2553
+ }
2554
+
1969
2555
  /* Footer / Input */
1970
2556
  .footer {
1971
2557
  padding: 16px 16px 16px;
@@ -2473,6 +3059,33 @@ __decorate([
2473
3059
  __decorate([
2474
3060
  state()
2475
3061
  ], KRChatbot.prototype, "_error", void 0);
3062
+ __decorate([
3063
+ state()
3064
+ ], KRChatbot.prototype, "_contextWarning", void 0);
3065
+ __decorate([
3066
+ state()
3067
+ ], KRChatbot.prototype, "_contextLimitReached", void 0);
3068
+ __decorate([
3069
+ state()
3070
+ ], KRChatbot.prototype, "_contextLimitMessage", void 0);
3071
+ __decorate([
3072
+ state()
3073
+ ], KRChatbot.prototype, "_telemetry", void 0);
3074
+ __decorate([
3075
+ state()
3076
+ ], KRChatbot.prototype, "_iterationLog", void 0);
3077
+ __decorate([
3078
+ state()
3079
+ ], KRChatbot.prototype, "_cacheCheckpoints", void 0);
3080
+ __decorate([
3081
+ state()
3082
+ ], KRChatbot.prototype, "_debugPanelOpen", void 0);
3083
+ __decorate([
3084
+ state()
3085
+ ], KRChatbot.prototype, "_debugDrawerWidth", void 0);
3086
+ __decorate([
3087
+ state()
3088
+ ], KRChatbot.prototype, "_isDebugResizing", void 0);
2476
3089
  __decorate([
2477
3090
  state()
2478
3091
  ], KRChatbot.prototype, "_pendingInternalFiles", void 0);