aimeat 1.23.2 → 1.23.3
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/locales/en.json +536 -21
- package/dist/locales/fi.json +548 -33
- package/dist/public/components/ContactCard.js +28 -0
- package/dist/public/css/views/admin.css +12 -0
- package/dist/public/css/views/agents-detail.css +62 -10
- package/dist/public/css/views/landing.css +87 -0
- package/dist/public/css/views/offers.css +96 -0
- package/dist/public/css/views/portfolio.css +18 -0
- package/dist/public/css/views/profile.css +153 -8
- package/dist/public/css/views/start.css +96 -0
- package/dist/public/img/business-hero.png +0 -0
- package/dist/public/img/toimitus.png +0 -0
- package/dist/public/js/components/tag-cloud.js +46 -31
- package/dist/public/js/components/tag-editor.js +2 -2
- package/dist/public/js/recents.js +40 -0
- package/dist/public/js/services/admin.js +6 -0
- package/dist/public/js/services/agents.js +34 -0
- package/dist/public/js/services/offers.js +152 -0
- package/dist/public/js/services/packages.js +1 -0
- package/dist/public/spa.html +32 -2
- package/dist/public/views/admin/subdomains-tab.js +182 -0
- package/dist/public/views/admin.js +2 -0
- package/dist/public/views/business.js +119 -0
- package/dist/public/views/how-it-works.js +159 -0
- package/dist/public/views/landing.js +235 -0
- package/dist/public/views/portfolio.js +120 -23
- package/dist/public/views/pricing.js +75 -0
- package/dist/public/views/profile/access-tab.js +124 -51
- package/dist/public/views/profile/agents/agent-card.js +68 -37
- package/dist/public/views/profile/agents/shared-board.js +56 -22
- package/dist/public/views/profile/agents/tab-activity.js +24 -12
- package/dist/public/views/profile/agents/tab-agent-config.js +31 -1
- package/dist/public/views/profile/agents/tab-data-access.js +9 -13
- package/dist/public/views/profile/agents/tab-integration.js +27 -1
- package/dist/public/views/profile/agents/tab-messages.js +6 -11
- package/dist/public/views/profile/agents/tab-quality.js +45 -5
- package/dist/public/views/profile/agents-tab.js +41 -6
- package/dist/public/views/profile/apps-tab.js +7 -1
- package/dist/public/views/profile/knowledge-tab.js +4 -1
- package/dist/public/views/profile/landing-page.js +427 -123
- package/dist/public/views/profile/memory-tab.js +409 -211
- package/dist/public/views/profile/nodes-tab.js +18 -3
- package/dist/public/views/profile/offers-tab.js +310 -0
- package/dist/public/views/profile/organisms-tab.js +289 -28
- package/dist/public/views/profile/packages-tab.js +152 -16
- package/dist/public/views/profile/portfolio-tab.js +98 -31
- package/dist/public/views/profile.js +2 -0
- package/dist/public/views/start-flows.js +281 -0
- package/dist/public/views/start.js +258 -0
- package/dist/src/generated/api-types.d.ts +856 -17
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/generated/prisma-postgres/edge.js +15 -3
- package/dist/src/generated/prisma-postgres/index-browser.js +12 -0
- package/dist/src/generated/prisma-postgres/index.d.ts +1476 -160
- package/dist/src/generated/prisma-postgres/index.js +15 -3
- package/dist/src/generated/prisma-postgres/package.json +1 -1
- package/dist/src/generated/prisma-postgres/schema.prisma +11 -0
- package/dist/src/generated/prisma-postgres/wasm.js +15 -3
- package/dist/src/middleware/subdomain.d.ts +28 -0
- package/dist/src/middleware/subdomain.d.ts.map +1 -0
- package/dist/src/middleware/subdomain.js +32 -0
- package/dist/src/middleware/subdomain.js.map +1 -0
- package/dist/src/models/offer-schemas.d.ts +229 -0
- package/dist/src/models/offer-schemas.d.ts.map +1 -0
- package/dist/src/models/offer-schemas.js +100 -0
- package/dist/src/models/offer-schemas.js.map +1 -0
- package/dist/src/routes/agent-tasks.d.ts.map +1 -1
- package/dist/src/routes/agent-tasks.js +33 -0
- package/dist/src/routes/agent-tasks.js.map +1 -1
- package/dist/src/routes/agents.d.ts.map +1 -1
- package/dist/src/routes/agents.js +146 -1
- package/dist/src/routes/agents.js.map +1 -1
- package/dist/src/routes/apps-backup.d.ts +20 -0
- package/dist/src/routes/apps-backup.d.ts.map +1 -0
- package/dist/src/routes/apps-backup.js +165 -0
- package/dist/src/routes/apps-backup.js.map +1 -0
- package/dist/src/routes/ghii.d.ts.map +1 -1
- package/dist/src/routes/ghii.js +4 -0
- package/dist/src/routes/ghii.js.map +1 -1
- package/dist/src/routes/instances.d.ts +2 -1
- package/dist/src/routes/instances.d.ts.map +1 -1
- package/dist/src/routes/instances.js +59 -1
- package/dist/src/routes/instances.js.map +1 -1
- package/dist/src/routes/packages.d.ts.map +1 -1
- package/dist/src/routes/packages.js +32 -0
- package/dist/src/routes/packages.js.map +1 -1
- package/dist/src/routes/permissions.d.ts +12 -0
- package/dist/src/routes/permissions.d.ts.map +1 -1
- package/dist/src/routes/permissions.js +35 -3
- package/dist/src/routes/permissions.js.map +1 -1
- package/dist/src/routes/portal.d.ts +3 -0
- package/dist/src/routes/portal.d.ts.map +1 -1
- package/dist/src/routes/portal.js +12 -0
- package/dist/src/routes/portal.js.map +1 -1
- package/dist/src/routes/public-stats.d.ts +15 -0
- package/dist/src/routes/public-stats.d.ts.map +1 -0
- package/dist/src/routes/public-stats.js +90 -0
- package/dist/src/routes/public-stats.js.map +1 -0
- package/dist/src/routes/sse.d.ts +7 -2
- package/dist/src/routes/sse.d.ts.map +1 -1
- package/dist/src/routes/sse.js +35 -5
- package/dist/src/routes/sse.js.map +1 -1
- package/dist/src/routes/subdomains.d.ts +33 -0
- package/dist/src/routes/subdomains.d.ts.map +1 -0
- package/dist/src/routes/subdomains.js +208 -0
- package/dist/src/routes/subdomains.js.map +1 -0
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +12 -1
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +4 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/services/apps-backup-export.d.ts +22 -0
- package/dist/src/services/apps-backup-export.d.ts.map +1 -0
- package/dist/src/services/apps-backup-export.js +136 -0
- package/dist/src/services/apps-backup-export.js.map +1 -0
- package/dist/src/services/apps-backup-import.d.ts +120 -0
- package/dist/src/services/apps-backup-import.d.ts.map +1 -0
- package/dist/src/services/apps-backup-import.js +259 -0
- package/dist/src/services/apps-backup-import.js.map +1 -0
- package/dist/src/services/component-registrar.d.ts +17 -0
- package/dist/src/services/component-registrar.d.ts.map +1 -1
- package/dist/src/services/component-registrar.js +63 -5
- package/dist/src/services/component-registrar.js.map +1 -1
- package/dist/src/services/handbooks/appdev.d.ts +1 -1
- package/dist/src/services/handbooks/appdev.d.ts.map +1 -1
- package/dist/src/services/handbooks/appdev.js +5 -2
- package/dist/src/services/handbooks/appdev.js.map +1 -1
- package/dist/src/storage/interface.d.ts +11 -1
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/interface.js.map +1 -1
- package/dist/src/storage/providers/mongodb/index.d.ts +10 -1
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +76 -0
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/index.d.ts +8 -1
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +61 -0
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +11 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/index.d.ts +1 -0
- package/dist/src/storage/repositories/index.d.ts.map +1 -1
- package/dist/src/storage/repositories/package.repository.d.ts +2 -0
- package/dist/src/storage/repositories/package.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/subdomain-site.repository.d.ts +19 -0
- package/dist/src/storage/repositories/subdomain-site.repository.d.ts.map +1 -0
- package/dist/src/storage/repositories/subdomain-site.repository.js +2 -0
- package/dist/src/storage/repositories/subdomain-site.repository.js.map +1 -0
- package/dist/static/app-catalog.html +1139 -28
- package/package.json +1 -1
- package/prisma/schema.postgres.prisma +11 -0
- 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"
|
|
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
|
-
?
|
|
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-
|
|
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"
|
|
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="
|
|
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
|
-
<
|
|
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"
|
|
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
|
-
?
|
|
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-
|
|
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"
|
|
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
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
<div class="mem-item"><span class="mem-key">${t('profile.access.jwtValid')}</span><span
|
|
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"
|
|
862
|
-
<div class="card"
|
|
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"
|
|
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"
|
|
947
|
+
<h3 class="card-h3 access-h3 mt-section">${t('profile.access.mcpEndpoint')}</h3>
|
|
885
948
|
<div class="card">
|
|
886
|
-
<div class="
|
|
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"
|
|
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="
|
|
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
|
-
${
|
|
902
|
-
<
|
|
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
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
<
|
|
914
|
-
|
|
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.
|
|
115
|
-
//
|
|
116
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
242
|
-
|
|
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"
|
|
299
|
-
|
|
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
|
-
|
|
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}"
|
|
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} />`;
|