@agirails/sdk 2.0.1-beta → 2.0.2
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/LICENSE +190 -0
- package/README.md +116 -108
- package/bin/actp +10 -0
- package/dist/ACTPClient.d.ts +456 -33
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +477 -93
- package/dist/ACTPClient.js.map +1 -1
- package/dist/abi/AgentRegistry.json +782 -0
- package/dist/abi/EscrowVault.json +106 -38
- package/dist/abi/IdentityRegistry.json +316 -0
- package/dist/adapters/BaseAdapter.d.ts +231 -0
- package/dist/adapters/BaseAdapter.d.ts.map +1 -0
- package/dist/adapters/BaseAdapter.js +393 -0
- package/dist/adapters/BaseAdapter.js.map +1 -0
- package/dist/adapters/BeginnerAdapter.d.ts +152 -0
- package/dist/adapters/BeginnerAdapter.d.ts.map +1 -0
- package/dist/adapters/BeginnerAdapter.js +168 -0
- package/dist/adapters/BeginnerAdapter.js.map +1 -0
- package/dist/adapters/IntermediateAdapter.d.ts +211 -0
- package/dist/adapters/IntermediateAdapter.d.ts.map +1 -0
- package/dist/adapters/IntermediateAdapter.js +260 -0
- package/dist/adapters/IntermediateAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +15 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +26 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/builders/DeliveryProofBuilder.d.ts +60 -1
- package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -1
- package/dist/builders/DeliveryProofBuilder.js +81 -5
- package/dist/builders/DeliveryProofBuilder.js.map +1 -1
- package/dist/builders/QuoteBuilder.d.ts +101 -0
- package/dist/builders/QuoteBuilder.d.ts.map +1 -1
- package/dist/builders/QuoteBuilder.js +120 -3
- package/dist/builders/QuoteBuilder.js.map +1 -1
- package/dist/builders/index.d.ts +4 -0
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +4 -0
- package/dist/builders/index.js.map +1 -1
- package/dist/cli/commands/balance.d.ts +13 -0
- package/dist/cli/commands/balance.d.ts.map +1 -0
- package/dist/cli/commands/balance.js +89 -0
- package/dist/cli/commands/balance.js.map +1 -0
- package/dist/cli/commands/batch.d.ts +24 -0
- package/dist/cli/commands/batch.d.ts.map +1 -0
- package/dist/cli/commands/batch.js +424 -0
- package/dist/cli/commands/batch.js.map +1 -0
- package/dist/cli/commands/config.d.ts +13 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +192 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +143 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/mint.d.ts +13 -0
- package/dist/cli/commands/mint.d.ts.map +1 -0
- package/dist/cli/commands/mint.js +91 -0
- package/dist/cli/commands/mint.js.map +1 -0
- package/dist/cli/commands/pay.d.ts +18 -0
- package/dist/cli/commands/pay.d.ts.map +1 -0
- package/dist/cli/commands/pay.js +87 -0
- package/dist/cli/commands/pay.js.map +1 -0
- package/dist/cli/commands/simulate.d.ts +32 -0
- package/dist/cli/commands/simulate.d.ts.map +1 -0
- package/dist/cli/commands/simulate.js +290 -0
- package/dist/cli/commands/simulate.js.map +1 -0
- package/dist/cli/commands/time.d.ts +29 -0
- package/dist/cli/commands/time.d.ts.map +1 -0
- package/dist/cli/commands/time.js +252 -0
- package/dist/cli/commands/time.js.map +1 -0
- package/dist/cli/commands/tx.d.ts +16 -0
- package/dist/cli/commands/tx.d.ts.map +1 -0
- package/dist/cli/commands/tx.js +379 -0
- package/dist/cli/commands/tx.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +20 -0
- package/dist/cli/commands/watch.d.ts.map +1 -0
- package/dist/cli/commands/watch.js +160 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +104 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/client.d.ts +70 -0
- package/dist/cli/utils/client.d.ts.map +1 -0
- package/dist/cli/utils/client.js +240 -0
- package/dist/cli/utils/client.js.map +1 -0
- package/dist/cli/utils/config.d.ts +91 -0
- package/dist/cli/utils/config.d.ts.map +1 -0
- package/dist/cli/utils/config.js +240 -0
- package/dist/cli/utils/config.js.map +1 -0
- package/dist/cli/utils/output.d.ts +174 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +380 -0
- package/dist/cli/utils/output.js.map +1 -0
- package/dist/config/networks.d.ts +28 -0
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +60 -12
- package/dist/config/networks.js.map +1 -1
- package/dist/errors/index.d.ts +165 -2
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +260 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +61 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +141 -36
- package/dist/index.js.map +1 -1
- package/dist/level0/Provider.d.ts +106 -0
- package/dist/level0/Provider.d.ts.map +1 -0
- package/dist/level0/Provider.js +10 -0
- package/dist/level0/Provider.js.map +1 -0
- package/dist/level0/ServiceDirectory.d.ts +74 -0
- package/dist/level0/ServiceDirectory.d.ts.map +1 -0
- package/dist/level0/ServiceDirectory.js +122 -0
- package/dist/level0/ServiceDirectory.js.map +1 -0
- package/dist/level0/index.d.ts +10 -0
- package/dist/level0/index.d.ts.map +1 -0
- package/dist/level0/index.js +15 -0
- package/dist/level0/index.js.map +1 -0
- package/dist/level0/provide.d.ts +51 -0
- package/dist/level0/provide.d.ts.map +1 -0
- package/dist/level0/provide.js +113 -0
- package/dist/level0/provide.js.map +1 -0
- package/dist/level0/request.d.ts +53 -0
- package/dist/level0/request.d.ts.map +1 -0
- package/dist/level0/request.js +462 -0
- package/dist/level0/request.js.map +1 -0
- package/dist/level1/Agent.d.ts +472 -0
- package/dist/level1/Agent.d.ts.map +1 -0
- package/dist/level1/Agent.js +1091 -0
- package/dist/level1/Agent.js.map +1 -0
- package/dist/level1/index.d.ts +10 -0
- package/dist/level1/index.d.ts.map +1 -0
- package/dist/level1/index.js +30 -0
- package/dist/level1/index.js.map +1 -0
- package/dist/level1/pricing/PriceCalculator.d.ts +62 -0
- package/dist/level1/pricing/PriceCalculator.d.ts.map +1 -0
- package/dist/level1/pricing/PriceCalculator.js +237 -0
- package/dist/level1/pricing/PriceCalculator.js.map +1 -0
- package/dist/level1/pricing/PricingStrategy.d.ts +179 -0
- package/dist/level1/pricing/PricingStrategy.d.ts.map +1 -0
- package/dist/level1/pricing/PricingStrategy.js +11 -0
- package/dist/level1/pricing/PricingStrategy.js.map +1 -0
- package/dist/level1/types/Job.d.ts +166 -0
- package/dist/level1/types/Job.d.ts.map +1 -0
- package/dist/level1/types/Job.js +11 -0
- package/dist/level1/types/Job.js.map +1 -0
- package/dist/level1/types/Options.d.ts +258 -0
- package/dist/level1/types/Options.d.ts.map +1 -0
- package/dist/level1/types/Options.js +8 -0
- package/dist/level1/types/Options.js.map +1 -0
- package/dist/level1/types/index.d.ts +8 -0
- package/dist/level1/types/index.d.ts.map +1 -0
- package/dist/level1/types/index.js +8 -0
- package/dist/level1/types/index.js.map +1 -0
- package/dist/protocol/ACTPKernel.d.ts +229 -2
- package/dist/protocol/ACTPKernel.d.ts.map +1 -1
- package/dist/protocol/ACTPKernel.js +367 -33
- package/dist/protocol/ACTPKernel.js.map +1 -1
- package/dist/protocol/AgentRegistry.d.ts +177 -0
- package/dist/protocol/AgentRegistry.d.ts.map +1 -0
- package/dist/protocol/AgentRegistry.js +449 -0
- package/dist/protocol/AgentRegistry.js.map +1 -0
- package/dist/protocol/DIDManager.d.ts +289 -0
- package/dist/protocol/DIDManager.d.ts.map +1 -0
- package/dist/protocol/DIDManager.js +481 -0
- package/dist/protocol/DIDManager.js.map +1 -0
- package/dist/protocol/DIDResolver.d.ts +236 -0
- package/dist/protocol/DIDResolver.d.ts.map +1 -0
- package/dist/protocol/DIDResolver.js +495 -0
- package/dist/protocol/DIDResolver.js.map +1 -0
- package/dist/protocol/EASHelper.d.ts +57 -2
- package/dist/protocol/EASHelper.d.ts.map +1 -1
- package/dist/protocol/EASHelper.js +230 -37
- package/dist/protocol/EASHelper.js.map +1 -1
- package/dist/protocol/EscrowVault.d.ts +93 -2
- package/dist/protocol/EscrowVault.d.ts.map +1 -1
- package/dist/protocol/EscrowVault.js +122 -33
- package/dist/protocol/EscrowVault.js.map +1 -1
- package/dist/protocol/EventMonitor.d.ts +45 -1
- package/dist/protocol/EventMonitor.d.ts.map +1 -1
- package/dist/protocol/EventMonitor.js +64 -8
- package/dist/protocol/EventMonitor.js.map +1 -1
- package/dist/protocol/MessageSigner.d.ts +116 -2
- package/dist/protocol/MessageSigner.d.ts.map +1 -1
- package/dist/protocol/MessageSigner.js +215 -9
- package/dist/protocol/MessageSigner.js.map +1 -1
- package/dist/protocol/ProofGenerator.d.ts +93 -0
- package/dist/protocol/ProofGenerator.d.ts.map +1 -1
- package/dist/protocol/ProofGenerator.js +194 -9
- package/dist/protocol/ProofGenerator.js.map +1 -1
- package/dist/protocol/QuoteBuilder.d.ts +8 -0
- package/dist/protocol/QuoteBuilder.d.ts.map +1 -1
- package/dist/protocol/QuoteBuilder.js +8 -0
- package/dist/protocol/QuoteBuilder.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts +360 -0
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -0
- package/dist/runtime/BlockchainRuntime.js +767 -0
- package/dist/runtime/BlockchainRuntime.js.map +1 -0
- package/dist/runtime/IACTPRuntime.d.ts +271 -0
- package/dist/runtime/IACTPRuntime.d.ts.map +1 -0
- package/dist/runtime/IACTPRuntime.js +15 -0
- package/dist/runtime/IACTPRuntime.js.map +1 -0
- package/dist/runtime/MockRuntime.d.ts +445 -0
- package/dist/runtime/MockRuntime.d.ts.map +1 -0
- package/dist/runtime/MockRuntime.js +1065 -0
- package/dist/runtime/MockRuntime.js.map +1 -0
- package/dist/runtime/MockStateManager.d.ts +233 -0
- package/dist/runtime/MockStateManager.d.ts.map +1 -0
- package/dist/runtime/MockStateManager.js +533 -0
- package/dist/runtime/MockStateManager.js.map +1 -0
- package/dist/runtime/index.d.ts +14 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +42 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/types/MockState.d.ts +167 -0
- package/dist/runtime/types/MockState.d.ts.map +1 -0
- package/dist/runtime/types/MockState.js +43 -0
- package/dist/runtime/types/MockState.js.map +1 -0
- package/dist/types/agent.d.ts +76 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +8 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/did.d.ts +192 -0
- package/dist/types/did.d.ts.map +1 -0
- package/dist/types/did.js +38 -0
- package/dist/types/did.js.map +1 -0
- package/dist/types/eip712.d.ts +34 -0
- package/dist/types/eip712.d.ts.map +1 -1
- package/dist/types/eip712.js +31 -5
- package/dist/types/eip712.js.map +1 -1
- package/dist/types/escrow.d.ts +17 -10
- package/dist/types/escrow.d.ts.map +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/message.d.ts +32 -0
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js +4 -0
- package/dist/types/message.js.map +1 -1
- package/dist/types/state.d.ts +28 -0
- package/dist/types/state.d.ts.map +1 -1
- package/dist/types/state.js +37 -6
- package/dist/types/state.js.map +1 -1
- package/dist/types/transaction.d.ts +17 -0
- package/dist/types/transaction.d.ts.map +1 -1
- package/dist/utils/ErrorRecoveryGuide.d.ts +125 -0
- package/dist/utils/ErrorRecoveryGuide.d.ts.map +1 -0
- package/dist/utils/ErrorRecoveryGuide.js +579 -0
- package/dist/utils/ErrorRecoveryGuide.js.map +1 -0
- package/dist/utils/Helpers.d.ts +453 -0
- package/dist/utils/Helpers.d.ts.map +1 -0
- package/dist/utils/Helpers.js +623 -0
- package/dist/utils/Helpers.js.map +1 -0
- package/dist/utils/IPFSClient.d.ts +113 -0
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +128 -7
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/utils/Logger.d.ts +195 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/Logger.js +382 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/NonceManager.d.ts +234 -1
- package/dist/utils/NonceManager.d.ts.map +1 -1
- package/dist/utils/NonceManager.js +372 -7
- package/dist/utils/NonceManager.js.map +1 -1
- package/dist/utils/RateLimiter.d.ts +253 -0
- package/dist/utils/RateLimiter.d.ts.map +1 -0
- package/dist/utils/RateLimiter.js +424 -0
- package/dist/utils/RateLimiter.js.map +1 -0
- package/dist/utils/ReceivedNonceTracker.d.ts +175 -0
- package/dist/utils/ReceivedNonceTracker.d.ts.map +1 -1
- package/dist/utils/ReceivedNonceTracker.js +261 -5
- package/dist/utils/ReceivedNonceTracker.js.map +1 -1
- package/dist/utils/SDKLifecycle.d.ts +156 -0
- package/dist/utils/SDKLifecycle.d.ts.map +1 -0
- package/dist/utils/SDKLifecycle.js +347 -0
- package/dist/utils/SDKLifecycle.js.map +1 -0
- package/dist/utils/SecureNonce.d.ts +57 -0
- package/dist/utils/SecureNonce.d.ts.map +1 -0
- package/dist/utils/SecureNonce.js +80 -0
- package/dist/utils/SecureNonce.js.map +1 -0
- package/dist/utils/Semaphore.d.ts +123 -0
- package/dist/utils/Semaphore.d.ts.map +1 -0
- package/dist/utils/Semaphore.js +247 -0
- package/dist/utils/Semaphore.js.map +1 -0
- package/dist/utils/UsedAttestationTracker.d.ts +167 -0
- package/dist/utils/UsedAttestationTracker.d.ts.map +1 -0
- package/dist/utils/UsedAttestationTracker.js +309 -0
- package/dist/utils/UsedAttestationTracker.js.map +1 -0
- package/dist/utils/canonicalJson.d.ts +22 -0
- package/dist/utils/canonicalJson.d.ts.map +1 -1
- package/dist/utils/canonicalJson.js +26 -3
- package/dist/utils/canonicalJson.js.map +1 -1
- package/dist/utils/computeTypeHash.d.ts +14 -0
- package/dist/utils/computeTypeHash.d.ts.map +1 -1
- package/dist/utils/computeTypeHash.js +19 -2
- package/dist/utils/computeTypeHash.js.map +1 -1
- package/dist/utils/fsSafe.d.ts +14 -0
- package/dist/utils/fsSafe.d.ts.map +1 -0
- package/dist/utils/fsSafe.js +89 -0
- package/dist/utils/fsSafe.js.map +1 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +51 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/security.d.ts +147 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +391 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/utils/validation.d.ts +40 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +184 -7
- package/dist/utils/validation.js.map +1 -1
- package/package.json +54 -37
- package/src/ACTPClient.ts +692 -178
- package/src/abi/AgentRegistry.json +782 -0
- package/src/abi/EscrowVault.json +106 -38
- package/src/abi/IdentityRegistry.json +316 -0
- package/src/adapters/BaseAdapter.ts +473 -0
- package/src/adapters/BeginnerAdapter.ts +232 -0
- package/src/adapters/IntermediateAdapter.ts +316 -0
- package/src/adapters/index.ts +25 -0
- package/src/builders/DeliveryProofBuilder.ts +3 -2
- package/src/cli/commands/balance.ts +110 -0
- package/src/cli/commands/batch.ts +487 -0
- package/src/cli/commands/config.ts +231 -0
- package/src/cli/commands/init.ts +161 -0
- package/src/cli/commands/mint.ts +116 -0
- package/src/cli/commands/pay.ts +113 -0
- package/src/cli/commands/simulate.ts +345 -0
- package/src/cli/commands/time.ts +303 -0
- package/src/cli/commands/tx.ts +448 -0
- package/src/cli/commands/watch.ts +211 -0
- package/src/cli/index.ts +116 -0
- package/src/cli/utils/client.ts +249 -0
- package/src/cli/utils/config.ts +282 -0
- package/src/cli/utils/output.ts +465 -0
- package/src/config/networks.ts +32 -9
- package/src/errors/index.ts +298 -1
- package/src/index.ts +207 -71
- package/src/level0/Provider.ts +117 -0
- package/src/level0/ServiceDirectory.ts +131 -0
- package/src/level0/index.ts +10 -0
- package/src/level0/provide.ts +131 -0
- package/src/level0/request.ts +494 -0
- package/src/level1/Agent.ts +1432 -0
- package/src/level1/index.ts +10 -0
- package/src/level1/pricing/PriceCalculator.ts +255 -0
- package/src/level1/pricing/PricingStrategy.ts +198 -0
- package/src/level1/types/Job.ts +179 -0
- package/src/level1/types/Options.ts +291 -0
- package/src/level1/types/index.ts +8 -0
- package/src/protocol/ACTPKernel.ts +175 -23
- package/src/protocol/AgentRegistry.ts +559 -0
- package/src/protocol/DIDManager.ts +629 -0
- package/src/protocol/DIDResolver.ts +554 -0
- package/src/protocol/EASHelper.ts +230 -46
- package/src/protocol/EscrowVault.ts +68 -50
- package/src/protocol/EventMonitor.ts +44 -15
- package/src/protocol/MessageSigner.ts +193 -13
- package/src/protocol/ProofGenerator.ts +223 -4
- package/src/runtime/BlockchainRuntime.ts +993 -0
- package/src/runtime/IACTPRuntime.ts +284 -0
- package/src/runtime/MockRuntime.ts +1244 -0
- package/src/runtime/MockStateManager.ts +576 -0
- package/src/runtime/index.ts +25 -0
- package/src/runtime/types/MockState.ts +227 -0
- package/src/types/agent.ts +79 -0
- package/src/types/did.ts +223 -0
- package/src/types/escrow.ts +12 -11
- package/src/types/index.ts +5 -1
- package/src/types/state.ts +12 -3
- package/src/types/transaction.ts +4 -1
- package/src/utils/ErrorRecoveryGuide.ts +675 -0
- package/src/utils/Helpers.ts +688 -0
- package/src/utils/IPFSClient.ts +122 -5
- package/src/utils/Logger.ts +484 -0
- package/src/utils/NonceManager.ts +305 -8
- package/src/utils/RateLimiter.ts +534 -0
- package/src/utils/ReceivedNonceTracker.ts +170 -0
- package/src/utils/SDKLifecycle.ts +416 -0
- package/src/utils/SecureNonce.ts +78 -0
- package/src/utils/Semaphore.ts +276 -0
- package/src/utils/UsedAttestationTracker.ts +387 -0
- package/src/utils/fsSafe.ts +75 -0
- package/src/utils/index.ts +80 -0
- package/src/utils/security.ts +418 -0
- package/src/utils/validation.ts +164 -0
- package/src/__tests__/ProofGenerator.test.ts +0 -124
- package/src/__tests__/QuoteBuilder.test.ts +0 -516
- package/src/__tests__/StateMachine.test.ts +0 -82
- package/src/__tests__/builders/DeliveryProofBuilder.test.ts +0 -581
- package/src/__tests__/integration/ACTPClient.test.ts +0 -263
- package/src/__tests__/integration.test.ts +0 -289
- package/src/__tests__/protocol/EASHelper.test.ts +0 -472
- package/src/__tests__/protocol/EventMonitor.test.ts +0 -382
- package/src/__tests__/security/ACTPKernel.security.test.ts +0 -1167
- package/src/__tests__/security/EscrowVault.security.test.ts +0 -570
- package/src/__tests__/security/MessageSigner.security.test.ts +0 -286
- package/src/__tests__/security/NonceReplay.security.test.ts +0 -501
- package/src/__tests__/security/validation.security.test.ts +0 -376
- package/src/__tests__/utils/IPFSClient.test.ts +0 -262
- package/src/__tests__/utils/NonceManager.test.ts +0 -205
- package/src/__tests__/utils/canonicalJson.test.ts +0 -153
|
@@ -0,0 +1,1091 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent - Standard API for AI agents
|
|
4
|
+
*
|
|
5
|
+
* Provides agent-level abstractions: lifecycle, service provision,
|
|
6
|
+
* job handling, events, and statistics.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
27
|
+
if (mod && mod.__esModule) return mod;
|
|
28
|
+
var result = {};
|
|
29
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
30
|
+
__setModuleDefault(result, mod);
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.Agent = void 0;
|
|
35
|
+
const events_1 = require("events");
|
|
36
|
+
const path = __importStar(require("path"));
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const ethers_1 = require("ethers");
|
|
40
|
+
const ACTPClient_1 = require("../ACTPClient");
|
|
41
|
+
const errors_1 = require("../errors");
|
|
42
|
+
const security_1 = require("../utils/security");
|
|
43
|
+
const Logger_1 = require("../utils/Logger");
|
|
44
|
+
const Helpers_1 = require("../utils/Helpers");
|
|
45
|
+
const Semaphore_1 = require("../utils/Semaphore");
|
|
46
|
+
const ProofGenerator_1 = require("../protocol/ProofGenerator");
|
|
47
|
+
/**
|
|
48
|
+
* Agent class - Standard API
|
|
49
|
+
*
|
|
50
|
+
* Represents an autonomous AI agent that can provide services,
|
|
51
|
+
* request services from other agents, and manage its lifecycle.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const agent = new Agent({ name: 'Translator', network: 'mock' });
|
|
56
|
+
*
|
|
57
|
+
* agent.provide('translation', async (job, ctx) => {
|
|
58
|
+
* ctx.progress(50, 'Translating...');
|
|
59
|
+
* return { translated: translate(job.input.text) };
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* agent.on('payment:received', (amount) => {
|
|
63
|
+
* console.log(`Earned ${amount} USDC!`);
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* await agent.start();
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
class Agent extends events_1.EventEmitter {
|
|
70
|
+
/**
|
|
71
|
+
* Creates a new Agent instance
|
|
72
|
+
*
|
|
73
|
+
* @param config - Agent configuration
|
|
74
|
+
*/
|
|
75
|
+
constructor(config) {
|
|
76
|
+
super();
|
|
77
|
+
/**
|
|
78
|
+
* Current agent status
|
|
79
|
+
*/
|
|
80
|
+
this._status = 'idle';
|
|
81
|
+
/**
|
|
82
|
+
* Registered services
|
|
83
|
+
*/
|
|
84
|
+
this.services = new Map();
|
|
85
|
+
/**
|
|
86
|
+
* Active jobs
|
|
87
|
+
*
|
|
88
|
+
* SECURITY FIX (C-2): Changed from Map to LRUCache to prevent unbounded growth
|
|
89
|
+
* Maximum 1000 active jobs with LRU eviction
|
|
90
|
+
*/
|
|
91
|
+
this.activeJobs = new security_1.LRUCache(1000);
|
|
92
|
+
/**
|
|
93
|
+
* Processed job IDs (for deduplication)
|
|
94
|
+
*
|
|
95
|
+
* SECURITY FIX (C-1): Track jobs we've attempted to process
|
|
96
|
+
* This prevents race conditions where the same job is processed multiple times
|
|
97
|
+
* before the state transition completes
|
|
98
|
+
*/
|
|
99
|
+
this.processedJobs = new security_1.LRUCache(10000);
|
|
100
|
+
/**
|
|
101
|
+
* Processing locks (for atomic locking)
|
|
102
|
+
*
|
|
103
|
+
* SECURITY FIX (C-1): Mutex for job processing.
|
|
104
|
+
* When we see a job, we IMMEDIATELY add to this set (atomic in single-threaded JS).
|
|
105
|
+
* This prevents race conditions where two poll cycles both pass the processedJobs.has()
|
|
106
|
+
* check before either calls processedJobs.set().
|
|
107
|
+
*/
|
|
108
|
+
this.processingLocks = new Set();
|
|
109
|
+
/**
|
|
110
|
+
* Statistics
|
|
111
|
+
*/
|
|
112
|
+
this._stats = {
|
|
113
|
+
jobsReceived: 0,
|
|
114
|
+
jobsCompleted: 0,
|
|
115
|
+
jobsFailed: 0,
|
|
116
|
+
totalEarned: 0,
|
|
117
|
+
totalSpent: 0,
|
|
118
|
+
averageJobTime: 0,
|
|
119
|
+
successRate: 0,
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Cached balance (updated periodically during polling)
|
|
123
|
+
*/
|
|
124
|
+
this._balance = {
|
|
125
|
+
eth: '0',
|
|
126
|
+
usdc: '0',
|
|
127
|
+
locked: '0',
|
|
128
|
+
pending: '0',
|
|
129
|
+
};
|
|
130
|
+
if (!config.name) {
|
|
131
|
+
throw new errors_1.ServiceConfigError('name', 'Agent name is required');
|
|
132
|
+
}
|
|
133
|
+
// SECURITY FIX (H-6): Use dedicated AGIRAILS directory as base
|
|
134
|
+
// This prevents writes anywhere in the project directory
|
|
135
|
+
const AGIRAILS_BASE = path.join(os.homedir(), '.agirails');
|
|
136
|
+
// Ensure base directory exists
|
|
137
|
+
if (!fs.existsSync(AGIRAILS_BASE)) {
|
|
138
|
+
fs.mkdirSync(AGIRAILS_BASE, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
// Validate state directory path if provided
|
|
141
|
+
if (config.stateDirectory) {
|
|
142
|
+
try {
|
|
143
|
+
// Validate the path is safe (no traversal, within AGIRAILS_BASE)
|
|
144
|
+
config.stateDirectory = (0, security_1.validatePath)(config.stateDirectory, AGIRAILS_BASE);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
throw new errors_1.ServiceConfigError('stateDirectory', `Invalid state directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.name = config.name;
|
|
151
|
+
this.description = config.description;
|
|
152
|
+
this.network = config.network || 'mock';
|
|
153
|
+
this.config = config;
|
|
154
|
+
this.logger = new Logger_1.Logger({
|
|
155
|
+
source: `Agent:${config.name}`,
|
|
156
|
+
minLevel: config.logging?.level || 'info',
|
|
157
|
+
});
|
|
158
|
+
// SECURITY FIX (MEDIUM-4): Initialize concurrency semaphore
|
|
159
|
+
// Default to 10 concurrent jobs if not specified
|
|
160
|
+
const maxConcurrency = config.behavior?.concurrency || 10;
|
|
161
|
+
this.concurrencySemaphore = new Semaphore_1.Semaphore(maxConcurrency);
|
|
162
|
+
this.logger.debug('Initialized concurrency semaphore', { maxConcurrency });
|
|
163
|
+
}
|
|
164
|
+
// =========================================================================
|
|
165
|
+
// Lifecycle Methods
|
|
166
|
+
// =========================================================================
|
|
167
|
+
/**
|
|
168
|
+
* Start the agent
|
|
169
|
+
*
|
|
170
|
+
* Initializes ACTP client and begins polling for jobs.
|
|
171
|
+
*
|
|
172
|
+
* @throws {AgentLifecycleError} If agent is not in idle or stopped state
|
|
173
|
+
*/
|
|
174
|
+
async start() {
|
|
175
|
+
if (this._status !== 'idle' && this._status !== 'stopped') {
|
|
176
|
+
throw new errors_1.AgentLifecycleError(this._status, 'start');
|
|
177
|
+
}
|
|
178
|
+
this._status = 'starting';
|
|
179
|
+
this.emit('starting');
|
|
180
|
+
try {
|
|
181
|
+
// SECURITY FIX (RPCURL): Use rpcUrl from config or fallback to network default
|
|
182
|
+
// This allows Agent to work with testnet/mainnet without requiring explicit rpcUrl
|
|
183
|
+
// if user is okay with public RPC endpoints.
|
|
184
|
+
let rpcUrl = this.config.rpcUrl;
|
|
185
|
+
if (!rpcUrl && (this.network === 'testnet' || this.network === 'mainnet')) {
|
|
186
|
+
// Import getNetwork to get default rpcUrl from network config
|
|
187
|
+
const { getNetwork } = await Promise.resolve().then(() => __importStar(require('../config/networks')));
|
|
188
|
+
const networkName = this.network === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
189
|
+
const networkConfig = getNetwork(networkName);
|
|
190
|
+
rpcUrl = networkConfig.rpcUrl;
|
|
191
|
+
this.logger.info(`Using default RPC URL for ${networkName}: ${rpcUrl}`);
|
|
192
|
+
}
|
|
193
|
+
// Initialize ACTP client
|
|
194
|
+
this._client = await ACTPClient_1.ACTPClient.create({
|
|
195
|
+
mode: this.network === 'testnet' ? 'testnet' : this.network === 'mainnet' ? 'mainnet' : 'mock',
|
|
196
|
+
requesterAddress: this.address || this.generateAddress(),
|
|
197
|
+
stateDirectory: this.config.stateDirectory,
|
|
198
|
+
privateKey: this.getPrivateKey(),
|
|
199
|
+
rpcUrl,
|
|
200
|
+
});
|
|
201
|
+
// Start polling for jobs
|
|
202
|
+
this.startPolling();
|
|
203
|
+
this._status = 'running';
|
|
204
|
+
this.emit('started');
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
this._status = 'stopped';
|
|
208
|
+
this.emit('error', error);
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Stop the agent
|
|
214
|
+
*
|
|
215
|
+
* Stops polling and waits for active jobs to complete.
|
|
216
|
+
*/
|
|
217
|
+
async stop() {
|
|
218
|
+
if (this._status === 'stopped' || this._status === 'stopping') {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this._status = 'stopping';
|
|
222
|
+
this.emit('stopping');
|
|
223
|
+
// Stop polling
|
|
224
|
+
this.stopPolling();
|
|
225
|
+
// Wait for active jobs to complete (with timeout)
|
|
226
|
+
await this.waitForActiveJobs(30000); // 30s timeout
|
|
227
|
+
this._status = 'stopped';
|
|
228
|
+
this.emit('stopped');
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Pause the agent
|
|
232
|
+
*
|
|
233
|
+
* Stops accepting new jobs but keeps active jobs running.
|
|
234
|
+
*/
|
|
235
|
+
pause() {
|
|
236
|
+
if (this._status !== 'running') {
|
|
237
|
+
throw new errors_1.AgentLifecycleError(this._status, 'pause');
|
|
238
|
+
}
|
|
239
|
+
this.stopPolling();
|
|
240
|
+
this._status = 'paused';
|
|
241
|
+
this.emit('paused');
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Resume the agent
|
|
245
|
+
*
|
|
246
|
+
* Resumes accepting new jobs after being paused.
|
|
247
|
+
*/
|
|
248
|
+
resume() {
|
|
249
|
+
if (this._status !== 'paused') {
|
|
250
|
+
throw new errors_1.AgentLifecycleError(this._status, 'resume');
|
|
251
|
+
}
|
|
252
|
+
this.startPolling();
|
|
253
|
+
this._status = 'running';
|
|
254
|
+
this.emit('resumed');
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Restart the agent
|
|
258
|
+
*/
|
|
259
|
+
async restart() {
|
|
260
|
+
await this.stop();
|
|
261
|
+
await this.start();
|
|
262
|
+
}
|
|
263
|
+
// =========================================================================
|
|
264
|
+
// Service Registration
|
|
265
|
+
// =========================================================================
|
|
266
|
+
/**
|
|
267
|
+
* Register a service handler
|
|
268
|
+
*
|
|
269
|
+
* @param serviceOrConfig - Service name or full configuration
|
|
270
|
+
* @param handler - Job handler function
|
|
271
|
+
* @param options - Optional pricing/filter configuration
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* // Simple
|
|
276
|
+
* agent.provide('echo', async (job) => job.input);
|
|
277
|
+
*
|
|
278
|
+
* // With pricing
|
|
279
|
+
* agent.provide({
|
|
280
|
+
* name: 'translation',
|
|
281
|
+
* pricing: {
|
|
282
|
+
* cost: { base: 0.5, perUnit: { unit: 'word', rate: 0.005 } },
|
|
283
|
+
* margin: 0.40
|
|
284
|
+
* }
|
|
285
|
+
* }, async (job, ctx) => {
|
|
286
|
+
* // ... translation logic
|
|
287
|
+
* });
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
provide(serviceOrConfig, handler, options) {
|
|
291
|
+
const config = typeof serviceOrConfig === 'string'
|
|
292
|
+
? { name: serviceOrConfig, ...options }
|
|
293
|
+
: serviceOrConfig;
|
|
294
|
+
if (!config.name) {
|
|
295
|
+
throw new errors_1.ServiceConfigError('name', 'Service name is required');
|
|
296
|
+
}
|
|
297
|
+
// SECURITY FIX (H-2): Validate service name to prevent injection
|
|
298
|
+
try {
|
|
299
|
+
config.name = (0, security_1.validateServiceName)(config.name);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
throw new errors_1.ServiceConfigError('name', `Invalid service name: ${error instanceof Error ? error.message : String(error)}`);
|
|
303
|
+
}
|
|
304
|
+
if (this.services.has(config.name)) {
|
|
305
|
+
throw new errors_1.ServiceConfigError('name', `Service "${config.name}" already registered`);
|
|
306
|
+
}
|
|
307
|
+
this.services.set(config.name, { config, handler });
|
|
308
|
+
this.emit('service:registered', config.name);
|
|
309
|
+
this.logger.info('Service registered', { service: config.name });
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Request a service from another agent
|
|
314
|
+
*
|
|
315
|
+
* @param service - Service name
|
|
316
|
+
* @param options - Request options
|
|
317
|
+
* @returns Promise resolving to result
|
|
318
|
+
*/
|
|
319
|
+
async request(service, options) {
|
|
320
|
+
if (!this._client) {
|
|
321
|
+
throw new errors_1.AgentLifecycleError(this._status, 'request (agent not started)');
|
|
322
|
+
}
|
|
323
|
+
// Import Basic API request function
|
|
324
|
+
const { request: basicRequest } = await Promise.resolve().then(() => __importStar(require('../level0/request')));
|
|
325
|
+
// Call Basic API request with agent's network
|
|
326
|
+
const result = await basicRequest(service, {
|
|
327
|
+
...options,
|
|
328
|
+
network: this.network,
|
|
329
|
+
});
|
|
330
|
+
// Update stats
|
|
331
|
+
this._stats.totalSpent += options.budget;
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
// =========================================================================
|
|
335
|
+
// Properties
|
|
336
|
+
// =========================================================================
|
|
337
|
+
/**
|
|
338
|
+
* Current agent status
|
|
339
|
+
*/
|
|
340
|
+
get status() {
|
|
341
|
+
return this._status;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Agent's Ethereum address
|
|
345
|
+
*/
|
|
346
|
+
get address() {
|
|
347
|
+
return this._client?.getAddress() || '';
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Registered service names
|
|
351
|
+
*/
|
|
352
|
+
get serviceNames() {
|
|
353
|
+
return Array.from(this.services.keys());
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Active jobs
|
|
357
|
+
*
|
|
358
|
+
* SECURITY FIX (N-2): Now uses LRUCache.values() iterator.
|
|
359
|
+
* Returns a snapshot of currently active jobs.
|
|
360
|
+
*/
|
|
361
|
+
get jobs() {
|
|
362
|
+
return this.activeJobs.values();
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Statistics
|
|
366
|
+
*/
|
|
367
|
+
get stats() {
|
|
368
|
+
return { ...this._stats };
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get agent balance
|
|
372
|
+
*
|
|
373
|
+
* Returns current USDC balance plus locked/pending amounts from active transactions.
|
|
374
|
+
* Note: This is an async operation wrapped in a sync getter for convenience.
|
|
375
|
+
* For real-time balance, use getBalanceAsync() instead.
|
|
376
|
+
*/
|
|
377
|
+
get balance() {
|
|
378
|
+
// Return cached balance (updated periodically during polling)
|
|
379
|
+
return { ...this._balance };
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get agent balance asynchronously (real-time)
|
|
383
|
+
*
|
|
384
|
+
* @returns Promise resolving to current balance
|
|
385
|
+
*/
|
|
386
|
+
async getBalanceAsync() {
|
|
387
|
+
if (!this._client?.runtime) {
|
|
388
|
+
return {
|
|
389
|
+
eth: '0',
|
|
390
|
+
usdc: '0',
|
|
391
|
+
locked: '0',
|
|
392
|
+
pending: '0',
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
// Get USDC balance (if runtime supports it)
|
|
397
|
+
let usdc = '0';
|
|
398
|
+
if ('getBalance' in this._client.runtime) {
|
|
399
|
+
usdc = await this._client.runtime.getBalance(this.address);
|
|
400
|
+
}
|
|
401
|
+
// Get transactions where this agent is provider
|
|
402
|
+
let locked = BigInt(0);
|
|
403
|
+
let pending = BigInt(0);
|
|
404
|
+
if ('getTransactionsByProvider' in this._client.runtime) {
|
|
405
|
+
// Get all active transactions for this provider
|
|
406
|
+
const allTx = await this._client.runtime.getTransactionsByProvider(this.address, undefined, // all states
|
|
407
|
+
1000);
|
|
408
|
+
for (const tx of allTx) {
|
|
409
|
+
const amount = BigInt(tx.amount || '0');
|
|
410
|
+
// Locked: funds in escrow for active work (COMMITTED, IN_PROGRESS, DELIVERED)
|
|
411
|
+
if (tx.state === 'COMMITTED' || tx.state === 'IN_PROGRESS' || tx.state === 'DELIVERED') {
|
|
412
|
+
locked += amount;
|
|
413
|
+
}
|
|
414
|
+
// Pending: potential earnings waiting to be accepted (INITIATED, QUOTED)
|
|
415
|
+
if (tx.state === 'INITIATED' || tx.state === 'QUOTED') {
|
|
416
|
+
pending += amount;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
this._balance = {
|
|
421
|
+
eth: '0', // ETH balance not tracked in USDC-only system
|
|
422
|
+
usdc: usdc,
|
|
423
|
+
locked: locked.toString(),
|
|
424
|
+
pending: pending.toString(),
|
|
425
|
+
};
|
|
426
|
+
return { ...this._balance };
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
this.logger.warn('Failed to fetch balance', { error });
|
|
430
|
+
return { ...this._balance };
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* ACTP Client reference (for advanced usage)
|
|
435
|
+
*/
|
|
436
|
+
get client() {
|
|
437
|
+
return this._client;
|
|
438
|
+
}
|
|
439
|
+
// =========================================================================
|
|
440
|
+
// Private Methods
|
|
441
|
+
// =========================================================================
|
|
442
|
+
/**
|
|
443
|
+
* Start polling for new jobs
|
|
444
|
+
*/
|
|
445
|
+
startPolling() {
|
|
446
|
+
if (this.pollingIntervalId) {
|
|
447
|
+
return; // Already polling
|
|
448
|
+
}
|
|
449
|
+
const pollingInterval = 5000; // 5 seconds
|
|
450
|
+
this.pollingIntervalId = setInterval(() => {
|
|
451
|
+
this.pollForJobs().catch((error) => {
|
|
452
|
+
this.emit('error', error);
|
|
453
|
+
});
|
|
454
|
+
}, pollingInterval);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Stop polling
|
|
458
|
+
*/
|
|
459
|
+
stopPolling() {
|
|
460
|
+
if (this.pollingIntervalId) {
|
|
461
|
+
clearInterval(this.pollingIntervalId);
|
|
462
|
+
this.pollingIntervalId = undefined;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Poll for new jobs
|
|
467
|
+
*
|
|
468
|
+
* SECURITY FIXES:
|
|
469
|
+
* - C-1: Race condition prevention using processedJobs deduplication
|
|
470
|
+
* - C-2: Memory leak prevention using LRUCache
|
|
471
|
+
* - H-1: DoS prevention by filtering transactions before loading all
|
|
472
|
+
* - H-4: Authorization checks for state transitions
|
|
473
|
+
*
|
|
474
|
+
* Queries runtime for transactions where this agent is the provider
|
|
475
|
+
* and the transaction is in INITIATED state (awaiting acceptance).
|
|
476
|
+
* For each pending transaction, creates a Job object and invokes
|
|
477
|
+
* the appropriate service handler.
|
|
478
|
+
*/
|
|
479
|
+
async pollForJobs() {
|
|
480
|
+
if (!this._client) {
|
|
481
|
+
return; // Agent not started yet
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
// SECURITY FIX (H-1): Use filtered query instead of getAllTransactions
|
|
485
|
+
// This prevents DoS via memory exhaustion by only fetching relevant transactions
|
|
486
|
+
let pendingJobs = [];
|
|
487
|
+
// Check if runtime has the filtered query method
|
|
488
|
+
if ('getTransactionsByProvider' in this._client.runtime) {
|
|
489
|
+
// Use optimized filtered query (max 100 jobs per poll)
|
|
490
|
+
pendingJobs = await this._client.runtime.getTransactionsByProvider(this.address, 'INITIATED', 100);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
// Fallback to getAllTransactions (for older runtime versions)
|
|
494
|
+
const allTransactions = await this._client.runtime.getAllTransactions();
|
|
495
|
+
pendingJobs = allTransactions.filter((tx) => tx.provider === this.address && tx.state === 'INITIATED');
|
|
496
|
+
}
|
|
497
|
+
this.logger.debug('Polling for jobs', {
|
|
498
|
+
pendingJobs: pendingJobs.length,
|
|
499
|
+
});
|
|
500
|
+
// Process each pending job
|
|
501
|
+
for (const tx of pendingJobs) {
|
|
502
|
+
try {
|
|
503
|
+
// SECURITY FIX (C-1): Check processingLocks first (atomic check)
|
|
504
|
+
// This prevents race conditions where two poll cycles both try to process
|
|
505
|
+
// the same job before either transitions the state
|
|
506
|
+
if (this.processingLocks.has(tx.id) || this.processedJobs.has(tx.id)) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
// IMMEDIATELY acquire lock (atomic in single-threaded JS)
|
|
510
|
+
this.processingLocks.add(tx.id);
|
|
511
|
+
// SECURITY FIX (C-2): Check if already in active jobs (LRUCache handles size limit)
|
|
512
|
+
if (this.activeJobs.has(tx.id)) {
|
|
513
|
+
this.processingLocks.delete(tx.id);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
// SECURITY FIX (H-4): Verify this agent is authorized to accept this transaction
|
|
517
|
+
// Check that tx.provider matches our address (prevents unauthorized state transitions)
|
|
518
|
+
if (tx.provider !== this.address) {
|
|
519
|
+
this.logger.warn('Unauthorized transaction detected', {
|
|
520
|
+
txId: tx.id,
|
|
521
|
+
expectedProvider: this.address,
|
|
522
|
+
actualProvider: tx.provider,
|
|
523
|
+
});
|
|
524
|
+
this.processingLocks.delete(tx.id);
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
// Find matching service handler
|
|
528
|
+
const serviceHandler = this.findServiceHandler(tx);
|
|
529
|
+
if (!serviceHandler) {
|
|
530
|
+
// No handler registered for this service type
|
|
531
|
+
this.logger.debug('No handler for transaction', { txId: tx.id });
|
|
532
|
+
this.processingLocks.delete(tx.id);
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
// Check auto-accept behavior
|
|
536
|
+
const shouldAccept = await this.shouldAutoAccept(tx);
|
|
537
|
+
if (!shouldAccept) {
|
|
538
|
+
this.logger.debug('Auto-accept declined', { txId: tx.id });
|
|
539
|
+
this.processingLocks.delete(tx.id);
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
// Create Job object from transaction
|
|
543
|
+
const job = this.createJobFromTransaction(tx);
|
|
544
|
+
// SECURITY FIX (C-2): Add to active jobs (LRUCache prevents unbounded growth)
|
|
545
|
+
this.activeJobs.set(job.id, job);
|
|
546
|
+
// Link escrow immediately to transition out of INITIATED state
|
|
547
|
+
// This prevents polling from picking up this job again
|
|
548
|
+
try {
|
|
549
|
+
if (this._client && tx.state === 'INITIATED') {
|
|
550
|
+
await this._client.runtime.linkEscrow(tx.id, tx.amount);
|
|
551
|
+
}
|
|
552
|
+
// Successfully processed - mark as processed and release lock
|
|
553
|
+
this.processedJobs.set(job.id, true);
|
|
554
|
+
}
|
|
555
|
+
catch (escrowError) {
|
|
556
|
+
// If linking escrow fails, remove from active jobs and release lock (allow retry)
|
|
557
|
+
this.activeJobs.delete(job.id);
|
|
558
|
+
this.logger.error('Failed to link escrow', { txId: tx.id }, escrowError);
|
|
559
|
+
this.processingLocks.delete(tx.id);
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
finally {
|
|
563
|
+
// Always release the lock
|
|
564
|
+
this.processingLocks.delete(tx.id);
|
|
565
|
+
}
|
|
566
|
+
this._stats.jobsReceived++;
|
|
567
|
+
this.emit('job:received', job);
|
|
568
|
+
this.logger.info('Job accepted', { jobId: job.id, service: job.service });
|
|
569
|
+
// Process the job asynchronously (don't await here to continue polling)
|
|
570
|
+
this.processJob(job, serviceHandler.handler).catch((error) => {
|
|
571
|
+
this.logger.error('Job processing failed', { jobId: job.id }, error);
|
|
572
|
+
this.emit('error', error);
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
// Log error but continue processing other jobs
|
|
577
|
+
this.logger.error('Error processing pending job', { txId: tx.id }, error);
|
|
578
|
+
this.emit('error', error);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Update cached balance (non-blocking, don't await)
|
|
582
|
+
this.getBalanceAsync().catch(() => {
|
|
583
|
+
// Silently ignore balance update errors during polling
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
// Polling error - will retry on next interval
|
|
588
|
+
this.logger.error('Polling error', {}, error);
|
|
589
|
+
this.emit('error', error);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Find service handler for a transaction
|
|
594
|
+
*
|
|
595
|
+
* SECURITY FIX (MEDIUM): Use exact field matching instead of substring search
|
|
596
|
+
* to prevent service routing spoofing attacks.
|
|
597
|
+
*
|
|
598
|
+
* Supports multiple formats (in priority order):
|
|
599
|
+
* 1. JSON: {"service":"name","input":...} - new structured format
|
|
600
|
+
* 2. Legacy: "service:name;input:..." - backward compatibility
|
|
601
|
+
* 3. Plain string exact match - simple service name
|
|
602
|
+
* 4. bytes32 hash - on-chain only (requires off-chain lookup)
|
|
603
|
+
*/
|
|
604
|
+
findServiceHandler(tx) {
|
|
605
|
+
const serviceDesc = tx.serviceDescription;
|
|
606
|
+
if (!serviceDesc) {
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
let parsedService;
|
|
610
|
+
// 1. Try JSON format first (new structured format from request())
|
|
611
|
+
try {
|
|
612
|
+
const jsonMetadata = JSON.parse(serviceDesc);
|
|
613
|
+
if (jsonMetadata && typeof jsonMetadata.service === 'string') {
|
|
614
|
+
parsedService = jsonMetadata.service;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
// Not JSON, try other formats
|
|
619
|
+
}
|
|
620
|
+
// 2. Try legacy "service:NAME;input:JSON" format
|
|
621
|
+
if (!parsedService) {
|
|
622
|
+
const legacyMetadata = Helpers_1.ServiceHash.fromLegacy(serviceDesc);
|
|
623
|
+
if (legacyMetadata) {
|
|
624
|
+
parsedService = legacyMetadata.service;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// 3. If we parsed a service name, do EXACT match
|
|
628
|
+
if (parsedService) {
|
|
629
|
+
const handler = this.services.get(parsedService);
|
|
630
|
+
if (handler) {
|
|
631
|
+
return handler;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// 4. Check if it's a bytes32 hash (from on-chain BlockchainRuntime)
|
|
635
|
+
// NOTE: For hashed metadata, the original data must be retrieved from
|
|
636
|
+
// event logs or off-chain storage. This is a fallback for hash-only matching.
|
|
637
|
+
if (Helpers_1.ServiceHash.isValidHash(serviceDesc)) {
|
|
638
|
+
this.logger.debug('Service description is a hash - cannot extract service name', {
|
|
639
|
+
hash: serviceDesc.slice(0, 18) + '...',
|
|
640
|
+
});
|
|
641
|
+
// Cannot match hashes without original data
|
|
642
|
+
// In production, use event indexing to get original metadata
|
|
643
|
+
return undefined;
|
|
644
|
+
}
|
|
645
|
+
// 5. Fallback: Plain string exact match (service name directly)
|
|
646
|
+
for (const [serviceName, handler] of this.services.entries()) {
|
|
647
|
+
// EXACT match only - prevent substring spoofing
|
|
648
|
+
if (serviceDesc === serviceName) {
|
|
649
|
+
return handler;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return undefined;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Check if job should be auto-accepted
|
|
656
|
+
*
|
|
657
|
+
* SECURITY FIX (MVP): Added pricing strategy evaluation
|
|
658
|
+
* - Checks service-level filters (budget constraints)
|
|
659
|
+
* - Evaluates pricing strategy if configured
|
|
660
|
+
* - Only accepts jobs that meet pricing requirements
|
|
661
|
+
*/
|
|
662
|
+
async shouldAutoAccept(tx) {
|
|
663
|
+
// Get the service config for this transaction
|
|
664
|
+
const serviceHandler = this.findServiceHandler(tx);
|
|
665
|
+
// Check service-level filters first (budget constraints)
|
|
666
|
+
if (serviceHandler?.config.filter) {
|
|
667
|
+
const filter = serviceHandler.config.filter;
|
|
668
|
+
const budget = this.convertAmountToNumber(tx.amount);
|
|
669
|
+
// If filter is a ServiceFilter object, check budget constraints
|
|
670
|
+
if (typeof filter === 'object' && !Array.isArray(filter)) {
|
|
671
|
+
// Check minBudget
|
|
672
|
+
if (filter.minBudget !== undefined && budget < filter.minBudget) {
|
|
673
|
+
this.logger.debug('Job rejected: budget below minimum', {
|
|
674
|
+
txId: tx.id,
|
|
675
|
+
budget,
|
|
676
|
+
minBudget: filter.minBudget,
|
|
677
|
+
});
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
// Check maxBudget
|
|
681
|
+
if (filter.maxBudget !== undefined && budget > filter.maxBudget) {
|
|
682
|
+
this.logger.debug('Job rejected: budget above maximum', {
|
|
683
|
+
txId: tx.id,
|
|
684
|
+
budget,
|
|
685
|
+
maxBudget: filter.maxBudget,
|
|
686
|
+
});
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
// Check custom filter function
|
|
690
|
+
if (filter.custom && typeof filter.custom === 'function') {
|
|
691
|
+
const job = this.createJobFromTransaction(tx);
|
|
692
|
+
const customResult = filter.custom(job);
|
|
693
|
+
if (!customResult) {
|
|
694
|
+
this.logger.debug('Job rejected: custom filter declined', { txId: tx.id });
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// If filter is a function (legacy support)
|
|
700
|
+
else if (typeof filter === 'function') {
|
|
701
|
+
const job = this.createJobFromTransaction(tx);
|
|
702
|
+
const filterResult = filter(job);
|
|
703
|
+
if (!filterResult) {
|
|
704
|
+
this.logger.debug('Job rejected: filter function declined', { txId: tx.id });
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// MVP: Check pricing strategy if configured
|
|
710
|
+
if (serviceHandler?.config.pricing) {
|
|
711
|
+
const { calculatePrice } = await Promise.resolve().then(() => __importStar(require('./pricing/PriceCalculator')));
|
|
712
|
+
const job = this.createJobFromTransaction(tx);
|
|
713
|
+
try {
|
|
714
|
+
const calculation = calculatePrice(serviceHandler.config.pricing, job);
|
|
715
|
+
this.logger.debug('Pricing calculation', {
|
|
716
|
+
txId: tx.id,
|
|
717
|
+
cost: calculation.cost,
|
|
718
|
+
price: calculation.price,
|
|
719
|
+
profit: calculation.profit,
|
|
720
|
+
margin: calculation.marginPercent,
|
|
721
|
+
decision: calculation.decision,
|
|
722
|
+
reason: calculation.reason,
|
|
723
|
+
});
|
|
724
|
+
// Only accept if pricing decision is 'accept'
|
|
725
|
+
if (calculation.decision === 'reject') {
|
|
726
|
+
this.logger.info('Job rejected by pricing strategy', {
|
|
727
|
+
txId: tx.id,
|
|
728
|
+
reason: calculation.reason,
|
|
729
|
+
});
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
// If decision is 'counter-offer', we could implement QUOTED state here
|
|
733
|
+
// For MVP, we treat 'counter-offer' as reject (no automatic negotiation)
|
|
734
|
+
if (calculation.decision === 'counter-offer') {
|
|
735
|
+
this.logger.info('Job requires counter-offer (not implemented in MVP)', {
|
|
736
|
+
txId: tx.id,
|
|
737
|
+
reason: calculation.reason,
|
|
738
|
+
});
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
// If pricing calculation fails, reject the job for safety
|
|
744
|
+
this.logger.error('Pricing calculation failed, rejecting job', { txId: tx.id }, error);
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
// Check agent-level autoAccept behavior
|
|
749
|
+
const autoAccept = this.config.behavior?.autoAccept;
|
|
750
|
+
if (autoAccept === undefined || autoAccept === true) {
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
if (autoAccept === false) {
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
// It's a function - evaluate it
|
|
757
|
+
if (typeof autoAccept === 'function') {
|
|
758
|
+
const job = this.createJobFromTransaction(tx);
|
|
759
|
+
return await autoAccept(job);
|
|
760
|
+
}
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Create Job object from MockTransaction
|
|
765
|
+
*/
|
|
766
|
+
createJobFromTransaction(tx) {
|
|
767
|
+
return {
|
|
768
|
+
id: tx.id,
|
|
769
|
+
service: this.extractServiceName(tx),
|
|
770
|
+
input: this.extractJobInput(tx),
|
|
771
|
+
budget: this.convertAmountToNumber(tx.amount),
|
|
772
|
+
deadline: new Date(tx.deadline * 1000), // Convert unix timestamp to Date
|
|
773
|
+
requester: tx.requester,
|
|
774
|
+
metadata: this.extractMetadata(tx),
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Extract service name from transaction
|
|
779
|
+
*
|
|
780
|
+
* Supports multiple formats:
|
|
781
|
+
* 1. JSON: {"service":"name","input":...}
|
|
782
|
+
* 2. Legacy: "service:name;input:..."
|
|
783
|
+
* 3. Plain string (service name directly)
|
|
784
|
+
*/
|
|
785
|
+
extractServiceName(tx) {
|
|
786
|
+
if (!tx.serviceDescription) {
|
|
787
|
+
return 'unknown';
|
|
788
|
+
}
|
|
789
|
+
// Try JSON format first (new structured format)
|
|
790
|
+
try {
|
|
791
|
+
const parsed = JSON.parse(tx.serviceDescription);
|
|
792
|
+
if (parsed && typeof parsed.service === 'string') {
|
|
793
|
+
return parsed.service;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
catch {
|
|
797
|
+
// Not JSON, try other formats
|
|
798
|
+
}
|
|
799
|
+
// Try legacy format: "service:serviceName;input:..."
|
|
800
|
+
const legacyMatch = tx.serviceDescription.match(/^service:([^;]+)/);
|
|
801
|
+
if (legacyMatch) {
|
|
802
|
+
return legacyMatch[1];
|
|
803
|
+
}
|
|
804
|
+
// Plain string - might be just the service name
|
|
805
|
+
if (typeof tx.serviceDescription === 'string' && tx.serviceDescription.length < 64) {
|
|
806
|
+
return tx.serviceDescription;
|
|
807
|
+
}
|
|
808
|
+
return 'unknown';
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Extract job input from transaction
|
|
812
|
+
*
|
|
813
|
+
* Supports multiple formats:
|
|
814
|
+
* 1. JSON: {"service":"name","input":{...}}
|
|
815
|
+
* 2. Legacy: "service:name;input:JSON"
|
|
816
|
+
*/
|
|
817
|
+
extractJobInput(tx) {
|
|
818
|
+
if (!tx.serviceDescription) {
|
|
819
|
+
return {};
|
|
820
|
+
}
|
|
821
|
+
// Try JSON format first (new structured format)
|
|
822
|
+
try {
|
|
823
|
+
const parsed = JSON.parse(tx.serviceDescription);
|
|
824
|
+
if (parsed && parsed.input !== undefined) {
|
|
825
|
+
return parsed.input;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
catch {
|
|
829
|
+
// Not JSON, try other formats
|
|
830
|
+
}
|
|
831
|
+
// Try legacy format: "service:serviceName;input:JSON"
|
|
832
|
+
const legacyMatch = tx.serviceDescription.match(/;input:(.+)$/);
|
|
833
|
+
if (legacyMatch) {
|
|
834
|
+
try {
|
|
835
|
+
return JSON.parse(legacyMatch[1]);
|
|
836
|
+
}
|
|
837
|
+
catch {
|
|
838
|
+
return legacyMatch[1]; // Return as string if not valid JSON
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return {};
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Extract metadata from transaction
|
|
845
|
+
*/
|
|
846
|
+
extractMetadata(tx) {
|
|
847
|
+
return {
|
|
848
|
+
transactionId: tx.id,
|
|
849
|
+
createdAt: tx.createdAt,
|
|
850
|
+
disputeWindow: tx.disputeWindow,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Convert amount string to number (USDC has 6 decimals)
|
|
855
|
+
*/
|
|
856
|
+
convertAmountToNumber(amount) {
|
|
857
|
+
const amountBigInt = BigInt(amount);
|
|
858
|
+
return Number(amountBigInt) / 1000000; // Convert from USDC wei to USDC
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Process a job by invoking the handler
|
|
862
|
+
*
|
|
863
|
+
* SECURITY FIX (C-2): Always cleanup activeJobs on completion/failure
|
|
864
|
+
* SECURITY FIX (MEDIUM-4): Uses semaphore to limit concurrent execution
|
|
865
|
+
*/
|
|
866
|
+
async processJob(job, handler) {
|
|
867
|
+
const startTime = Date.now();
|
|
868
|
+
// SECURITY FIX (MEDIUM-4): Check concurrency limit before processing
|
|
869
|
+
// If semaphore is full, wait up to 30 seconds for a slot
|
|
870
|
+
const CONCURRENCY_TIMEOUT_MS = 30000;
|
|
871
|
+
try {
|
|
872
|
+
// Try to acquire semaphore permit (wait if at limit)
|
|
873
|
+
await this.concurrencySemaphore.acquire(CONCURRENCY_TIMEOUT_MS);
|
|
874
|
+
}
|
|
875
|
+
catch (acquireError) {
|
|
876
|
+
// Timeout waiting for concurrency slot
|
|
877
|
+
this.logger.warn('Job rejected due to concurrency limit', {
|
|
878
|
+
jobId: job.id,
|
|
879
|
+
activeJobs: this.concurrencySemaphore.limit - this.concurrencySemaphore.availablePermits,
|
|
880
|
+
maxConcurrency: this.concurrencySemaphore.limit,
|
|
881
|
+
queueLength: this.concurrencySemaphore.queueLength,
|
|
882
|
+
});
|
|
883
|
+
// Remove from active jobs since we couldn't process it
|
|
884
|
+
this.activeJobs.delete(job.id);
|
|
885
|
+
this.processedJobs.delete(job.id);
|
|
886
|
+
this.emit('job:rejected', job, 'concurrency_limit');
|
|
887
|
+
throw new Error(`Job ${job.id} rejected: concurrency limit reached (${this.concurrencySemaphore.limit} concurrent jobs max). ` +
|
|
888
|
+
`Try again later or increase behavior.concurrency.`);
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
// Create job context
|
|
892
|
+
const context = this.createJobContext(job);
|
|
893
|
+
// Invoke handler
|
|
894
|
+
const result = await handler(job, context);
|
|
895
|
+
// SECURITY FIX (CRITICAL-2): Use ProofGenerator to create authenticated delivery proof
|
|
896
|
+
// This ensures the proof has proper structure with txId, contentHash, and timestamp
|
|
897
|
+
const proofGenerator = new ProofGenerator_1.ProofGenerator();
|
|
898
|
+
const deliverable = typeof result === 'string' ? result : JSON.stringify(result);
|
|
899
|
+
const deliveryProof = proofGenerator.generateDeliveryProof({
|
|
900
|
+
txId: job.id,
|
|
901
|
+
deliverable,
|
|
902
|
+
metadata: {
|
|
903
|
+
service: job.service,
|
|
904
|
+
completedAt: Date.now(),
|
|
905
|
+
},
|
|
906
|
+
});
|
|
907
|
+
// Encode proof with content hash for verification
|
|
908
|
+
const deliveryProofJson = JSON.stringify({
|
|
909
|
+
...deliveryProof,
|
|
910
|
+
result, // Include original result for convenience
|
|
911
|
+
});
|
|
912
|
+
// Transition transaction to DELIVERED state
|
|
913
|
+
if (this._client) {
|
|
914
|
+
// Store delivery proof by directly accessing MockRuntime's state
|
|
915
|
+
// This is a workaround - in production, we'd use a proper method
|
|
916
|
+
const runtime = this._client.runtime;
|
|
917
|
+
if (runtime.stateManager) {
|
|
918
|
+
await runtime.stateManager.withLock(async (state) => {
|
|
919
|
+
const tx = state.transactions[job.id];
|
|
920
|
+
if (tx) {
|
|
921
|
+
tx.deliveryProof = deliveryProofJson;
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
// Transition to DELIVERED (escrow was already linked in pollForJobs)
|
|
926
|
+
await this._client.runtime.transitionState(job.id, 'DELIVERED');
|
|
927
|
+
}
|
|
928
|
+
// SECURITY FIX (C-2): Remove from active jobs on SUCCESS
|
|
929
|
+
this.activeJobs.delete(job.id);
|
|
930
|
+
// Update stats
|
|
931
|
+
this._stats.jobsCompleted++;
|
|
932
|
+
const duration = Date.now() - startTime;
|
|
933
|
+
this._stats.averageJobTime =
|
|
934
|
+
(this._stats.averageJobTime * (this._stats.jobsCompleted - 1) + duration) /
|
|
935
|
+
this._stats.jobsCompleted;
|
|
936
|
+
this._stats.successRate =
|
|
937
|
+
this._stats.jobsCompleted / (this._stats.jobsCompleted + this._stats.jobsFailed);
|
|
938
|
+
this._stats.totalEarned += job.budget;
|
|
939
|
+
// Emit events
|
|
940
|
+
this.logger.info('Job completed', {
|
|
941
|
+
jobId: job.id,
|
|
942
|
+
duration,
|
|
943
|
+
earned: job.budget,
|
|
944
|
+
});
|
|
945
|
+
this.emit('job:completed', job, result);
|
|
946
|
+
this.emit('payment:received', job.budget);
|
|
947
|
+
}
|
|
948
|
+
catch (error) {
|
|
949
|
+
// SECURITY FIX (C-2): Remove from active jobs on FAILURE
|
|
950
|
+
this.activeJobs.delete(job.id);
|
|
951
|
+
this._stats.jobsFailed++;
|
|
952
|
+
this._stats.successRate =
|
|
953
|
+
this._stats.jobsCompleted / (this._stats.jobsCompleted + this._stats.jobsFailed);
|
|
954
|
+
this.logger.error('Job failed', { jobId: job.id }, error);
|
|
955
|
+
this.emit('job:failed', job, error);
|
|
956
|
+
}
|
|
957
|
+
finally {
|
|
958
|
+
// SECURITY FIX (MEDIUM-4): Always release semaphore permit
|
|
959
|
+
this.concurrencySemaphore.release();
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Create JobContext for handler execution
|
|
964
|
+
*/
|
|
965
|
+
createJobContext(job) {
|
|
966
|
+
const state = new Map();
|
|
967
|
+
let cancelled = false;
|
|
968
|
+
const cancelHandlers = [];
|
|
969
|
+
const agent = this; // Capture 'this' for use in closures
|
|
970
|
+
return {
|
|
971
|
+
agent: agent,
|
|
972
|
+
progress(percent, message) {
|
|
973
|
+
// Emit progress event
|
|
974
|
+
agent.emit('job:progress', job.id, percent, message);
|
|
975
|
+
},
|
|
976
|
+
log: {
|
|
977
|
+
debug: (message, meta) => {
|
|
978
|
+
if (agent.config.logging?.level === 'debug') {
|
|
979
|
+
console.debug(`[${job.id}] ${message}`, meta);
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
info: (message, meta) => {
|
|
983
|
+
if (['debug', 'info'].includes(agent.config.logging?.level || 'info')) {
|
|
984
|
+
console.info(`[${job.id}] ${message}`, meta);
|
|
985
|
+
}
|
|
986
|
+
},
|
|
987
|
+
warn: (message, meta) => {
|
|
988
|
+
if (['debug', 'info', 'warn'].includes(agent.config.logging?.level || 'info')) {
|
|
989
|
+
console.warn(`[${job.id}] ${message}`, meta);
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
error: (message, meta) => {
|
|
993
|
+
console.error(`[${job.id}] ${message}`, meta);
|
|
994
|
+
},
|
|
995
|
+
},
|
|
996
|
+
state: {
|
|
997
|
+
get(key) {
|
|
998
|
+
return state.get(key);
|
|
999
|
+
},
|
|
1000
|
+
set(key, value) {
|
|
1001
|
+
state.set(key, value);
|
|
1002
|
+
},
|
|
1003
|
+
},
|
|
1004
|
+
get cancelled() {
|
|
1005
|
+
return cancelled;
|
|
1006
|
+
},
|
|
1007
|
+
onCancel(handler) {
|
|
1008
|
+
cancelHandlers.push(handler);
|
|
1009
|
+
},
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Wait for active jobs to complete
|
|
1014
|
+
*/
|
|
1015
|
+
async waitForActiveJobs(timeoutMs) {
|
|
1016
|
+
const startTime = Date.now();
|
|
1017
|
+
while (this.activeJobs.size > 0) {
|
|
1018
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
1019
|
+
this.logger.warn('Active jobs still running after timeout', {
|
|
1020
|
+
activeJobs: this.activeJobs.size,
|
|
1021
|
+
});
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Generate address based on wallet configuration
|
|
1029
|
+
*
|
|
1030
|
+
* SECURITY FIX (HIGH): For testnet/mainnet, MUST derive from private key.
|
|
1031
|
+
* For mock mode, can use deterministic address for convenience.
|
|
1032
|
+
*/
|
|
1033
|
+
generateAddress() {
|
|
1034
|
+
// If wallet has private key, ALWAYS derive address from it
|
|
1035
|
+
const privateKey = this.getPrivateKey();
|
|
1036
|
+
if (privateKey) {
|
|
1037
|
+
try {
|
|
1038
|
+
const wallet = new ethers_1.ethers.Wallet(privateKey);
|
|
1039
|
+
return wallet.address.toLowerCase();
|
|
1040
|
+
}
|
|
1041
|
+
catch (error) {
|
|
1042
|
+
throw new errors_1.ValidationError('wallet', 'Invalid private key format');
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
// For non-mock networks, require a valid private key or address
|
|
1046
|
+
if (this.network === 'testnet' || this.network === 'mainnet') {
|
|
1047
|
+
throw new errors_1.ValidationError('wallet', `${this.network} mode requires a valid private key or address in wallet configuration`);
|
|
1048
|
+
}
|
|
1049
|
+
// For mock mode only: generate deterministic address from agent name
|
|
1050
|
+
// This is safe because mock mode doesn't involve real funds
|
|
1051
|
+
return `0x${Buffer.from(this.name).toString('hex').padEnd(40, '0').slice(0, 40)}`;
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Get private key from configuration
|
|
1055
|
+
*
|
|
1056
|
+
* SECURITY FIX (HIGH): Validate private key format before use
|
|
1057
|
+
*/
|
|
1058
|
+
getPrivateKey() {
|
|
1059
|
+
if (!this.config.wallet || this.config.wallet === 'auto' || this.config.wallet === 'connect') {
|
|
1060
|
+
return undefined;
|
|
1061
|
+
}
|
|
1062
|
+
if (typeof this.config.wallet === 'string') {
|
|
1063
|
+
// Check if it looks like a private key (0x + 64 hex chars)
|
|
1064
|
+
if (/^0x[0-9a-fA-F]{64}$/.test(this.config.wallet)) {
|
|
1065
|
+
// Validate by trying to create a wallet
|
|
1066
|
+
try {
|
|
1067
|
+
new ethers_1.ethers.Wallet(this.config.wallet);
|
|
1068
|
+
return this.config.wallet;
|
|
1069
|
+
}
|
|
1070
|
+
catch {
|
|
1071
|
+
throw new errors_1.ValidationError('wallet', 'Invalid private key format');
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
// It's an address, not a private key
|
|
1075
|
+
return undefined;
|
|
1076
|
+
}
|
|
1077
|
+
// Validate private key format
|
|
1078
|
+
if (this.config.wallet.privateKey) {
|
|
1079
|
+
try {
|
|
1080
|
+
new ethers_1.ethers.Wallet(this.config.wallet.privateKey);
|
|
1081
|
+
return this.config.wallet.privateKey;
|
|
1082
|
+
}
|
|
1083
|
+
catch {
|
|
1084
|
+
throw new errors_1.ValidationError('wallet.privateKey', 'Invalid private key format');
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return undefined;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
exports.Agent = Agent;
|
|
1091
|
+
//# sourceMappingURL=Agent.js.map
|