* Probe every known agent to refresh capability + health snapshots in the DB, * and fill in agent type for any that are still `unknown`. The registry * page reads these snapshot tables instead of calling agents live, so this * pass is the materialization step that keeps the public API fast.
(agents: Agent[])
| 496 | * CapabilityDiscovery; inferTypeFromProfile collapses them to one. |
| 497 | */ |
| 498 | private async refreshAgentSnapshots(agents: Agent[]): Promise<void> { |
| 499 | log.debug('Refreshing agent health + capability snapshots'); |
| 500 | |
| 501 | const allAgents = await this.federatedIndex.listAllAgents(); |
| 502 | const knownTypes = new Map<string, string>(); |
| 503 | for (const a of allAgents) { |
| 504 | if (a.type && a.type !== 'unknown') { |
| 505 | knownTypes.set(a.url, a.type); |
| 506 | } |
| 507 | } |
| 508 | |
| 509 | // Build one probe entry per unique URL; prefer registered-agent metadata |
| 510 | // (name/protocol) when we have it, else derive from URL. |
| 511 | const pausedUrls = await this.getPausedAgentUrls(); |
| 512 | const seen = new Set<string>(); |
| 513 | const toProbe: Agent[] = []; |
| 514 | for (const src of [...agents, ...allAgents.map(a => ({ |
| 515 | name: a.name || a.url, |
| 516 | url: a.url, |
| 517 | type: (a.type as Agent['type']) || 'unknown', |
| 518 | protocol: (a.protocol as 'mcp' | 'a2a') || 'mcp', |
| 519 | description: '', |
| 520 | mcp_endpoint: a.url, |
| 521 | contact: { name: '', email: '', website: '' }, |
| 522 | added_date: new Date().toISOString().split('T')[0], |
| 523 | } satisfies Agent))]) { |
| 524 | if (seen.has(src.url) || pausedUrls.has(src.url)) continue; |
| 525 | seen.add(src.url); |
| 526 | toProbe.push(src); |
| 527 | } |
| 528 | |
| 529 | if (toProbe.length === 0) { |
| 530 | log.debug('No agents to probe'); |
| 531 | return; |
| 532 | } |
| 533 | |
| 534 | const CONCURRENCY = 5; |
| 535 | const PROBE_TIMEOUT_MS = 10000; |
| 536 | let typesUpdated = 0; |
| 537 | let snapshotsWritten = 0; |
| 538 | let failed = 0; |
| 539 | |
| 540 | for (let i = 0; i < toProbe.length; i += CONCURRENCY) { |
| 541 | const batch = toProbe.slice(i, i + CONCURRENCY); |
| 542 | const results = await Promise.allSettled( |
| 543 | batch.map(async (agent) => { |
| 544 | const profile = await Promise.race([ |
| 545 | this.capabilityDiscovery.discoverCapabilities(agent), |
| 546 | new Promise<never>((_, reject) => |
| 547 | setTimeout(() => reject(new Error('Probe timeout')), PROBE_TIMEOUT_MS) |
| 548 | ), |
| 549 | ]); |
| 550 | |
| 551 | const inferredType = this.capabilityDiscovery.inferTypeFromProfile(profile); |
| 552 | const effectiveType = knownTypes.get(agent.url) || inferredType; |
| 553 | const agentForHealth: Agent = { ...agent, type: effectiveType as Agent['type'], protocol: profile.protocol }; |
| 554 | |
| 555 | const [health, stats] = await Promise.all([ |
no test coverage detected