@highway1/core 0.1.53 → 0.1.55

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.
Files changed (39) hide show
  1. package/package.json +5 -18
  2. package/src/discovery/agent-card-encoder.ts +0 -119
  3. package/src/discovery/agent-card-schema.ts +0 -87
  4. package/src/discovery/agent-card-types.ts +0 -99
  5. package/src/discovery/agent-card.ts +0 -190
  6. package/src/discovery/bootstrap.ts +0 -63
  7. package/src/discovery/capability-matcher.ts +0 -167
  8. package/src/discovery/dht.ts +0 -310
  9. package/src/discovery/index.ts +0 -3
  10. package/src/discovery/relay-index.ts +0 -98
  11. package/src/discovery/search-index.ts +0 -247
  12. package/src/discovery/semantic-search.ts +0 -218
  13. package/src/identity/did.ts +0 -48
  14. package/src/identity/document.ts +0 -77
  15. package/src/identity/index.ts +0 -4
  16. package/src/identity/keys.ts +0 -79
  17. package/src/identity/signer.ts +0 -55
  18. package/src/index.ts +0 -39
  19. package/src/messaging/codec.ts +0 -47
  20. package/src/messaging/defense.ts +0 -236
  21. package/src/messaging/envelope.ts +0 -107
  22. package/src/messaging/index.ts +0 -8
  23. package/src/messaging/queue.ts +0 -181
  24. package/src/messaging/rate-limiter.ts +0 -85
  25. package/src/messaging/router.ts +0 -209
  26. package/src/messaging/storage.ts +0 -281
  27. package/src/messaging/types.ts +0 -149
  28. package/src/transport/connection.ts +0 -77
  29. package/src/transport/index.ts +0 -2
  30. package/src/transport/node.ts +0 -154
  31. package/src/transport/relay-client.ts +0 -437
  32. package/src/transport/relay-types.ts +0 -196
  33. package/src/trust/endorsement.ts +0 -167
  34. package/src/trust/index.ts +0 -194
  35. package/src/trust/interaction-history.ts +0 -155
  36. package/src/trust/sybil-defense.ts +0 -232
  37. package/src/trust/trust-score.ts +0 -136
  38. package/src/utils/errors.ts +0 -38
  39. package/src/utils/logger.ts +0 -48
@@ -1,55 +0,0 @@
1
- import { sign, verify } from './keys.js';
2
- import { deriveDID } from './did.js';
3
- import { IdentityError } from '../utils/errors.js';
4
-
5
- export interface SignedMessage {
6
- payload: Uint8Array;
7
- signature: Uint8Array;
8
- signer: string; // DID
9
- }
10
-
11
- /**
12
- * Sign a message and return a signed message object
13
- */
14
- export async function signMessage(
15
- payload: Uint8Array,
16
- privateKey: Uint8Array,
17
- publicKey: Uint8Array
18
- ): Promise<SignedMessage> {
19
- try {
20
- const signature = await sign(payload, privateKey);
21
- const signer = deriveDID(publicKey);
22
-
23
- return {
24
- payload,
25
- signature,
26
- signer,
27
- };
28
- } catch (error) {
29
- throw new IdentityError('Failed to sign message', error);
30
- }
31
- }
32
-
33
- /**
34
- * Verify a signed message
35
- */
36
- export async function verifyMessage(
37
- signedMessage: SignedMessage,
38
- expectedPublicKey: Uint8Array
39
- ): Promise<boolean> {
40
- try {
41
- const expectedDID = deriveDID(expectedPublicKey);
42
-
43
- if (signedMessage.signer !== expectedDID) {
44
- return false;
45
- }
46
-
47
- return await verify(
48
- signedMessage.signature,
49
- signedMessage.payload,
50
- expectedPublicKey
51
- );
52
- } catch (error) {
53
- throw new IdentityError('Failed to verify message', error);
54
- }
55
- }
package/src/index.ts DELETED
@@ -1,39 +0,0 @@
1
- /**
2
- * Clawiverse Core - Main Export
3
- */
4
-
5
- // Identity
6
- export * from './identity/keys.js';
7
- export * from './identity/did.js';
8
- export * from './identity/signer.js';
9
-
10
- // Transport (CVP-0011: relay-based)
11
- export * from './transport/relay-client.js';
12
- export * from './transport/relay-types.js';
13
-
14
- // Discovery
15
- export * from './discovery/agent-card.js';
16
- export * from './discovery/agent-card-types.js';
17
- export * from './discovery/agent-card-schema.js';
18
- export * from './discovery/agent-card-encoder.js';
19
- export * from './discovery/relay-index.js';
20
- export * from './discovery/search-index.js';
21
- export * from './discovery/capability-matcher.js';
22
- export * from './discovery/semantic-search.js';
23
-
24
- // Messaging
25
- export * from './messaging/envelope.js';
26
- export * from './messaging/codec.js';
27
- export * from './messaging/router.js';
28
- export * from './messaging/types.js';
29
- export * from './messaging/storage.js';
30
- export * from './messaging/queue.js';
31
- export * from './messaging/defense.js';
32
- export * from './messaging/rate-limiter.js';
33
-
34
- // Trust (Phase 2)
35
- export * from './trust/index.js';
36
-
37
- // Utils
38
- export * from './utils/logger.js';
39
- export * from './utils/errors.js';
@@ -1,47 +0,0 @@
1
- import { encode, decode } from 'cbor-x';
2
- import type { MessageEnvelope } from './envelope.js';
3
- import { MessagingError } from '../utils/errors.js';
4
-
5
- /**
6
- * Encode a message envelope to CBOR
7
- */
8
- export function encodeMessage(envelope: MessageEnvelope): Uint8Array {
9
- try {
10
- return encode(envelope);
11
- } catch (error) {
12
- throw new MessagingError('Failed to encode message', error);
13
- }
14
- }
15
-
16
- /**
17
- * Decode a CBOR message to envelope
18
- */
19
- export function decodeMessage(data: Uint8Array): MessageEnvelope {
20
- try {
21
- return decode(data) as MessageEnvelope;
22
- } catch (error) {
23
- throw new MessagingError('Failed to decode message', error);
24
- }
25
- }
26
-
27
- /**
28
- * Encode message to JSON (for debugging/logging)
29
- */
30
- export function encodeMessageJSON(envelope: MessageEnvelope): string {
31
- try {
32
- return JSON.stringify(envelope, null, 2);
33
- } catch (error) {
34
- throw new MessagingError('Failed to encode message to JSON', error);
35
- }
36
- }
37
-
38
- /**
39
- * Decode JSON message to envelope
40
- */
41
- export function decodeMessageJSON(json: string): MessageEnvelope {
42
- try {
43
- return JSON.parse(json) as MessageEnvelope;
44
- } catch (error) {
45
- throw new MessagingError('Failed to decode message from JSON', error);
46
- }
47
- }
@@ -1,236 +0,0 @@
1
- /**
2
- * Defense Middleware
3
- *
4
- * Checks incoming messages against:
5
- * 1. Allowlist bypass
6
- * 2. Blocklist rejection
7
- * 3. Deduplication (seen cache)
8
- * 4. Trust score filtering
9
- * 5. Rate limiting (token bucket, tiered by trust)
10
- */
11
-
12
- import { createLogger } from '../utils/logger.js';
13
- import type { MessageEnvelope } from './envelope.js';
14
- import type { TrustSystem } from '../trust/index.js';
15
- import type { MessageStorage } from './storage.js';
16
- import type { DefenseResult, RateLimitResult } from './types.js';
17
- import {
18
- TokenBucket,
19
- DEFAULT_RATE_LIMIT_TIERS,
20
- getTierConfig,
21
- type RateLimitTiers,
22
- } from './rate-limiter.js';
23
-
24
- const logger = createLogger('defense');
25
-
26
- export interface DefenseConfig {
27
- trustSystem: TrustSystem;
28
- storage: MessageStorage;
29
- /** Minimum trust score to accept messages (0 = accept all) */
30
- minTrustScore?: number;
31
- /** Auto-block agents below this score */
32
- autoBlockThreshold?: number;
33
- rateLimitTiers?: RateLimitTiers;
34
- /** TTL for seen-cache entries in ms (default: 1 hour) */
35
- seenTtlMs?: number;
36
- }
37
-
38
- export class DefenseMiddleware {
39
- private readonly trust: TrustSystem;
40
- private readonly storage: MessageStorage;
41
- private readonly minTrustScore: number;
42
- private readonly autoBlockThreshold: number;
43
- private readonly tiers: RateLimitTiers;
44
- private readonly seenTtlMs: number;
45
-
46
- // In-memory LRU-style seen cache (backed by LevelDB for persistence)
47
- private readonly seenCache = new Map<string, number>(); // id → seenAt
48
- private readonly MAX_SEEN_CACHE = 10_000;
49
-
50
- // In-memory token buckets (backed by LevelDB for persistence)
51
- private readonly buckets = new Map<string, TokenBucket>();
52
-
53
- constructor(config: DefenseConfig) {
54
- this.trust = config.trustSystem;
55
- this.storage = config.storage;
56
- this.minTrustScore = config.minTrustScore ?? 0;
57
- this.autoBlockThreshold = config.autoBlockThreshold ?? 0.1;
58
- this.tiers = config.rateLimitTiers ?? DEFAULT_RATE_LIMIT_TIERS;
59
- this.seenTtlMs = config.seenTtlMs ?? 60 * 60 * 1000; // 1 hour
60
- }
61
-
62
- /**
63
- * Run all defense checks on an incoming message.
64
- * Returns { allowed: true } if the message should be processed,
65
- * or { allowed: false, reason } if it should be dropped.
66
- */
67
- async checkMessage(envelope: MessageEnvelope): Promise<DefenseResult> {
68
- const did = envelope.from;
69
-
70
- // 1. Allowlist bypass — skip all other checks
71
- if (await this.isAllowed(did)) {
72
- this.markAsSeen(envelope.id);
73
- return { allowed: true };
74
- }
75
-
76
- // 2. Blocklist check
77
- if (await this.isBlocked(did)) {
78
- logger.debug('Message rejected: blocked', { id: envelope.id, from: did });
79
- return { allowed: false, reason: 'blocked' };
80
- }
81
-
82
- // 3. Deduplication
83
- if (this.hasSeen(envelope.id)) {
84
- logger.debug('Message rejected: duplicate', { id: envelope.id });
85
- return { allowed: false, reason: 'duplicate' };
86
- }
87
-
88
- // 4. Trust score check
89
- let trustScore = 0;
90
- try {
91
- const score = await this.trust.getTrustScore(did);
92
- trustScore = score.interactionScore;
93
-
94
- // Auto-block very low trust agents
95
- if (trustScore < this.autoBlockThreshold && trustScore > 0) {
96
- logger.warn('Auto-blocking low-trust agent', { did, trustScore });
97
- await this.blockAgent(did, `Auto-blocked: trust score ${trustScore.toFixed(2)} below threshold`);
98
- return { allowed: false, reason: 'blocked' };
99
- }
100
-
101
- if (trustScore < this.minTrustScore) {
102
- logger.debug('Message rejected: trust too low', { id: envelope.id, trustScore });
103
- return { allowed: false, reason: 'trust_too_low', trustScore };
104
- }
105
- } catch (err) {
106
- logger.warn('Trust score lookup failed, using 0', { did, error: (err as Error).message });
107
- }
108
-
109
- // 5. Rate limiting (tiered by trust)
110
- const rateLimitResult = await this.checkRateLimit(did, trustScore);
111
- if (!rateLimitResult.allowed) {
112
- logger.debug('Message rejected: rate limited', {
113
- id: envelope.id,
114
- from: did,
115
- resetTime: rateLimitResult.resetTime,
116
- });
117
- return {
118
- allowed: false,
119
- reason: 'rate_limited',
120
- remainingTokens: rateLimitResult.remaining,
121
- resetTime: rateLimitResult.resetTime,
122
- };
123
- }
124
-
125
- // All checks passed
126
- this.markAsSeen(envelope.id);
127
- return { allowed: true, trustScore, remainingTokens: rateLimitResult.remaining };
128
- }
129
-
130
- // ─── Blocklist ────────────────────────────────────────────────────────────
131
-
132
- async blockAgent(did: string, reason: string, blockedBy = 'local'): Promise<void> {
133
- await this.storage.putBlock({ did, reason, blockedAt: Date.now(), blockedBy });
134
- logger.info('Agent blocked', { did, reason });
135
- }
136
-
137
- async unblockAgent(did: string): Promise<void> {
138
- await this.storage.deleteBlock(did);
139
- logger.info('Agent unblocked', { did });
140
- }
141
-
142
- async isBlocked(did: string): Promise<boolean> {
143
- return (await this.storage.getBlock(did)) !== null;
144
- }
145
-
146
- // ─── Allowlist ────────────────────────────────────────────────────────────
147
-
148
- async allowAgent(did: string, note?: string): Promise<void> {
149
- await this.storage.putAllow({ did, addedAt: Date.now(), note });
150
- logger.info('Agent allowlisted', { did });
151
- }
152
-
153
- async removeFromAllowlist(did: string): Promise<void> {
154
- await this.storage.deleteAllow(did);
155
- logger.info('Agent removed from allowlist', { did });
156
- }
157
-
158
- async isAllowed(did: string): Promise<boolean> {
159
- return (await this.storage.getAllow(did)) !== null;
160
- }
161
-
162
- // ─── Rate Limiting ────────────────────────────────────────────────────────
163
-
164
- async checkRateLimit(did: string, trustScore: number): Promise<RateLimitResult> {
165
- const tierConfig = getTierConfig(trustScore, this.tiers);
166
-
167
- // Load or create bucket
168
- let bucket = this.buckets.get(did);
169
- if (!bucket) {
170
- // Try to restore from LevelDB
171
- const persisted = await this.storage.getRateLimit(did);
172
- if (persisted) {
173
- bucket = new TokenBucket(tierConfig, persisted.tokens, persisted.lastRefill);
174
- } else {
175
- bucket = new TokenBucket(tierConfig);
176
- }
177
- this.buckets.set(did, bucket);
178
- }
179
-
180
- const allowed = bucket.consume();
181
- const state = bucket.toState();
182
-
183
- // Persist updated state
184
- await this.storage.putRateLimit({
185
- did,
186
- tokens: state.tokens,
187
- lastRefill: state.lastRefill,
188
- totalRequests: 0,
189
- firstSeen: Date.now(),
190
- });
191
-
192
- return {
193
- allowed,
194
- remaining: bucket.getRemaining(),
195
- resetTime: bucket.getResetTime(),
196
- limit: tierConfig.capacity,
197
- };
198
- }
199
-
200
- // ─── Seen Cache (deduplication) ───────────────────────────────────────────
201
-
202
- hasSeen(messageId: string): boolean {
203
- return this.seenCache.has(messageId);
204
- }
205
-
206
- markAsSeen(messageId: string): void {
207
- // Evict oldest entries if at capacity
208
- if (this.seenCache.size >= this.MAX_SEEN_CACHE) {
209
- const firstKey = this.seenCache.keys().next().value;
210
- if (firstKey) this.seenCache.delete(firstKey);
211
- }
212
- this.seenCache.set(messageId, Date.now());
213
-
214
- // Persist to LevelDB (fire-and-forget)
215
- this.storage.putSeen({ messageId, seenAt: Date.now(), fromDid: '' }).catch(() => {});
216
- }
217
-
218
- /** Periodic cleanup of expired seen entries */
219
- async cleanupSeen(): Promise<void> {
220
- const cutoff = Date.now() - this.seenTtlMs;
221
- for (const [id, seenAt] of this.seenCache) {
222
- if (seenAt < cutoff) this.seenCache.delete(id);
223
- }
224
- await this.storage.cleanupSeen(this.seenTtlMs);
225
- }
226
-
227
- /** Periodic cleanup of stale rate limit buckets (24h inactive) */
228
- async cleanupRateLimits(): Promise<void> {
229
- const staleMs = 24 * 60 * 60 * 1000;
230
- const cutoff = Date.now() - staleMs;
231
- for (const [did, bucket] of this.buckets) {
232
- if (bucket.toState().lastRefill < cutoff) this.buckets.delete(did);
233
- }
234
- await this.storage.cleanupRateLimits(staleMs);
235
- }
236
- }
@@ -1,107 +0,0 @@
1
- import { MessagingError } from '../utils/errors.js';
2
-
3
- export interface MessageEnvelope {
4
- id: string;
5
- from: string; // DID
6
- to: string; // DID
7
- type: 'request' | 'response' | 'notification';
8
- protocol: string;
9
- payload: unknown;
10
- timestamp: number;
11
- signature: string;
12
- replyTo?: string; // For responses, the ID of the request
13
- }
14
-
15
- /**
16
- * Create a message envelope
17
- */
18
- export function createEnvelope(
19
- from: string,
20
- to: string,
21
- type: 'request' | 'response' | 'notification',
22
- protocol: string,
23
- payload: unknown,
24
- replyTo?: string
25
- ): Omit<MessageEnvelope, 'signature'> {
26
- return {
27
- id: generateMessageId(),
28
- from,
29
- to,
30
- type,
31
- protocol,
32
- payload,
33
- timestamp: Date.now(),
34
- replyTo,
35
- };
36
- }
37
-
38
- /**
39
- * Sign a message envelope
40
- */
41
- export async function signEnvelope(
42
- envelope: Omit<MessageEnvelope, 'signature'>,
43
- signFn: (data: Uint8Array) => Promise<Uint8Array>
44
- ): Promise<MessageEnvelope> {
45
- try {
46
- const envelopeJson = JSON.stringify(envelope);
47
- const envelopeBytes = new TextEncoder().encode(envelopeJson);
48
- const signature = await signFn(envelopeBytes);
49
-
50
- return {
51
- ...envelope,
52
- signature: Buffer.from(signature).toString('hex'),
53
- };
54
- } catch (error) {
55
- throw new MessagingError('Failed to sign envelope', error);
56
- }
57
- }
58
-
59
- /**
60
- * Verify a message envelope signature
61
- */
62
- export async function verifyEnvelope(
63
- envelope: MessageEnvelope,
64
- verifyFn: (signature: Uint8Array, data: Uint8Array) => Promise<boolean>
65
- ): Promise<boolean> {
66
- try {
67
- const { signature, ...envelopeWithoutSig } = envelope;
68
- const envelopeJson = JSON.stringify(envelopeWithoutSig);
69
- const envelopeBytes = new TextEncoder().encode(envelopeJson);
70
- const signatureBytes = Buffer.from(signature, 'hex');
71
-
72
- return await verifyFn(signatureBytes, envelopeBytes);
73
- } catch (error) {
74
- throw new MessagingError('Failed to verify envelope', error);
75
- }
76
- }
77
-
78
- /**
79
- * Validate message envelope structure
80
- */
81
- export function validateEnvelope(msg: unknown): msg is MessageEnvelope {
82
- if (typeof msg !== 'object' || msg === null) {
83
- return false;
84
- }
85
-
86
- const m = msg as Partial<MessageEnvelope>;
87
-
88
- return (
89
- typeof m.id === 'string' &&
90
- typeof m.from === 'string' &&
91
- m.from.startsWith('did:clawiverse:') &&
92
- typeof m.to === 'string' &&
93
- m.to.startsWith('did:clawiverse:') &&
94
- (m.type === 'request' || m.type === 'response' || m.type === 'notification') &&
95
- typeof m.protocol === 'string' &&
96
- m.payload !== undefined &&
97
- typeof m.timestamp === 'number' &&
98
- typeof m.signature === 'string'
99
- );
100
- }
101
-
102
- /**
103
- * Generate a unique message ID
104
- */
105
- function generateMessageId(): string {
106
- return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
107
- }
@@ -1,8 +0,0 @@
1
- export * from './envelope.js';
2
- export * from './codec.js';
3
- export * from './router.js';
4
- export * from './types.js';
5
- export * from './storage.js';
6
- export * from './queue.js';
7
- export * from './defense.js';
8
- export * from './rate-limiter.js';
@@ -1,181 +0,0 @@
1
- /**
2
- * Message Queue
3
- *
4
- * Persistent inbox/outbox backed by LevelDB.
5
- * Supports real-time subscriptions and pagination.
6
- */
7
-
8
- import { createLogger } from '../utils/logger.js';
9
- import type { MessageEnvelope } from './envelope.js';
10
- import { MessageStorage } from './storage.js';
11
- import type {
12
- StoredMessage,
13
- MessageFilter,
14
- PaginationOptions,
15
- MessagePage,
16
- MessageCallback,
17
- SubscriptionFilter,
18
- QueueStats,
19
- } from './types.js';
20
-
21
- const logger = createLogger('message-queue');
22
-
23
- export interface MessageQueueConfig {
24
- dbPath: string;
25
- }
26
-
27
- interface Subscription {
28
- id: string;
29
- filter: SubscriptionFilter;
30
- callback: MessageCallback;
31
- }
32
-
33
- export class MessageQueue {
34
- private storage: MessageStorage;
35
- private subscriptions = new Map<string, Subscription>();
36
- private subCounter = 0;
37
-
38
- constructor(config: MessageQueueConfig) {
39
- this.storage = new MessageStorage(config.dbPath);
40
- }
41
-
42
- get store(): MessageStorage {
43
- return this.storage;
44
- }
45
-
46
- async start(): Promise<void> {
47
- await this.storage.open();
48
- logger.info('Message queue started');
49
- }
50
-
51
- async stop(): Promise<void> {
52
- await this.storage.close();
53
- this.subscriptions.clear();
54
- logger.info('Message queue stopped');
55
- }
56
-
57
- // ─── Inbox ────────────────────────────────────────────────────────────────
58
-
59
- async getInbox(filter: MessageFilter = {}, pagination: PaginationOptions = {}): Promise<MessagePage> {
60
- return this.storage.queryMessages('inbound', filter, pagination);
61
- }
62
-
63
- async getMessage(id: string): Promise<StoredMessage | null> {
64
- return this.storage.getMessage(id);
65
- }
66
-
67
- async markAsRead(id: string): Promise<void> {
68
- await this.storage.updateMessage(id, { readAt: Date.now() });
69
- }
70
-
71
- async deleteMessage(id: string): Promise<void> {
72
- await this.storage.deleteMessage(id);
73
- }
74
-
75
- // ─── Outbox ───────────────────────────────────────────────────────────────
76
-
77
- async getOutbox(pagination: PaginationOptions = {}): Promise<MessagePage> {
78
- return this.storage.queryMessages('outbound', {}, pagination);
79
- }
80
-
81
- async retryMessage(id: string): Promise<void> {
82
- await this.storage.updateMessage(id, { status: 'pending', error: undefined });
83
- }
84
-
85
- // ─── Enqueue ──────────────────────────────────────────────────────────────
86
-
87
- async enqueueInbound(envelope: MessageEnvelope, trustScore?: number): Promise<StoredMessage> {
88
- const msg: StoredMessage = {
89
- envelope,
90
- direction: 'inbound',
91
- status: 'pending',
92
- receivedAt: Date.now(),
93
- trustScore,
94
- };
95
- await this.storage.putMessage(msg);
96
- logger.debug('Enqueued inbound message', { id: envelope.id, from: envelope.from });
97
- this.notifySubscribers(msg);
98
- return msg;
99
- }
100
-
101
- async enqueueOutbound(envelope: MessageEnvelope): Promise<StoredMessage> {
102
- const msg: StoredMessage = {
103
- envelope,
104
- direction: 'outbound',
105
- status: 'pending',
106
- sentAt: Date.now(),
107
- };
108
- await this.storage.putMessage(msg);
109
- logger.debug('Enqueued outbound message', { id: envelope.id, to: envelope.to });
110
- return msg;
111
- }
112
-
113
- async markOutboundDelivered(id: string): Promise<void> {
114
- await this.storage.updateMessage(id, { status: 'delivered' });
115
- }
116
-
117
- async markOutboundFailed(id: string, error: string): Promise<void> {
118
- await this.storage.updateMessage(id, { status: 'failed', error });
119
- }
120
-
121
- // ─── Subscriptions ────────────────────────────────────────────────────────
122
-
123
- subscribe(filter: SubscriptionFilter, callback: MessageCallback): string {
124
- const id = `sub_${++this.subCounter}`;
125
- this.subscriptions.set(id, { id, filter, callback });
126
- logger.debug('Subscription added', { id });
127
- return id;
128
- }
129
-
130
- unsubscribe(subscriptionId: string): void {
131
- this.subscriptions.delete(subscriptionId);
132
- logger.debug('Subscription removed', { id: subscriptionId });
133
- }
134
-
135
- private notifySubscribers(msg: StoredMessage): void {
136
- for (const sub of this.subscriptions.values()) {
137
- if (this.matchesSubscriptionFilter(msg, sub.filter)) {
138
- Promise.resolve(sub.callback(msg)).catch((err) => {
139
- logger.warn('Subscription callback error', { id: sub.id, error: (err as Error).message });
140
- });
141
- }
142
- }
143
- }
144
-
145
- private matchesSubscriptionFilter(msg: StoredMessage, filter: SubscriptionFilter): boolean {
146
- if (filter.fromDid) {
147
- const froms = Array.isArray(filter.fromDid) ? filter.fromDid : [filter.fromDid];
148
- if (!froms.includes(msg.envelope.from)) return false;
149
- }
150
- if (filter.protocol) {
151
- const protos = Array.isArray(filter.protocol) ? filter.protocol : [filter.protocol];
152
- if (!protos.includes(msg.envelope.protocol)) return false;
153
- }
154
- if (filter.type && msg.envelope.type !== filter.type) return false;
155
- return true;
156
- }
157
-
158
- // ─── Stats ────────────────────────────────────────────────────────────────
159
-
160
- async getStats(): Promise<QueueStats> {
161
- const [inboxTotal, inboxUnread, outboxPending, outboxFailed, blocked, allowed] =
162
- await Promise.all([
163
- this.storage.countMessages('inbound'),
164
- this.storage.countMessages('inbound', { unreadOnly: true }),
165
- this.storage.countMessages('outbound', { status: 'pending' }),
166
- this.storage.countMessages('outbound', { status: 'failed' }),
167
- this.storage.listBlocked().then((l) => l.length),
168
- this.storage.listAllowed().then((l) => l.length),
169
- ]);
170
-
171
- return {
172
- inboxTotal,
173
- inboxUnread,
174
- outboxPending,
175
- outboxFailed,
176
- blockedAgents: blocked,
177
- allowedAgents: allowed,
178
- rateLimitedAgents: 0,
179
- };
180
- }
181
- }