@holoscript/framework 6.0.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/ALL-test-results.json +1 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/ROADMAP.md +175 -0
- package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
- package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
- package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
- package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
- package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
- package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
- package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
- package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
- package/dist/agents/index.cjs +4790 -0
- package/dist/agents/index.d.cts +1788 -0
- package/dist/agents/index.d.ts +1788 -0
- package/dist/agents/index.js +4695 -0
- package/dist/ai/index.cjs +5347 -0
- package/dist/ai/index.d.cts +1753 -0
- package/dist/ai/index.d.ts +1753 -0
- package/dist/ai/index.js +5244 -0
- package/dist/behavior.cjs +449 -0
- package/dist/behavior.d.cts +130 -0
- package/dist/behavior.d.ts +130 -0
- package/dist/behavior.js +407 -0
- package/dist/economy/index.cjs +3659 -0
- package/dist/economy/index.d.cts +747 -0
- package/dist/economy/index.d.ts +747 -0
- package/dist/economy/index.js +3617 -0
- package/dist/implementations-D9T3un9D.d.cts +236 -0
- package/dist/implementations-D9T3un9D.d.ts +236 -0
- package/dist/index.cjs +24550 -0
- package/dist/index.d.cts +1729 -0
- package/dist/index.d.ts +1729 -0
- package/dist/index.js +24277 -0
- package/dist/learning/index.cjs +219 -0
- package/dist/learning/index.d.cts +104 -0
- package/dist/learning/index.d.ts +104 -0
- package/dist/learning/index.js +189 -0
- package/dist/negotiation/index.cjs +970 -0
- package/dist/negotiation/index.d.cts +610 -0
- package/dist/negotiation/index.d.ts +610 -0
- package/dist/negotiation/index.js +931 -0
- package/dist/skills/index.cjs +1118 -0
- package/dist/skills/index.d.cts +289 -0
- package/dist/skills/index.d.ts +289 -0
- package/dist/skills/index.js +1079 -0
- package/dist/swarm/index.cjs +5268 -0
- package/dist/swarm/index.d.cts +2433 -0
- package/dist/swarm/index.d.ts +2433 -0
- package/dist/swarm/index.js +5221 -0
- package/dist/training/index.cjs +2745 -0
- package/dist/training/index.d.cts +1734 -0
- package/dist/training/index.d.ts +1734 -0
- package/dist/training/index.js +2687 -0
- package/extract-failures.js +10 -0
- package/package.json +82 -0
- package/src/__tests__/bounty-marketplace.test.ts +374 -0
- package/src/__tests__/delegation.test.ts +144 -0
- package/src/__tests__/distributed-claimer.test.ts +147 -0
- package/src/__tests__/done-log-audit.test.ts +342 -0
- package/src/__tests__/framework.test.ts +865 -0
- package/src/__tests__/goal-synthesizer.test.ts +236 -0
- package/src/__tests__/presence.test.ts +223 -0
- package/src/__tests__/protocol-agent.test.ts +254 -0
- package/src/__tests__/revenue-splitter.test.ts +114 -0
- package/src/__tests__/scenario-driven-todo.test.ts +197 -0
- package/src/__tests__/self-improve.test.ts +349 -0
- package/src/__tests__/service-lifecycle.test.ts +237 -0
- package/src/__tests__/skill-router.test.ts +121 -0
- package/src/agents/AgentManifest.ts +493 -0
- package/src/agents/AgentRegistry.ts +475 -0
- package/src/agents/AgentTypes.ts +585 -0
- package/src/agents/AgentWalletRegistry.ts +83 -0
- package/src/agents/AuthenticatedCRDT.ts +388 -0
- package/src/agents/CapabilityMatcher.ts +453 -0
- package/src/agents/CrossRealityHandoff.ts +305 -0
- package/src/agents/CulturalMemory.ts +454 -0
- package/src/agents/FederatedRegistryAdapter.ts +429 -0
- package/src/agents/NormEngine.ts +450 -0
- package/src/agents/OrchestratorAgent.ts +414 -0
- package/src/agents/SkillWorkflowEngine.ts +472 -0
- package/src/agents/TaskDelegationService.ts +551 -0
- package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
- package/src/agents/__tests__/AgentManifest.test.ts +182 -0
- package/src/agents/__tests__/AgentModule.test.ts +864 -0
- package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
- package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
- package/src/agents/__tests__/AgentTypes.test.ts +534 -0
- package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
- package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
- package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
- package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
- package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
- package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
- package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
- package/src/agents/__tests__/NormEngine.test.ts +276 -0
- package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
- package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
- package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
- package/src/agents/index.ts +107 -0
- package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
- package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
- package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
- package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
- package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
- package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
- package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
- package/src/agents/spatial-comms/index.ts +66 -0
- package/src/ai/AIAdapter.ts +313 -0
- package/src/ai/AICopilot.ts +331 -0
- package/src/ai/AIOutputValidator.ts +203 -0
- package/src/ai/BTNodes.ts +239 -0
- package/src/ai/BehaviorSelector.ts +135 -0
- package/src/ai/BehaviorTree.ts +153 -0
- package/src/ai/Blackboard.ts +165 -0
- package/src/ai/GenerationAnalytics.ts +461 -0
- package/src/ai/GenerationCache.ts +265 -0
- package/src/ai/GoalPlanner.ts +165 -0
- package/src/ai/HoloScriptGenerator.ts +580 -0
- package/src/ai/InfluenceMap.ts +180 -0
- package/src/ai/NavMesh.ts +168 -0
- package/src/ai/PerceptionSystem.ts +178 -0
- package/src/ai/PromptTemplates.ts +453 -0
- package/src/ai/SemanticSearchService.ts +80 -0
- package/src/ai/StateMachine.ts +196 -0
- package/src/ai/SteeringBehavior.ts +150 -0
- package/src/ai/SteeringBehaviors.ts +244 -0
- package/src/ai/TrainingDataGenerator.ts +1082 -0
- package/src/ai/UtilityAI.ts +145 -0
- package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
- package/src/ai/__tests__/AIAdapter.test.ts +109 -0
- package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
- package/src/ai/__tests__/AICopilot.test.ts +178 -0
- package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
- package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
- package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
- package/src/ai/__tests__/BTNodes.test.ts +263 -0
- package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
- package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
- package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
- package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
- package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
- package/src/ai/__tests__/Blackboard.test.ts +183 -0
- package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
- package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
- package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
- package/src/ai/__tests__/GenerationCache.test.ts +171 -0
- package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
- package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
- package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
- package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
- package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
- package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
- package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
- package/src/ai/__tests__/NavMesh.test.ts +159 -0
- package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
- package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
- package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
- package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
- package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
- package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
- package/src/ai/__tests__/StateMachine.test.ts +163 -0
- package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
- package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
- package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
- package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
- package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
- package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
- package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
- package/src/ai/__tests__/UtilityAI.test.ts +155 -0
- package/src/ai/__tests__/adapters.prod.test.ts +263 -0
- package/src/ai/__tests__/adapters.test.ts +320 -0
- package/src/ai/adapters.ts +1585 -0
- package/src/ai/index.ts +130 -0
- package/src/behavior/BehaviorPresets.ts +140 -0
- package/src/behavior/BehaviorTree.ts +236 -0
- package/src/behavior/StateMachine.ts +176 -0
- package/src/behavior/StateTrait.ts +67 -0
- package/src/behavior/index.ts +8 -0
- package/src/behavior.ts +8 -0
- package/src/board/audit.ts +284 -0
- package/src/board/board-ops.ts +336 -0
- package/src/board/board-types.ts +302 -0
- package/src/board/index.ts +69 -0
- package/src/define-agent.ts +46 -0
- package/src/define-team.ts +33 -0
- package/src/delegation.ts +265 -0
- package/src/distributed-claimer.ts +228 -0
- package/src/economy/AgentBudgetEnforcer.ts +464 -0
- package/src/economy/BountyManager.ts +185 -0
- package/src/economy/CreatorRevenueAggregator.ts +460 -0
- package/src/economy/InvisibleWallet.ts +82 -0
- package/src/economy/KnowledgeMarketplace.ts +193 -0
- package/src/economy/PaymentWebhookService.ts +512 -0
- package/src/economy/RevenueSplitter.ts +156 -0
- package/src/economy/SubscriptionManager.ts +546 -0
- package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
- package/src/economy/UsageMeter.ts +440 -0
- package/src/economy/_core-stubs.ts +219 -0
- package/src/economy/index.ts +100 -0
- package/src/economy/x402-facilitator.ts +1978 -0
- package/src/index.ts +348 -0
- package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
- package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
- package/src/knowledge/brain.ts +167 -0
- package/src/knowledge/consolidation.ts +581 -0
- package/src/knowledge/knowledge-consolidator.ts +510 -0
- package/src/knowledge/knowledge-store.ts +616 -0
- package/src/learning/MemoryConsolidator.ts +102 -0
- package/src/learning/MemoryScorer.ts +69 -0
- package/src/learning/ProceduralCompiler.ts +45 -0
- package/src/learning/SemanticClusterer.ts +66 -0
- package/src/learning/index.ts +8 -0
- package/src/llm/llm-adapter.ts +159 -0
- package/src/mesh/index.ts +309 -0
- package/src/negotiation/NegotiationProtocol.ts +694 -0
- package/src/negotiation/NegotiationTypes.ts +473 -0
- package/src/negotiation/VotingMechanisms.ts +691 -0
- package/src/negotiation/index.ts +49 -0
- package/src/protocol/goal-synthesizer.ts +317 -0
- package/src/protocol/implementations.ts +474 -0
- package/src/protocol/micro-phase-decomposer.ts +299 -0
- package/src/protocol/micro-step-decomposer.test.ts +306 -0
- package/src/protocol-agent.test.ts +353 -0
- package/src/protocol-agent.ts +670 -0
- package/src/self-improve/absorb-scanner.ts +252 -0
- package/src/self-improve/evolution-engine.ts +149 -0
- package/src/self-improve/framework-absorber.ts +214 -0
- package/src/self-improve/index.ts +50 -0
- package/src/self-improve/prompt-optimizer.ts +212 -0
- package/src/self-improve/test-generator.ts +175 -0
- package/src/skill-router.ts +186 -0
- package/src/skills/index.ts +5 -0
- package/src/skills/skill-md-bridge.ts +1699 -0
- package/src/swarm/ACOEngine.ts +261 -0
- package/src/swarm/CollectiveIntelligence.ts +383 -0
- package/src/swarm/ContributionSynthesizer.ts +481 -0
- package/src/swarm/LeaderElection.ts +393 -0
- package/src/swarm/PSOEngine.ts +206 -0
- package/src/swarm/QuorumPolicy.ts +173 -0
- package/src/swarm/SwarmCoordinator.ts +335 -0
- package/src/swarm/SwarmManager.ts +442 -0
- package/src/swarm/SwarmMembership.ts +456 -0
- package/src/swarm/VotingRound.ts +255 -0
- package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
- package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
- package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
- package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
- package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
- package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
- package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
- package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
- package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
- package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
- package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
- package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
- package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
- package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
- package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
- package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
- package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
- package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
- package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
- package/src/swarm/__tests__/VotingRound.test.ts +174 -0
- package/src/swarm/analytics/SwarmInspector.ts +476 -0
- package/src/swarm/analytics/SwarmMetrics.ts +449 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
- package/src/swarm/analytics/index.ts +7 -0
- package/src/swarm/index.ts +69 -0
- package/src/swarm/messaging/BroadcastChannel.ts +509 -0
- package/src/swarm/messaging/GossipProtocol.ts +565 -0
- package/src/swarm/messaging/SwarmEventBus.ts +443 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
- package/src/swarm/messaging/index.ts +8 -0
- package/src/swarm/spatial/FlockingBehavior.ts +462 -0
- package/src/swarm/spatial/FormationController.ts +500 -0
- package/src/swarm/spatial/Vector3.ts +170 -0
- package/src/swarm/spatial/ZoneClaiming.ts +509 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
- package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
- package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
- package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
- package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
- package/src/swarm/spatial/index.ts +28 -0
- package/src/team.ts +1245 -0
- package/src/training/LRScheduler.ts +377 -0
- package/src/training/QualityScoringPipeline.ts +139 -0
- package/src/training/SoftDedup.ts +461 -0
- package/src/training/SparsityMonitor.ts +685 -0
- package/src/training/SparsityMonitorTypes.ts +209 -0
- package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
- package/src/training/SpatialTrainingDataTypes.ts +216 -0
- package/src/training/TrainingPipelineConfig.ts +215 -0
- package/src/training/constants.ts +94 -0
- package/src/training/index.ts +138 -0
- package/src/training/schema.ts +147 -0
- package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
- package/src/training/scripts/generate-spatial-dataset.ts +521 -0
- package/src/training/training/data/novel-use-cases.jsonl +153 -0
- package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
- package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
- package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
- package/src/training/trainingmonkey/index.ts +26 -0
- package/src/training/trait-mappings.ts +157 -0
- package/src/types/core-stubs.d.ts +113 -0
- package/src/types.ts +304 -0
- package/test-output.txt +0 -0
- package/test-result.json +1 -0
- package/tsc-errors.txt +4 -0
- package/tsc_output.txt +0 -0
- package/tsconfig.json +14 -0
- package/tsup-learning-esm.config.ts +12 -0
- package/tsup.config.ts +21 -0
- package/typescript-errors-2.txt +0 -0
- package/typescript-errors.txt +22 -0
- package/vitest-log-utf8.txt +268 -0
- package/vitest-log.txt +0 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for CulturalMemory — Dual Memory Architecture
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { CulturalMemory } from '../CulturalMemory';
|
|
7
|
+
|
|
8
|
+
describe('CulturalMemory', () => {
|
|
9
|
+
// ── Episodic Memory ────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
describe('Episodic Memory', () => {
|
|
12
|
+
it('records and recalls memories', () => {
|
|
13
|
+
const mem = new CulturalMemory();
|
|
14
|
+
mem.record('agent1', 'Met agent2 at market', {
|
|
15
|
+
participants: ['agent2'],
|
|
16
|
+
valence: 0.8,
|
|
17
|
+
normId: 'fair_trade',
|
|
18
|
+
tags: ['trade'],
|
|
19
|
+
});
|
|
20
|
+
const recalled = mem.recall('agent1');
|
|
21
|
+
expect(recalled).toHaveLength(1);
|
|
22
|
+
expect(recalled[0].event).toBe('Met agent2 at market');
|
|
23
|
+
expect(recalled[0].valence).toBe(0.8);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('filters by normId', () => {
|
|
27
|
+
const mem = new CulturalMemory();
|
|
28
|
+
mem.record('agent1', 'Traded fairly', { normId: 'fair_trade' });
|
|
29
|
+
mem.record('agent1', 'Greeted neighbor', { normId: 'greeting_convention' });
|
|
30
|
+
const filtered = mem.recall('agent1', { normId: 'fair_trade' });
|
|
31
|
+
expect(filtered).toHaveLength(1);
|
|
32
|
+
expect(filtered[0].normId).toBe('fair_trade');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('filters by tags', () => {
|
|
36
|
+
const mem = new CulturalMemory();
|
|
37
|
+
mem.record('agent1', 'Found resource', { tags: ['resource', 'discovery'] });
|
|
38
|
+
mem.record('agent1', 'Had a fight', { tags: ['conflict'] });
|
|
39
|
+
const filtered = mem.recall('agent1', { tags: ['resource'] });
|
|
40
|
+
expect(filtered).toHaveLength(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('evicts oldest when over capacity', () => {
|
|
44
|
+
const mem = new CulturalMemory({ episodicCapacity: 3 });
|
|
45
|
+
for (let i = 0; i < 5; i++) {
|
|
46
|
+
mem.record('agent1', `Event ${i}`, { importance: i * 0.2 });
|
|
47
|
+
}
|
|
48
|
+
expect(mem.memoryCount('agent1')).toBe(3);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('decays memory strength over ticks', () => {
|
|
52
|
+
const mem = new CulturalMemory({ episodicDecayRate: 0.1 });
|
|
53
|
+
mem.record('agent1', 'Old event');
|
|
54
|
+
const before = mem.recall('agent1')[0].strength;
|
|
55
|
+
mem.tick();
|
|
56
|
+
const after = mem.recall('agent1')[0].strength;
|
|
57
|
+
expect(after).toBeLessThan(before);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('prunes memories below threshold', () => {
|
|
61
|
+
const mem = new CulturalMemory({ episodicDecayRate: 0.99 }); // Extreme decay
|
|
62
|
+
mem.record('agent1', 'Fragile memory');
|
|
63
|
+
for (let i = 0; i < 10; i++) mem.tick(); // Decay to near-zero
|
|
64
|
+
expect(mem.memoryCount('agent1')).toBe(0);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ── Stigmergic Traces ────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
describe('Stigmergic Traces', () => {
|
|
71
|
+
it('leaves and perceives traces', () => {
|
|
72
|
+
const mem = new CulturalMemory();
|
|
73
|
+
mem.leaveTrace('agent1', 'zone_a', 'danger', { x: 10, y: 0, z: 10 });
|
|
74
|
+
const perceived = mem.perceiveTraces('zone_a', { x: 12, y: 0, z: 10 });
|
|
75
|
+
expect(perceived).toHaveLength(1);
|
|
76
|
+
expect(perceived[0].label).toBe('danger');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('does not perceive out-of-range traces', () => {
|
|
80
|
+
const mem = new CulturalMemory();
|
|
81
|
+
mem.leaveTrace('agent1', 'zone_a', 'far away', { x: 0, y: 0, z: 0 }, { radius: 5 });
|
|
82
|
+
const perceived = mem.perceiveTraces('zone_a', { x: 100, y: 0, z: 100 });
|
|
83
|
+
expect(perceived).toHaveLength(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('reinforces traces', () => {
|
|
87
|
+
const mem = new CulturalMemory();
|
|
88
|
+
const trace = mem.leaveTrace('agent1', 'zone_a', 'food here', { x: 5, y: 0, z: 5 });
|
|
89
|
+
const initialIntensity = trace.intensity;
|
|
90
|
+
mem.reinforceTrace(trace.id, 'zone_a');
|
|
91
|
+
const reinforced = mem.zoneTraces('zone_a').find((t) => t.id === trace.id);
|
|
92
|
+
expect(reinforced!.intensity).toBeGreaterThan(initialIntensity);
|
|
93
|
+
expect(reinforced!.reinforcements).toBe(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('traces decay over ticks', () => {
|
|
97
|
+
const mem = new CulturalMemory({ traceDecayRate: 0.1 });
|
|
98
|
+
const trace = mem.leaveTrace(
|
|
99
|
+
'agent1',
|
|
100
|
+
'zone_a',
|
|
101
|
+
'temp',
|
|
102
|
+
{ x: 0, y: 0, z: 0 },
|
|
103
|
+
{ decayRate: 0.1 }
|
|
104
|
+
);
|
|
105
|
+
const before = trace.intensity;
|
|
106
|
+
mem.tick();
|
|
107
|
+
const after = mem.zoneTraces('zone_a').find((t) => t.id === trace.id);
|
|
108
|
+
expect(after!.intensity).toBeLessThan(before);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('evaporated traces are pruned', () => {
|
|
112
|
+
const mem = new CulturalMemory();
|
|
113
|
+
mem.leaveTrace('agent1', 'zone_a', 'ephemeral', { x: 0, y: 0, z: 0 }, { decayRate: 1.0 });
|
|
114
|
+
const result = mem.tick();
|
|
115
|
+
expect(result.evaporatedTraces).toBe(1);
|
|
116
|
+
expect(mem.zoneTraces('zone_a')).toHaveLength(0);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ── SOP Consolidation ────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
describe('SOP Consolidation', () => {
|
|
123
|
+
it('forms SOP when threshold reached', () => {
|
|
124
|
+
const mem = new CulturalMemory({ consolidationThreshold: 3 });
|
|
125
|
+
for (let i = 0; i < 5; i++) {
|
|
126
|
+
mem.record('agent1', `Traded fairly ${i}`, { normId: 'fair_trade', valence: 0.8 });
|
|
127
|
+
}
|
|
128
|
+
const sops = mem.consolidate('agent1');
|
|
129
|
+
expect(sops).toHaveLength(1);
|
|
130
|
+
expect(sops[0].normId).toBe('fair_trade');
|
|
131
|
+
expect(sops[0].actions).toContain('comply');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('does not form SOP below threshold', () => {
|
|
135
|
+
const mem = new CulturalMemory({ consolidationThreshold: 10 });
|
|
136
|
+
for (let i = 0; i < 3; i++) {
|
|
137
|
+
mem.record('agent1', `Event ${i}`, { normId: 'rare_norm' });
|
|
138
|
+
}
|
|
139
|
+
const sops = mem.consolidate('agent1');
|
|
140
|
+
expect(sops).toHaveLength(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('forms avoidance SOP for negative experiences', () => {
|
|
144
|
+
const mem = new CulturalMemory({ consolidationThreshold: 3 });
|
|
145
|
+
for (let i = 0; i < 5; i++) {
|
|
146
|
+
mem.record('agent1', `Got griefed ${i}`, { normId: 'no_griefing', valence: -0.9 });
|
|
147
|
+
}
|
|
148
|
+
const sops = mem.consolidate('agent1');
|
|
149
|
+
expect(sops[0].actions).toContain('avoid');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('retrieves SOPs by agent', () => {
|
|
153
|
+
const mem = new CulturalMemory({ consolidationThreshold: 2 });
|
|
154
|
+
for (let i = 0; i < 3; i++) {
|
|
155
|
+
mem.record('agent1', 'Trade', { normId: 'fair_trade' });
|
|
156
|
+
mem.record('agent2', 'Greet', { normId: 'greeting_convention' });
|
|
157
|
+
}
|
|
158
|
+
mem.consolidate('agent1');
|
|
159
|
+
mem.consolidate('agent2');
|
|
160
|
+
expect(mem.getSOPs('agent1')).toHaveLength(1);
|
|
161
|
+
expect(mem.getSOPs('agent2')).toHaveLength(1);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ── State Export/Import ──────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
describe('State Persistence', () => {
|
|
168
|
+
it('exports and imports state', () => {
|
|
169
|
+
const mem = new CulturalMemory({ consolidationThreshold: 2 });
|
|
170
|
+
mem.record('agent1', 'Event A', { normId: 'test_norm', tags: ['a'] });
|
|
171
|
+
mem.record('agent1', 'Event B', { normId: 'test_norm', tags: ['b'] });
|
|
172
|
+
mem.leaveTrace('agent1', 'zone_a', 'marker', { x: 1, y: 2, z: 3 });
|
|
173
|
+
mem.consolidate('agent1');
|
|
174
|
+
|
|
175
|
+
const state = mem.exportState();
|
|
176
|
+
const mem2 = new CulturalMemory();
|
|
177
|
+
mem2.importState(state);
|
|
178
|
+
|
|
179
|
+
expect(mem2.memoryCount('agent1')).toBe(2);
|
|
180
|
+
expect(mem2.zoneTraces('zone_a')).toHaveLength(1);
|
|
181
|
+
expect(mem2.getSOPs('agent1')).toHaveLength(1);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// ── Stats ────────────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
describe('Stats', () => {
|
|
188
|
+
it('reports correct stats', () => {
|
|
189
|
+
const mem = new CulturalMemory();
|
|
190
|
+
mem.record('agent1', 'A');
|
|
191
|
+
mem.record('agent2', 'B');
|
|
192
|
+
mem.leaveTrace('agent1', 'zone_a', 'x', { x: 0, y: 0, z: 0 });
|
|
193
|
+
const s = mem.stats();
|
|
194
|
+
expect(s.agents).toBe(2);
|
|
195
|
+
expect(s.totalMemories).toBe(2);
|
|
196
|
+
expect(s.totalTraces).toBe(1);
|
|
197
|
+
expect(s.zones).toBe(1);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FederatedRegistryAdapter Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests cross-composition agent discovery via remote A2A Agent Card fetching.
|
|
5
|
+
* Part of HoloScript v5.5 "Agents as Universal Orchestrators".
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
9
|
+
import {
|
|
10
|
+
FederatedRegistryAdapter,
|
|
11
|
+
type FederatedRegistryConfig,
|
|
12
|
+
type A2AAgentCard,
|
|
13
|
+
} from '../FederatedRegistryAdapter';
|
|
14
|
+
import { AgentRegistry, resetDefaultRegistry } from '../AgentRegistry';
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// TEST FIXTURES
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
function makeAgentCard(overrides: Partial<A2AAgentCard> = {}): A2AAgentCard {
|
|
21
|
+
return {
|
|
22
|
+
id: 'remote-agent-1',
|
|
23
|
+
name: 'Remote Agent',
|
|
24
|
+
description: 'A remote agent for testing',
|
|
25
|
+
endpoint: 'https://remote.example.com/a2a',
|
|
26
|
+
version: '1.0.0',
|
|
27
|
+
provider: { organization: 'TestOrg', url: 'https://example.com' },
|
|
28
|
+
capabilities: { streaming: false, pushNotifications: false, stateTransitionHistory: false },
|
|
29
|
+
skills: [
|
|
30
|
+
{
|
|
31
|
+
id: 'parse_hs',
|
|
32
|
+
name: 'Parse HoloScript',
|
|
33
|
+
description: 'Parses HoloScript source code',
|
|
34
|
+
tags: ['parsing', 'spatial'],
|
|
35
|
+
inputModes: ['application/json'],
|
|
36
|
+
outputModes: ['application/json'],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'compile_hs',
|
|
40
|
+
name: 'Compile HoloScript',
|
|
41
|
+
description: 'Compiles HoloScript to target',
|
|
42
|
+
tags: ['compilation', 'spatial'],
|
|
43
|
+
inputModes: ['application/json'],
|
|
44
|
+
outputModes: ['application/json'],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
...overrides,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function makeMockFetch(
|
|
52
|
+
cards: Record<string, A2AAgentCard | null>
|
|
53
|
+
): (url: string, init?: RequestInit) => Promise<Response> {
|
|
54
|
+
return async (url: string) => {
|
|
55
|
+
const card = cards[url];
|
|
56
|
+
if (card === null || card === undefined) {
|
|
57
|
+
return { ok: false, status: 404, json: async () => ({}) } as Response;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
status: 200,
|
|
62
|
+
json: async () => card,
|
|
63
|
+
} as Response;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// =============================================================================
|
|
68
|
+
// TESTS
|
|
69
|
+
// =============================================================================
|
|
70
|
+
|
|
71
|
+
describe('FederatedRegistryAdapter', () => {
|
|
72
|
+
let registry: AgentRegistry;
|
|
73
|
+
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
resetDefaultRegistry();
|
|
76
|
+
registry = new AgentRegistry();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
afterEach(() => {
|
|
80
|
+
registry.stop();
|
|
81
|
+
registry.clear();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('a2aCardToManifest', () => {
|
|
85
|
+
it('converts an A2A AgentCard to an AgentManifest', () => {
|
|
86
|
+
const adapter = new FederatedRegistryAdapter(registry);
|
|
87
|
+
const card = makeAgentCard();
|
|
88
|
+
const manifest = adapter.a2aCardToManifest(
|
|
89
|
+
card,
|
|
90
|
+
'https://remote.example.com/.well-known/agent-card.json'
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(manifest.id).toBe('remote-agent-1');
|
|
94
|
+
expect(manifest.name).toBe('Remote Agent');
|
|
95
|
+
expect(manifest.version).toBe('1.0.0');
|
|
96
|
+
expect(manifest.trustLevel).toBe('external');
|
|
97
|
+
expect(manifest.tags).toContain('remote');
|
|
98
|
+
expect(manifest.tags).toContain('a2a');
|
|
99
|
+
expect(manifest.tags).toContain('TestOrg');
|
|
100
|
+
expect(manifest.endpoints).toHaveLength(1);
|
|
101
|
+
expect(manifest.endpoints[0].protocol).toBe('https');
|
|
102
|
+
expect(manifest.endpoints[0].address).toBe('https://remote.example.com/a2a');
|
|
103
|
+
expect(manifest.capabilities.length).toBeGreaterThan(0);
|
|
104
|
+
expect(manifest.metadata?.sourceUrl).toBe(
|
|
105
|
+
'https://remote.example.com/.well-known/agent-card.json'
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('extracts capability type from skill tags', () => {
|
|
110
|
+
const adapter = new FederatedRegistryAdapter(registry);
|
|
111
|
+
const card = makeAgentCard({
|
|
112
|
+
skills: [
|
|
113
|
+
{
|
|
114
|
+
id: 'skill-1',
|
|
115
|
+
name: 'Analyzer',
|
|
116
|
+
tags: ['analysis', 'nlp'],
|
|
117
|
+
inputModes: [],
|
|
118
|
+
outputModes: [],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
const manifest = adapter.a2aCardToManifest(card, 'https://example.com');
|
|
123
|
+
|
|
124
|
+
expect(manifest.capabilities[0].type).toBe('analyze');
|
|
125
|
+
expect(manifest.capabilities[0].domain).toBe('nlp');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('deduplicates capabilities by type+domain', () => {
|
|
129
|
+
const adapter = new FederatedRegistryAdapter(registry);
|
|
130
|
+
const card = makeAgentCard({
|
|
131
|
+
skills: [
|
|
132
|
+
{
|
|
133
|
+
id: 's1',
|
|
134
|
+
name: 'Parse 1',
|
|
135
|
+
tags: ['parsing', 'spatial'],
|
|
136
|
+
inputModes: [],
|
|
137
|
+
outputModes: [],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 's2',
|
|
141
|
+
name: 'Parse 2',
|
|
142
|
+
tags: ['parsing', 'spatial'],
|
|
143
|
+
inputModes: [],
|
|
144
|
+
outputModes: [],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 's3',
|
|
148
|
+
name: 'Compile',
|
|
149
|
+
tags: ['compilation', 'spatial'],
|
|
150
|
+
inputModes: [],
|
|
151
|
+
outputModes: [],
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
const manifest = adapter.a2aCardToManifest(card, 'https://example.com');
|
|
156
|
+
|
|
157
|
+
// Should have 2 unique type+domain pairs: analyze:spatial and transform:spatial
|
|
158
|
+
expect(manifest.capabilities).toHaveLength(2);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('handles cards with no skills', () => {
|
|
162
|
+
const adapter = new FederatedRegistryAdapter(registry);
|
|
163
|
+
const card = makeAgentCard({ skills: [] });
|
|
164
|
+
const manifest = adapter.a2aCardToManifest(card, 'https://example.com');
|
|
165
|
+
|
|
166
|
+
expect(manifest.capabilities).toHaveLength(1);
|
|
167
|
+
expect(manifest.capabilities[0].type).toBe('custom');
|
|
168
|
+
expect(manifest.capabilities[0].domain).toBe('general');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('respects custom trustRemoteAs config', () => {
|
|
172
|
+
const adapter = new FederatedRegistryAdapter(registry, { trustRemoteAs: 'known' });
|
|
173
|
+
const card = makeAgentCard();
|
|
174
|
+
const manifest = adapter.a2aCardToManifest(card, 'https://example.com');
|
|
175
|
+
|
|
176
|
+
expect(manifest.trustLevel).toBe('known');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('fetchAndRegister', () => {
|
|
181
|
+
it('fetches a remote agent card and registers into registry', async () => {
|
|
182
|
+
const card = makeAgentCard();
|
|
183
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
184
|
+
fetchFn: makeMockFetch({
|
|
185
|
+
'https://remote.example.com/.well-known/agent-card.json': card,
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const manifest = await adapter.fetchAndRegister(
|
|
190
|
+
'https://remote.example.com/.well-known/agent-card.json'
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(manifest).not.toBeNull();
|
|
194
|
+
expect(manifest!.id).toBe('remote-agent-1');
|
|
195
|
+
expect(registry.has('remote-agent-1')).toBe(true);
|
|
196
|
+
expect(adapter.remoteAgentCount).toBe(1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('returns null for failed HTTP requests', async () => {
|
|
200
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
201
|
+
fetchFn: makeMockFetch({ 'https://fail.example.com': null }),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const manifest = await adapter.fetchAndRegister('https://fail.example.com');
|
|
205
|
+
|
|
206
|
+
expect(manifest).toBeNull();
|
|
207
|
+
expect(adapter.remoteAgentCount).toBe(0);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('returns null when card is missing required fields', async () => {
|
|
211
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
212
|
+
fetchFn: async () => ({ ok: true, json: async () => ({ version: '1.0.0' }) }) as Response,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const manifest = await adapter.fetchAndRegister('https://bad.example.com');
|
|
216
|
+
expect(manifest).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('enforces maxRemoteAgents capacity', async () => {
|
|
220
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
221
|
+
maxRemoteAgents: 1,
|
|
222
|
+
fetchFn: makeMockFetch({
|
|
223
|
+
'https://agent1.com': makeAgentCard({ id: 'agent-1', name: 'Agent 1' }),
|
|
224
|
+
'https://agent2.com': makeAgentCard({ id: 'agent-2', name: 'Agent 2' }),
|
|
225
|
+
}),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const m1 = await adapter.fetchAndRegister('https://agent1.com');
|
|
229
|
+
const m2 = await adapter.fetchAndRegister('https://agent2.com');
|
|
230
|
+
|
|
231
|
+
expect(m1).not.toBeNull();
|
|
232
|
+
expect(m2).toBeNull(); // Capacity reached
|
|
233
|
+
expect(adapter.remoteAgentCount).toBe(1);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('allows re-registering an existing remote agent (update)', async () => {
|
|
237
|
+
const card = makeAgentCard();
|
|
238
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
239
|
+
maxRemoteAgents: 1,
|
|
240
|
+
fetchFn: makeMockFetch({
|
|
241
|
+
'https://agent1.com': card,
|
|
242
|
+
}),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await adapter.fetchAndRegister('https://agent1.com');
|
|
246
|
+
const m2 = await adapter.fetchAndRegister('https://agent1.com');
|
|
247
|
+
|
|
248
|
+
expect(m2).not.toBeNull(); // Same ID, should update, not be blocked by capacity
|
|
249
|
+
expect(adapter.remoteAgentCount).toBe(1);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('pollAll', () => {
|
|
254
|
+
it('polls all seed URLs and returns summary', async () => {
|
|
255
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
256
|
+
seedUrls: [
|
|
257
|
+
'https://agent1.com/.well-known/agent-card.json',
|
|
258
|
+
'https://agent2.com/.well-known/agent-card.json',
|
|
259
|
+
'https://fail.com/.well-known/agent-card.json',
|
|
260
|
+
],
|
|
261
|
+
fetchFn: makeMockFetch({
|
|
262
|
+
'https://agent1.com/.well-known/agent-card.json': makeAgentCard({
|
|
263
|
+
id: 'agent-1',
|
|
264
|
+
name: 'Agent 1',
|
|
265
|
+
}),
|
|
266
|
+
'https://agent2.com/.well-known/agent-card.json': makeAgentCard({
|
|
267
|
+
id: 'agent-2',
|
|
268
|
+
name: 'Agent 2',
|
|
269
|
+
}),
|
|
270
|
+
}),
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const result = await adapter.pollAll();
|
|
274
|
+
|
|
275
|
+
expect(result.added).toBe(2);
|
|
276
|
+
expect(result.failed).toHaveLength(1);
|
|
277
|
+
expect(result.failed[0]).toBe('https://fail.com/.well-known/agent-card.json');
|
|
278
|
+
expect(registry.size).toBe(2);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('reports updated on subsequent polls', async () => {
|
|
282
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
283
|
+
seedUrls: ['https://agent1.com'],
|
|
284
|
+
fetchFn: makeMockFetch({
|
|
285
|
+
'https://agent1.com': makeAgentCard({ id: 'agent-1', name: 'Agent 1' }),
|
|
286
|
+
}),
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const first = await adapter.pollAll();
|
|
290
|
+
expect(first.added).toBe(1);
|
|
291
|
+
expect(first.updated).toBe(0);
|
|
292
|
+
|
|
293
|
+
const second = await adapter.pollAll();
|
|
294
|
+
expect(second.added).toBe(0);
|
|
295
|
+
expect(second.updated).toBe(1);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('polling lifecycle', () => {
|
|
300
|
+
it('starts and stops polling', () => {
|
|
301
|
+
const adapter = new FederatedRegistryAdapter(registry, { pollIntervalMs: 100_000 });
|
|
302
|
+
|
|
303
|
+
expect(adapter.isPolling).toBe(false);
|
|
304
|
+
adapter.startPolling();
|
|
305
|
+
expect(adapter.isPolling).toBe(true);
|
|
306
|
+
adapter.stopPolling();
|
|
307
|
+
expect(adapter.isPolling).toBe(false);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('does not start duplicate polling timers', () => {
|
|
311
|
+
const adapter = new FederatedRegistryAdapter(registry, { pollIntervalMs: 100_000 });
|
|
312
|
+
|
|
313
|
+
adapter.startPolling();
|
|
314
|
+
adapter.startPolling(); // Should not create a second timer
|
|
315
|
+
expect(adapter.isPolling).toBe(true);
|
|
316
|
+
adapter.stopPolling();
|
|
317
|
+
expect(adapter.isPolling).toBe(false);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe('discoverFederated', () => {
|
|
322
|
+
it('discovers agents across local and remote registries', async () => {
|
|
323
|
+
// Register a local agent
|
|
324
|
+
await registry.register({
|
|
325
|
+
id: 'local-agent',
|
|
326
|
+
name: 'Local Agent',
|
|
327
|
+
version: '1.0.0',
|
|
328
|
+
capabilities: [{ type: 'analyze', domain: 'spatial' }],
|
|
329
|
+
endpoints: [{ protocol: 'local', address: 'local://agent' }],
|
|
330
|
+
trustLevel: 'local',
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Set up adapter with a remote agent
|
|
334
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
335
|
+
seedUrls: ['https://remote.com/.well-known/agent-card.json'],
|
|
336
|
+
fetchFn: makeMockFetch({
|
|
337
|
+
'https://remote.com/.well-known/agent-card.json': makeAgentCard({
|
|
338
|
+
id: 'remote-1',
|
|
339
|
+
name: 'Remote 1',
|
|
340
|
+
skills: [
|
|
341
|
+
{
|
|
342
|
+
id: 's1',
|
|
343
|
+
name: 'Analyze',
|
|
344
|
+
tags: ['analysis', 'spatial'],
|
|
345
|
+
inputModes: [],
|
|
346
|
+
outputModes: [],
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
}),
|
|
350
|
+
}),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const matches = await adapter.discoverFederated({
|
|
354
|
+
type: 'analyze',
|
|
355
|
+
domain: 'spatial',
|
|
356
|
+
includeOffline: true,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(matches.length).toBe(2);
|
|
360
|
+
const ids = matches.map((m) => m.manifest.id);
|
|
361
|
+
expect(ids).toContain('local-agent');
|
|
362
|
+
expect(ids).toContain('remote-1');
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('seed URL management', () => {
|
|
367
|
+
it('addSeedUrl adds a new URL', () => {
|
|
368
|
+
const adapter = new FederatedRegistryAdapter(registry, { seedUrls: ['https://a.com'] });
|
|
369
|
+
adapter.addSeedUrl('https://b.com');
|
|
370
|
+
adapter.addSeedUrl('https://a.com'); // Duplicate, should not add
|
|
371
|
+
|
|
372
|
+
expect(adapter.getRemoteAgentIds()).toHaveLength(0); // No agents yet
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('removeSeedUrl deregisters the associated agent', async () => {
|
|
376
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
377
|
+
seedUrls: ['https://agent1.com'],
|
|
378
|
+
fetchFn: makeMockFetch({
|
|
379
|
+
'https://agent1.com': makeAgentCard({ id: 'agent-1', name: 'Agent 1' }),
|
|
380
|
+
}),
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
await adapter.fetchAndRegister('https://agent1.com');
|
|
384
|
+
expect(registry.has('agent-1')).toBe(true);
|
|
385
|
+
|
|
386
|
+
await adapter.removeSeedUrl('https://agent1.com');
|
|
387
|
+
expect(registry.has('agent-1')).toBe(false);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe('getPollResult', () => {
|
|
392
|
+
it('tracks poll results per URL', async () => {
|
|
393
|
+
const adapter = new FederatedRegistryAdapter(registry, {
|
|
394
|
+
fetchFn: makeMockFetch({
|
|
395
|
+
'https://ok.com': makeAgentCard(),
|
|
396
|
+
}),
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
await adapter.fetchAndRegister('https://ok.com');
|
|
400
|
+
await adapter.fetchAndRegister('https://fail.com');
|
|
401
|
+
|
|
402
|
+
const okResult = adapter.getPollResult('https://ok.com');
|
|
403
|
+
expect(okResult?.success).toBe(true);
|
|
404
|
+
|
|
405
|
+
const failResult = adapter.getPollResult('https://fail.com');
|
|
406
|
+
expect(failResult?.success).toBe(false);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
});
|