aimeat 1.7.0 → 1.8.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.
- package/dist/bin/aimeat.js +0 -0
- package/dist/locales/en.json +46 -3
- package/dist/locales/fi.json +46 -3
- package/dist/public/css/views/agents-detail.css +150 -0
- package/dist/public/css/views/profile.css +22 -2
- package/dist/public/js/services/generator-spec-validate.js +23 -0
- package/dist/public/js/services/memory.js +39 -0
- package/dist/public/views/admin/federation-tab.js +5 -5
- package/dist/public/views/admin/prompts-tab.js +9 -10
- package/dist/public/views/profile/access-tab.js +18 -0
- package/dist/public/views/profile/agents-messages-subtab.js +3 -2
- package/dist/public/views/profile/agents-tab.js +10 -6
- package/dist/public/views/profile/agents-tasks-subtab.js +123 -64
- package/dist/public/views/profile/generator-detail.js +8 -1
- package/dist/public/views/profile/memory-tab.js +371 -7
- package/dist/public/views/profile/wallet-tab.js +14 -0
- package/dist/src/models/agent-capabilities-schemas.d.ts +3 -0
- package/dist/src/models/agent-capabilities-schemas.d.ts.map +1 -1
- package/dist/src/models/agent-capabilities-schemas.js +3 -0
- package/dist/src/models/agent-capabilities-schemas.js.map +1 -1
- package/dist/src/routes/admin-monitoring.js +12 -7
- package/dist/src/routes/admin-monitoring.js.map +1 -1
- package/dist/src/routes/agent-capabilities.d.ts +1 -0
- package/dist/src/routes/agent-capabilities.d.ts.map +1 -1
- package/dist/src/routes/agent-capabilities.js +9 -0
- package/dist/src/routes/agent-capabilities.js.map +1 -1
- package/dist/src/routes/agent-tasks.d.ts +1 -0
- package/dist/src/routes/agent-tasks.d.ts.map +1 -1
- package/dist/src/routes/agent-tasks.js +9 -7
- package/dist/src/routes/agent-tasks.js.map +1 -1
- package/dist/src/routes/federation-auth.d.ts.map +1 -1
- package/dist/src/routes/federation-auth.js +3 -1
- package/dist/src/routes/federation-auth.js.map +1 -1
- package/dist/src/routes/federation-peer.d.ts.map +1 -1
- package/dist/src/routes/federation-peer.js +11 -2
- package/dist/src/routes/federation-peer.js.map +1 -1
- package/dist/src/routes/federation-sync.d.ts +1 -0
- package/dist/src/routes/federation-sync.d.ts.map +1 -1
- package/dist/src/routes/federation-sync.js +29 -0
- package/dist/src/routes/federation-sync.js.map +1 -1
- package/dist/src/routes/memory.d.ts +9 -0
- package/dist/src/routes/memory.d.ts.map +1 -1
- package/dist/src/routes/memory.js +304 -9
- package/dist/src/routes/memory.js.map +1 -1
- package/dist/src/routes/prompts.d.ts +1 -0
- package/dist/src/routes/prompts.d.ts.map +1 -1
- package/dist/src/routes/prompts.js +40 -1
- package/dist/src/routes/prompts.js.map +1 -1
- package/dist/src/routes/sharing-groups.d.ts.map +1 -1
- package/dist/src/routes/sharing-groups.js +8 -2
- package/dist/src/routes/sharing-groups.js.map +1 -1
- package/dist/src/routes/wallet.d.ts.map +1 -1
- package/dist/src/routes/wallet.js +2 -0
- package/dist/src/routes/wallet.js.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +31 -0
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/services/federation-helpers.d.ts +1 -0
- package/dist/src/services/federation-helpers.d.ts.map +1 -1
- package/dist/src/services/federation-helpers.js +1 -1
- package/dist/src/services/federation-helpers.js.map +1 -1
- package/dist/src/services/generator-autopilot.d.ts.map +1 -1
- package/dist/src/services/generator-autopilot.js +125 -26
- package/dist/src/services/generator-autopilot.js.map +1 -1
- package/dist/src/services/generator-prompts/index.d.ts +1 -1
- package/dist/src/services/generator-prompts/index.d.ts.map +1 -1
- package/dist/src/services/generator-prompts/index.js +1 -1
- package/dist/src/services/generator-prompts/index.js.map +1 -1
- package/dist/src/services/generator-prompts/spec-validate.d.ts +1 -0
- package/dist/src/services/generator-prompts/spec-validate.d.ts.map +1 -1
- package/dist/src/services/generator-prompts/spec-validate.js +20 -0
- package/dist/src/services/generator-prompts/spec-validate.js.map +1 -1
- package/dist/src/services/generator-prompts/validate.d.ts +7 -1
- package/dist/src/services/generator-prompts/validate.d.ts.map +1 -1
- package/dist/src/services/generator-prompts/validate.js +70 -0
- package/dist/src/services/generator-prompts/validate.js.map +1 -1
- package/dist/src/services/prompt-defaults.d.ts +1 -0
- package/dist/src/services/prompt-defaults.d.ts.map +1 -1
- package/dist/src/services/prompt-defaults.js +830 -8
- package/dist/src/services/prompt-defaults.js.map +1 -1
- package/dist/src/services/prompt-seeder.js +1 -1
- package/dist/src/services/prompt-seeder.js.map +1 -1
- package/dist/src/storage/interface.d.ts +2 -0
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +5 -2
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +7 -2
- 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 +2 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/package.json +77 -97
- package/prisma/schema.prisma +2 -0
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* @version-history
|
|
6
6
|
* v1.0.0 — 2026-03-17 — Refactor: replace inline styles with CSS utility classes
|
|
7
7
|
* v1.1.0 — 2026-03-18 — Fix: use AuthImage for thumbnails and authenticated download to avoid 401
|
|
8
|
+
* v1.2.0 — 2026-05-22 — Add Browse Home / Browse Remote panels for federation memory sync
|
|
9
|
+
* v1.3.0 — 2026-05-22 — Add timestamps, sort controls, fix group picker rotation bug
|
|
8
10
|
*/
|
|
9
11
|
import { h } from 'preact';
|
|
10
12
|
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
@@ -16,6 +18,7 @@ import { Spinner, recipientBadge, VisibilityPill } from './shared.js';
|
|
|
16
18
|
import * as memoryService from '/js/services/memory.js';
|
|
17
19
|
import AuthImage from '/js/components/auth-image.js';
|
|
18
20
|
import { listAgents } from '/js/services/agents.js';
|
|
21
|
+
import { listPeers } from '/js/services/federation.js';
|
|
19
22
|
import { getKeyPermissions, listConsents, grantConsent, revokeConsent } from '/js/services/consent.js';
|
|
20
23
|
import { getNodeUrl } from '/js/services/auth.js';
|
|
21
24
|
import { listGroups } from '/js/services/sharing-groups.js';
|
|
@@ -42,7 +45,21 @@ export default function MemoryTab({ session, showToast, onStats }) {
|
|
|
42
45
|
const [fedConsents, setFedConsents] = useState({}); // { memoryKey: consentId }
|
|
43
46
|
const [togglingFed, setTogglingFed] = useState(null);
|
|
44
47
|
const [groups, setGroups] = useState([]);
|
|
45
|
-
const [groupPickerFor, setGroupPickerFor] = useState(null);
|
|
48
|
+
const [groupPickerFor, setGroupPickerFor] = useState(null);
|
|
49
|
+
const [browseMode, setBrowseMode] = useState(null); // 'home' | 'remote' | null
|
|
50
|
+
const [remoteEntries, setRemoteEntries] = useState(null);
|
|
51
|
+
const [remotePeers, setRemotePeers] = useState([]);
|
|
52
|
+
const [selectedPeer, setSelectedPeer] = useState('');
|
|
53
|
+
const [browseLoading, setBrowseLoading] = useState(false);
|
|
54
|
+
const [browseError, setBrowseError] = useState(null);
|
|
55
|
+
const [pullingKeys, setPullingKeys] = useState(new Set());
|
|
56
|
+
const [sortBy, setSortBy] = useState('updated');
|
|
57
|
+
const [discoverEntries, setDiscoverEntries] = useState(null);
|
|
58
|
+
const [discoverLoading, setDiscoverLoading] = useState(false);
|
|
59
|
+
const [discoverError, setDiscoverError] = useState(null);
|
|
60
|
+
const [discoverSearch, setDiscoverSearch] = useState('');
|
|
61
|
+
const [copyingKeys, setCopyingKeys] = useState(new Set());
|
|
62
|
+
const [expandedDiscover, setExpandedDiscover] = useState(null);
|
|
46
63
|
|
|
47
64
|
useEffect(() => {
|
|
48
65
|
if (session) { loadAgents(); loadMemories(); loadFiles(); loadFedConsents(); loadGroups(); }
|
|
@@ -279,6 +296,35 @@ export default function MemoryTab({ session, showToast, onStats }) {
|
|
|
279
296
|
}
|
|
280
297
|
}
|
|
281
298
|
|
|
299
|
+
function formatRelativeTime(isoStr) {
|
|
300
|
+
if (!isoStr) return '';
|
|
301
|
+
const d = new Date(isoStr);
|
|
302
|
+
const now = Date.now();
|
|
303
|
+
const diff = now - d.getTime();
|
|
304
|
+
const mins = Math.floor(diff / 60000);
|
|
305
|
+
if (mins < 1) return t('profile.memory.timeJustNow');
|
|
306
|
+
if (mins < 60) return t('profile.memory.timeMinsAgo').replace('{n}', mins);
|
|
307
|
+
const hrs = Math.floor(mins / 60);
|
|
308
|
+
if (hrs < 24) return t('profile.memory.timeHoursAgo').replace('{n}', hrs);
|
|
309
|
+
const days = Math.floor(hrs / 24);
|
|
310
|
+
if (days < 30) return t('profile.memory.timeDaysAgo').replace('{n}', days);
|
|
311
|
+
return d.toLocaleDateString();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function sortEntries(entries) {
|
|
315
|
+
const sorted = [...entries];
|
|
316
|
+
switch (sortBy) {
|
|
317
|
+
case 'updated':
|
|
318
|
+
return sorted.sort((a, b) => new Date(b.updated_at || 0) - new Date(a.updated_at || 0));
|
|
319
|
+
case 'created':
|
|
320
|
+
return sorted.sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0));
|
|
321
|
+
case 'alpha':
|
|
322
|
+
return sorted.sort((a, b) => a.key.localeCompare(b.key));
|
|
323
|
+
default:
|
|
324
|
+
return sorted;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
282
328
|
const renderEntries = () => {
|
|
283
329
|
const searchRef = useRef(null);
|
|
284
330
|
if (!memories) return html`<${Spinner} text=${t('profile.memory.loading')} />`;
|
|
@@ -289,9 +335,11 @@ export default function MemoryTab({ session, showToast, onStats }) {
|
|
|
289
335
|
if (m.tags) for (const tag of m.tags) allMemTags.add(tag);
|
|
290
336
|
}
|
|
291
337
|
|
|
292
|
-
// Filter by selected tags
|
|
293
|
-
const filtered =
|
|
294
|
-
|
|
338
|
+
// Filter by selected tags, then sort
|
|
339
|
+
const filtered = sortEntries(
|
|
340
|
+
memTagFilter.size === 0 ? memories : memories.filter(m =>
|
|
341
|
+
m.tags && [...memTagFilter].every(tag => m.tags.includes(tag))
|
|
342
|
+
)
|
|
295
343
|
);
|
|
296
344
|
|
|
297
345
|
const toggleMemTag = (tag) => {
|
|
@@ -309,6 +357,14 @@ export default function MemoryTab({ session, showToast, onStats }) {
|
|
|
309
357
|
<button class="btn-sm" onClick=${() => handleSearch(searchRef.current?.value)}>${t('profile.memory.searchBtn')}</button>
|
|
310
358
|
<button class="btn-outline btn-sm" onClick=${() => { if (searchRef.current) searchRef.current.value = ''; loadMemories(); }}>${t('profile.memory.clearBtn')}</button>
|
|
311
359
|
</div>
|
|
360
|
+
<div class="mem-sort-bar">
|
|
361
|
+
<label class="text-meta-sm">${t('profile.memory.sortLabel')}</label>
|
|
362
|
+
<select class="input-field mem-sort-select" value=${sortBy} onChange=${e => setSortBy(e.target.value)}>
|
|
363
|
+
<option value="updated">${t('profile.memory.sortUpdated')}</option>
|
|
364
|
+
<option value="created">${t('profile.memory.sortCreated')}</option>
|
|
365
|
+
<option value="alpha">${t('profile.memory.sortAlpha')}</option>
|
|
366
|
+
</select>
|
|
367
|
+
</div>
|
|
312
368
|
<button class="btn-primary" onClick=${() => setShowMemForm(!showMemForm)}>${t('profile.memory.newBtn')}</button>
|
|
313
369
|
</div>
|
|
314
370
|
<${TagCloud} tags=${[...allMemTags]} selected=${memTagFilter} onToggle=${toggleMemTag} onClear=${() => setMemTagFilter(new Set())} />
|
|
@@ -319,6 +375,9 @@ export default function MemoryTab({ session, showToast, onStats }) {
|
|
|
319
375
|
<div>
|
|
320
376
|
<div class="mem-item" onClick=${() => setExpandedMem(expandedMem === m.key ? null : m.key)}>
|
|
321
377
|
<span class="mem-key">${escHtml(m.key)}</span>
|
|
378
|
+
<span class="mem-time" title="${m.created_at ? new Date(m.created_at).toLocaleString() : ''} / ${m.updated_at ? new Date(m.updated_at).toLocaleString() : ''}">
|
|
379
|
+
${formatRelativeTime(m.updated_at || m.created_at)}
|
|
380
|
+
</span>
|
|
322
381
|
${(() => {
|
|
323
382
|
const vis = m.visibility || 'private';
|
|
324
383
|
const nextVis = cycleVis[(cycleVis.indexOf(vis) + 1) % cycleVis.length];
|
|
@@ -343,7 +402,7 @@ export default function MemoryTab({ session, showToast, onStats }) {
|
|
|
343
402
|
}}>${g.name} (${g.members?.length || 0} ${t('profile.access.sharingGroups.members')})</button>
|
|
344
403
|
`)
|
|
345
404
|
}
|
|
346
|
-
<button class="btn-outline btn-sm mt-xs" onClick=${() => { setGroupPickerFor(null); }}>${t('profile.memory.
|
|
405
|
+
<button class="btn-outline btn-sm mt-xs" onClick=${() => { setGroupPickerFor(null); handleUpdateVisibility(m.key, 'public', m.version); }}>${t('profile.memory.skipGroup')}</button>
|
|
347
406
|
</div>
|
|
348
407
|
`}
|
|
349
408
|
${expandedMem === m.key && html`
|
|
@@ -502,16 +561,301 @@ export default function MemoryTab({ session, showToast, onStats }) {
|
|
|
502
561
|
} catch (e) { showToast(e.message, true); }
|
|
503
562
|
}
|
|
504
563
|
|
|
564
|
+
function browseErrorMessage(e) {
|
|
565
|
+
const code = e.code || '';
|
|
566
|
+
const msg = e.message || '';
|
|
567
|
+
if (code === 'FEDERATION_PROXY_ERROR' || msg.includes('Localhost not allowed') || msg.includes('ocalhost'))
|
|
568
|
+
return t('profile.memory.errorLocalhost');
|
|
569
|
+
if (code === 'PEER_NOT_FOUND' || msg.includes('not found'))
|
|
570
|
+
return t('profile.memory.errorPeerNotFound');
|
|
571
|
+
if (code === 'ROUTE_NOT_FOUND' || e.status === 404)
|
|
572
|
+
return t('profile.memory.errorPeerUnsupported');
|
|
573
|
+
return msg;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async function loadBrowseHome() {
|
|
577
|
+
setBrowseMode('home');
|
|
578
|
+
setBrowseLoading(true);
|
|
579
|
+
setBrowseError(null);
|
|
580
|
+
setRemoteEntries(null);
|
|
581
|
+
try {
|
|
582
|
+
const entries = await memoryService.listHomeMemories();
|
|
583
|
+
setRemoteEntries(entries);
|
|
584
|
+
} catch (e) {
|
|
585
|
+
setBrowseError(browseErrorMessage(e));
|
|
586
|
+
setRemoteEntries([]);
|
|
587
|
+
} finally { setBrowseLoading(false); }
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function loadBrowseRemote(peerNodeId) {
|
|
591
|
+
if (!peerNodeId) return;
|
|
592
|
+
setSelectedPeer(peerNodeId);
|
|
593
|
+
setBrowseMode('remote');
|
|
594
|
+
setBrowseLoading(true);
|
|
595
|
+
setBrowseError(null);
|
|
596
|
+
setRemoteEntries(null);
|
|
597
|
+
try {
|
|
598
|
+
const entries = await memoryService.listRemoteMemories(peerNodeId);
|
|
599
|
+
setRemoteEntries(entries);
|
|
600
|
+
} catch (e) {
|
|
601
|
+
setBrowseError(browseErrorMessage(e));
|
|
602
|
+
setRemoteEntries([]);
|
|
603
|
+
} finally { setBrowseLoading(false); }
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function initBrowseRemote() {
|
|
607
|
+
setBrowseMode('remote');
|
|
608
|
+
setRemoteEntries(null);
|
|
609
|
+
setSelectedPeer('');
|
|
610
|
+
try {
|
|
611
|
+
const peers = await listPeers();
|
|
612
|
+
setRemotePeers(Array.isArray(peers) ? peers.filter(p => p.status === 'active' || p.status === 'healthy') : []);
|
|
613
|
+
} catch { setRemotePeers([]); }
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async function handlePullRemoteEntry(key, peerNodeId) {
|
|
617
|
+
const nodeId = peerNodeId || selectedPeer;
|
|
618
|
+
setPullingKeys(prev => new Set([...prev, key]));
|
|
619
|
+
try {
|
|
620
|
+
if (session?.federated) {
|
|
621
|
+
await memoryService.pullFromHome(key);
|
|
622
|
+
} else {
|
|
623
|
+
await memoryService.pullFromRemote(nodeId, key);
|
|
624
|
+
}
|
|
625
|
+
showToast(t('profile.memory.pullSuccess'));
|
|
626
|
+
loadMemories();
|
|
627
|
+
} catch (e) {
|
|
628
|
+
showToast(e.message, true);
|
|
629
|
+
} finally {
|
|
630
|
+
setPullingKeys(prev => { const n = new Set(prev); n.delete(key); return n; });
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async function handlePullAll() {
|
|
635
|
+
if (!remoteEntries?.length) return;
|
|
636
|
+
const node = session?.federated ? (session.homeNode || 'home') : selectedPeer;
|
|
637
|
+
const msg = t('profile.memory.pullAllConfirm').replace('{count}', remoteEntries.length).replace('{node}', node);
|
|
638
|
+
confirm(msg, async () => {
|
|
639
|
+
let pulled = 0;
|
|
640
|
+
for (const entry of remoteEntries) {
|
|
641
|
+
try {
|
|
642
|
+
if (session?.federated) {
|
|
643
|
+
await memoryService.pullFromHome(entry.key);
|
|
644
|
+
} else {
|
|
645
|
+
await memoryService.pullFromRemote(selectedPeer, entry.key);
|
|
646
|
+
}
|
|
647
|
+
pulled++;
|
|
648
|
+
} catch { /* skip failed entries */ }
|
|
649
|
+
}
|
|
650
|
+
showToast(t('profile.memory.pullAllSuccess').replace('{count}', pulled));
|
|
651
|
+
loadMemories();
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async function loadDiscoverEntries(query) {
|
|
656
|
+
setDiscoverLoading(true);
|
|
657
|
+
setDiscoverError(null);
|
|
658
|
+
setDiscoverEntries(null);
|
|
659
|
+
setExpandedDiscover(null);
|
|
660
|
+
try {
|
|
661
|
+
const result = await memoryService.discoverPublicMemories({ q: query || undefined, limit: 100 });
|
|
662
|
+
setDiscoverEntries(result.items || []);
|
|
663
|
+
} catch (e) {
|
|
664
|
+
setDiscoverError(e.message || t('profile.error'));
|
|
665
|
+
setDiscoverEntries([]);
|
|
666
|
+
} finally { setDiscoverLoading(false); }
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function initDiscover() {
|
|
670
|
+
setBrowseMode('discover');
|
|
671
|
+
loadDiscoverEntries('');
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async function handleCopyEntry(ownerGaii, key) {
|
|
675
|
+
setCopyingKeys(prev => new Set([...prev, key]));
|
|
676
|
+
try {
|
|
677
|
+
const resp = await memoryService.copyPublicMemory(ownerGaii, key, 'private');
|
|
678
|
+
if (resp.ok === false) { showToast(resp.error?.message || t('profile.error'), true); return; }
|
|
679
|
+
showToast(t('profile.memory.discoverCopied'));
|
|
680
|
+
loadMemories();
|
|
681
|
+
} catch (e) {
|
|
682
|
+
showToast(e.message || t('profile.error'), true);
|
|
683
|
+
} finally {
|
|
684
|
+
setCopyingKeys(prev => { const n = new Set(prev); n.delete(key); return n; });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function closeBrowse() {
|
|
689
|
+
setBrowseMode(null);
|
|
690
|
+
setRemoteEntries(null);
|
|
691
|
+
setSelectedPeer('');
|
|
692
|
+
setDiscoverEntries(null);
|
|
693
|
+
setDiscoverSearch('');
|
|
694
|
+
setExpandedDiscover(null);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const renderBrowsePanel = () => {
|
|
698
|
+
if (!browseMode) return null;
|
|
699
|
+
|
|
700
|
+
if (browseMode === 'discover') {
|
|
701
|
+
return html`
|
|
702
|
+
<div class="mem-browse-panel">
|
|
703
|
+
<div class="mem-browse-header">
|
|
704
|
+
<div>
|
|
705
|
+
<div class="section-title">${t('profile.memory.discoverTitle')}</div>
|
|
706
|
+
<div class="section-desc">${t('profile.memory.discoverDesc')}</div>
|
|
707
|
+
</div>
|
|
708
|
+
<button class="btn-outline btn-sm" onClick=${closeBrowse}>${t('profile.memory.cancelBtn')}</button>
|
|
709
|
+
</div>
|
|
710
|
+
<div class="mem-discover-search mb-half">
|
|
711
|
+
<input type="text" class="input-field" placeholder=${t('profile.memory.discoverSearchPlaceholder')}
|
|
712
|
+
value=${discoverSearch}
|
|
713
|
+
onInput=${e => setDiscoverSearch(e.target.value)}
|
|
714
|
+
onKeyDown=${e => e.key === 'Enter' && loadDiscoverEntries(discoverSearch)} />
|
|
715
|
+
<button class="btn-sm" onClick=${() => loadDiscoverEntries(discoverSearch)}>${t('profile.memory.searchBtn')}</button>
|
|
716
|
+
</div>
|
|
717
|
+
|
|
718
|
+
${discoverLoading && html`<${Spinner} text=${t('profile.memory.discoverLoading')} />`}
|
|
719
|
+
|
|
720
|
+
${discoverError && !discoverLoading && html`
|
|
721
|
+
<div class="alert alert-warning"><span class="alert-msg">${discoverError}</span></div>
|
|
722
|
+
`}
|
|
723
|
+
|
|
724
|
+
${discoverEntries && !discoverLoading && !discoverError && html`
|
|
725
|
+
<div class="mb-half text-meta">
|
|
726
|
+
${t('profile.memory.discoverCount').replace('{count}', discoverEntries.length)}
|
|
727
|
+
</div>
|
|
728
|
+
${discoverEntries.length === 0
|
|
729
|
+
? html`<div class="empty">${t('profile.memory.discoverEmpty')}</div>`
|
|
730
|
+
: html`<div class="mem-browse-list">
|
|
731
|
+
${discoverEntries.map(entry => {
|
|
732
|
+
const ownerShort = entry.owner_gaii?.split('@')[0] || entry.owner_gaii;
|
|
733
|
+
const isExpanded = expandedDiscover === entry.owner_gaii + '/' + entry.key;
|
|
734
|
+
return html`
|
|
735
|
+
<div key=${entry.owner_gaii + '/' + entry.key} class="mem-discover-item">
|
|
736
|
+
<div class="mem-discover-row" onClick=${() => setExpandedDiscover(isExpanded ? null : entry.owner_gaii + '/' + entry.key)}>
|
|
737
|
+
<div class="mem-discover-info">
|
|
738
|
+
<div class="mem-browse-key" title=${entry.key}>${escHtml(entry.key)}</div>
|
|
739
|
+
<div class="mem-discover-owner">${escHtml(ownerShort)}</div>
|
|
740
|
+
</div>
|
|
741
|
+
<div class="mem-browse-meta">
|
|
742
|
+
${entry.tags?.length > 0 && html`<span class="text-meta-sm mem-browse-tags" title=${entry.tags.join(', ')}>${entry.tags.join(', ')}</span>`}
|
|
743
|
+
<span class="mem-time">${formatRelativeTime(entry.updated_at || entry.created_at)}</span>
|
|
744
|
+
</div>
|
|
745
|
+
<button class="btn-primary btn-sm"
|
|
746
|
+
disabled=${copyingKeys.has(entry.key)}
|
|
747
|
+
onClick=${(e) => { e.stopPropagation(); handleCopyEntry(entry.owner_gaii, entry.key); }}>
|
|
748
|
+
${copyingKeys.has(entry.key) ? '...' : t('profile.memory.discoverCopy')}
|
|
749
|
+
</button>
|
|
750
|
+
</div>
|
|
751
|
+
${isExpanded && html`
|
|
752
|
+
<${DiscoverPreview} ownerGaii=${entry.owner_gaii} memKey=${entry.key} />
|
|
753
|
+
`}
|
|
754
|
+
</div>
|
|
755
|
+
`;
|
|
756
|
+
})}
|
|
757
|
+
</div>`
|
|
758
|
+
}
|
|
759
|
+
`}
|
|
760
|
+
</div>
|
|
761
|
+
`;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const isHome = browseMode === 'home';
|
|
765
|
+
const title = isHome ? t('profile.memory.browseHome') : t('profile.memory.browseRemote');
|
|
766
|
+
const desc = isHome ? t('profile.memory.browseHomeDesc') : t('profile.memory.browseRemoteDesc');
|
|
767
|
+
|
|
768
|
+
return html`
|
|
769
|
+
<div class="mem-browse-panel">
|
|
770
|
+
<div class="mem-browse-header">
|
|
771
|
+
<div>
|
|
772
|
+
<div class="section-title">${title}</div>
|
|
773
|
+
<div class="section-desc">${desc}</div>
|
|
774
|
+
</div>
|
|
775
|
+
<button class="btn-outline btn-sm" onClick=${closeBrowse}>${t('profile.memory.cancelBtn')}</button>
|
|
776
|
+
</div>
|
|
777
|
+
|
|
778
|
+
${!isHome && !selectedPeer && html`
|
|
779
|
+
<div class="mb-1">
|
|
780
|
+
${remotePeers.length === 0
|
|
781
|
+
? html`<div class="empty">${t('profile.memory.noPeers')}</div>`
|
|
782
|
+
: html`
|
|
783
|
+
<select class="input-field" onChange=${e => loadBrowseRemote(e.target.value)}>
|
|
784
|
+
<option value="">${t('profile.memory.browseRemoteSelect')}</option>
|
|
785
|
+
${remotePeers.map(p => html`<option key=${p.node_id} value=${p.node_id}>${escHtml(p.node_id)} (${escHtml(p.url || '')})</option>`)}
|
|
786
|
+
</select>
|
|
787
|
+
`}
|
|
788
|
+
</div>
|
|
789
|
+
`}
|
|
790
|
+
|
|
791
|
+
${browseLoading && html`<${Spinner} text=${isHome ? t('profile.memory.loadingHome') : t('profile.memory.loadingRemote')} />`}
|
|
792
|
+
|
|
793
|
+
${browseError && !browseLoading && html`
|
|
794
|
+
<div class="alert alert-warning">
|
|
795
|
+
<span class="alert-msg">${browseError}</span>
|
|
796
|
+
</div>
|
|
797
|
+
`}
|
|
798
|
+
|
|
799
|
+
${remoteEntries && !browseLoading && !browseError && html`
|
|
800
|
+
<div class="mb-half text-meta">
|
|
801
|
+
${isHome
|
|
802
|
+
? t('profile.memory.homeEntries').replace('{count}', remoteEntries.length)
|
|
803
|
+
: t('profile.memory.remoteEntries').replace('{count}', remoteEntries.length).replace('{node}', selectedPeer)}
|
|
804
|
+
${remoteEntries.length > 0 && html`
|
|
805
|
+
<button class="btn-ghost btn-sm pf-ml-half" onClick=${handlePullAll}>${t('profile.memory.pullAllBtn')}</button>
|
|
806
|
+
`}
|
|
807
|
+
</div>
|
|
808
|
+
${remoteEntries.length === 0
|
|
809
|
+
? html`<div class="empty">${isHome ? t('profile.memory.noHomeEntries') : t('profile.memory.noRemoteEntries')}</div>`
|
|
810
|
+
: html`<div class="mem-browse-list">
|
|
811
|
+
${remoteEntries.map(entry => html`
|
|
812
|
+
<div key=${entry.key} class="mem-browse-item">
|
|
813
|
+
<div class="mem-browse-key" title=${entry.key}>${escHtml(entry.key)}</div>
|
|
814
|
+
<div class="mem-browse-meta">
|
|
815
|
+
<${VisibilityPill} visibility=${entry.visibility} />
|
|
816
|
+
${entry.tags?.length > 0 && html`<span class="text-meta-sm mem-browse-tags" title=${entry.tags.join(', ')}>${entry.tags.join(', ')}</span>`}
|
|
817
|
+
</div>
|
|
818
|
+
<button class="btn-primary btn-sm"
|
|
819
|
+
disabled=${pullingKeys.has(entry.key)}
|
|
820
|
+
onClick=${() => handlePullRemoteEntry(entry.key)}>
|
|
821
|
+
${pullingKeys.has(entry.key) ? '...' : t('profile.memory.pullEntry')}
|
|
822
|
+
</button>
|
|
823
|
+
</div>
|
|
824
|
+
`)}
|
|
825
|
+
</div>`
|
|
826
|
+
}
|
|
827
|
+
`}
|
|
828
|
+
</div>
|
|
829
|
+
`;
|
|
830
|
+
};
|
|
831
|
+
|
|
505
832
|
return html`
|
|
506
833
|
<div class="section-title">${t('profile.memory.title')}</div>
|
|
507
834
|
<div class="section-desc">${t('profile.memory.desc')}</div>
|
|
508
835
|
|
|
836
|
+
<div class="mem-browse-buttons mb-1">
|
|
837
|
+
<button class="btn-outline btn-sm" onClick=${initDiscover}>
|
|
838
|
+
${t('profile.memory.discoverTitle')}
|
|
839
|
+
</button>
|
|
840
|
+
${session.federated && html`
|
|
841
|
+
<button class="btn-outline btn-sm" onClick=${loadBrowseHome}>
|
|
842
|
+
${t('profile.memory.browseHome')}
|
|
843
|
+
</button>
|
|
844
|
+
`}
|
|
845
|
+
${!session.federated && html`
|
|
846
|
+
<button class="btn-outline btn-sm" onClick=${initBrowseRemote}>
|
|
847
|
+
${t('profile.memory.browseRemote')}
|
|
848
|
+
</button>
|
|
849
|
+
`}
|
|
850
|
+
</div>
|
|
509
851
|
${session.federated && html`
|
|
510
|
-
<div class="alert alert-info">
|
|
852
|
+
<div class="alert alert-info mb-half">
|
|
511
853
|
<span class="alert-msg">${t('profile.memory.federatedSession')}</span>
|
|
512
854
|
</div>
|
|
513
855
|
`}
|
|
514
856
|
|
|
857
|
+
${renderBrowsePanel()}
|
|
858
|
+
|
|
515
859
|
${agents.length > 1 && html`
|
|
516
860
|
<div class="agent-selector mb-half">
|
|
517
861
|
<label class="text-meta pf-mr-half">${t('profile.memory.agent') || 'Agent'}:</label>
|
|
@@ -541,6 +885,26 @@ export default function MemoryTab({ session, showToast, onStats }) {
|
|
|
541
885
|
`;
|
|
542
886
|
}
|
|
543
887
|
|
|
888
|
+
function DiscoverPreview({ ownerGaii, memKey }) {
|
|
889
|
+
const [value, setValue] = useState(null);
|
|
890
|
+
const [loading, setLoading] = useState(true);
|
|
891
|
+
const [err, setErr] = useState(null);
|
|
892
|
+
useEffect(() => {
|
|
893
|
+
setLoading(true);
|
|
894
|
+
const url = getNodeUrl() + '/v1/memory/' + encodeURIComponent(ownerGaii) + '/' + encodeURIComponent(memKey);
|
|
895
|
+
fetch(url).then(r => r.json()).then(res => {
|
|
896
|
+
if (res.ok) setValue(res.data.value);
|
|
897
|
+
else setErr(res.error?.message || 'Not found');
|
|
898
|
+
}).catch(e => setErr(e.message)).finally(() => setLoading(false));
|
|
899
|
+
}, [ownerGaii, memKey]);
|
|
900
|
+
|
|
901
|
+
if (loading) return html`<div class="mem-discover-preview"><span class="text-meta-sm">...</span></div>`;
|
|
902
|
+
if (err) return html`<div class="mem-discover-preview"><span class="text-meta-sm" style="color:var(--danger)">${err}</span></div>`;
|
|
903
|
+
const text = typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value || '');
|
|
904
|
+
const truncated = text.length > 2000 ? text.slice(0, 2000) + '\n...' : text;
|
|
905
|
+
return html`<div class="mem-discover-preview"><pre>${truncated}</pre></div>`;
|
|
906
|
+
}
|
|
907
|
+
|
|
544
908
|
function MemoryForm({ onSave, onCancel, groups }) {
|
|
545
909
|
const [key, setKey] = useState('');
|
|
546
910
|
const [value, setValue] = useState('');
|
|
@@ -739,7 +1103,7 @@ function EditMemoryModal({ memKey, initialValue, initialVisibility, initialVersi
|
|
|
739
1103
|
${(groups || []).map(g => html`<option value=${g.id}>${g.name}</option>`)}
|
|
740
1104
|
</select>
|
|
741
1105
|
${(groups || []).length === 0 && html`<div class="text-meta-sm">${t('profile.memory.noGroups')}</div>`}
|
|
742
|
-
<button class="btn-outline btn-sm mt-xs" onClick=${() => setShowGroupSelect(false)}>${t('profile.memory.
|
|
1106
|
+
<button class="btn-outline btn-sm mt-xs" onClick=${() => { setVis('public'); setShowGroupSelect(false); setGroupId(''); }}>${t('profile.memory.skipGroup')}</button>
|
|
743
1107
|
</div>
|
|
744
1108
|
`}
|
|
745
1109
|
<textarea class="input-field" rows="6" value=${value} onInput=${e => setValue(e.target.value)}></textarea>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @version-history
|
|
6
6
|
* v1.0.0 — 2026-03-16 — Initial wallet tab
|
|
7
7
|
* v1.1.0 — 2026-03-17 — Replace inline styles with CSS classes
|
|
8
|
+
* v1.2.0 — 2026-05-22 — Show info message for federated users instead of infinite spinner
|
|
8
9
|
*/
|
|
9
10
|
import { h } from 'preact';
|
|
10
11
|
import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
|
|
@@ -131,6 +132,19 @@ export default function WalletTab({ session, showToast, onStats }) {
|
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
if (session?.federated) {
|
|
136
|
+
return html`
|
|
137
|
+
<div class="section-title">${t('profile.wallet.title')}</div>
|
|
138
|
+
<div class="section-desc">${t('profile.wallet.desc')}</div>
|
|
139
|
+
<div class="alert alert-info">
|
|
140
|
+
<span class="alert-msg">${t('profile.wallet.federatedInfo')}</span>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="text-meta mt-1">
|
|
143
|
+
${t('profile.wallet.federatedHomeNode')}: <strong>${session.homeNode || '?'}</strong>
|
|
144
|
+
</div>
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
|
|
134
148
|
if (!walletData) return html`<${Spinner} text=${t('profile.wallet.loading')} />`;
|
|
135
149
|
const w = walletData;
|
|
136
150
|
const escrow = w.in_escrow ?? w.escrow ?? 0;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @file agent-capabilities-schemas.ts
|
|
3
3
|
* @description Zod validation schemas for agent capability reporting endpoints
|
|
4
4
|
* @version-history
|
|
5
|
+
* v1.1.0 -- 2026-05-22 -- Add modules_loaded and limitations fields
|
|
5
6
|
* v1.0.0 -- 2026-05-22 -- Initial schemas for capabilities PUT
|
|
6
7
|
*/
|
|
7
8
|
import { z } from 'zod';
|
|
@@ -16,5 +17,7 @@ export declare const AgentCapabilitiesUpdateSchema: z.ZodObject<{
|
|
|
16
17
|
}, z.core.$strip>>>>;
|
|
17
18
|
domain: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
18
19
|
languages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
20
|
+
modules_loaded: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
21
|
+
limitations: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
19
22
|
}, z.core.$strip>;
|
|
20
23
|
//# sourceMappingURL=agent-capabilities-schemas.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-capabilities-schemas.d.ts","sourceRoot":"","sources":["../../../src/models/agent-capabilities-schemas.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"agent-capabilities-schemas.d.ts","sourceRoot":"","sources":["../../../src/models/agent-capabilities-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;iBASxC,CAAC"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @file agent-capabilities-schemas.ts
|
|
3
3
|
* @description Zod validation schemas for agent capability reporting endpoints
|
|
4
4
|
* @version-history
|
|
5
|
+
* v1.1.0 -- 2026-05-22 -- Add modules_loaded and limitations fields
|
|
5
6
|
* v1.0.0 -- 2026-05-22 -- Initial schemas for capabilities PUT
|
|
6
7
|
*/
|
|
7
8
|
import { z } from 'zod';
|
|
@@ -12,5 +13,7 @@ export const AgentCapabilitiesUpdateSchema = z.object({
|
|
|
12
13
|
})).max(100).optional().default([]),
|
|
13
14
|
domain: z.array(z.string().min(1).max(256)).max(50).optional().default([]),
|
|
14
15
|
languages: z.array(z.string().min(1).max(10)).max(20).optional(),
|
|
16
|
+
modules_loaded: z.array(z.string().min(1).max(64)).max(20).optional(),
|
|
17
|
+
limitations: z.array(z.string().min(1).max(500)).max(50).optional(),
|
|
15
18
|
});
|
|
16
19
|
//# sourceMappingURL=agent-capabilities-schemas.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-capabilities-schemas.js","sourceRoot":"","sources":["../../../src/models/agent-capabilities-schemas.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"agent-capabilities-schemas.js","sourceRoot":"","sources":["../../../src/models/agent-capabilities-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;QAC1B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QAChC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;KACvC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACnC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC1E,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IAChE,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACrE,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;CACpE,CAAC,CAAC"}
|
|
@@ -136,10 +136,10 @@ export function adminMonitoringRouter(config, storage, peers) {
|
|
|
136
136
|
emitChange('config');
|
|
137
137
|
const targetNodeId = targetInfo.node_id ?? targetUrl;
|
|
138
138
|
if (remoteStatus === 'auto_approved') {
|
|
139
|
-
completeJoin(targetUrl, targetNodeId,
|
|
139
|
+
completeJoin(targetUrl, targetNodeId, config, storage, peers);
|
|
140
140
|
}
|
|
141
141
|
else {
|
|
142
|
-
pollForApprovalAndComplete(targetUrl, targetNodeId, requestId,
|
|
142
|
+
pollForApprovalAndComplete(targetUrl, targetNodeId, requestId, config, storage, peers);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
catch (e) {
|
|
@@ -371,12 +371,12 @@ export function adminMonitoringRouter(config, storage, peers) {
|
|
|
371
371
|
return router;
|
|
372
372
|
}
|
|
373
373
|
/** Save the remote node as a local peer and perform key exchange. */
|
|
374
|
-
async function completeJoin(targetUrl, targetNodeId,
|
|
374
|
+
async function completeJoin(targetUrl, targetNodeId, config, storage, peers) {
|
|
375
375
|
const now = new Date().toISOString();
|
|
376
376
|
const newPeer = {
|
|
377
377
|
nodeId: targetNodeId,
|
|
378
378
|
url: targetUrl,
|
|
379
|
-
publicKey:
|
|
379
|
+
publicKey: '',
|
|
380
380
|
status: 'active',
|
|
381
381
|
addedAt: now,
|
|
382
382
|
lastSeen: now,
|
|
@@ -391,16 +391,21 @@ async function completeJoin(targetUrl, targetNodeId, targetPublicKey, config, st
|
|
|
391
391
|
peers.set(targetNodeId, newPeer);
|
|
392
392
|
await storage.saveFederationPeer(newPeer);
|
|
393
393
|
const result = await performKeyExchange(targetUrl, config, storage);
|
|
394
|
-
if (result.success) {
|
|
394
|
+
if (result.success && result.peerPublicKey) {
|
|
395
|
+
newPeer.publicKey = result.peerPublicKey;
|
|
396
|
+
await storage.saveFederationPeer(newPeer);
|
|
395
397
|
logger.info(`Join complete: peer ${targetNodeId} added and keys exchanged`);
|
|
396
398
|
}
|
|
399
|
+
else if (result.success) {
|
|
400
|
+
logger.info(`Join complete: peer ${targetNodeId} added (no public key received)`);
|
|
401
|
+
}
|
|
397
402
|
else {
|
|
398
403
|
logger.warn(`Join: peer ${targetNodeId} saved but key exchange failed: ${result.error}`);
|
|
399
404
|
}
|
|
400
405
|
emitChange('federation');
|
|
401
406
|
}
|
|
402
407
|
/** Background poll for remote approval, then complete the join. */
|
|
403
|
-
function pollForApprovalAndComplete(targetUrl, targetNodeId, requestId,
|
|
408
|
+
function pollForApprovalAndComplete(targetUrl, targetNodeId, requestId, config, storage, peers) {
|
|
404
409
|
const startedAt = Date.now();
|
|
405
410
|
const timer = setInterval(async () => {
|
|
406
411
|
if (Date.now() - startedAt > MAX_POLL_DURATION_MS) {
|
|
@@ -417,7 +422,7 @@ function pollForApprovalAndComplete(targetUrl, targetNodeId, requestId, targetPu
|
|
|
417
422
|
if (status === 'approved' || status === 'auto_approved') {
|
|
418
423
|
clearInterval(timer);
|
|
419
424
|
logger.info(`Join request ${requestId} approved by ${targetNodeId}`);
|
|
420
|
-
await completeJoin(targetUrl, targetNodeId,
|
|
425
|
+
await completeJoin(targetUrl, targetNodeId, config, storage, peers);
|
|
421
426
|
}
|
|
422
427
|
else if (status === 'rejected') {
|
|
423
428
|
clearInterval(timer);
|