@kodaris/krubble-app-components 1.0.79 → 1.0.81

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.
@@ -5621,8 +5621,9 @@ let KRChatbot = class KRChatbot extends i$3 {
5621
5621
  * responses are dropped (mirrors the kr-combo-box `_requestId` pattern). Org and
5622
5622
  * global searches run concurrently into separate buffers, so each has its own.
5623
5623
  */
5624
- this._skillRequestId = 0;
5624
+ this._orgSkillRequestId = 0;
5625
5625
  this._globalSkillRequestId = 0;
5626
+ this._invocableSkillRequestId = 0;
5626
5627
  /**
5627
5628
  * Codes of global skills with an in-flight enable/disable request. The `+`
5628
5629
  * button reads this to show a pending state and ignore repeat clicks. Mutated
@@ -5731,24 +5732,31 @@ let KRChatbot = class KRChatbot extends i$3 {
5731
5732
  this._isDraggingFile = false;
5732
5733
  /** Whether the `/` skill menu is open above the composer. */
5733
5734
  this._skillMenuOpened = false;
5734
- /** Current `/` query (text after the slash), sent to `_searchSkills`. */
5735
+ /** Current `/` query (text after the slash), sent to `_searchOrgSkills`. */
5735
5736
  this._skillQuery = '';
5736
5737
  /**
5737
- * Latest org skill search results (this instance's own skills). Backs the `/`
5738
- * menu and the browse dialog's "Your Org" tab.
5738
+ * Latest org skill search results (this instance's own skills). Backs the
5739
+ * browse dialog's "Your Org" tab only — global skills never appear here.
5739
5740
  */
5740
- this._skillResults = [];
5741
+ this._orgSkills = [];
5741
5742
  /**
5742
5743
  * Latest shared-Kodaris (global) skill results, for the browse dialog's
5743
- * "Kodaris" tab. Kept separate from `_skillResults` so switching tabs shows
5744
+ * "Kodaris" tab. Kept separate from `_orgSkills` so switching tabs shows
5744
5745
  * fresh results immediately instead of the other tab's stale list — the browse
5745
5746
  * search fills both buffers at once.
5746
5747
  */
5747
- this._globalSkillResults = [];
5748
+ this._globalSkills = [];
5749
+ /**
5750
+ * Latest results for the `/` composer menu: this instance's own org skills PLUS
5751
+ * the global skills installed on it (both are invocable by code). A separate
5752
+ * buffer from the two browse-tab lists so merging installed globals into the
5753
+ * menu never leaks them into the "Your Org" tab.
5754
+ */
5755
+ this._invocableSkills = [];
5748
5756
  /**
5749
5757
  * AI framework UI config flags fetched from `aiFrameworkConfig` on connect.
5750
5758
  * `debugTelemetryEnabled` (same server-side gate as TOKEN_USAGE emission) turns
5751
- * on the debug drawer toggle and the `/test-warning` / `/test-limit` composer
5759
+ * on the debug drawer toggle and the `/test-context-warning` / `/test-context-limit` composer
5752
5760
  * options — known up front, before any message.
5753
5761
  */
5754
5762
  this._aiFrameworkConfig = {};
@@ -5759,8 +5767,8 @@ let KRChatbot = class KRChatbot extends i$3 {
5759
5767
  /** Whether the "Browse all skills" dialog is open. */
5760
5768
  this._skillBrowseOpened = false;
5761
5769
  /**
5762
- * Active tab in the browse dialog. `org` shows `_skillResults` (this instance's
5763
- * skills); `kodaris` shows `_globalSkillResults` (the shared global catalog).
5770
+ * Active tab in the browse dialog. `org` shows `_orgSkills` (this instance's
5771
+ * skills); `kodaris` shows `_globalSkills` (the shared global catalog).
5764
5772
  */
5765
5773
  this._skillBrowseSource = 'org';
5766
5774
  /** Search text in the browse dialog, routed to the active tab's search. */
@@ -6153,13 +6161,14 @@ let KRChatbot = class KRChatbot extends i$3 {
6153
6161
  textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
6154
6162
  // Open the skill menu while the message is a `/` command being typed (a
6155
6163
  // leading slash with no space yet). The text after the slash is the search
6156
- // query, run server-side via `_searchSkills`.
6164
+ // query, run server-side via `_searchInvocableSkills`.
6165
+ // (org skills + installed globals — everything invocable by /code)
6157
6166
  const slashMatch = this._inputValue.match(/^\/(\S*)$/);
6158
6167
  if (slashMatch) {
6159
6168
  this._skillMenuOpened = true;
6160
6169
  this._skillQuery = slashMatch[1];
6161
6170
  this._skillHighlighted = 0;
6162
- this._searchSkills(this._skillQuery);
6171
+ this._searchInvocableSkills(this._skillQuery);
6163
6172
  }
6164
6173
  else {
6165
6174
  this._skillMenuOpened = false;
@@ -6175,7 +6184,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6175
6184
  if (debugActions) {
6176
6185
  actionCount = 4;
6177
6186
  }
6178
- const count = actionCount + this._skillResults.length;
6187
+ const count = actionCount + this._invocableSkills.length;
6179
6188
  if (e.key === 'ArrowDown') {
6180
6189
  e.preventDefault();
6181
6190
  this._skillHighlighted = (this._skillHighlighted + 1) % count;
@@ -6220,7 +6229,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6220
6229
  if (debugActions && index === 2) {
6221
6230
  this._skillMenuOpened = false;
6222
6231
  this._send({
6223
- message: '/test-warning',
6232
+ message: '/test-context-warning',
6224
6233
  internalFiles: [],
6225
6234
  });
6226
6235
  return;
@@ -6228,19 +6237,19 @@ let KRChatbot = class KRChatbot extends i$3 {
6228
6237
  if (debugActions && index === 3) {
6229
6238
  this._skillMenuOpened = false;
6230
6239
  this._send({
6231
- message: '/test-limit',
6240
+ message: '/test-context-limit',
6232
6241
  internalFiles: [],
6233
6242
  });
6234
6243
  return;
6235
6244
  }
6236
- const skill = this._skillResults[index - actionCount];
6245
+ const skill = this._invocableSkills[index - actionCount];
6237
6246
  if (skill) {
6238
6247
  this._handleSkillSelect(skill);
6239
6248
  }
6240
6249
  }
6241
6250
  // --- Skills ---
6242
6251
  /**
6243
- * Search the Kodaris AI skills index and store the results in `_skillResults`.
6252
+ * Search the Kodaris AI skills index and store the results in `_orgSkills`.
6244
6253
  * The `/` menu and the browse dialog both call this; results are shared since
6245
6254
  * the two are never open at once. Mirrors the backend's own skill search (BM25
6246
6255
  * over name^3/description^2/instructions, active-only) so the menu ranks skills
@@ -6251,9 +6260,9 @@ let KRChatbot = class KRChatbot extends i$3 {
6251
6260
  * that arrive out of order. `/aiskills/search` returns a raw OpenSearch response,
6252
6261
  * so we lift each hit's `_source` (already in KRChatbotSkill shape) out.
6253
6262
  */
6254
- _searchSkills(query) {
6255
- this._skillRequestId++;
6256
- const requestId = this._skillRequestId;
6263
+ _searchOrgSkills(query) {
6264
+ this._orgSkillRequestId++;
6265
+ const requestId = this._orgSkillRequestId;
6257
6266
  const trimmed = query.trim();
6258
6267
  // An empty query matches everything; otherwise substring-match the query
6259
6268
  // against name/code/description. We use case-insensitive wildcard queries on
@@ -6303,6 +6312,11 @@ let KRChatbot = class KRChatbot extends i$3 {
6303
6312
  bool: {
6304
6313
  must: [must],
6305
6314
  filter: [{ term: { active: true } }],
6315
+ // The "Your Org" tab lists only this instance's own skills. Globally-
6316
+ // published skills belong to the "Kodaris" tab (fed by the registry),
6317
+ // so exclude them here. Documents without the field aren't matched by
6318
+ // the term, so ordinary org skills are kept.
6319
+ must_not: [{ term: { globalPublished: true } }],
6306
6320
  },
6307
6321
  },
6308
6322
  },
@@ -6310,17 +6324,106 @@ let KRChatbot = class KRChatbot extends i$3 {
6310
6324
  .then((r) => r.json())
6311
6325
  .then((res) => {
6312
6326
  // Ignore a stale response superseded by a newer search.
6313
- if (requestId !== this._skillRequestId) {
6327
+ if (requestId !== this._orgSkillRequestId) {
6314
6328
  return;
6315
6329
  }
6316
- this._skillResults = (res?.data?.hits?.hits || []).map((hit) => hit._source);
6330
+ this._orgSkills = (res?.data?.hits?.hits || []).map((hit) => hit._source);
6317
6331
  })
6318
6332
  .catch((error) => {
6319
6333
  console.error('kr-chatbot: skill search failed', error);
6320
6334
  });
6321
6335
  }
6322
6336
  /**
6323
- * Search the shared Kodaris global-skill registry into `_globalSkillResults`
6337
+ * Search skills for the `/` composer menu into `_invocableSkills`: this instance's
6338
+ * own org skills PLUS the global skills installed on it, since both are
6339
+ * invocable by code. They come from two indexes (the local aiskills search and
6340
+ * the registry search filtered client-side to enabled), so we fire both and
6341
+ * concat — org skills first, then installed globals. Skill codes are unique
6342
+ * across the two, so no dedup is needed. Kept out of `_orgSkills` so
6343
+ * installed globals never leak into the browse dialog's "Your Org" tab.
6344
+ * (Cross-list relevance ranking is a later concern; for now org skills lead.)
6345
+ */
6346
+ _searchInvocableSkills(query) {
6347
+ this._invocableSkillRequestId++;
6348
+ const requestId = this._invocableSkillRequestId;
6349
+ // Same wildcard matching as _searchOrgSkills (see there for the rationale).
6350
+ let must = { match_all: {} };
6351
+ if (query.trim()) {
6352
+ must = {
6353
+ bool: {
6354
+ should: [
6355
+ {
6356
+ wildcard: {
6357
+ 'name.raw': {
6358
+ value: `*${query.trim().replace(/([*?\\])/g, '\\$1')}*`,
6359
+ case_insensitive: true,
6360
+ }
6361
+ }
6362
+ },
6363
+ {
6364
+ wildcard: {
6365
+ 'code.raw': {
6366
+ value: `*${query.trim().replace(/([*?\\])/g, '\\$1')}*`,
6367
+ case_insensitive: true,
6368
+ }
6369
+ }
6370
+ },
6371
+ {
6372
+ wildcard: {
6373
+ 'description.raw': {
6374
+ value: `*${query.trim().replace(/([*?\\])/g, '\\$1')}*`,
6375
+ case_insensitive: true,
6376
+ }
6377
+ }
6378
+ },
6379
+ ],
6380
+ minimum_should_match: 1,
6381
+ },
6382
+ };
6383
+ }
6384
+ // Org skills — this instance's own catalog (globalPublished excluded).
6385
+ const orgSearch = KRHttp.fetch({
6386
+ url: '/api/system/opensearch/aiskills/search',
6387
+ method: 'POST',
6388
+ body: {
6389
+ size: 100,
6390
+ query: {
6391
+ bool: {
6392
+ must: [must],
6393
+ filter: [{ term: { active: true } }],
6394
+ must_not: [{ term: { globalPublished: true } }],
6395
+ },
6396
+ },
6397
+ },
6398
+ })
6399
+ .then((r) => r.json())
6400
+ .then((res) => (res?.data?.hits?.hits || []).map((hit) => hit._source))
6401
+ .catch((error) => {
6402
+ console.error('kr-chatbot: invocable skill search failed', error);
6403
+ return [];
6404
+ });
6405
+ // Installed globals — the registry catalog, kept only where enabled=true.
6406
+ const globalSearch = KRHttp.fetch({
6407
+ url: '/api/system/opensearch/aiskills/global/search',
6408
+ method: 'POST',
6409
+ body: { query: query.trim() },
6410
+ })
6411
+ .then((r) => r.json())
6412
+ .then((res) => (res?.data || []).filter((s) => !!s.enabled))
6413
+ .catch((error) => {
6414
+ console.error('kr-chatbot: installed global skill search failed', error);
6415
+ return [];
6416
+ });
6417
+ Promise.all([orgSearch, globalSearch]).then(([orgSkills, globalSkills]) => {
6418
+ // Ignore a stale response superseded by a newer search.
6419
+ if (requestId !== this._invocableSkillRequestId) {
6420
+ return;
6421
+ }
6422
+ this._invocableSkills = [...orgSkills, ...globalSkills];
6423
+ });
6424
+ }
6425
+ /**
6426
+ * Search the shared Kodaris global-skill registry into `_globalSkills`
6324
6427
  * (the browse dialog's "Kodaris" tab). Unlike `/aiskills/search`,
6325
6428
  * `/aiskills/global/search` returns a digested list
6326
6429
  * (`data: [{ code, name, description, enabled }]`) already in `KRChatbotSkill`
@@ -6341,7 +6444,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6341
6444
  if (requestId !== this._globalSkillRequestId) {
6342
6445
  return;
6343
6446
  }
6344
- this._globalSkillResults = (res?.data || []);
6447
+ this._globalSkills = (res?.data || []);
6345
6448
  })
6346
6449
  .catch((error) => {
6347
6450
  console.error('kr-chatbot: global skill search failed', error);
@@ -6353,13 +6456,16 @@ let KRChatbot = class KRChatbot extends i$3 {
6353
6456
  * matching card. Ignores clicks while a request for the same code is pending.
6354
6457
  */
6355
6458
  _handleGlobalSkillEnable(skill, e) {
6356
- // The card body inserts the skill; the `+` button must not also do that.
6459
+ // The card body inserts the skill; the install button must not also do that.
6357
6460
  e.stopPropagation();
6358
6461
  if (this._globalSkillPending.has(skill.code)) {
6359
6462
  return;
6360
6463
  }
6464
+ // Drive the clicked button's own spinner (KRButton disables itself while
6465
+ // loading, so a second click can't fire until the request settles).
6466
+ const btn = e.currentTarget;
6361
6467
  this._globalSkillPending.add(skill.code);
6362
- this.requestUpdate();
6468
+ btn?.showLoading();
6363
6469
  KRHttp.fetch({
6364
6470
  url: `/api/system/opensearch/aiskills/global/${encodeURIComponent(skill.code)}/enabled`,
6365
6471
  method: 'POST',
@@ -6367,16 +6473,18 @@ let KRChatbot = class KRChatbot extends i$3 {
6367
6473
  })
6368
6474
  .then((r) => r.json())
6369
6475
  .then((res) => {
6370
- // Reflect the server's returned flag on the card.
6476
+ // Reflect the server's returned flag on the card. The button label/style
6477
+ // re-render from skill.enabled; reset() clears the spinner to idle.
6371
6478
  skill.enabled = res?.data?.enabled;
6479
+ btn?.reset();
6372
6480
  this.requestUpdate();
6373
6481
  })
6374
6482
  .catch((error) => {
6375
6483
  console.error('kr-chatbot: global skill enable failed', error);
6484
+ btn?.showError('Error');
6376
6485
  })
6377
6486
  .finally(() => {
6378
6487
  this._globalSkillPending.delete(skill.code);
6379
- this.requestUpdate();
6380
6488
  });
6381
6489
  }
6382
6490
  /** The `+` button toggles the skill/attachment menu open (showing all skills). */
@@ -6388,7 +6496,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6388
6496
  this._skillQuery = '';
6389
6497
  this._skillHighlighted = 0;
6390
6498
  this._skillMenuOpened = true;
6391
- this._searchSkills('');
6499
+ this._searchInvocableSkills('');
6392
6500
  }
6393
6501
  /** Insert `/code ` into the composer and focus it, ready for the message. */
6394
6502
  _handleSkillSelect(skill) {
@@ -6421,7 +6529,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6421
6529
  this._skillBrowseQuery = '';
6422
6530
  this._skillBrowseOpened = true;
6423
6531
  // Fill both tabs up front so switching between them is instant, not stale.
6424
- this._searchSkills('');
6532
+ this._searchOrgSkills('');
6425
6533
  this._searchGlobalSkills('');
6426
6534
  }
6427
6535
  _handleSkillBrowseClose() {
@@ -6430,7 +6538,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6430
6538
  _handleSkillBrowseSearch(e) {
6431
6539
  this._skillBrowseQuery = e.target.value;
6432
6540
  // Filter both catalogs so whichever tab the user switches to is already current.
6433
- this._searchSkills(this._skillBrowseQuery);
6541
+ this._searchOrgSkills(this._skillBrowseQuery);
6434
6542
  this._searchGlobalSkills(this._skillBrowseQuery);
6435
6543
  }
6436
6544
  _handleSkillBrowseTabChange(e) {
@@ -6875,7 +6983,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6875
6983
  // --- Render ---
6876
6984
  /** The `/` skill menu shown above the composer while typing a slash command. */
6877
6985
  _renderSkillMenu() {
6878
- const skills = this._skillResults;
6986
+ const skills = this._invocableSkills;
6879
6987
  // Debug test triggers are extra actions after Attach Files / Browse. The
6880
6988
  // flat keyboard index counts actions first, then skills, so the skills'
6881
6989
  // index offset is the number of actions shown.
@@ -6913,7 +7021,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6913
7021
  })}
6914
7022
  @click=${() => this._activateSkillMenuItem(2)}
6915
7023
  >
6916
- Test: context warning
7024
+ Test Context Warning
6917
7025
  </button>
6918
7026
  <button
6919
7027
  class=${e({
@@ -6922,7 +7030,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6922
7030
  })}
6923
7031
  @click=${() => this._activateSkillMenuItem(3)}
6924
7032
  >
6925
- Test: context limit
7033
+ Test Context Limit
6926
7034
  </button>
6927
7035
  ` : A}
6928
7036
  </div>
@@ -6982,69 +7090,71 @@ let KRChatbot = class KRChatbot extends i$3 {
6982
7090
  /**
6983
7091
  * The card grid of skill search results for the browse dialog. Clicking a card
6984
7092
  * inserts `/code ` into the composer. In the shared-Kodaris (`global`) tab each
6985
- * card also gets a `+` install button that enables the skill on this instance
6986
- * (turning into a ✓ once installed).
7093
+ * card also gets a footer row with an Enabled toggle that installs/removes the
7094
+ * skill on this instance.
6987
7095
  */
6988
7096
  _renderSkillGrid(global = false) {
6989
- let skills = this._skillResults;
7097
+ let skills = this._orgSkills;
6990
7098
  if (global) {
6991
- skills = this._globalSkillResults;
7099
+ skills = this._globalSkills;
6992
7100
  }
6993
7101
  if (!skills.length) {
6994
7102
  return A;
6995
7103
  }
6996
7104
  return b `
6997
7105
  <div class="skill-browse__grid">
6998
- ${skills.map((skill) => b `
6999
- <div
7000
- class="skill-card"
7001
- role="button"
7002
- tabindex="0"
7003
- @click=${() => this._handleSkillSelect(skill)}
7004
- @keydown=${(e) => this._handleSkillCardKeydown(e, skill)}
7005
- >
7006
- <span class="skill-card__name">${skill.name}</span>
7007
- ${skill.description ? b `
7008
- <span class="skill-card__desc">${skill.description}</span>
7009
- ` : A}
7010
- ${global ? this._renderSkillInstall(skill) : A}
7011
- </div>
7012
- `)}
7106
+ ${skills.map((skill) => {
7107
+ // A global skill must be installed before it can be chosen — inserting
7108
+ // /code for a skill not enabled on this instance would just fail. So an
7109
+ // uninstalled global card does nothing on click; only its Install button
7110
+ // is interactive.
7111
+ const selectable = !global || !!skill.enabled;
7112
+ return b `
7113
+ <div
7114
+ class="skill-card"
7115
+ role=${selectable ? 'button' : A}
7116
+ tabindex=${selectable ? 0 : A}
7117
+ @click=${selectable ? () => this._handleSkillSelect(skill) : A}
7118
+ @keydown=${selectable ? (e) => this._handleSkillCardKeydown(e, skill) : A}
7119
+ >
7120
+ <span class="skill-card__name">${skill.name}</span>
7121
+ ${skill.description ? b `
7122
+ <span class="skill-card__desc">${skill.description}</span>
7123
+ ` : A}
7124
+ ${global ? this._renderSkillToggle(skill) : A}
7125
+ </div>
7126
+ `;
7127
+ })}
7013
7128
  </div>
7014
7129
  `;
7015
7130
  }
7131
+ /** Keep a click on the card footer from bubbling to the card's insert handler. */
7132
+ _stopFooterClick(e) {
7133
+ e.stopPropagation();
7134
+ }
7016
7135
  /**
7017
- * The install affordance on a global skill card: a `+` button that enables the
7018
- * skill on this instance, showing a ✓ once installed and a disabled state
7019
- * while the enable/disable request is in flight.
7136
+ * The install affordance on a global skill card: a footer row with an
7137
+ * install/remove button. The button owns its own spinner via KRButton's
7138
+ * showLoading()/showError() while the enable/disable request is in flight, so
7139
+ * the async call has clear feedback. Clicks are stopped from bubbling so the
7140
+ * button never also inserts the skill (the card body does that).
7020
7141
  */
7021
- _renderSkillInstall(skill) {
7142
+ _renderSkillToggle(skill) {
7143
+ const on = !!skill.enabled;
7022
7144
  return b `
7023
- <button
7024
- class=${e({
7025
- 'skill-card__install': true,
7026
- 'skill-card__install--installed': !!skill.enabled,
7027
- })}
7028
- ?disabled=${this._globalSkillPending.has(skill.code)}
7029
- title=${!!skill.enabled ? 'Installed — click to remove' : 'Install this skill'}
7030
- aria-label=${!!skill.enabled ? 'Remove skill' : 'Install skill'}
7031
- @click=${(e) => this._handleGlobalSkillEnable(skill, e)}
7145
+ <div
7146
+ class="skill-card__footer"
7147
+ @click=${this._stopFooterClick}
7032
7148
  >
7033
- <svg
7034
- xmlns="http://www.w3.org/2000/svg"
7035
- fill="none"
7036
- viewBox="0 0 24 24"
7037
- stroke-width="2"
7038
- stroke="currentColor"
7039
- class="skill-card__install-icon"
7040
- >
7041
- <path
7042
- stroke-linecap="round"
7043
- stroke-linejoin="round"
7044
- d=${!!skill.enabled ? 'm4.5 12.75 6 6 9-13.5' : 'M12 4.5v15m7.5-7.5h-15'}
7045
- />
7046
- </svg>
7047
- </button>
7149
+ <span class="skill-card__price">Free</span>
7150
+ <kr-button
7151
+ class="skill-card__install-btn"
7152
+ size="small"
7153
+ variant=${on ? 'outline' : 'flat'}
7154
+ color=${on ? 'secondary' : 'primary'}
7155
+ @click=${(e) => this._handleGlobalSkillEnable(skill, e)}
7156
+ >${on ? 'Uninstall' : 'Install'}</kr-button>
7157
+ </div>
7048
7158
  `;
7049
7159
  }
7050
7160
  /**
@@ -7505,9 +7615,7 @@ let KRChatbot = class KRChatbot extends i$3 {
7505
7615
  </svg>
7506
7616
  </button>
7507
7617
  </div>
7508
- ` : A}
7509
-
7510
- ${this._contextLimitReached ? b `
7618
+ ` : this._contextLimitReached ? b `
7511
7619
  <div class="context-limit" role="alert">
7512
7620
  ${this._contextLimitMessage
7513
7621
  || 'This conversation has reached its limit. Please start a new one to continue.'}
@@ -8093,14 +8201,22 @@ KRChatbot.styles = i$6 `
8093
8201
 
8094
8202
  /* Context warning banner (user-facing, dismissible, non-blocking) */
8095
8203
  .context-warning {
8096
- padding: 8px 16px;
8204
+ /* Match the message text column: 768px cap minus its 32px side padding
8205
+ (704px), and viewport minus that padding on narrower screens. */
8206
+ width: 100%;
8207
+ max-width: 735px;
8208
+ margin: 8px auto 0;
8209
+ padding: 10px 14px;
8210
+ border: 1px solid #fde68a;
8211
+ border-radius: 8px;
8097
8212
  background: #fffbeb;
8098
8213
  color: #92400e;
8099
8214
  font-size: 13px;
8215
+ line-height: 1.4;
8100
8216
  display: flex;
8101
8217
  align-items: center;
8102
8218
  justify-content: space-between;
8103
- gap: 8px;
8219
+ gap: 10px;
8104
8220
  flex-shrink: 0;
8105
8221
  }
8106
8222
 
@@ -8113,6 +8229,11 @@ KRChatbot.styles = i$6 `
8113
8229
  display: flex;
8114
8230
  align-items: center;
8115
8231
  flex-shrink: 0;
8232
+ opacity: 0.7;
8233
+ }
8234
+
8235
+ .context-warning-dismiss:hover {
8236
+ opacity: 1;
8116
8237
  }
8117
8238
 
8118
8239
  .context-warning-dismiss-icon {
@@ -8122,11 +8243,18 @@ KRChatbot.styles = i$6 `
8122
8243
 
8123
8244
  /* Context-limit notice (user-facing, blocking — steers to a new conversation) */
8124
8245
  .context-limit {
8125
- padding: 10px 16px;
8246
+ /* Match the message text column: 768px cap minus its 32px side padding
8247
+ (704px), and viewport minus that padding on narrower screens. */
8248
+ width: 100%;
8249
+ max-width: 735px;
8250
+ margin: 8px auto 0;
8251
+ padding: 10px 14px;
8252
+ border: 1px solid #fecaca;
8253
+ border-radius: 8px;
8126
8254
  background: var(--kr-chatbot-error-bg);
8127
8255
  color: var(--kr-chatbot-error-text);
8128
8256
  font-size: 13px;
8129
- line-height: 1.5;
8257
+ line-height: 1.4;
8130
8258
  flex-shrink: 0;
8131
8259
  }
8132
8260
 
@@ -8654,7 +8782,7 @@ KRChatbot.styles = i$6 `
8654
8782
  height: 100%;
8655
8783
  overflow-y: auto;
8656
8784
  display: grid;
8657
- grid-template-columns: repeat(3, 1fr);
8785
+ grid-template-columns: repeat(2, 1fr);
8658
8786
  grid-auto-rows: min-content;
8659
8787
  align-content: start;
8660
8788
  gap: 12px;
@@ -8682,53 +8810,31 @@ KRChatbot.styles = i$6 `
8682
8810
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
8683
8811
  }
8684
8812
 
8685
- /* Install button on global skill cards. Absolutely positioned in the corner
8686
- so it sits above the card's own click target; leave room via the name's
8687
- padding-right below. */
8688
- .skill-card__install {
8689
- position: absolute;
8690
- top: 10px;
8691
- right: 10px;
8692
- display: inline-flex;
8813
+ /* Footer row on global skill cards: install button on the left, price on the
8814
+ right. margin-top:auto pins it to the bottom so cards in a row align their
8815
+ footers regardless of description length. Negative horizontal margins
8816
+ (matching the card's 14px padding) pull the top border out to the full
8817
+ card width; the content is re-padded horizontally to stay inset. */
8818
+ .skill-card__footer {
8819
+ display: flex;
8693
8820
  align-items: center;
8694
- justify-content: center;
8695
- width: 24px;
8696
- height: 24px;
8697
- padding: 0;
8698
- border: 1px solid #d1d5db;
8699
- border-radius: 6px;
8700
- background: white;
8701
- color: #374151;
8702
- cursor: pointer;
8703
- transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
8704
- }
8705
-
8706
- .skill-card__install:hover:not(:disabled) {
8707
- border-color: var(--kr-chatbot-primary);
8708
- color: var(--kr-chatbot-primary);
8709
- }
8710
-
8711
- .skill-card__install:disabled {
8712
- opacity: 0.5;
8713
- cursor: default;
8714
- }
8715
-
8716
- /* Installed state: filled check. */
8717
- .skill-card__install--installed {
8718
- background: var(--kr-chatbot-primary);
8719
- border-color: var(--kr-chatbot-primary);
8720
- color: white;
8821
+ justify-content: space-between;
8822
+ gap: 8px;
8823
+ margin-top: 8px;
8824
+ margin-left: -14px;
8825
+ margin-right: -14px;
8826
+ padding: 14px 14px 0;
8827
+ border-top: 1px solid #e5e7eb;
8721
8828
  }
8722
8829
 
8723
- .skill-card__install-icon {
8724
- width: 15px;
8725
- height: 15px;
8830
+ .skill-card__price {
8831
+ font-size: 13px;
8832
+ font-weight: 600;
8833
+ color: #000000;
8726
8834
  }
8727
8835
 
8728
8836
  .skill-card__name {
8729
8837
  font-weight: 600;
8730
- /* Leave room for the corner install button on global cards. */
8731
- padding-right: 28px;
8732
8838
  }
8733
8839
 
8734
8840
  .skill-card__desc {
@@ -8917,10 +9023,13 @@ __decorate([
8917
9023
  ], KRChatbot.prototype, "_skillQuery", void 0);
8918
9024
  __decorate([
8919
9025
  r()
8920
- ], KRChatbot.prototype, "_skillResults", void 0);
9026
+ ], KRChatbot.prototype, "_orgSkills", void 0);
9027
+ __decorate([
9028
+ r()
9029
+ ], KRChatbot.prototype, "_globalSkills", void 0);
8921
9030
  __decorate([
8922
9031
  r()
8923
- ], KRChatbot.prototype, "_globalSkillResults", void 0);
9032
+ ], KRChatbot.prototype, "_invocableSkills", void 0);
8924
9033
  __decorate([
8925
9034
  r()
8926
9035
  ], KRChatbot.prototype, "_aiFrameworkConfig", void 0);