@kodaris/krubble-app-components 1.0.76 → 1.0.78

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,15 +72,27 @@ 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. */
77
82
  this._pendingSeq = 0;
78
83
  /**
79
- * Request-id guard for skill searches — bumped on each search so out-of-order
80
- * responses are dropped (mirrors the kr-combo-box `_requestId` pattern).
84
+ * Request-id guards for skill searches — bumped on each search so out-of-order
85
+ * responses are dropped (mirrors the kr-combo-box `_requestId` pattern). Org and
86
+ * global searches run concurrently into separate buffers, so each has its own.
81
87
  */
82
88
  this._skillRequestId = 0;
89
+ this._globalSkillRequestId = 0;
90
+ /**
91
+ * Codes of global skills with an in-flight enable/disable request. The `+`
92
+ * button reads this to show a pending state and ignore repeat clicks. Mutated
93
+ * in place; call requestUpdate() after changing it to re-render.
94
+ */
95
+ this._globalSkillPending = new Set();
83
96
  // --- @property (public API) ---
84
97
  /**
85
98
  * Callback function for sending messages. Receives the user's message and
@@ -143,6 +156,39 @@ let KRChatbot = class KRChatbot extends LitElement {
143
156
  this._inputValue = '';
144
157
  this._isStreaming = false;
145
158
  this._error = '';
159
+ /**
160
+ * User-facing context warning text (`CONVERSATION_TOKEN_WARNING`). Shown as a
161
+ * dismissible banner in all variants. Carries only friendly guidance, no numbers.
162
+ */
163
+ this._contextWarning = '';
164
+ /**
165
+ * True once the conversation hit the context-window limit
166
+ * (`CONVERSATION_LIMIT_REACHED`, or `contextExceeded` on load). Drives a
167
+ * blocking "start a new conversation" notice. User-facing, all variants.
168
+ */
169
+ this._contextLimitReached = false;
170
+ /** Server-provided limit text (`CONVERSATION_LIMIT_REACHED`), if any. */
171
+ this._contextLimitMessage = '';
172
+ /**
173
+ * INTERNAL-ONLY latest telemetry snapshot (last-value-wins). Populated from
174
+ * `TOKEN_USAGE` events during streaming and hydrated from the conversation
175
+ * DTO on load. Only surfaced in the `inline` variant's debug panel.
176
+ */
177
+ this._telemetry = null;
178
+ /**
179
+ * INTERNAL-ONLY per-iteration telemetry log for the debug panel's verbose
180
+ * table. Append-only within a turn; reset at the start of each send. Each row
181
+ * may be augmented with its `CACHE_CHECKPOINT` info.
182
+ */
183
+ this._iterationLog = [];
184
+ /** Latest cache-checkpoint info keyed by iteration, for the verbose table. */
185
+ this._cacheCheckpoints = {};
186
+ /** Whether the internal debug panel is expanded (off by default). */
187
+ this._debugPanelOpen = false;
188
+ /** Current width of the debug drawer in px (user-resizable, persisted). */
189
+ this._debugDrawerWidth = 430;
190
+ /** True while the user is dragging the debug drawer's resize handle. */
191
+ this._isDebugResizing = false;
146
192
  /** Images staged in the composer, before the message is sent. */
147
193
  this._pendingInternalFiles = [];
148
194
  /** True while a file is being dragged over the panel (shows the drop overlay). */
@@ -152,10 +198,24 @@ let KRChatbot = class KRChatbot extends LitElement {
152
198
  /** Current `/` query (text after the slash), sent to `_searchSkills`. */
153
199
  this._skillQuery = '';
154
200
  /**
155
- * Latest skill search results. Shared by the `/` menu and the browse dialog —
156
- * they're mutually exclusive (opening browse closes the menu), so one buffer.
201
+ * Latest org skill search results (this instance's own skills). Backs the `/`
202
+ * menu and the browse dialog's "Your Org" tab.
157
203
  */
158
204
  this._skillResults = [];
205
+ /**
206
+ * Latest shared-Kodaris (global) skill results, for the browse dialog's
207
+ * "Kodaris" tab. Kept separate from `_skillResults` so switching tabs shows
208
+ * fresh results immediately instead of the other tab's stale list — the browse
209
+ * search fills both buffers at once.
210
+ */
211
+ this._globalSkillResults = [];
212
+ /**
213
+ * AI framework UI config flags fetched from `aiFrameworkConfig` on connect.
214
+ * `debugTelemetryEnabled` (same server-side gate as TOKEN_USAGE emission) turns
215
+ * on the debug drawer toggle and the `/test-warning` / `/test-limit` composer
216
+ * options — known up front, before any message.
217
+ */
218
+ this._aiFrameworkConfig = {};
159
219
  /** Highlighted item in the skill menu for keyboard navigation (-1 = none). */
160
220
  this._skillHighlighted = 0;
161
221
  /** Index of the message whose copy button just flashed "Copied" (-1 = none). */
@@ -163,11 +223,11 @@ let KRChatbot = class KRChatbot extends LitElement {
163
223
  /** Whether the "Browse all skills" dialog is open. */
164
224
  this._skillBrowseOpened = false;
165
225
  /**
166
- * Active tab in the browse dialog. `org` lists this org's skills (searched via
167
- * `_searchSkills`); `kodaris` is the shared global catalog, added later.
226
+ * Active tab in the browse dialog. `org` shows `_skillResults` (this instance's
227
+ * skills); `kodaris` shows `_globalSkillResults` (the shared global catalog).
168
228
  */
169
229
  this._skillBrowseSource = 'org';
170
- /** Search text in the browse dialog, sent to `_searchSkills`. */
230
+ /** Search text in the browse dialog, routed to the active tab's search. */
171
231
  this._skillBrowseQuery = '';
172
232
  this._handleMouseMove = (e) => {
173
233
  if (this._isDragging) {
@@ -226,6 +286,25 @@ let KRChatbot = class KRChatbot extends LitElement {
226
286
  document.removeEventListener('mouseup', this._handleMouseUp);
227
287
  this._saveState();
228
288
  };
289
+ this._handleDebugResizeMouseMove = (e) => {
290
+ if (!this._isDebugResizing) {
291
+ return;
292
+ }
293
+ // Handle is on the drawer's left edge; dragging left (negative dx) widens it.
294
+ const dx = e.clientX - this._debugResizeStartX;
295
+ const minW = 300;
296
+ // Never let the chat column shrink below 300px — cap the drawer at the
297
+ // container width minus that reserved chat width.
298
+ const minChatWidth = 300;
299
+ const maxW = Math.max(minW, this.offsetWidth - minChatWidth);
300
+ this._debugDrawerWidth = Math.max(minW, Math.min(this._debugResizeStartW - dx, maxW));
301
+ };
302
+ this._handleDebugResizeMouseUp = () => {
303
+ this._isDebugResizing = false;
304
+ document.removeEventListener('mousemove', this._handleDebugResizeMouseMove);
305
+ document.removeEventListener('mouseup', this._handleDebugResizeMouseUp);
306
+ this._saveState();
307
+ };
229
308
  }
230
309
  // --- Lifecycle ---
231
310
  connectedCallback() {
@@ -246,11 +325,22 @@ let KRChatbot = class KRChatbot extends LitElement {
246
325
  this.messages = messages;
247
326
  }
248
327
  }).catch(() => { });
328
+ // Fetch the framework config on load (debugTelemetryEnabled, v2ChatEnabled,
329
+ // registryServer). debugTelemetryEnabled gates the debug affordances and is
330
+ // known before any message.
331
+ KRHttp.fetch({
332
+ url: '/api/system/integration/aws/bedrock/aiFrameworkConfig',
333
+ })
334
+ .then((r) => r.json())
335
+ .then((res) => {
336
+ this._aiFrameworkConfig = res?.data || {};
337
+ })
338
+ .catch(() => { });
249
339
  // 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.
340
+ // toggle/drag/resize state doesn't apply, but the composer message and debug
341
+ // drawer state (restored inside _restoreState) do.
251
342
  if (this.variant === 'inline') {
252
343
  this.opened = true;
253
- return;
254
344
  }
255
345
  this._restoreState();
256
346
  }
@@ -440,7 +530,12 @@ let KRChatbot = class KRChatbot extends LitElement {
440
530
  }
441
531
  _saveState() {
442
532
  try {
443
- const state = { opened: String(this.opened) };
533
+ const state = {
534
+ opened: String(this.opened),
535
+ message: this._inputValue,
536
+ debugPanelOpen: String(this._debugPanelOpen),
537
+ debugDrawerWidth: String(this._debugDrawerWidth),
538
+ };
444
539
  if (this._panelEl) {
445
540
  const s = this._panelEl.style;
446
541
  if (s.left) {
@@ -470,7 +565,17 @@ let KRChatbot = class KRChatbot extends LitElement {
470
565
  return;
471
566
  }
472
567
  const state = JSON.parse(raw);
473
- if (state.opened === 'true') {
568
+ // Draft text and the debug drawer are persisted for all variants.
569
+ if (typeof state.message === 'string') {
570
+ this._inputValue = state.message;
571
+ }
572
+ this._debugPanelOpen = state.debugPanelOpen === 'true';
573
+ const w = Number(state.debugDrawerWidth);
574
+ if (w >= 300) {
575
+ this._debugDrawerWidth = w;
576
+ }
577
+ // The floating widget's open/layout state doesn't apply to inline mode.
578
+ if (this.variant !== 'inline' && state.opened === 'true') {
474
579
  this.opened = true;
475
580
  // Apply layout after first render
476
581
  this.updateComplete.then(() => this._applyStoredLayout());
@@ -505,6 +610,7 @@ let KRChatbot = class KRChatbot extends LitElement {
505
610
  }
506
611
  _handleInputChange(e) {
507
612
  this._inputValue = e.target.value;
613
+ this._saveState();
508
614
  // Auto-resize textarea
509
615
  const textarea = e.target;
510
616
  textarea.style.height = 'auto';
@@ -594,17 +700,33 @@ let KRChatbot = class KRChatbot extends LitElement {
594
700
  // specials so user input is treated literally, then wrap with `*...*`.
595
701
  let must = { match_all: {} };
596
702
  if (trimmed) {
597
- const pattern = `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`;
598
- const value = {
599
- value: pattern,
600
- case_insensitive: true,
601
- };
602
703
  must = {
603
704
  bool: {
604
705
  should: [
605
- { wildcard: { 'name.raw': value } },
606
- { wildcard: { 'code.raw': value } },
607
- { wildcard: { 'description.raw': value } },
706
+ {
707
+ wildcard: {
708
+ 'name.raw': {
709
+ value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
710
+ case_insensitive: true,
711
+ }
712
+ }
713
+ },
714
+ {
715
+ wildcard: {
716
+ 'code.raw': {
717
+ value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
718
+ case_insensitive: true,
719
+ }
720
+ }
721
+ },
722
+ {
723
+ wildcard: {
724
+ 'description.raw': {
725
+ value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
726
+ case_insensitive: true,
727
+ }
728
+ }
729
+ },
608
730
  ],
609
731
  minimum_should_match: 1,
610
732
  },
@@ -635,6 +757,66 @@ let KRChatbot = class KRChatbot extends LitElement {
635
757
  console.error('kr-chatbot: skill search failed', error);
636
758
  });
637
759
  }
760
+ /**
761
+ * Search the shared Kodaris global-skill registry into `_globalSkillResults`
762
+ * (the browse dialog's "Kodaris" tab). Unlike `/aiskills/search`,
763
+ * `/aiskills/global/search` returns a digested list
764
+ * (`data: [{ code, name, description, enabled }]`) already in `KRChatbotSkill`
765
+ * shape, so no `_source` lifting is needed. Only the browse dialog calls this
766
+ * (the `/` menu is org-only), so it isn't run per slash keystroke.
767
+ */
768
+ _searchGlobalSkills(query) {
769
+ this._globalSkillRequestId++;
770
+ const requestId = this._globalSkillRequestId;
771
+ KRHttp.fetch({
772
+ url: '/api/system/opensearch/aiskills/global/search',
773
+ method: 'POST',
774
+ body: { query: query.trim() },
775
+ })
776
+ .then((r) => r.json())
777
+ .then((res) => {
778
+ // Ignore a stale response superseded by a newer search.
779
+ if (requestId !== this._globalSkillRequestId) {
780
+ return;
781
+ }
782
+ this._globalSkillResults = (res?.data || []);
783
+ })
784
+ .catch((error) => {
785
+ console.error('kr-chatbot: global skill search failed', error);
786
+ });
787
+ }
788
+ /**
789
+ * Enable or disable a global skill on this instance via
790
+ * `/aiskills/global/{code}/enabled`, then reflect the server's result on the
791
+ * matching card. Ignores clicks while a request for the same code is pending.
792
+ */
793
+ _handleGlobalSkillEnable(skill, e) {
794
+ // The card body inserts the skill; the `+` button must not also do that.
795
+ e.stopPropagation();
796
+ if (this._globalSkillPending.has(skill.code)) {
797
+ return;
798
+ }
799
+ this._globalSkillPending.add(skill.code);
800
+ this.requestUpdate();
801
+ KRHttp.fetch({
802
+ url: `/api/system/opensearch/aiskills/global/${encodeURIComponent(skill.code)}/enabled`,
803
+ method: 'POST',
804
+ body: { enabled: !skill.enabled },
805
+ })
806
+ .then((r) => r.json())
807
+ .then((res) => {
808
+ // Reflect the server's returned flag on the card.
809
+ skill.enabled = res?.data?.enabled;
810
+ this.requestUpdate();
811
+ })
812
+ .catch((error) => {
813
+ console.error('kr-chatbot: global skill enable failed', error);
814
+ })
815
+ .finally(() => {
816
+ this._globalSkillPending.delete(skill.code);
817
+ this.requestUpdate();
818
+ });
819
+ }
638
820
  /** The `+` button toggles the skill/attachment menu open (showing all skills). */
639
821
  _handlePlusClick() {
640
822
  if (this._skillMenuOpened) {
@@ -649,10 +831,18 @@ let KRChatbot = class KRChatbot extends LitElement {
649
831
  /** Insert `/code ` into the composer and focus it, ready for the message. */
650
832
  _handleSkillSelect(skill) {
651
833
  this._inputValue = `/${skill.code} `;
834
+ this._saveState();
652
835
  this._skillMenuOpened = false;
653
836
  this._skillBrowseOpened = false;
654
837
  this._inputEl?.focus();
655
838
  }
839
+ /** Activate a skill card via keyboard (Enter/Space), since it's a div role=button. */
840
+ _handleSkillCardKeydown(e, skill) {
841
+ if (e.key === 'Enter' || e.key === ' ') {
842
+ e.preventDefault();
843
+ this._handleSkillSelect(skill);
844
+ }
845
+ }
656
846
  /** Copy a message's text to the clipboard and briefly flash "Copied". */
657
847
  _handleCopyMessage(content, index) {
658
848
  navigator.clipboard.writeText(content).then(() => {
@@ -668,16 +858,22 @@ let KRChatbot = class KRChatbot extends LitElement {
668
858
  this._skillMenuOpened = false;
669
859
  this._skillBrowseQuery = '';
670
860
  this._skillBrowseOpened = true;
861
+ // Fill both tabs up front so switching between them is instant, not stale.
671
862
  this._searchSkills('');
863
+ this._searchGlobalSkills('');
672
864
  }
673
865
  _handleSkillBrowseClose() {
674
866
  this._skillBrowseOpened = false;
675
867
  }
676
868
  _handleSkillBrowseSearch(e) {
677
869
  this._skillBrowseQuery = e.target.value;
870
+ // Filter both catalogs so whichever tab the user switches to is already current.
678
871
  this._searchSkills(this._skillBrowseQuery);
872
+ this._searchGlobalSkills(this._skillBrowseQuery);
679
873
  }
680
874
  _handleSkillBrowseTabChange(e) {
875
+ // Both buffers are kept current by open/search, so switching just swaps which
876
+ // one is shown — no re-search needed.
681
877
  this._skillBrowseSource = e.detail.activeTabId;
682
878
  }
683
879
  // --- Attachments ---
@@ -821,6 +1017,7 @@ let KRChatbot = class KRChatbot extends LitElement {
821
1017
  };
822
1018
  this.messages = [...this.messages, userMessage];
823
1019
  this._inputValue = '';
1020
+ this._saveState();
824
1021
  this._error = '';
825
1022
  this._userHasScrolledUp = false;
826
1023
  // Clear the composer and release its preview object URLs.
@@ -930,6 +1127,14 @@ let KRChatbot = class KRChatbot extends LitElement {
930
1127
  this._error = '';
931
1128
  this._inputValue = '';
932
1129
  this._userHasScrolledUp = false;
1130
+ // Reset conversation-scoped context + telemetry state for the new convo.
1131
+ this._contextWarning = '';
1132
+ this._contextLimitReached = false;
1133
+ this._contextLimitMessage = '';
1134
+ this._telemetry = null;
1135
+ this._iterationLog = [];
1136
+ this._cacheCheckpoints = {};
1137
+ this._saveState();
933
1138
  });
934
1139
  }
935
1140
  _handleMessagesScroll(e) {
@@ -945,6 +1150,22 @@ let KRChatbot = class KRChatbot extends LitElement {
945
1150
  _handleErrorDismiss() {
946
1151
  this._error = '';
947
1152
  }
1153
+ _handleContextWarningDismiss() {
1154
+ this._contextWarning = '';
1155
+ }
1156
+ _handleToggleDebugPanel() {
1157
+ this._debugPanelOpen = !this._debugPanelOpen;
1158
+ this._saveState();
1159
+ }
1160
+ _handleDebugResizeMouseDown(e) {
1161
+ this._isDebugResizing = true;
1162
+ this._debugResizeStartX = e.clientX;
1163
+ this._debugResizeStartW = this._debugDrawerWidth;
1164
+ e.preventDefault();
1165
+ e.stopPropagation();
1166
+ document.addEventListener('mousemove', this._handleDebugResizeMouseMove);
1167
+ document.addEventListener('mouseup', this._handleDebugResizeMouseUp);
1168
+ }
948
1169
  _readStreamChunk() {
949
1170
  if (!this._reader) {
950
1171
  return;
@@ -1008,6 +1229,47 @@ let KRChatbot = class KRChatbot extends LitElement {
1008
1229
  else if (data.type === 'ERROR') {
1009
1230
  this._handleStreamError(String(data.message || 'Unknown error'));
1010
1231
  }
1232
+ else if (data.type === 'CONVERSATION_TOKEN_WARNING') {
1233
+ // User-facing: friendly "conversation is getting long" guidance.
1234
+ // Text only, never sent to the model — render verbatim as a banner.
1235
+ this._contextWarning = String(data.message || '');
1236
+ }
1237
+ else if (data.type === 'CONVERSATION_LIMIT_REACHED') {
1238
+ // User-facing hard stop. Carries finished:true (handled below).
1239
+ // Clear any soft warning so we don't double-message the user.
1240
+ this._contextLimitReached = true;
1241
+ this._contextLimitMessage = String(data.message || '');
1242
+ this._contextWarning = '';
1243
+ }
1244
+ else if (data.type === 'TOKEN_USAGE') {
1245
+ // INTERNAL-ONLY debug telemetry. Update the panel snapshot and the
1246
+ // per-iteration log; never render as a chat bubble.
1247
+ if (data.data) {
1248
+ const usage = data.data;
1249
+ this._telemetry = usage;
1250
+ // A new turn starts at iteration 1 — clear the prior turn's log
1251
+ // only now (not at send), so it stays visible until fresh data
1252
+ // actually arrives rather than blanking out on submit.
1253
+ if (usage.iteration === 1) {
1254
+ this._iterationLog = [usage];
1255
+ this._cacheCheckpoints = {};
1256
+ }
1257
+ else {
1258
+ this._iterationLog = [...this._iterationLog, usage];
1259
+ }
1260
+ }
1261
+ }
1262
+ else if (data.type === 'CACHE_CHECKPOINT') {
1263
+ // INTERNAL-ONLY debug telemetry. Records when/under which mode the
1264
+ // history checkpoint was placed, keyed by iteration for the table.
1265
+ if (data.data) {
1266
+ const checkpoint = data.data;
1267
+ this._cacheCheckpoints = {
1268
+ ...this._cacheCheckpoints,
1269
+ [checkpoint.iteration]: checkpoint,
1270
+ };
1271
+ }
1272
+ }
1011
1273
  if (data.finished) {
1012
1274
  this._isStreaming = false;
1013
1275
  this._reader = null;
@@ -1108,37 +1370,81 @@ let KRChatbot = class KRChatbot extends LitElement {
1108
1370
  ${this._renderSkillGrid()}
1109
1371
  </kr-tab>
1110
1372
  <kr-tab id="kodaris" label="Kodaris">
1111
- <div class="skill-browse__empty">
1112
- Shared Kodaris skills are coming soon.
1113
- </div>
1373
+ ${this._renderSkillGrid(true)}
1114
1374
  </kr-tab>
1115
1375
  </kr-tab-group>
1116
1376
  </div>
1117
1377
  </kr-dialog>
1118
1378
  `;
1119
1379
  }
1120
- /** The card grid of this org's skill search results for the browse dialog. */
1121
- _renderSkillGrid() {
1122
- const skills = this._skillResults;
1380
+ /**
1381
+ * The card grid of skill search results for the browse dialog. Clicking a card
1382
+ * inserts `/code ` into the composer. In the shared-Kodaris (`global`) tab each
1383
+ * card also gets a `+` install button that enables the skill on this instance
1384
+ * (turning into a ✓ once installed).
1385
+ */
1386
+ _renderSkillGrid(global = false) {
1387
+ let skills = this._skillResults;
1388
+ if (global) {
1389
+ skills = this._globalSkillResults;
1390
+ }
1123
1391
  if (!skills.length) {
1124
1392
  return nothing;
1125
1393
  }
1126
1394
  return html `
1127
1395
  <div class="skill-browse__grid">
1128
1396
  ${skills.map((skill) => html `
1129
- <button
1397
+ <div
1130
1398
  class="skill-card"
1399
+ role="button"
1400
+ tabindex="0"
1131
1401
  @click=${() => this._handleSkillSelect(skill)}
1402
+ @keydown=${(e) => this._handleSkillCardKeydown(e, skill)}
1132
1403
  >
1133
1404
  <span class="skill-card__name">${skill.name}</span>
1134
1405
  ${skill.description ? html `
1135
1406
  <span class="skill-card__desc">${skill.description}</span>
1136
1407
  ` : nothing}
1137
- </button>
1408
+ ${global ? this._renderSkillInstall(skill) : nothing}
1409
+ </div>
1138
1410
  `)}
1139
1411
  </div>
1140
1412
  `;
1141
1413
  }
1414
+ /**
1415
+ * The install affordance on a global skill card: a `+` button that enables the
1416
+ * skill on this instance, showing a ✓ once installed and a disabled state
1417
+ * while the enable/disable request is in flight.
1418
+ */
1419
+ _renderSkillInstall(skill) {
1420
+ return html `
1421
+ <button
1422
+ class=${classMap({
1423
+ 'skill-card__install': true,
1424
+ 'skill-card__install--installed': !!skill.enabled,
1425
+ })}
1426
+ ?disabled=${this._globalSkillPending.has(skill.code)}
1427
+ title=${!!skill.enabled ? 'Installed — click to remove' : 'Install this skill'}
1428
+ aria-label=${!!skill.enabled ? 'Remove skill' : 'Install skill'}
1429
+ @click=${(e) => this._handleGlobalSkillEnable(skill, e)}
1430
+ >
1431
+ <svg
1432
+ xmlns="http://www.w3.org/2000/svg"
1433
+ fill="none"
1434
+ viewBox="0 0 24 24"
1435
+ stroke-width="2"
1436
+ stroke="currentColor"
1437
+ class="skill-card__install-icon"
1438
+ >
1439
+ <path
1440
+ stroke-linecap="round"
1441
+ stroke-linejoin="round"
1442
+ d=${!!skill.enabled ? 'm4.5 12.75 6 6 9-13.5' : 'M12 4.5v15m7.5-7.5h-15'}
1443
+ />
1444
+ </svg>
1445
+ </button>
1446
+ `;
1447
+ }
1142
1448
  /**
1143
1449
  * Render a user message, highlighting a leading `/skill-code` token (like the
1144
1450
  * way Claude highlights slash commands). The composer only inserts valid skill
@@ -1151,7 +1457,162 @@ let KRChatbot = class KRChatbot extends LitElement {
1151
1457
  }
1152
1458
  return content;
1153
1459
  }
1460
+ /** Compact integer formatting for the debug panel (e.g. 88928 → "88,928"). */
1461
+ _fmtTokens(n) {
1462
+ if (n == null || isNaN(n)) {
1463
+ return '—';
1464
+ }
1465
+ return n.toLocaleString('en-US');
1466
+ }
1467
+ /** Approximate USD formatting for the debug panel (e.g. 0.1434 → "~$0.1434"). */
1468
+ _fmtCost(n) {
1469
+ if (n == null || isNaN(n)) {
1470
+ return '—';
1471
+ }
1472
+ return '~$' + n.toFixed(4);
1473
+ }
1474
+ /**
1475
+ * INTERNAL-ONLY debug telemetry panel. Rendered only in the inline variant
1476
+ * (guarded by the caller) and only when the user has toggled it open. Shows
1477
+ * three scopes from the latest TOKEN_USAGE event — context gauge, this turn,
1478
+ * lifetime — plus a verbose per-iteration table. Never customer-facing.
1479
+ */
1480
+ _renderDebugPanel() {
1481
+ return html `
1482
+ <div
1483
+ class=${classMap({
1484
+ 'debug-drawer': true,
1485
+ 'debug-drawer--open': this._debugPanelOpen,
1486
+ 'debug-drawer--resizing': this._isDebugResizing,
1487
+ })}
1488
+ style=${styleMap({ width: `${this._debugDrawerWidth}px` })}
1489
+ >
1490
+ <div
1491
+ class="debug-drawer__resize"
1492
+ @mousedown=${this._handleDebugResizeMouseDown}
1493
+ ></div>
1494
+ <div class="debug-drawer__header">
1495
+ <span class="debug-drawer__title">Debug</span>
1496
+ <button
1497
+ class="header-action"
1498
+ @click=${this._handleToggleDebugPanel}
1499
+ title="Close debug panel"
1500
+ >
1501
+ <svg
1502
+ xmlns="http://www.w3.org/2000/svg"
1503
+ fill="none"
1504
+ viewBox="0 0 24 24"
1505
+ stroke-width="2"
1506
+ stroke="currentColor"
1507
+ class="header-action-icon"
1508
+ >
1509
+ <path
1510
+ stroke-linecap="round"
1511
+ stroke-linejoin="round"
1512
+ d="M6 18 18 6M6 6l12 12"
1513
+ />
1514
+ </svg>
1515
+ </button>
1516
+ </div>
1517
+ <div class="debug-drawer__body">
1518
+ ${this._telemetry ? html `
1519
+ <div class="debug-section">
1520
+ <div class="debug-section__label">Context</div>
1521
+ <div class="debug-gauge">
1522
+ <div class="debug-gauge__bar">
1523
+ <div
1524
+ class="debug-gauge__fill"
1525
+ style=${styleMap({ width: `${Math.min(100, Math.max(0, this._telemetry.contextUsedPercent || 0))}%` })}
1526
+ ></div>
1527
+ </div>
1528
+ <span class="debug-gauge__text">
1529
+ ${this._fmtTokens(this._telemetry.currentContextTokens)} / ${this._fmtTokens(this._telemetry.contextWindowTokens)}
1530
+ · ${this._telemetry.contextUsedPercent ?? 0}%
1531
+ </span>
1532
+ </div>
1533
+ </div>
1534
+
1535
+ <div class="debug-section">
1536
+ <div class="debug-section__label">This turn</div>
1537
+ <div class="debug-metrics">
1538
+ <span class="debug-metrics__key">Input</span>
1539
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.turnInputTokens)}</span>
1540
+ <span class="debug-metrics__key">Output</span>
1541
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.turnOutputTokens)}</span>
1542
+ <span class="debug-metrics__key">Cache read</span>
1543
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.turnCacheReadTokens)}</span>
1544
+ <span class="debug-metrics__key">Hit rate</span>
1545
+ <span class="debug-metrics__val">${this._telemetry.turnHitRate ?? '—'}%</span>
1546
+ <span class="debug-metrics__key">Cost</span>
1547
+ <span class="debug-metrics__val debug-cost">${this._fmtCost(this._telemetry.turnCostUsd)}</span>
1548
+ </div>
1549
+ </div>
1550
+
1551
+ <div class="debug-section">
1552
+ <div class="debug-section__label">Lifetime</div>
1553
+ <div class="debug-metrics">
1554
+ <span class="debug-metrics__key">Input</span>
1555
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationInputTokens)}</span>
1556
+ <span class="debug-metrics__key">Output</span>
1557
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationOutputTokens)}</span>
1558
+ <span class="debug-metrics__key">Cache read</span>
1559
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationCacheReadTokens)}</span>
1560
+ <span class="debug-metrics__key">Cache write</span>
1561
+ <span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationCacheWriteTokens)}</span>
1562
+ <span class="debug-metrics__key">Cost so far</span>
1563
+ <span class="debug-metrics__val debug-cost">${this._fmtCost(this._telemetry.conversationCostUsd)}</span>
1564
+ </div>
1565
+ </div>
1566
+ ` : html `
1567
+ <div class="debug-empty">No telemetry yet — send a message to populate.</div>
1568
+ `}
1569
+
1570
+ ${this._iterationLog.length ? html `
1571
+ <div class="debug-section">
1572
+ <div class="debug-section__label">Iterations (this turn)</div>
1573
+ <div class="debug-iterations-scroll">
1574
+ <table class="debug-iterations">
1575
+ <thead>
1576
+ <tr>
1577
+ <th>#</th>
1578
+ <th>In</th>
1579
+ <th>Out</th>
1580
+ <th>Cache R</th>
1581
+ <th>Cache W</th>
1582
+ <th>Hit</th>
1583
+ <th>Cost</th>
1584
+ <th>Ckpt</th>
1585
+ </tr>
1586
+ </thead>
1587
+ <tbody>
1588
+ ${this._iterationLog.map((it) => {
1589
+ const ckpt = this._cacheCheckpoints[it.iteration];
1590
+ return html `
1591
+ <tr>
1592
+ <td>${it.iteration}</td>
1593
+ <td>${this._fmtTokens(it.iterationInput)}</td>
1594
+ <td>${this._fmtTokens(it.iterationOutput)}</td>
1595
+ <td>${this._fmtTokens(it.iterationCacheRead)}</td>
1596
+ <td>${this._fmtTokens(it.iterationCacheWrite)}</td>
1597
+ <td>${it.iterationHitRate ?? '—'}%</td>
1598
+ <td class="debug-cost">${this._fmtCost(it.iterationCostUsd)}</td>
1599
+ <td>${ckpt ? ckpt.mode : '—'}</td>
1600
+ </tr>
1601
+ `;
1602
+ })}
1603
+ </tbody>
1604
+ </table>
1605
+ </div>
1606
+ </div>
1607
+ ` : nothing}
1608
+ </div>
1609
+ </div>
1610
+ `;
1611
+ }
1154
1612
  render() {
1613
+ // Debug affordances (drawer toggle, drawer, /test-* options) require the
1614
+ // inline variant AND the server-side debug flag from aiFrameworkConfig.
1615
+ const isDebugEnabled = this.variant === 'inline' && this._aiFrameworkConfig.debugTelemetryEnabled === true;
1155
1616
  return html `
1156
1617
  ${(this.hideToggle || this.variant === 'inline') ? nothing : html `
1157
1618
  <button
@@ -1180,7 +1641,9 @@ let KRChatbot = class KRChatbot extends LitElement {
1180
1641
  class=${classMap({
1181
1642
  'panel': true,
1182
1643
  'panel--opened': this.opened,
1644
+ 'panel--resizing-debug': this._isDebugResizing,
1183
1645
  })}
1646
+ style=${styleMap(this._debugPanelOpen ? { paddingRight: `${this._debugDrawerWidth}px` } : {})}
1184
1647
  @dragenter=${this._handleDragEnter}
1185
1648
  @dragover=${this._handleDragOver}
1186
1649
  @dragleave=${this._handleDragLeave}
@@ -1253,6 +1716,31 @@ let KRChatbot = class KRChatbot extends LitElement {
1253
1716
  </div>
1254
1717
  </div>
1255
1718
  <div class="header-actions">
1719
+ ${isDebugEnabled ? html `
1720
+ <button
1721
+ class=${classMap({
1722
+ 'header-action': true,
1723
+ 'header-action--active': this._debugPanelOpen,
1724
+ })}
1725
+ @click=${this._handleToggleDebugPanel}
1726
+ title="Debug telemetry"
1727
+ >
1728
+ <svg
1729
+ xmlns="http://www.w3.org/2000/svg"
1730
+ fill="none"
1731
+ viewBox="0 0 24 24"
1732
+ stroke-width="1.5"
1733
+ stroke="currentColor"
1734
+ class="header-action-icon"
1735
+ >
1736
+ <path
1737
+ stroke-linecap="round"
1738
+ stroke-linejoin="round"
1739
+ 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"
1740
+ />
1741
+ </svg>
1742
+ </button>
1743
+ ` : nothing}
1256
1744
  ${this.messages.length > 0 ? html `
1257
1745
  <button
1258
1746
  class="header-action"
@@ -1417,6 +1905,37 @@ let KRChatbot = class KRChatbot extends LitElement {
1417
1905
  </div>
1418
1906
  ` : nothing}
1419
1907
 
1908
+ ${this._contextLimitReached ? html `
1909
+ <div class="context-limit" role="alert">
1910
+ ${this._contextLimitMessage
1911
+ || 'This conversation has reached its limit. Please start a new one to continue.'}
1912
+ </div>
1913
+ ` : this._contextWarning ? html `
1914
+ <div class="context-warning" role="status">
1915
+ <span>${this._contextWarning}</span>
1916
+ <button
1917
+ class="context-warning-dismiss"
1918
+ title="Dismiss"
1919
+ @click=${this._handleContextWarningDismiss}
1920
+ >
1921
+ <svg
1922
+ xmlns="http://www.w3.org/2000/svg"
1923
+ fill="none"
1924
+ viewBox="0 0 24 24"
1925
+ stroke-width="2"
1926
+ stroke="currentColor"
1927
+ class="context-warning-dismiss-icon"
1928
+ >
1929
+ <path
1930
+ stroke-linecap="round"
1931
+ stroke-linejoin="round"
1932
+ d="M6 18 18 6M6 6l12 12"
1933
+ />
1934
+ </svg>
1935
+ </button>
1936
+ </div>
1937
+ ` : nothing}
1938
+
1420
1939
  <div class="footer">
1421
1940
  ${this._pendingInternalFiles.length ? html `
1422
1941
  <div class="composer-attachments">
@@ -1516,6 +2035,7 @@ let KRChatbot = class KRChatbot extends LitElement {
1516
2035
  </div>
1517
2036
  </div>
1518
2037
  ${this._skillBrowseOpened ? this._renderSkillBrowse() : nothing}
2038
+ ${isDebugEnabled ? this._renderDebugPanel() : nothing}
1519
2039
  </div>
1520
2040
  `;
1521
2041
  }
@@ -1697,6 +2217,11 @@ KRChatbot.styles = css `
1697
2217
  color: #000000;
1698
2218
  }
1699
2219
 
2220
+ .header-action--active {
2221
+ background: rgba(0, 0, 0, 0.08);
2222
+ color: #000000;
2223
+ }
2224
+
1700
2225
  .header-action-icon {
1701
2226
  width: 20px;
1702
2227
  height: 20px;
@@ -1726,19 +2251,6 @@ KRChatbot.styles = css `
1726
2251
  margin-right: auto;
1727
2252
  }
1728
2253
 
1729
- .messages::-webkit-scrollbar {
1730
- width: 4px;
1731
- }
1732
-
1733
- .messages::-webkit-scrollbar-track {
1734
- background: transparent;
1735
- }
1736
-
1737
- .messages::-webkit-scrollbar-thumb {
1738
- background: rgba(0, 0, 0, 0.08);
1739
- border-radius: 2px;
1740
- }
1741
-
1742
2254
  .empty {
1743
2255
  flex: 1;
1744
2256
  display: flex;
@@ -1977,6 +2489,238 @@ KRChatbot.styles = css `
1977
2489
  height: 14px;
1978
2490
  }
1979
2491
 
2492
+ /* Context warning banner (user-facing, dismissible, non-blocking) */
2493
+ .context-warning {
2494
+ padding: 8px 16px;
2495
+ background: #fffbeb;
2496
+ color: #92400e;
2497
+ font-size: 13px;
2498
+ display: flex;
2499
+ align-items: center;
2500
+ justify-content: space-between;
2501
+ gap: 8px;
2502
+ flex-shrink: 0;
2503
+ }
2504
+
2505
+ .context-warning-dismiss {
2506
+ background: none;
2507
+ border: none;
2508
+ color: #92400e;
2509
+ cursor: pointer;
2510
+ padding: 2px;
2511
+ display: flex;
2512
+ align-items: center;
2513
+ flex-shrink: 0;
2514
+ }
2515
+
2516
+ .context-warning-dismiss-icon {
2517
+ width: 14px;
2518
+ height: 14px;
2519
+ }
2520
+
2521
+ /* Context-limit notice (user-facing, blocking — steers to a new conversation) */
2522
+ .context-limit {
2523
+ padding: 10px 16px;
2524
+ background: var(--kr-chatbot-error-bg);
2525
+ color: var(--kr-chatbot-error-text);
2526
+ font-size: 13px;
2527
+ line-height: 1.5;
2528
+ flex-shrink: 0;
2529
+ }
2530
+
2531
+ /*
2532
+ * INTERNAL-ONLY debug telemetry drawer. A layout drawer docked to the right
2533
+ * of the inline chat — the chat column shrinks to make room; it does not
2534
+ * overlay. Closed by default (width 0), animates open. Not customer-facing —
2535
+ * token counts, cache internals, and approximate cost estimates for internal
2536
+ * debugging. Styled to match the chatbot's light theme (Inter, design tokens).
2537
+ */
2538
+ /*
2539
+ * The chat column reserves space for the drawer by animating padding-right,
2540
+ * so opening the drawer shrinks the chat (header/messages/composer) rather
2541
+ * than overlaying it. Only applies in inline mode.
2542
+ */
2543
+ :host([variant='inline']) .panel {
2544
+ transition: padding-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2545
+ }
2546
+
2547
+ /* Track the cursor 1:1 while dragging the drawer's resize handle. */
2548
+ :host([variant='inline']) .panel--resizing-debug {
2549
+ transition: none;
2550
+ }
2551
+
2552
+ .debug-drawer {
2553
+ position: absolute;
2554
+ top: 0;
2555
+ right: 0;
2556
+ bottom: 0;
2557
+ background: var(--kr-chatbot-bg);
2558
+ display: flex;
2559
+ flex-direction: column;
2560
+ transform: translateX(100%);
2561
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2562
+ color: #000000;
2563
+ z-index: 5;
2564
+ }
2565
+
2566
+ /* While dragging the resize handle, suppress the slide transition so the
2567
+ drawer tracks the cursor 1:1. */
2568
+ .debug-drawer--resizing {
2569
+ transition: none;
2570
+ }
2571
+
2572
+ .debug-drawer--open {
2573
+ transform: translateX(0);
2574
+ box-shadow: -4px 0 20px rgba(10, 26, 46, 0.07);
2575
+ }
2576
+
2577
+ /* Left-edge grab handle for resizing the drawer. */
2578
+ .debug-drawer__resize {
2579
+ position: absolute;
2580
+ top: 0;
2581
+ left: 0;
2582
+ bottom: 0;
2583
+ width: 10px;
2584
+ cursor: ew-resize;
2585
+ z-index: 10;
2586
+ }
2587
+
2588
+ .debug-drawer__header {
2589
+ display: flex;
2590
+ align-items: center;
2591
+ justify-content: space-between;
2592
+ padding: 0 12px 0 20px;
2593
+ height: 64px;
2594
+ flex-shrink: 0;
2595
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
2596
+ }
2597
+
2598
+ .debug-drawer__title {
2599
+ font-size: 14px;
2600
+ font-weight: 600;
2601
+ color: #000000;
2602
+ }
2603
+
2604
+ .debug-drawer__body {
2605
+ flex: 1;
2606
+ overflow-y: auto;
2607
+ overflow-x: hidden;
2608
+ padding: 4px 0;
2609
+ }
2610
+
2611
+ .debug-section {
2612
+ padding: 14px 20px;
2613
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
2614
+ }
2615
+
2616
+ .debug-section:last-child {
2617
+ border-bottom: none;
2618
+ }
2619
+
2620
+ .debug-section__label {
2621
+ text-transform: uppercase;
2622
+ letter-spacing: 0.06em;
2623
+ color: #000000;
2624
+ font-size: 11px;
2625
+ font-weight: 600;
2626
+ margin-bottom: 10px;
2627
+ }
2628
+
2629
+ /* Context-fullness gauge */
2630
+ .debug-gauge {
2631
+ display: flex;
2632
+ align-items: center;
2633
+ gap: 10px;
2634
+ }
2635
+
2636
+ .debug-gauge__bar {
2637
+ flex: 1;
2638
+ height: 8px;
2639
+ border-radius: 4px;
2640
+ background: rgb(244 244 244 / 100%);
2641
+ overflow: hidden;
2642
+ }
2643
+
2644
+ .debug-gauge__fill {
2645
+ height: 100%;
2646
+ border-radius: 4px;
2647
+ background: var(--kr-chatbot-primary);
2648
+ transition: width 0.3s ease;
2649
+ }
2650
+
2651
+ .debug-gauge__text {
2652
+ color: #000000;
2653
+ font-size: 12px;
2654
+ white-space: nowrap;
2655
+ font-variant-numeric: tabular-nums;
2656
+ }
2657
+
2658
+ /* Metric rows */
2659
+ .debug-metrics {
2660
+ display: grid;
2661
+ grid-template-columns: auto 1fr;
2662
+ column-gap: 12px;
2663
+ row-gap: 7px;
2664
+ font-size: 13px;
2665
+ }
2666
+
2667
+ .debug-metrics__key {
2668
+ color: #000000;
2669
+ }
2670
+
2671
+ .debug-metrics__val {
2672
+ color: #000000;
2673
+ text-align: right;
2674
+ font-variant-numeric: tabular-nums;
2675
+ }
2676
+
2677
+ .debug-cost {
2678
+ font-weight: 600;
2679
+ }
2680
+
2681
+ /* Per-iteration table (verbose). The wrapper scrolls horizontally on its own
2682
+ so a wide table never forces the whole drawer to scroll sideways. */
2683
+ .debug-iterations-scroll {
2684
+ overflow-x: auto;
2685
+ max-width: 100%;
2686
+ }
2687
+
2688
+ .debug-iterations {
2689
+ width: 100%;
2690
+ border-collapse: collapse;
2691
+ font-size: 12px;
2692
+ font-variant-numeric: tabular-nums;
2693
+ }
2694
+
2695
+ .debug-iterations th,
2696
+ .debug-iterations td {
2697
+ padding: 5px 6px;
2698
+ text-align: right;
2699
+ white-space: nowrap;
2700
+ }
2701
+
2702
+ .debug-iterations th {
2703
+ color: #000000;
2704
+ font-weight: 600;
2705
+ border-bottom: 1px solid rgba(0, 0, 0, 0.10);
2706
+ }
2707
+
2708
+ .debug-iterations tbody tr:not(:last-child) td {
2709
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
2710
+ }
2711
+
2712
+ .debug-iterations td:first-child,
2713
+ .debug-iterations th:first-child {
2714
+ text-align: left;
2715
+ }
2716
+
2717
+ .debug-empty {
2718
+ color: #000000;
2719
+ padding: 20px;
2720
+ font-size: 13px;
2721
+ text-align: center;
2722
+ }
2723
+
1980
2724
  /* Footer / Input */
1981
2725
  .footer {
1982
2726
  padding: 16px 16px 16px;
@@ -2316,6 +3060,7 @@ KRChatbot.styles = css `
2316
3060
  }
2317
3061
 
2318
3062
  .skill-card {
3063
+ position: relative;
2319
3064
  display: flex;
2320
3065
  flex-direction: column;
2321
3066
  gap: 6px;
@@ -2335,8 +3080,53 @@ KRChatbot.styles = css `
2335
3080
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
2336
3081
  }
2337
3082
 
3083
+ /* Install button on global skill cards. Absolutely positioned in the corner
3084
+ so it sits above the card's own click target; leave room via the name's
3085
+ padding-right below. */
3086
+ .skill-card__install {
3087
+ position: absolute;
3088
+ top: 10px;
3089
+ right: 10px;
3090
+ display: inline-flex;
3091
+ align-items: center;
3092
+ justify-content: center;
3093
+ width: 24px;
3094
+ height: 24px;
3095
+ padding: 0;
3096
+ border: 1px solid #d1d5db;
3097
+ border-radius: 6px;
3098
+ background: white;
3099
+ color: #374151;
3100
+ cursor: pointer;
3101
+ transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
3102
+ }
3103
+
3104
+ .skill-card__install:hover:not(:disabled) {
3105
+ border-color: var(--kr-chatbot-primary);
3106
+ color: var(--kr-chatbot-primary);
3107
+ }
3108
+
3109
+ .skill-card__install:disabled {
3110
+ opacity: 0.5;
3111
+ cursor: default;
3112
+ }
3113
+
3114
+ /* Installed state: filled check. */
3115
+ .skill-card__install--installed {
3116
+ background: var(--kr-chatbot-primary);
3117
+ border-color: var(--kr-chatbot-primary);
3118
+ color: white;
3119
+ }
3120
+
3121
+ .skill-card__install-icon {
3122
+ width: 15px;
3123
+ height: 15px;
3124
+ }
3125
+
2338
3126
  .skill-card__name {
2339
3127
  font-weight: 600;
3128
+ /* Leave room for the corner install button on global cards. */
3129
+ padding-right: 28px;
2340
3130
  }
2341
3131
 
2342
3132
  .skill-card__desc {
@@ -2484,6 +3274,33 @@ __decorate([
2484
3274
  __decorate([
2485
3275
  state()
2486
3276
  ], KRChatbot.prototype, "_error", void 0);
3277
+ __decorate([
3278
+ state()
3279
+ ], KRChatbot.prototype, "_contextWarning", void 0);
3280
+ __decorate([
3281
+ state()
3282
+ ], KRChatbot.prototype, "_contextLimitReached", void 0);
3283
+ __decorate([
3284
+ state()
3285
+ ], KRChatbot.prototype, "_contextLimitMessage", void 0);
3286
+ __decorate([
3287
+ state()
3288
+ ], KRChatbot.prototype, "_telemetry", void 0);
3289
+ __decorate([
3290
+ state()
3291
+ ], KRChatbot.prototype, "_iterationLog", void 0);
3292
+ __decorate([
3293
+ state()
3294
+ ], KRChatbot.prototype, "_cacheCheckpoints", void 0);
3295
+ __decorate([
3296
+ state()
3297
+ ], KRChatbot.prototype, "_debugPanelOpen", void 0);
3298
+ __decorate([
3299
+ state()
3300
+ ], KRChatbot.prototype, "_debugDrawerWidth", void 0);
3301
+ __decorate([
3302
+ state()
3303
+ ], KRChatbot.prototype, "_isDebugResizing", void 0);
2487
3304
  __decorate([
2488
3305
  state()
2489
3306
  ], KRChatbot.prototype, "_pendingInternalFiles", void 0);
@@ -2499,6 +3316,12 @@ __decorate([
2499
3316
  __decorate([
2500
3317
  state()
2501
3318
  ], KRChatbot.prototype, "_skillResults", void 0);
3319
+ __decorate([
3320
+ state()
3321
+ ], KRChatbot.prototype, "_globalSkillResults", void 0);
3322
+ __decorate([
3323
+ state()
3324
+ ], KRChatbot.prototype, "_aiFrameworkConfig", void 0);
2502
3325
  __decorate([
2503
3326
  state()
2504
3327
  ], KRChatbot.prototype, "_skillHighlighted", void 0);