aimeat 1.23.2 → 1.24.0

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.
Files changed (239) hide show
  1. package/dist/locales/en.json +580 -21
  2. package/dist/locales/fi.json +592 -33
  3. package/dist/public/components/ContactCard.js +28 -0
  4. package/dist/public/css/views/admin.css +12 -0
  5. package/dist/public/css/views/agents-detail.css +62 -10
  6. package/dist/public/css/views/landing.css +87 -0
  7. package/dist/public/css/views/offers.css +96 -0
  8. package/dist/public/css/views/portfolio.css +18 -0
  9. package/dist/public/css/views/profile.css +153 -8
  10. package/dist/public/css/views/start.css +96 -0
  11. package/dist/public/css/views/workflows.css +111 -0
  12. package/dist/public/img/business-hero.png +0 -0
  13. package/dist/public/img/toimitus.png +0 -0
  14. package/dist/public/js/components/tag-cloud.js +46 -31
  15. package/dist/public/js/components/tag-editor.js +2 -2
  16. package/dist/public/js/recents.js +40 -0
  17. package/dist/public/js/services/admin.js +6 -0
  18. package/dist/public/js/services/agents.js +34 -0
  19. package/dist/public/js/services/offers.js +152 -0
  20. package/dist/public/js/services/packages.js +1 -0
  21. package/dist/public/js/services/workflows.js +57 -0
  22. package/dist/public/llms-template.txt +7 -0
  23. package/dist/public/spa.html +35 -3
  24. package/dist/public/views/admin/subdomains-tab.js +182 -0
  25. package/dist/public/views/admin.js +2 -0
  26. package/dist/public/views/business.js +119 -0
  27. package/dist/public/views/how-it-works.js +159 -0
  28. package/dist/public/views/landing.js +235 -0
  29. package/dist/public/views/portfolio.js +120 -23
  30. package/dist/public/views/pricing.js +75 -0
  31. package/dist/public/views/profile/access-tab.js +124 -51
  32. package/dist/public/views/profile/agents/agent-card.js +68 -37
  33. package/dist/public/views/profile/agents/shared-board.js +56 -22
  34. package/dist/public/views/profile/agents/tab-activity.js +24 -12
  35. package/dist/public/views/profile/agents/tab-agent-config.js +31 -1
  36. package/dist/public/views/profile/agents/tab-data-access.js +9 -13
  37. package/dist/public/views/profile/agents/tab-integration.js +27 -1
  38. package/dist/public/views/profile/agents/tab-messages.js +6 -11
  39. package/dist/public/views/profile/agents/tab-quality.js +45 -5
  40. package/dist/public/views/profile/agents-tab.js +41 -6
  41. package/dist/public/views/profile/apps-tab.js +7 -1
  42. package/dist/public/views/profile/knowledge-tab.js +4 -1
  43. package/dist/public/views/profile/landing-page.js +428 -123
  44. package/dist/public/views/profile/memory-tab.js +409 -211
  45. package/dist/public/views/profile/nodes-tab.js +18 -3
  46. package/dist/public/views/profile/offers-tab.js +310 -0
  47. package/dist/public/views/profile/organisms-tab.js +289 -28
  48. package/dist/public/views/profile/packages-tab.js +152 -16
  49. package/dist/public/views/profile/portfolio-tab.js +98 -31
  50. package/dist/public/views/profile/workflows-form.js +220 -0
  51. package/dist/public/views/profile/workflows-tab.js +244 -0
  52. package/dist/public/views/profile.js +4 -0
  53. package/dist/public/views/start-flows.js +281 -0
  54. package/dist/public/views/start.js +258 -0
  55. package/dist/src/cli/connect/mcp/tools/index.d.ts.map +1 -1
  56. package/dist/src/cli/connect/mcp/tools/index.js +2 -0
  57. package/dist/src/cli/connect/mcp/tools/index.js.map +1 -1
  58. package/dist/src/cli/connect/mcp/tools/workflows.d.ts +12 -0
  59. package/dist/src/cli/connect/mcp/tools/workflows.d.ts.map +1 -0
  60. package/dist/src/cli/connect/mcp/tools/workflows.js +37 -0
  61. package/dist/src/cli/connect/mcp/tools/workflows.js.map +1 -0
  62. package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
  63. package/dist/src/cli/connect/tool-call.js +37 -0
  64. package/dist/src/cli/connect/tool-call.js.map +1 -1
  65. package/dist/src/generated/api-types.d.ts +1474 -66
  66. package/dist/src/generated/api-types.d.ts.map +1 -1
  67. package/dist/src/generated/prisma-postgres/edge.js +15 -3
  68. package/dist/src/generated/prisma-postgres/index-browser.js +12 -0
  69. package/dist/src/generated/prisma-postgres/index.d.ts +1476 -160
  70. package/dist/src/generated/prisma-postgres/index.js +15 -3
  71. package/dist/src/generated/prisma-postgres/package.json +1 -1
  72. package/dist/src/generated/prisma-postgres/schema.prisma +11 -0
  73. package/dist/src/generated/prisma-postgres/wasm.js +15 -3
  74. package/dist/src/mcp/annotations.d.ts.map +1 -1
  75. package/dist/src/mcp/annotations.js +3 -0
  76. package/dist/src/mcp/annotations.js.map +1 -1
  77. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  78. package/dist/src/mcp/catalog/definitions.js +27 -0
  79. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  80. package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
  81. package/dist/src/mcp/catalog/scopes.js +5 -1
  82. package/dist/src/mcp/catalog/scopes.js.map +1 -1
  83. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  84. package/dist/src/mcp/catalog/surfaces.js +1 -0
  85. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  86. package/dist/src/mcp/index.d.ts.map +1 -1
  87. package/dist/src/mcp/index.js +2 -0
  88. package/dist/src/mcp/index.js.map +1 -1
  89. package/dist/src/mcp/workflows.d.ts +18 -0
  90. package/dist/src/mcp/workflows.d.ts.map +1 -0
  91. package/dist/src/mcp/workflows.js +68 -0
  92. package/dist/src/mcp/workflows.js.map +1 -0
  93. package/dist/src/middleware/subdomain.d.ts +28 -0
  94. package/dist/src/middleware/subdomain.d.ts.map +1 -0
  95. package/dist/src/middleware/subdomain.js +32 -0
  96. package/dist/src/middleware/subdomain.js.map +1 -0
  97. package/dist/src/models/offer-schemas.d.ts +236 -0
  98. package/dist/src/models/offer-schemas.d.ts.map +1 -0
  99. package/dist/src/models/offer-schemas.js +112 -0
  100. package/dist/src/models/offer-schemas.js.map +1 -0
  101. package/dist/src/models/workflow-schemas.d.ts +220 -0
  102. package/dist/src/models/workflow-schemas.d.ts.map +1 -0
  103. package/dist/src/models/workflow-schemas.js +102 -0
  104. package/dist/src/models/workflow-schemas.js.map +1 -0
  105. package/dist/src/routes/agent-tasks.d.ts.map +1 -1
  106. package/dist/src/routes/agent-tasks.js +49 -0
  107. package/dist/src/routes/agent-tasks.js.map +1 -1
  108. package/dist/src/routes/agents.d.ts.map +1 -1
  109. package/dist/src/routes/agents.js +146 -1
  110. package/dist/src/routes/agents.js.map +1 -1
  111. package/dist/src/routes/apps-backup.d.ts +20 -0
  112. package/dist/src/routes/apps-backup.d.ts.map +1 -0
  113. package/dist/src/routes/apps-backup.js +165 -0
  114. package/dist/src/routes/apps-backup.js.map +1 -0
  115. package/dist/src/routes/ghii.d.ts.map +1 -1
  116. package/dist/src/routes/ghii.js +4 -0
  117. package/dist/src/routes/ghii.js.map +1 -1
  118. package/dist/src/routes/instances.d.ts +2 -1
  119. package/dist/src/routes/instances.d.ts.map +1 -1
  120. package/dist/src/routes/instances.js +59 -1
  121. package/dist/src/routes/instances.js.map +1 -1
  122. package/dist/src/routes/memory.d.ts.map +1 -1
  123. package/dist/src/routes/memory.js +5 -0
  124. package/dist/src/routes/memory.js.map +1 -1
  125. package/dist/src/routes/packages.d.ts.map +1 -1
  126. package/dist/src/routes/packages.js +32 -0
  127. package/dist/src/routes/packages.js.map +1 -1
  128. package/dist/src/routes/permissions.d.ts +12 -0
  129. package/dist/src/routes/permissions.d.ts.map +1 -1
  130. package/dist/src/routes/permissions.js +35 -3
  131. package/dist/src/routes/permissions.js.map +1 -1
  132. package/dist/src/routes/portal.d.ts +3 -0
  133. package/dist/src/routes/portal.d.ts.map +1 -1
  134. package/dist/src/routes/portal.js +12 -0
  135. package/dist/src/routes/portal.js.map +1 -1
  136. package/dist/src/routes/public-stats.d.ts +15 -0
  137. package/dist/src/routes/public-stats.d.ts.map +1 -0
  138. package/dist/src/routes/public-stats.js +90 -0
  139. package/dist/src/routes/public-stats.js.map +1 -0
  140. package/dist/src/routes/sse.d.ts +7 -2
  141. package/dist/src/routes/sse.d.ts.map +1 -1
  142. package/dist/src/routes/sse.js +35 -5
  143. package/dist/src/routes/sse.js.map +1 -1
  144. package/dist/src/routes/stats.d.ts.map +1 -1
  145. package/dist/src/routes/stats.js +13 -0
  146. package/dist/src/routes/stats.js.map +1 -1
  147. package/dist/src/routes/subdomains.d.ts +33 -0
  148. package/dist/src/routes/subdomains.d.ts.map +1 -0
  149. package/dist/src/routes/subdomains.js +208 -0
  150. package/dist/src/routes/subdomains.js.map +1 -0
  151. package/dist/src/routes/workflows.d.ts +30 -0
  152. package/dist/src/routes/workflows.d.ts.map +1 -0
  153. package/dist/src/routes/workflows.js +178 -0
  154. package/dist/src/routes/workflows.js.map +1 -0
  155. package/dist/src/server-bootstrap/routes-loader.d.ts +2 -0
  156. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  157. package/dist/src/server-bootstrap/routes-loader.js +19 -2
  158. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  159. package/dist/src/server-bootstrap/service-init.d.ts +2 -0
  160. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  161. package/dist/src/server-bootstrap/service-init.js +6 -0
  162. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  163. package/dist/src/server.d.ts.map +1 -1
  164. package/dist/src/server.js +5 -0
  165. package/dist/src/server.js.map +1 -1
  166. package/dist/src/services/apps-backup-export.d.ts +22 -0
  167. package/dist/src/services/apps-backup-export.d.ts.map +1 -0
  168. package/dist/src/services/apps-backup-export.js +136 -0
  169. package/dist/src/services/apps-backup-export.js.map +1 -0
  170. package/dist/src/services/apps-backup-import.d.ts +120 -0
  171. package/dist/src/services/apps-backup-import.d.ts.map +1 -0
  172. package/dist/src/services/apps-backup-import.js +259 -0
  173. package/dist/src/services/apps-backup-import.js.map +1 -0
  174. package/dist/src/services/component-registrar.d.ts +17 -0
  175. package/dist/src/services/component-registrar.d.ts.map +1 -1
  176. package/dist/src/services/component-registrar.js +63 -5
  177. package/dist/src/services/component-registrar.js.map +1 -1
  178. package/dist/src/services/handbooks/agent.d.ts +1 -1
  179. package/dist/src/services/handbooks/agent.d.ts.map +1 -1
  180. package/dist/src/services/handbooks/agent.js +10 -0
  181. package/dist/src/services/handbooks/agent.js.map +1 -1
  182. package/dist/src/services/handbooks/appdev.d.ts +1 -1
  183. package/dist/src/services/handbooks/appdev.d.ts.map +1 -1
  184. package/dist/src/services/handbooks/appdev.js +5 -2
  185. package/dist/src/services/handbooks/appdev.js.map +1 -1
  186. package/dist/src/services/scheduler.d.ts +6 -0
  187. package/dist/src/services/scheduler.d.ts.map +1 -1
  188. package/dist/src/services/scheduler.js +22 -0
  189. package/dist/src/services/scheduler.js.map +1 -1
  190. package/dist/src/services/schema-validator.d.ts +9 -0
  191. package/dist/src/services/schema-validator.d.ts.map +1 -1
  192. package/dist/src/services/schema-validator.js +16 -0
  193. package/dist/src/services/schema-validator.js.map +1 -1
  194. package/dist/src/services/workflow/engine.d.ts +84 -0
  195. package/dist/src/services/workflow/engine.d.ts.map +1 -0
  196. package/dist/src/services/workflow/engine.js +545 -0
  197. package/dist/src/services/workflow/engine.js.map +1 -0
  198. package/dist/src/services/workflow/eval-context.d.ts +23 -0
  199. package/dist/src/services/workflow/eval-context.d.ts.map +1 -0
  200. package/dist/src/services/workflow/eval-context.js +52 -0
  201. package/dist/src/services/workflow/eval-context.js.map +1 -0
  202. package/dist/src/services/workflow/lifecycle.d.ts +38 -0
  203. package/dist/src/services/workflow/lifecycle.d.ts.map +1 -0
  204. package/dist/src/services/workflow/lifecycle.js +99 -0
  205. package/dist/src/services/workflow/lifecycle.js.map +1 -0
  206. package/dist/src/services/workflow/signal-eval.d.ts +70 -0
  207. package/dist/src/services/workflow/signal-eval.d.ts.map +1 -0
  208. package/dist/src/services/workflow/signal-eval.js +174 -0
  209. package/dist/src/services/workflow/signal-eval.js.map +1 -0
  210. package/dist/src/services/workflow/store.d.ts +89 -0
  211. package/dist/src/services/workflow/store.d.ts.map +1 -0
  212. package/dist/src/services/workflow/store.js +284 -0
  213. package/dist/src/services/workflow/store.js.map +1 -0
  214. package/dist/src/storage/interface.d.ts +13 -3
  215. package/dist/src/storage/interface.d.ts.map +1 -1
  216. package/dist/src/storage/interface.js.map +1 -1
  217. package/dist/src/storage/providers/mongodb/index.d.ts +10 -1
  218. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  219. package/dist/src/storage/providers/mongodb/index.js +76 -0
  220. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  221. package/dist/src/storage/providers/sqlite/index.d.ts +8 -1
  222. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  223. package/dist/src/storage/providers/sqlite/index.js +61 -0
  224. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  225. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  226. package/dist/src/storage/providers/sqlite/schema.js +11 -0
  227. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  228. package/dist/src/storage/repositories/index.d.ts +1 -0
  229. package/dist/src/storage/repositories/index.d.ts.map +1 -1
  230. package/dist/src/storage/repositories/package.repository.d.ts +2 -0
  231. package/dist/src/storage/repositories/package.repository.d.ts.map +1 -1
  232. package/dist/src/storage/repositories/subdomain-site.repository.d.ts +19 -0
  233. package/dist/src/storage/repositories/subdomain-site.repository.d.ts.map +1 -0
  234. package/dist/src/storage/repositories/subdomain-site.repository.js +2 -0
  235. package/dist/src/storage/repositories/subdomain-site.repository.js.map +1 -0
  236. package/dist/static/app-catalog.html +1139 -28
  237. package/package.json +1 -1
  238. package/prisma/schema.postgres.prisma +11 -0
  239. package/prisma/schema.prisma +11 -0
@@ -12,12 +12,20 @@
12
12
  * v1.1.0 -- 2026-03-17 -- Replace inline styles with CSS classes
13
13
  * v1.2.0 -- 2026-05-21 -- Add Federation Access section for auth consent management
14
14
  * v1.3.0 -- 2026-05-21 -- Add Sharing Groups and Agent Defaults sections
15
+ * v1.4.0 -- 2026-06-10 -- Security-page clarity round: federation allow-all warning is a
16
+ * caption under the checkbox and the allowlist dims/disables while allow-all is on;
17
+ * Public Key reads the real key from GET /v1/ghii/me (+ explanatory text when absent);
18
+ * JWT row shows "Valid until HH:MM (h)" decoded from the token; copy buttons on
19
+ * GHII/Node/MCP endpoint; empty token/group states are one-line rows; section headers
20
+ * neutral (access-h3, emojis dropped) and Add Node / New Token / New Group de-accented;
21
+ * Token Budget value is click-to-edit and the empty-rules state shows an example rule;
22
+ * sharing-groups section accepts the Memory tab's deep link (aimeat.access.focus).
15
23
  */
16
24
  import { h } from 'preact';
17
25
  import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
18
26
  import htm from 'htm';
19
27
  const html = htm.bind(h);
20
- import { t } from '/js/i18n.js';
28
+ import { t, getLocale } from '/js/i18n.js';
21
29
  import { escHtml, copyToClipboard } from '/js/utils.js';
22
30
  import { getNodeUrl } from '/js/services/auth.js';
23
31
  import { apiGet, apiPost, apiDelete } from '/js/api.js';
@@ -67,6 +75,17 @@ function SharingGroupsSection({ showToast }) {
67
75
 
68
76
  useEffect(() => { loadGroups(); }, [loadGroups]);
69
77
 
78
+ // Deep link from the Memory tab's "Create a group →": scroll here and open the form.
79
+ useEffect(() => {
80
+ try {
81
+ if (sessionStorage.getItem('aimeat.access.focus') === 'groups') {
82
+ sessionStorage.removeItem('aimeat.access.focus');
83
+ setShowCreate(true);
84
+ setTimeout(() => document.getElementById('access-sharing-groups')?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 400);
85
+ }
86
+ } catch { /* noop */ }
87
+ }, []);
88
+
70
89
  // Live update listener
71
90
  const liveRef = useRef(loadGroups);
72
91
  liveRef.current = loadGroups;
@@ -332,23 +351,27 @@ function SharingGroupsSection({ showToast }) {
332
351
  };
333
352
 
334
353
  return html`
335
- <h3 class="card-h3 mt-section">\u{1F465} ${t('profile.access.sgTitle') || 'Sharing Groups'}</h3>
354
+ <h3 class="card-h3 access-h3 mt-section" id="access-sharing-groups">${t('profile.access.sgTitle') || 'Sharing Groups'}</h3>
336
355
  <div class="section-desc">${t('profile.access.sgDesc') || 'Define groups of identities to share memory entries with. Assign a sharing group to memory keys to control who can read or write.'}</div>
337
356
 
338
357
  ${groups === null
339
358
  ? html`<div class="empty">${t('profile.access.sgLoading') || 'Loading...'}</div>`
340
359
  : groups.length === 0
341
- ? html`<div class="empty">${t('profile.access.sgEmpty') || 'No sharing groups yet.'}</div>`
360
+ ? (!showCreate && html`
361
+ <div class="access-empty-row">
362
+ <span class="text-meta-sm">${t('profile.access.sgEmpty') || 'No sharing groups yet.'}</span>
363
+ <button class="btn-outline btn-sm" onClick=${() => setShowCreate(true)}>${t('profile.access.sgCreate') || 'New Group'}</button>
364
+ </div>`)
342
365
  : groups.map(renderGroupCard)
343
366
  }
344
367
 
345
- ${!showCreate ? html`
368
+ ${!showCreate ? ((groups?.length || 0) > 0 && html`
346
369
  <div class="mb-1">
347
- <button class="btn-primary" onClick=${() => setShowCreate(true)}>
370
+ <button class="btn-outline" onClick=${() => setShowCreate(true)}>
348
371
  ${t('profile.access.sgCreate') || 'New Group'}
349
372
  </button>
350
373
  </div>
351
- ` : html`
374
+ `) : html`
352
375
  <div class="create-form">
353
376
  <h4 class="card-h3 mb-half">${t('profile.access.sgCreateTitle') || 'Create Sharing Group'}</h4>
354
377
  <div class="flex-col">
@@ -461,7 +484,7 @@ function AgentDefaultsSection({ showToast }) {
461
484
  const budget = defaults?.default_token_budget;
462
485
 
463
486
  return html`
464
- <h3 class="card-h3 mt-section">\u{1F916} ${t('profile.access.adTitle') || 'Agent Defaults'}</h3>
487
+ <h3 class="card-h3 access-h3 mt-section">${t('profile.access.adTitle') || 'Agent Defaults'}</h3>
465
488
  <div class="section-desc">${t('profile.access.adDesc') || 'Owner-level defaults that apply to all your agents unless overridden by per-agent directives.'}</div>
466
489
 
467
490
  ${!editing ? html`
@@ -474,7 +497,7 @@ function AgentDefaultsSection({ showToast }) {
474
497
  </div>
475
498
 
476
499
  ${rules.length === 0
477
- ? html`<div class="empty">${t('profile.access.adNoRules') || 'No default rules set.'}</div>`
500
+ ? html`<div class="text-meta-sm mb-half">${t('profile.access.adNoRules') || 'No default rules set.'} ${t('profile.access.adRuleExample') || 'Example: "Always answer in Finnish" or "Never spend morsels without asking".'}</div>`
478
501
  : rules.map((rule, i) => html`
479
502
  <div class="mem-item" key=${i}>
480
503
  <span class="mem-key">${escHtml(rule)}</span>
@@ -484,7 +507,9 @@ function AgentDefaultsSection({ showToast }) {
484
507
 
485
508
  <div class="mem-item">
486
509
  <span class="mem-key">${t('profile.access.adTokenBudget') || 'Token Budget'}</span>
487
- <span>${budget != null ? budget.toLocaleString() : (t('profile.access.adUnlimited') || 'Unlimited')}</span>
510
+ <button class="pj-linklike" title=${t('profile.access.adEdit') || 'Edit'} onClick=${startEdit}>
511
+ ${budget != null ? budget.toLocaleString() : (t('profile.access.adUnlimited') || 'Unlimited')} ✎
512
+ </button>
488
513
  </div>
489
514
  </div>
490
515
  ` : html`
@@ -650,7 +675,7 @@ function AccessTokensSection({ session, showToast }) {
650
675
  };
651
676
 
652
677
  return html`
653
- <h3 class="card-h3 mt-section">\u{1F511} ${t('profile.access.patTitle') || 'Agent Access Tokens'}</h3>
678
+ <h3 class="card-h3 access-h3 mt-section">${t('profile.access.patTitle') || 'Agent Access Tokens'}</h3>
654
679
  <div class="section-desc">${t('profile.access.patDesc') || 'Create a revocable token an agent can use (as a Bearer header) to log in and test your apps. One token can be shared across all your agents.'}</div>
655
680
 
656
681
  ${created && html`
@@ -676,7 +701,11 @@ function AccessTokensSection({ session, showToast }) {
676
701
  ${tokens === null
677
702
  ? html`<div class="empty">${t('profile.access.patLoading') || 'Loading...'}</div>`
678
703
  : tokens.length === 0
679
- ? html`<div class="empty">${t('profile.access.patEmpty') || 'No access tokens yet.'}</div>`
704
+ ? (!showCreate && html`
705
+ <div class="access-empty-row">
706
+ <span class="text-meta-sm">${t('profile.access.patEmpty') || 'No access tokens yet.'}</span>
707
+ <button class="btn-outline btn-sm" onClick=${() => setShowCreate(true)}>${t('profile.access.patCreate') || 'New Token'}</button>
708
+ </div>`)
680
709
  : tokens.map(tok => html`
681
710
  <div class="card" key=${tok.id}>
682
711
  <div class="flex-between">
@@ -700,9 +729,9 @@ function AccessTokensSection({ session, showToast }) {
700
729
  `)
701
730
  }
702
731
 
703
- ${!showCreate ? html`
704
- <div class="mb-1"><button class="btn-primary" onClick=${() => setShowCreate(true)}>${t('profile.access.patCreate') || 'New Token'}</button></div>
705
- ` : html`
732
+ ${!showCreate ? (tokens?.length > 0 && html`
733
+ <div class="mb-1"><button class="btn-outline" onClick=${() => setShowCreate(true)}>${t('profile.access.patCreate') || 'New Token'}</button></div>
734
+ `) : html`
706
735
  <div class="create-form">
707
736
  <h4 class="card-h3 mb-half">${t('profile.access.patCreateTitle') || 'Create Access Token'}</h4>
708
737
  <div class="flex-col">
@@ -778,6 +807,16 @@ function AccessTokensSection({ session, showToast }) {
778
807
  Main Access Tab
779
808
  ═══════════════════════════════════════════════════════════════════ */
780
809
 
810
+ /** Decode the JWT's exp claim → { until: Date, hoursLeft } or null. Display-only. */
811
+ function jwtExpiry(jwt) {
812
+ try {
813
+ const payload = JSON.parse(atob(jwt.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
814
+ if (!payload.exp) return null;
815
+ const until = new Date(payload.exp * 1000);
816
+ return { until, hoursLeft: Math.max(0, (until.getTime() - Date.now()) / 3600000) };
817
+ } catch { return null; }
818
+ }
819
+
781
820
  export default function AccessTab({ session, showToast }) {
782
821
  const NODE_URL = getNodeUrl();
783
822
  const ownerKey = typeof localStorage !== 'undefined' ? localStorage.getItem('aimeat_owner_key') : null;
@@ -785,13 +824,16 @@ export default function AccessTab({ session, showToast }) {
785
824
  const [authConsents, setAuthConsents] = useState([]);
786
825
  const [newNodeId, setNewNodeId] = useState('');
787
826
  const [fedLoading, setFedLoading] = useState(false);
827
+ // undefined = loading, null = no keypair stored, string = the key
828
+ const [pubKey, setPubKey] = useState(undefined);
788
829
 
789
- // Load auth consents on mount
830
+ // Load auth consents + own GHII profile (public key) on mount
790
831
  useEffect(() => {
791
832
  apiGet('/v1/consent').then(data => {
792
833
  const all = data.data?.consents || [];
793
834
  setAuthConsents(all.filter(c => c.scope === 'auth' && c.status === 'active'));
794
835
  }).catch(() => {});
836
+ apiGet('/v1/ghii/me').then(r => setPubKey(r?.data?.public_key ?? null)).catch(() => setPubKey(null));
795
837
  }, []);
796
838
 
797
839
  async function addAuthNode() {
@@ -845,24 +887,45 @@ export default function AccessTab({ session, showToast }) {
845
887
  }
846
888
  }
847
889
 
890
+ const exp = session.jwt ? jwtExpiry(session.jwt) : null;
891
+ const copyRow = (label, value) => html`
892
+ <div class="mem-item"><span class="mem-key">${label}</span>
893
+ <span class="access-copy-val">${escHtml(value || '-')}
894
+ ${value && html`<${CopyButton} text=${value} className="btn-ghost btn-sm" label="📋" onCopied=${() => showToast(t('profile.access.copied') || 'Copied')} />`}
895
+ </span>
896
+ </div>`;
897
+
848
898
  return html`
849
899
  <div class="section-title">${t('profile.access.title')}</div>
850
900
  <div class="section-desc">${t('profile.access.desc')}</div>
851
901
 
852
- <h3 class="card-h3">\u{1F4BB} ${t('profile.access.session')}</h3>
902
+ <h3 class="card-h3 access-h3">${t('profile.access.session')}</h3>
853
903
  <div class="card">
854
904
  <div class="mem-item"><span class="mem-key">${t('profile.access.owner')}</span><span>${escHtml(session.owner || '-')}</span></div>
855
- <div class="mem-item"><span class="mem-key">${t('profile.access.ghii')}</span><span>${escHtml(session.ghii || '-')}</span></div>
856
- <div class="mem-item"><span class="mem-key">${t('profile.access.agentGaii')}</span><span>${escHtml(session.gaii || '-')}</span></div>
857
- <div class="mem-item"><span class="mem-key">${t('profile.access.node')}</span><span>${escHtml(NODE_URL)}</span></div>
858
- <div class="mem-item"><span class="mem-key">${t('profile.access.jwtValid')}</span><span>${session.valid ? html`<span class="badge badge-success">${t('profile.access.yes')}</span>` : html`<span class="badge badge-danger">${t('profile.access.expired')}</span>`}</span></div>
905
+ ${copyRow(t('profile.access.ghii'), session.ghii)}
906
+ ${session.gaii && copyRow(t('profile.access.agentGaii'), session.gaii)}
907
+ ${copyRow(t('profile.access.node'), NODE_URL)}
908
+ <div class="mem-item"><span class="mem-key">${t('profile.access.jwtValid')}</span><span>
909
+ ${session.valid
910
+ ? (exp
911
+ ? html`<span class="badge badge-success">${(t('profile.access.validUntil') || 'Valid until {time} ({h} h)').replace('{time}', exp.until.toLocaleTimeString(getLocale() === 'fi' ? 'fi-FI' : undefined, { hour: '2-digit', minute: '2-digit' })).replace('{h}', exp.hoursLeft < 1 ? '<1' : String(Math.round(exp.hoursLeft)))}</span>`
912
+ : html`<span class="badge badge-success">${t('profile.access.yes')}</span>`)
913
+ : html`<span class="badge badge-danger">${t('profile.access.expired')}</span>`}
914
+ </span></div>
859
915
  </div>
860
916
 
861
- <h3 class="card-h3 mt-section">\u{1F510} ${t('profile.access.publicKey')}</h3>
862
- <div class="card"><div class="access-mono">${escHtml(session.publicKey || 'N/A')}</div></div>
917
+ <h3 class="card-h3 access-h3 mt-section">${t('profile.access.publicKey')}</h3>
918
+ <div class="card">
919
+ ${pubKey === undefined
920
+ ? html`<div class="access-mono">…</div>`
921
+ : pubKey
922
+ ? html`<div class="flex-between"><div class="access-mono">${escHtml(pubKey)}</div>
923
+ <${CopyButton} text=${pubKey} className="btn-ghost btn-sm" label="📋" onCopied=${() => showToast(t('profile.access.copied') || 'Copied')} /></div>`
924
+ : html`<div class="text-meta-sm">${t('profile.access.noKeypair') || 'No keypair is stored for this account. Keys are generated at registration; older accounts may not have one.'}</div>`}
925
+ </div>
863
926
 
864
927
  ${ownerKey && html`
865
- <h3 class="card-h3 mt-section">\u{1F5DD}️ ${t('profile.access.ownerKey')}</h3>
928
+ <h3 class="card-h3 access-h3 mt-section">${t('profile.access.ownerKey')}</h3>
866
929
  <div class="card access-card-warn" onClick=${() => copyToClipboard(ownerKey).then(() => showToast(t('profile.access.keyCopied')))}>
867
930
  <div class="flex-between">
868
931
  <div class="access-mono ${keyBlurred ? 'access-key-blur' : 'access-key-blur revealed'}"
@@ -881,50 +944,60 @@ export default function AccessTab({ session, showToast }) {
881
944
  </div>
882
945
  `}
883
946
 
884
- <h3 class="card-h3 mt-section">\u{1F517} ${t('profile.access.mcpEndpoint')}</h3>
947
+ <h3 class="card-h3 access-h3 mt-section">${t('profile.access.mcpEndpoint')}</h3>
885
948
  <div class="card">
886
- <div class="access-mcp-url">${escHtml(NODE_URL + '/v1/mcp')}</div>
949
+ <div class="flex-between">
950
+ <div class="access-mcp-url">${escHtml(NODE_URL + '/v1/mcp')}</div>
951
+ <${CopyButton} text=${NODE_URL + '/v1/mcp'} className="btn-ghost btn-sm" label="📋" onCopied=${() => showToast(t('profile.access.copied') || 'Copied')} />
952
+ </div>
887
953
  <div class="access-mcp-desc">${t('profile.access.mcpDesc')}</div>
888
954
  </div>
889
955
 
890
- <h3 class="card-h3 mt-section">\u{1F310} ${t('profile.access.fedTitle')}</h3>
956
+ <h3 class="card-h3 access-h3 mt-section">${t('profile.access.fedTitle')}</h3>
891
957
  <div class="section-desc">${t('profile.access.fedDesc')}</div>
892
958
 
959
+ ${(() => {
960
+ const allowAll = authConsents.some(c => c.recipient === '*');
961
+ return html`
893
962
  <div class="card">
894
- <div class="mem-item">
963
+ <div class="access-fed-allowall">
895
964
  <label class="flex-row">
896
- <input type="checkbox"
897
- checked=${authConsents.some(c => c.recipient === '*')}
898
- onChange=${toggleAllowAll} />
965
+ <input type="checkbox" checked=${allowAll} onChange=${toggleAllowAll} />
899
966
  ${t('profile.access.fedAllowAll')}
900
967
  </label>
901
- ${authConsents.some(c => c.recipient === '*') && html`
902
- <span class="badge badge-warn">${t('profile.access.fedAllowAllWarn')}</span>
968
+ ${allowAll && html`
969
+ <div class="access-fed-warn-caption">⚠ ${t('profile.access.fedAllowAllCaption') || 'Any federation node can verify your identity. Uncheck to restrict to the list below.'}</div>
903
970
  `}
904
971
  </div>
905
972
 
906
- ${authConsents.filter(c => c.recipient !== '*').length === 0 && !authConsents.some(c => c.recipient === '*') && html`
907
- <p class="adm-text-dim">${t('profile.access.fedNoConsents')}</p>
908
- `}
909
- ${authConsents.filter(c => c.recipient !== '*').map(c => html`
910
- <div class="mem-item">
911
- <span class="mem-key">${escHtml(c.recipient.replace('node:', ''))}</span>
912
- <span class="adm-text-dim" style="font-size:.85em">${c.granted_at ? new Date(c.granted_at).toLocaleDateString() : ''}</span>
913
- <button class="btn-ghost btn-danger" onClick=${() => removeAuthConsent(c.id)}>
914
- ${t('profile.access.fedRemove')}
973
+ <!-- The allowlist is meaningless while allow-all is on — dim it so it cannot
974
+ masquerade as the active control. -->
975
+ <div class=${allowAll ? 'access-fed-list access-fed-list--inactive' : 'access-fed-list'}>
976
+ ${authConsents.filter(c => c.recipient !== '*').length === 0 && !allowAll && html`
977
+ <p class="adm-text-dim">${t('profile.access.fedNoConsents')}</p>
978
+ `}
979
+ ${authConsents.filter(c => c.recipient !== '*').map(c => html`
980
+ <div class="mem-item">
981
+ <span class="mem-key">${escHtml(c.recipient.replace('node:', ''))}</span>
982
+ <span class="adm-text-dim" style="font-size:.85em">${c.granted_at ? new Date(c.granted_at).toLocaleDateString() : ''}</span>
983
+ <button class="btn-ghost btn-danger" disabled=${allowAll} onClick=${() => removeAuthConsent(c.id)}>
984
+ ${t('profile.access.fedRemove')}
985
+ </button>
986
+ </div>
987
+ `)}
988
+
989
+ <div class="mem-item access-fed-add">
990
+ <input type="text" class="input-field input-sm" placeholder=${t('profile.access.fedNodeId')}
991
+ disabled=${allowAll}
992
+ value=${newNodeId} onInput=${e => setNewNodeId(e.target.value)}
993
+ onKeyDown=${e => e.key === 'Enter' && addAuthNode()} />
994
+ <button class="btn-outline" onClick=${addAuthNode} disabled=${fedLoading || allowAll}>
995
+ ${t('profile.access.fedAddNode')}
915
996
  </button>
916
997
  </div>
917
- `)}
918
-
919
- <div class="mem-item access-fed-add">
920
- <input type="text" class="input-field input-sm" placeholder=${t('profile.access.fedNodeId')}
921
- value=${newNodeId} onInput=${e => setNewNodeId(e.target.value)}
922
- onKeyDown=${e => e.key === 'Enter' && addAuthNode()} />
923
- <button class="btn-primary" onClick=${addAuthNode} disabled=${fedLoading}>
924
- ${t('profile.access.fedAddNode')}
925
- </button>
926
998
  </div>
927
- </div>
999
+ </div>`;
1000
+ })()}
928
1001
 
929
1002
  <${AccessTokensSection} session=${session} showToast=${showToast} />
930
1003
  <${SharingGroupsSection} showToast=${showToast} />
@@ -3,6 +3,12 @@
3
3
  * @description Agent card component with collapsed/expanded states,
4
4
  * Two-Zone Header (identity + state-dependent status), and tab bar.
5
5
  * @version-history
6
+ * v1.15.0 -- 2026-06-10 -- Glance round: collapsed bar drops the repeated mode/platform/
7
+ * readiness badges (detail-only now); capabilities collapse to a "N tools · N skills"
8
+ * summary (CapabilitiesSummary); tab badges only on Tasks/Messages, neutral gray with
9
+ * red reserved for unseen failed tasks; footer Delete removed (Agent Config danger zone
10
+ * owns it, onDeleteClick threaded there); federation button shows its state
11
+ * ("Federation: on/off"); readiness badge gets an explanatory tooltip.
6
12
  * v1.14.0 -- 2026-06-09 -- Tag strip in the expanded header is now editable
7
13
  * (inline add via "+ tag" input + per-chip remove ×), writing through
8
14
  * PATCH /v1/agents/:name/tags. Lets the owner manage the tags that drive the
@@ -111,14 +117,15 @@ async function fetchReadme(agent) {
111
117
  }
112
118
  }
113
119
 
114
- // Maps a tab id to its change-count key in the `changes` prop. Only these three
115
- // tabs carry unseen-change badges (Tasks, Messages, Memory/data-access).
116
- const TAB_CHANGE_KEY = { tasks: 'tasks', messages: 'messages', 'data-access': 'memory' };
120
+ // Maps a tab id to its change-count key in the `changes` prop. Badges live ONLY on
121
+ // Tasks and Messages — they are the action-bearing tabs. (Memory churn is not a
122
+ // to-do; its badge taught users to ignore badges.)
123
+ const TAB_CHANGE_KEY = { tasks: 'tasks', messages: 'messages' };
117
124
 
118
- // Total unseen changes across the three tracked tabs (collapsed mini-badge).
125
+ // Total unseen changes across the tracked tabs (collapsed mini-badge).
119
126
  function totalChanges(changes) {
120
127
  if (!changes) return 0;
121
- return (changes.tasks || 0) + (changes.messages || 0) + (changes.memory || 0);
128
+ return (changes.tasks || 0) + (changes.messages || 0);
122
129
  }
123
130
 
124
131
  export default function AgentCard({ agent, onboarding, expanded, onToggle, session, showToast, allAgents, changes, onTabSeen, onScopesClick, onDeleteClick, onFederateToggle, onPopOut, soloMode = false, preSelectedTab = null, openTaskId = null, openTaskNonce = 0 }) {
@@ -195,10 +202,9 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
195
202
  <span class="pf-agd-collapsed-icon">🤖</span>
196
203
  <span class="pf-agd-collapsed-name">${agent.display_name || agent.name}</span>
197
204
  ${renderChangeBadge(changes)}
205
+ <!-- Mode/platform/readiness badges live in the expanded detail only — repeated
206
+ identically on every row they were noise, not information. -->
198
207
  <div class="pf-agd-collapsed-badges">
199
- ${renderModeBadge(agent)}
200
- ${renderPlatformBadge(onboarding)}
201
- ${renderReadinessBadge(state, onboarding)}
202
208
  ${agent.federate && html`<span class="pf-agd-badge pf-agd-badge--federation">${t('profile.federated')}</span>`}
203
209
  </div>
204
210
  ${renderPopOut(onPopOut, agent)}
@@ -237,20 +243,9 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
237
243
  <!-- Tags (editable) -->
238
244
  <${TagStrip} agent=${agent} showToast=${showToast} />
239
245
 
240
- <!-- Capabilities -->
241
- ${(agent.technical_capabilities?.length > 0 || agent.domain_capabilities?.length > 0 || agent.languages?.length > 0) && html`
242
- <div class="pf-agd-capabilities">
243
- ${(agent.technical_capabilities || []).map(c => html`
244
- <span key=${c.name || c} class="pf-agd-cap-badge pf-agd-cap-badge--tech">${c.name || c}</span>
245
- `)}
246
- ${(agent.domain_capabilities || []).filter(c => !String(c).startsWith('Language: ')).map(c => html`
247
- <span key=${c} class="pf-agd-cap-badge pf-agd-cap-badge--domain">${c}</span>
248
- `)}
249
- ${(agent.languages || []).map(l => html`
250
- <span key=${'lang-' + l} class="pf-agd-cap-badge pf-agd-cap-badge--domain">${'Language: ' + l}</span>
251
- `)}
252
- </div>
253
- `}
246
+ <!-- Capabilities — collapsed to a one-line summary ("14 tools · 5 skills"); the full
247
+ pill wall took a third of the screen before the tabs. Click to expand. -->
248
+ <${CapabilitiesSummary} agent=${agent} />
254
249
 
255
250
  <!-- Zone 2: Status -->
256
251
  ${renderZone2(state, agent, onboarding, setActiveTab, showToast)}
@@ -263,6 +258,8 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
263
258
  // The tab you're currently viewing never shows a badge — you're
264
259
  // looking at it (and the effect above keeps it marked-seen).
265
260
  const count = (changeKey && activeTab !== tab.id) ? (changes?.[changeKey] || 0) : 0;
261
+ // Neutral gray badge; red ONLY when there are unseen FAILED tasks.
262
+ const failed = tab.id === 'tasks' && (changes?.tasksFailed || 0) > 0;
266
263
  return html`
267
264
  <button key=${tab.id}
268
265
  class="pf-agd-tab ${activeTab === tab.id ? 'pf-agd-tab--active' : ''}"
@@ -274,7 +271,7 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
274
271
  if (changeKey && onTabSeen) onTabSeen(agent.name, changeKey);
275
272
  }}>
276
273
  ${label !== tab.key ? label : tab.id.charAt(0).toUpperCase() + tab.id.slice(1)}
277
- ${count > 0 ? html`<span class="pf-agd-tab-badge">${count > 99 ? '99+' : count}</span>` : ''}
274
+ ${count > 0 ? html`<span class="pf-agd-tab-badge ${failed ? 'pf-agd-tab-badge--failed' : ''}">${count > 99 ? '99+' : count}</span>` : ''}
278
275
  </button>
279
276
  `;
280
277
  })}
@@ -282,10 +279,11 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
282
279
 
283
280
  <!-- Tab Content -->
284
281
  <div class="pf-agd-tab-content" onClick=${(e) => e.stopPropagation()}>
285
- ${renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents, readme, openTaskId, openTaskNonce)}
282
+ ${renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents, readme, openTaskId, openTaskNonce, onDeleteClick)}
286
283
  </div>
287
284
 
288
- <!-- Card Footer: Scopes + Delete -->
285
+ <!-- Card Footer: Scopes + federation toggle. Delete lives in the Agent Config tab's
286
+ danger zone (typed-name confirm) — not on every tab's footer. -->
289
287
  <div class="pf-agd-card-footer">
290
288
  <div class="pf-agd-card-actions">
291
289
  ${renderPopOut(onPopOut, agent)}
@@ -295,22 +293,53 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
295
293
  </button>
296
294
  `}
297
295
  ${onFederateToggle && html`
298
- <button class="btn-ghost btn-sm" onClick=${(e) => { e.stopPropagation(); onFederateToggle(agent); }}>
299
- ${agent.federate ? t('profile.federated') : t('profile.notFederated')}
296
+ <button class="btn-ghost btn-sm"
297
+ title=${agent.federate ? (t('profile.agents.detail.federationOnHint') || 'Click to make local only') : (t('profile.agents.detail.federationOffHint') || 'Click to share via federation')}
298
+ onClick=${(e) => { e.stopPropagation(); onFederateToggle(agent); }}>
299
+ ${(t('profile.agents.detail.federationLabel') || 'Federation')}: ${agent.federate ? (t('profile.agents.detail.federationOn') || 'on') : (t('profile.agents.detail.federationOff') || 'off')}
300
300
  </button>
301
301
  `}
302
302
  </div>
303
- ${onDeleteClick && html`
304
- <button class="btn-danger btn-sm" onClick=${(e) => { e.stopPropagation(); onDeleteClick(agent.name); }}>
305
- ${t('profile.agents.deleteAgent')}
306
- </button>
307
- `}
308
303
  </div>
309
304
  </div>
310
305
  </div>
311
306
  `;
312
307
  }
313
308
 
309
+ // Capabilities summary — "14 tools · 5 skills · 2 languages" on one line, expanding to the
310
+ // full pill list on click. Headers should identify, not enumerate.
311
+ function CapabilitiesSummary({ agent }) {
312
+ const [open, setOpen] = useState(false);
313
+ const tools = agent.technical_capabilities || [];
314
+ const skills = (agent.domain_capabilities || []).filter(c => !String(c).startsWith('Language: '));
315
+ const langs = agent.languages || [];
316
+ if (tools.length === 0 && skills.length === 0 && langs.length === 0) return null;
317
+ const parts = [];
318
+ if (tools.length) parts.push(`${tools.length} ${t('profile.agents.detail.capTools') || 'tools'}`);
319
+ if (skills.length) parts.push(`${skills.length} ${t('profile.agents.detail.capSkills') || 'skills'}`);
320
+ if (langs.length) parts.push(`${langs.length} ${t('profile.agents.detail.capLangs') || 'languages'}`);
321
+ return html`
322
+ <div class="pf-agd-capwrap">
323
+ <button class="pf-agd-cap-summary" onClick=${(e) => { e.stopPropagation(); setOpen(o => !o); }}>
324
+ ${parts.join(' · ')} <span class="pf-chevron ${open ? 'pf-chevron-open' : ''}">▼</span>
325
+ </button>
326
+ ${open && html`
327
+ <div class="pf-agd-capabilities">
328
+ ${tools.map(c => html`
329
+ <span key=${c.name || c} class="pf-agd-cap-badge pf-agd-cap-badge--tech">${c.name || c}</span>
330
+ `)}
331
+ ${skills.map(c => html`
332
+ <span key=${c} class="pf-agd-cap-badge pf-agd-cap-badge--domain">${c}</span>
333
+ `)}
334
+ ${langs.map(l => html`
335
+ <span key=${'lang-' + l} class="pf-agd-cap-badge pf-agd-cap-badge--domain">${'Language: ' + l}</span>
336
+ `)}
337
+ </div>
338
+ `}
339
+ </div>
340
+ `;
341
+ }
342
+
314
343
  // Pop-out button: opens this agent in its own window (one window per agent,
315
344
  // so re-clicking focuses the existing one). Only rendered when the parent
316
345
  // supplies onPopOut — the standalone solo view omits it.
@@ -330,9 +359,10 @@ function renderChangeBadge(changes) {
330
359
  const parts = [];
331
360
  if (changes.tasks) parts.push(`${t('profile.agents.detail.tabs.tasks')}: ${changes.tasks}`);
332
361
  if (changes.messages) parts.push(`${t('profile.agents.detail.tabs.messages')}: ${changes.messages}`);
333
- if (changes.memory) parts.push(`${t('profile.agents.detail.tabs.data_access')}: ${changes.memory}`);
334
362
  const title = `${t('profile.agents.detail.changes.title')} — ${parts.join(', ')}`;
335
- return html`<span class="pf-agd-change-badge" title=${title}>${total > 99 ? '99+' : total}</span>`;
363
+ // Neutral gray; red is reserved for unseen FAILED tasks.
364
+ const failed = (changes.tasksFailed || 0) > 0;
365
+ return html`<span class="pf-agd-change-badge ${failed ? 'pf-agd-change-badge--failed' : ''}" title=${title}>${total > 99 ? '99+' : total}</span>`;
336
366
  }
337
367
 
338
368
  function renderDeliveryIndicator(agent) {
@@ -386,7 +416,8 @@ function renderReadinessBadge(state, onboarding) {
386
416
  const score = onboarding?.readinessScore;
387
417
  if (!score && score !== 0) return html`<span class="pf-agd-badge pf-agd-badge--readiness-none">--</span>`;
388
418
  const label = t(`agentOnboarding.readiness.${level}`);
389
- return html`<span class="pf-agd-badge pf-agd-badge--readiness-${level}">${label} (${score})</span>`;
419
+ return html`<span class="pf-agd-badge pf-agd-badge--readiness-${level}"
420
+ title=${t('profile.agents.detail.readinessTooltip') || 'Readiness score 0–100 from onboarding checks. Levels: none → basic → standard → advanced → full.'}>${label} (${score})</span>`;
390
421
  }
391
422
 
392
423
  function renderCollapsedStats(state, agent, onboarding) {
@@ -629,7 +660,7 @@ function ProblemZone2({ agent, setActiveTab, showToast }) {
629
660
  `;
630
661
  }
631
662
 
632
- function renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents, readme, openTaskId, openTaskNonce) {
663
+ function renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents, readme, openTaskId, openTaskNonce, onDeleteClick) {
633
664
  const props = { agent, onboarding, session, showToast, agentName: agent.name, allAgents };
634
665
  switch (activeTab) {
635
666
  case 'readme': return html`<${TabReadme} readme=${readme} />`;
@@ -638,7 +669,7 @@ function renderTabContent(activeTab, agent, onboarding, session, showToast, allA
638
669
  case 'messages': return html`<${TabMessages} ...${props} />`;
639
670
  case 'data-access': return html`<${TabDataAccess} ...${props} />`;
640
671
  case 'directives': return html`<${TabDirectives} ...${props} />`;
641
- case 'agent-config': return html`<${TabAgentConfig} ...${props} />`;
672
+ case 'agent-config': return html`<${TabAgentConfig} ...${props} onDeleteClick=${onDeleteClick} />`;
642
673
  case 'activity': return html`<${TabActivity} ...${props} />`;
643
674
  case 'schedules': return html`<${TabSchedules} ...${props} />`;
644
675
  case 'quality': return html`<${TabQuality} ...${props} />`;