@rookdaemon/agora 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ import {
46
46
  validateVerificationRecord,
47
47
  verifyReveal,
48
48
  verifyVerificationSignature
49
- } from "./chunk-MGIGMVXH.js";
49
+ } from "./chunk-3PEGG7D3.js";
50
50
  import {
51
51
  MessageBuffer,
52
52
  createRestRouter,
@@ -69,6 +69,80 @@ import {
69
69
  verifySignature
70
70
  } from "./chunk-HGXMAZZI.js";
71
71
 
72
+ // src/identity/seen-keys.ts
73
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
74
+ import { dirname, join } from "path";
75
+ var SEEN_KEYS_FILE_NAME = "seen-keys.json";
76
+ function getSeenKeysPath(storageDir) {
77
+ if (storageDir) {
78
+ return join(storageDir, SEEN_KEYS_FILE_NAME);
79
+ }
80
+ const configPath = getDefaultConfigPath();
81
+ return join(dirname(configPath), SEEN_KEYS_FILE_NAME);
82
+ }
83
+ var SeenKeyStore = class {
84
+ constructor(filePath) {
85
+ this.filePath = filePath;
86
+ this.load();
87
+ }
88
+ keys = /* @__PURE__ */ new Map();
89
+ dirty = false;
90
+ /**
91
+ * Record a public key sighting. Adds or updates the entry.
92
+ */
93
+ record(publicKey) {
94
+ const now = Date.now();
95
+ const existing = this.keys.get(publicKey);
96
+ if (existing) {
97
+ existing.lastSeen = now;
98
+ existing.seenCount++;
99
+ } else {
100
+ this.keys.set(publicKey, {
101
+ publicKey,
102
+ firstSeen: now,
103
+ lastSeen: now,
104
+ seenCount: 1
105
+ });
106
+ }
107
+ this.dirty = true;
108
+ }
109
+ has(publicKey) {
110
+ return this.keys.has(publicKey);
111
+ }
112
+ get(publicKey) {
113
+ return this.keys.get(publicKey);
114
+ }
115
+ getAll() {
116
+ return Array.from(this.keys.values());
117
+ }
118
+ /**
119
+ * Flush changes to disk. Call periodically or on shutdown.
120
+ */
121
+ flush() {
122
+ if (!this.dirty) return;
123
+ const dir = dirname(this.filePath);
124
+ if (!existsSync(dir)) {
125
+ mkdirSync(dir, { recursive: true });
126
+ }
127
+ const entries = Array.from(this.keys.values());
128
+ writeFileSync(this.filePath, JSON.stringify(entries, null, 2), "utf-8");
129
+ this.dirty = false;
130
+ }
131
+ load() {
132
+ if (!existsSync(this.filePath)) return;
133
+ try {
134
+ const raw = readFileSync(this.filePath, "utf-8");
135
+ const entries = JSON.parse(raw);
136
+ for (const entry of entries) {
137
+ if (typeof entry.publicKey === "string" && entry.publicKey.length > 0) {
138
+ this.keys.set(entry.publicKey, entry);
139
+ }
140
+ }
141
+ } catch {
142
+ }
143
+ }
144
+ };
145
+
72
146
  // src/registry/capability.ts
73
147
  import { createHash } from "crypto";
74
148
  function stableStringify(value) {
@@ -438,27 +512,27 @@ function validatePeerReferral(payload) {
438
512
  }
439
513
 
440
514
  // src/relay/ignored-peers.ts
441
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
442
- import { join, dirname } from "path";
515
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
516
+ import { join as join2, dirname as dirname2 } from "path";
443
517
  var IGNORED_FILE_NAME = "IGNORED_PEERS.md";
444
518
  function getIgnoredPeersPath(storageDir) {
445
519
  if (storageDir) {
446
- return join(storageDir, IGNORED_FILE_NAME);
520
+ return join2(storageDir, IGNORED_FILE_NAME);
447
521
  }
448
522
  const configPath = getDefaultConfigPath();
449
- return join(dirname(configPath), IGNORED_FILE_NAME);
523
+ return join2(dirname2(configPath), IGNORED_FILE_NAME);
450
524
  }
451
525
  function loadIgnoredPeers(filePath) {
452
526
  const path = filePath ?? getIgnoredPeersPath();
453
- if (!existsSync(path)) return [];
454
- const lines = readFileSync(path, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
527
+ if (!existsSync2(path)) return [];
528
+ const lines = readFileSync2(path, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
455
529
  return Array.from(new Set(lines));
456
530
  }
457
531
  function saveIgnoredPeers(peers, filePath) {
458
532
  const path = filePath ?? getIgnoredPeersPath();
459
- const dir = dirname(path);
460
- if (!existsSync(dir)) {
461
- mkdirSync(dir, { recursive: true });
533
+ const dir = dirname2(path);
534
+ if (!existsSync2(dir)) {
535
+ mkdirSync2(dir, { recursive: true });
462
536
  }
463
537
  const unique = Array.from(new Set(peers.map((peer) => peer.trim()).filter(Boolean))).sort();
464
538
  const content = [
@@ -467,7 +541,7 @@ function saveIgnoredPeers(peers, filePath) {
467
541
  ...unique,
468
542
  ""
469
543
  ].join("\n");
470
- writeFileSync(path, content, "utf-8");
544
+ writeFileSync2(path, content, "utf-8");
471
545
  }
472
546
  var IgnoredPeersManager = class {
473
547
  peers;
@@ -854,8 +928,8 @@ var AgoraService = class {
854
928
  this.relayClient.on("error", (error) => {
855
929
  this.logger?.debug(`Agora relay error: ${error.message}`);
856
930
  });
857
- this.relayClient.on("message", (envelope, from, fromName) => {
858
- this.onRelayMessage(envelope, from, fromName);
931
+ this.relayClient.on("message", (envelope, from) => {
932
+ this.onRelayMessage(envelope, from);
859
933
  });
860
934
  try {
861
935
  await this.relayClient.connect();
@@ -977,6 +1051,8 @@ export {
977
1051
  RelayClient,
978
1052
  RelayServer,
979
1053
  ReputationStore,
1054
+ SEEN_KEYS_FILE_NAME,
1055
+ SeenKeyStore,
980
1056
  canonicalize,
981
1057
  compactInlineReferences,
982
1058
  compactKnownInlineReferences,
@@ -1006,6 +1082,7 @@ export {
1006
1082
  getDefaultConfigPath,
1007
1083
  getIgnoredPeersPath,
1008
1084
  getProfileConfigPath,
1085
+ getSeenKeysPath,
1009
1086
  handleReputationQuery,
1010
1087
  hashPrediction,
1011
1088
  importConfig,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry/capability.ts","../src/registry/peer-store.ts","../src/registry/discovery-service.ts","../src/message/types/peer-discovery.ts","../src/relay/ignored-peers.ts","../src/relay/inbound-message-guard.ts","../src/service.ts","../src/reputation/network.ts","../src/reputation/sync.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\r\n\r\n/**\r\n * A capability describes something an agent can do\r\n */\r\nexport interface Capability {\r\n /** Unique ID (content-addressed hash of name + version + schema) */\r\n id: string;\r\n /** Human-readable name: 'code-review', 'summarization', 'translation' */\r\n name: string;\r\n /** Semantic version */\r\n version: string;\r\n /** What the capability does */\r\n description: string;\r\n /** JSON Schema for expected input */\r\n inputSchema?: object;\r\n /** JSON Schema for expected output */\r\n outputSchema?: object;\r\n /** Discovery tags: ['code', 'typescript', 'review'] */\r\n tags: string[];\r\n}\r\n\r\n/**\r\n * Deterministic JSON serialization for capability hashing.\r\n * Recursively sorts object keys.\r\n */\r\nfunction stableStringify(value: unknown): string {\r\n if (value === null || value === undefined) return JSON.stringify(value);\r\n if (typeof value !== 'object') return JSON.stringify(value);\r\n if (Array.isArray(value)) {\r\n return '[' + value.map(stableStringify).join(',') + ']';\r\n }\r\n const keys = Object.keys(value as Record<string, unknown>).sort();\r\n const pairs = keys.map(k => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k]));\r\n return '{' + pairs.join(',') + '}';\r\n}\r\n\r\n/**\r\n * Compute content-addressed ID for a capability based on name, version, and schemas.\r\n */\r\nfunction computeCapabilityId(name: string, version: string, inputSchema?: object, outputSchema?: object): string {\r\n const data = {\r\n name,\r\n version,\r\n ...(inputSchema !== undefined ? { inputSchema } : {}),\r\n ...(outputSchema !== undefined ? { outputSchema } : {}),\r\n };\r\n const canonical = stableStringify(data);\r\n return createHash('sha256').update(canonical).digest('hex');\r\n}\r\n\r\n/**\r\n * Creates a capability with a content-addressed ID.\r\n * \r\n * @param name - Human-readable capability name\r\n * @param version - Semantic version string\r\n * @param description - Description of what the capability does\r\n * @param options - Optional input/output schemas and tags\r\n * @returns A Capability object with computed ID\r\n */\r\nexport function createCapability(\r\n name: string,\r\n version: string,\r\n description: string,\r\n options: {\r\n inputSchema?: object;\r\n outputSchema?: object;\r\n tags?: string[];\r\n } = {}\r\n): Capability {\r\n const { inputSchema, outputSchema, tags = [] } = options;\r\n \r\n const id = computeCapabilityId(name, version, inputSchema, outputSchema);\r\n \r\n return {\r\n id,\r\n name,\r\n version,\r\n description,\r\n ...(inputSchema !== undefined ? { inputSchema } : {}),\r\n ...(outputSchema !== undefined ? { outputSchema } : {}),\r\n tags,\r\n };\r\n}\r\n\r\n/**\r\n * Validates that a capability has all required fields.\r\n * \r\n * @param capability - The capability to validate\r\n * @returns Object with `valid` boolean and optional `errors` array\r\n */\r\nexport function validateCapability(capability: unknown): { valid: boolean; errors?: string[] } {\r\n const errors: string[] = [];\r\n \r\n if (!capability || typeof capability !== 'object') {\r\n return { valid: false, errors: ['Capability must be an object'] };\r\n }\r\n \r\n const cap = capability as Record<string, unknown>;\r\n \r\n if (!cap.id || typeof cap.id !== 'string') {\r\n errors.push('Missing or invalid field: id (must be a string)');\r\n }\r\n \r\n if (!cap.name || typeof cap.name !== 'string') {\r\n errors.push('Missing or invalid field: name (must be a string)');\r\n }\r\n \r\n if (!cap.version || typeof cap.version !== 'string') {\r\n errors.push('Missing or invalid field: version (must be a string)');\r\n }\r\n \r\n if (!cap.description || typeof cap.description !== 'string') {\r\n errors.push('Missing or invalid field: description (must be a string)');\r\n }\r\n \r\n if (!Array.isArray(cap.tags)) {\r\n errors.push('Missing or invalid field: tags (must be an array)');\r\n } else if (!cap.tags.every(tag => typeof tag === 'string')) {\r\n errors.push('Invalid field: tags (all elements must be strings)');\r\n }\r\n \r\n if (cap.inputSchema !== undefined && (typeof cap.inputSchema !== 'object' || cap.inputSchema === null)) {\r\n errors.push('Invalid field: inputSchema (must be an object)');\r\n }\r\n \r\n if (cap.outputSchema !== undefined && (typeof cap.outputSchema !== 'object' || cap.outputSchema === null)) {\r\n errors.push('Invalid field: outputSchema (must be an object)');\r\n }\r\n \r\n if (errors.length > 0) {\r\n return { valid: false, errors };\r\n }\r\n \r\n return { valid: true };\r\n}\r\n","import type { Peer } from './peer';\n\n/**\n * In-memory store for known peers on the network\n */\nexport class PeerStore {\n private peers: Map<string, Peer> = new Map();\n\n /**\n * Add or update a peer in the store.\n * If a peer with the same publicKey exists, it will be updated.\n * \n * @param peer - The peer to add or update\n */\n addOrUpdatePeer(peer: Peer): void {\n this.peers.set(peer.publicKey, peer);\n }\n\n /**\n * Remove a peer from the store.\n * \n * @param publicKey - The public key of the peer to remove\n * @returns true if the peer was removed, false if it didn't exist\n */\n removePeer(publicKey: string): boolean {\n return this.peers.delete(publicKey);\n }\n\n /**\n * Get a peer by their public key.\n * \n * @param publicKey - The public key of the peer to retrieve\n * @returns The peer if found, undefined otherwise\n */\n getPeer(publicKey: string): Peer | undefined {\n return this.peers.get(publicKey);\n }\n\n /**\n * Find all peers that offer a specific capability by name.\n * \n * @param name - The capability name to search for\n * @returns Array of peers that have a capability with the given name\n */\n findByCapability(name: string): Peer[] {\n const result: Peer[] = [];\n \n for (const peer of this.peers.values()) {\n const hasCapability = peer.capabilities.some(cap => cap.name === name);\n if (hasCapability) {\n result.push(peer);\n }\n }\n \n return result;\n }\n\n /**\n * Find all peers that have capabilities with a specific tag.\n * \n * @param tag - The tag to search for\n * @returns Array of peers that have at least one capability with the given tag\n */\n findByTag(tag: string): Peer[] {\n const result: Peer[] = [];\n \n for (const peer of this.peers.values()) {\n const hasTag = peer.capabilities.some(cap => cap.tags.includes(tag));\n if (hasTag) {\n result.push(peer);\n }\n }\n \n return result;\n }\n\n /**\n * Get all peers in the store.\n * \n * @returns Array of all peers\n */\n allPeers(): Peer[] {\n return Array.from(this.peers.values());\n }\n\n /**\n * Remove peers that haven't been seen within the specified time window.\n * \n * @param maxAgeMs - Maximum age in milliseconds. Peers older than this will be removed.\n * @param currentTime - Current timestamp (ms), defaults to Date.now()\n * @returns Number of peers removed\n */\n prune(maxAgeMs: number, currentTime: number = Date.now()): number {\n const cutoff = currentTime - maxAgeMs;\n let removed = 0;\n \n for (const [publicKey, peer] of this.peers.entries()) {\n if (peer.lastSeen < cutoff) {\n this.peers.delete(publicKey);\n removed++;\n }\n }\n \n return removed;\n }\n}\n","import { createEnvelope, type Envelope } from '../message/envelope';\nimport { PeerStore } from './peer-store';\nimport type { Capability } from './capability';\nimport type { Peer } from './peer';\nimport type {\n CapabilityAnnouncePayload,\n CapabilityQueryPayload,\n CapabilityResponsePayload,\n} from './messages';\n\n/**\n * DiscoveryService manages capability-based peer discovery.\n * It maintains a local index of peer capabilities and handles\n * capability announce, query, and response messages.\n */\nexport class DiscoveryService {\n constructor(\n private peerStore: PeerStore,\n private identity: { publicKey: string; privateKey: string }\n ) {}\n\n /**\n * Announce own capabilities to the network.\n * Creates a capability_announce envelope that can be broadcast to peers.\n * \n * @param capabilities - List of capabilities this agent offers\n * @param metadata - Optional metadata about this agent\n * @returns A signed capability_announce envelope\n */\n announce(\n capabilities: Capability[],\n metadata?: { name?: string; version?: string },\n recipients: string[] = [this.identity.publicKey]\n ): Envelope<CapabilityAnnouncePayload> {\n const payload: CapabilityAnnouncePayload = {\n publicKey: this.identity.publicKey,\n capabilities,\n metadata: metadata ? {\n ...metadata,\n lastSeen: Date.now(),\n } : {\n lastSeen: Date.now(),\n },\n };\n\n return createEnvelope(\n 'capability_announce',\n this.identity.publicKey,\n this.identity.privateKey,\n payload,\n Date.now(),\n undefined,\n recipients\n );\n }\n\n /**\n * Handle an incoming capability_announce message.\n * Updates the peer store with the announced capabilities.\n * \n * @param envelope - The capability_announce envelope to process\n */\n handleAnnounce(envelope: Envelope<CapabilityAnnouncePayload>): void {\n const { payload } = envelope;\n \n const peer: Peer = {\n publicKey: payload.publicKey,\n capabilities: payload.capabilities,\n lastSeen: payload.metadata?.lastSeen || envelope.timestamp,\n metadata: payload.metadata ? {\n name: payload.metadata.name,\n version: payload.metadata.version,\n } : undefined,\n };\n\n this.peerStore.addOrUpdatePeer(peer);\n }\n\n /**\n * Create a capability query payload.\n * \n * @param queryType - Type of query: 'name', 'tag', or 'schema'\n * @param query - The query value (capability name, tag, or schema)\n * @param filters - Optional filters (limit, minTrustScore)\n * @returns A capability_query payload\n */\n query(\n queryType: 'name' | 'tag' | 'schema',\n query: string | object,\n filters?: { limit?: number; minTrustScore?: number }\n ): CapabilityQueryPayload {\n return {\n queryType,\n query,\n filters,\n };\n }\n\n /**\n * Handle an incoming capability_query message.\n * Searches the local peer store and returns matching peers.\n * \n * @param envelope - The capability_query envelope to process\n * @returns A capability_response envelope with matching peers\n */\n handleQuery(\n envelope: Envelope<CapabilityQueryPayload>\n ): Envelope<CapabilityResponsePayload> {\n const { payload } = envelope;\n let peers: Peer[] = [];\n\n // Execute query based on type\n if (payload.queryType === 'name' && typeof payload.query === 'string') {\n peers = this.peerStore.findByCapability(payload.query);\n } else if (payload.queryType === 'tag' && typeof payload.query === 'string') {\n peers = this.peerStore.findByTag(payload.query);\n } else if (payload.queryType === 'schema') {\n // Schema-based matching is deferred to Phase 2b\n // For now, return empty results\n peers = [];\n }\n\n // Apply filters\n const limit = payload.filters?.limit;\n const totalMatches = peers.length;\n \n if (limit !== undefined && limit > 0) {\n peers = peers.slice(0, limit);\n }\n\n // Transform peers to response format\n const responsePeers = peers.map(peer => ({\n publicKey: peer.publicKey,\n capabilities: peer.capabilities,\n metadata: peer.metadata ? {\n name: peer.metadata.name,\n version: peer.metadata.version,\n lastSeen: peer.lastSeen,\n } : {\n lastSeen: peer.lastSeen,\n },\n // Trust score integration deferred to Phase 2b (RFC-001)\n trustScore: undefined,\n }));\n\n const responsePayload: CapabilityResponsePayload = {\n queryId: envelope.id,\n peers: responsePeers,\n totalMatches,\n };\n\n return createEnvelope(\n 'capability_response',\n this.identity.publicKey,\n this.identity.privateKey,\n responsePayload,\n Date.now(),\n envelope.id, // inReplyTo\n [envelope.from]\n );\n }\n\n /**\n * Remove peers that haven't been seen within the specified time window.\n * \n * @param maxAgeMs - Maximum age in milliseconds\n * @returns Number of peers removed\n */\n pruneStale(maxAgeMs: number, currentTime: number = Date.now()): number {\n return this.peerStore.prune(maxAgeMs, currentTime);\n }\n}\n","/**\r\n * Peer discovery message types for the Agora network.\r\n */\r\n\r\n/**\r\n * Request peer list from relay\r\n */\r\nexport interface PeerListRequestPayload {\r\n /** Optional filters */\r\n filters?: {\r\n /** Only peers seen in last N ms */\r\n activeWithin?: number;\r\n /** Maximum peers to return */\r\n limit?: number;\r\n };\r\n}\r\n\r\n/**\r\n * Relay responds with connected peers\r\n */\r\nexport interface PeerListResponsePayload {\r\n /** List of known peers */\r\n peers: Array<{\r\n /** Peer's Ed25519 public key */\r\n publicKey: string;\r\n /** Optional metadata (if peer announced) */\r\n metadata?: {\r\n name?: string;\r\n version?: string;\r\n capabilities?: string[];\r\n };\r\n /** Last seen timestamp (ms) */\r\n lastSeen: number;\r\n }>;\r\n /** Total peer count (may be > peers.length if limited) */\r\n totalPeers: number;\r\n /** Relay's public key (for trust verification) */\r\n relayPublicKey: string;\r\n}\r\n\r\n/**\r\n * Agent recommends another agent\r\n */\r\nexport interface PeerReferralPayload {\r\n /** Referred peer's public key */\r\n publicKey: string;\r\n /** Optional endpoint (if known) */\r\n endpoint?: string;\r\n /** Optional metadata */\r\n metadata?: {\r\n name?: string;\r\n version?: string;\r\n capabilities?: string[];\r\n };\r\n /** Referrer's comment */\r\n comment?: string;\r\n /** Trust hint (RFC-001 integration) */\r\n trustScore?: number;\r\n}\r\n\r\n/**\r\n * Validate PeerListRequestPayload\r\n */\r\nexport function validatePeerListRequest(payload: unknown): { valid: boolean; errors: string[] } {\r\n const errors: string[] = [];\r\n\r\n if (typeof payload !== 'object' || payload === null) {\r\n errors.push('Payload must be an object');\r\n return { valid: false, errors };\r\n }\r\n\r\n const p = payload as Record<string, unknown>;\r\n\r\n if (p.filters !== undefined) {\r\n if (typeof p.filters !== 'object' || p.filters === null) {\r\n errors.push('filters must be an object');\r\n } else {\r\n const filters = p.filters as Record<string, unknown>;\r\n if (filters.activeWithin !== undefined && typeof filters.activeWithin !== 'number') {\r\n errors.push('filters.activeWithin must be a number');\r\n }\r\n if (filters.limit !== undefined && typeof filters.limit !== 'number') {\r\n errors.push('filters.limit must be a number');\r\n }\r\n }\r\n }\r\n\r\n return { valid: errors.length === 0, errors };\r\n}\r\n\r\n/**\r\n * Validate PeerListResponsePayload\r\n */\r\nexport function validatePeerListResponse(payload: unknown): { valid: boolean; errors: string[] } {\r\n const errors: string[] = [];\r\n\r\n if (typeof payload !== 'object' || payload === null) {\r\n errors.push('Payload must be an object');\r\n return { valid: false, errors };\r\n }\r\n\r\n const p = payload as Record<string, unknown>;\r\n\r\n if (!Array.isArray(p.peers)) {\r\n errors.push('peers must be an array');\r\n } else {\r\n p.peers.forEach((peer, index) => {\r\n if (typeof peer !== 'object' || peer === null) {\r\n errors.push(`peers[${index}] must be an object`);\r\n return;\r\n }\r\n const peerObj = peer as Record<string, unknown>;\r\n if (typeof peerObj.publicKey !== 'string') {\r\n errors.push(`peers[${index}].publicKey must be a string`);\r\n }\r\n if (typeof peerObj.lastSeen !== 'number') {\r\n errors.push(`peers[${index}].lastSeen must be a number`);\r\n }\r\n });\r\n }\r\n\r\n if (typeof p.totalPeers !== 'number') {\r\n errors.push('totalPeers must be a number');\r\n }\r\n\r\n if (typeof p.relayPublicKey !== 'string') {\r\n errors.push('relayPublicKey must be a string');\r\n }\r\n\r\n return { valid: errors.length === 0, errors };\r\n}\r\n\r\n/**\r\n * Validate PeerReferralPayload\r\n */\r\nexport function validatePeerReferral(payload: unknown): { valid: boolean; errors: string[] } {\r\n const errors: string[] = [];\r\n\r\n if (typeof payload !== 'object' || payload === null) {\r\n errors.push('Payload must be an object');\r\n return { valid: false, errors };\r\n }\r\n\r\n const p = payload as Record<string, unknown>;\r\n\r\n if (typeof p.publicKey !== 'string') {\r\n errors.push('publicKey must be a string');\r\n }\r\n\r\n if (p.endpoint !== undefined && typeof p.endpoint !== 'string') {\r\n errors.push('endpoint must be a string');\r\n }\r\n\r\n if (p.comment !== undefined && typeof p.comment !== 'string') {\r\n errors.push('comment must be a string');\r\n }\r\n\r\n if (p.trustScore !== undefined && typeof p.trustScore !== 'number') {\r\n errors.push('trustScore must be a number');\r\n }\r\n\r\n return { valid: errors.length === 0, errors };\r\n}\r\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\r\nimport { join, dirname } from 'node:path';\r\nimport { getDefaultConfigPath } from '../config';\r\n\r\nexport const IGNORED_FILE_NAME = 'IGNORED_PEERS.md';\r\n\r\nexport function getIgnoredPeersPath(storageDir?: string): string {\r\n if (storageDir) {\r\n return join(storageDir, IGNORED_FILE_NAME);\r\n }\r\n const configPath = getDefaultConfigPath();\r\n return join(dirname(configPath), IGNORED_FILE_NAME);\r\n}\r\n\r\nexport function loadIgnoredPeers(filePath?: string): string[] {\r\n const path = filePath ?? getIgnoredPeersPath();\r\n if (!existsSync(path)) return [];\r\n\r\n const lines = readFileSync(path, 'utf-8')\r\n .split('\\n')\r\n .map((line) => line.trim())\r\n .filter((line) => line.length > 0 && !line.startsWith('#'));\r\n\r\n return Array.from(new Set(lines));\r\n}\r\n\r\nexport function saveIgnoredPeers(peers: string[], filePath?: string): void {\r\n const path = filePath ?? getIgnoredPeersPath();\r\n const dir = dirname(path);\r\n if (!existsSync(dir)) {\r\n mkdirSync(dir, { recursive: true });\r\n }\r\n\r\n const unique = Array.from(new Set(peers.map((peer) => peer.trim()).filter(Boolean))).sort();\r\n const content = [\r\n '# Ignored peers',\r\n '# One public key per line',\r\n ...unique,\r\n '',\r\n ].join('\\n');\r\n\r\n writeFileSync(path, content, 'utf-8');\r\n}\r\n\r\nexport class IgnoredPeersManager {\r\n private readonly peers: Set<string>;\r\n private readonly filePath: string;\r\n\r\n constructor(filePath?: string) {\r\n this.filePath = filePath ?? getIgnoredPeersPath();\r\n this.peers = new Set(loadIgnoredPeers(this.filePath));\r\n }\r\n\r\n ignorePeer(publicKey: string): boolean {\r\n const normalized = publicKey.trim();\r\n if (!normalized) {\r\n return false;\r\n }\r\n const added = !this.peers.has(normalized);\r\n this.peers.add(normalized);\r\n if (added) {\r\n this.persist();\r\n }\r\n return added;\r\n }\r\n\r\n unignorePeer(publicKey: string): boolean {\r\n const normalized = publicKey.trim();\r\n const removed = this.peers.delete(normalized);\r\n if (removed) {\r\n this.persist();\r\n }\r\n return removed;\r\n }\r\n\r\n listIgnoredPeers(): string[] {\r\n return Array.from(this.peers.values()).sort();\r\n }\r\n\r\n private persist(): void {\r\n saveIgnoredPeers(this.listIgnoredPeers(), this.filePath);\r\n }\r\n}\r\n","import { createHash } from 'node:crypto';\r\nimport type { Envelope } from '../message/envelope';\r\n\r\ninterface SenderWindow {\r\n count: number;\r\n windowStart: number;\r\n}\r\n\r\nexport interface InboundSecurityOptions {\r\n rateLimitEnabled?: boolean;\r\n rateLimitMaxMessages?: number;\r\n rateLimitWindowMs?: number;\r\n envelopeDedupEnabled?: boolean;\r\n envelopeDedupMaxIds?: number;\r\n contentDedupEnabled?: boolean;\r\n contentDedupWindowMs?: number;\r\n ignoredPeers?: string[];\r\n}\r\n\r\n/**\r\n * Shared inbound guard for relay-fed consumers.\r\n *\r\n * Provides:\r\n * - static ignore list checks\r\n * - per-sender rate limiting\r\n * - envelope ID deduplication\r\n * - content-window deduplication\r\n */\r\nexport class InboundMessageGuard {\r\n private readonly senderWindows = new Map<string, SenderWindow>();\r\n private readonly envelopeIds = new Set<string>();\r\n private readonly contentDedup = new Map<string, number>();\r\n private readonly ignoredPeers = new Set<string>();\r\n\r\n private rateLimitEnabled: boolean;\r\n private rateLimitMaxMessages: number;\r\n private rateLimitWindowMs: number;\r\n private envelopeDedupEnabled: boolean;\r\n private envelopeDedupMaxIds: number;\r\n private contentDedupEnabled: boolean;\r\n private contentDedupWindowMs: number;\r\n\r\n private static readonly MAX_SENDER_ENTRIES = 500;\r\n private static readonly MAX_CONTENT_ENTRIES = 5000;\r\n\r\n constructor(options: InboundSecurityOptions = {}) {\r\n this.rateLimitEnabled = options.rateLimitEnabled ?? true;\r\n this.rateLimitMaxMessages = options.rateLimitMaxMessages ?? 10;\r\n this.rateLimitWindowMs = options.rateLimitWindowMs ?? 60_000;\r\n this.envelopeDedupEnabled = options.envelopeDedupEnabled ?? true;\r\n this.envelopeDedupMaxIds = options.envelopeDedupMaxIds ?? 1000;\r\n this.contentDedupEnabled = options.contentDedupEnabled ?? true;\r\n this.contentDedupWindowMs = options.contentDedupWindowMs ?? 1_800_000;\r\n\r\n for (const peer of options.ignoredPeers ?? []) {\r\n const normalized = peer.trim();\r\n if (normalized) {\r\n this.ignoredPeers.add(normalized);\r\n }\r\n }\r\n }\r\n\r\n shouldDrop(envelope: Envelope, senderPublicKey: string): { drop: boolean; reason?: string } {\r\n if (this.ignoredPeers.has(senderPublicKey)) {\r\n return { drop: true, reason: 'ignored_peer' };\r\n }\r\n\r\n if (this.isRateLimited(senderPublicKey)) {\r\n return { drop: true, reason: 'rate_limited' };\r\n }\r\n\r\n if (this.isDuplicateEnvelopeId(envelope.id)) {\r\n return { drop: true, reason: 'duplicate_envelope_id' };\r\n }\r\n\r\n if (this.isDuplicateContent(senderPublicKey, envelope.type, envelope.payload)) {\r\n return { drop: true, reason: 'duplicate_content' };\r\n }\r\n\r\n return { drop: false };\r\n }\r\n\r\n ignorePeer(publicKey: string): boolean {\r\n const normalized = publicKey.trim();\r\n if (!normalized) {\r\n return false;\r\n }\r\n const existed = this.ignoredPeers.has(normalized);\r\n this.ignoredPeers.add(normalized);\r\n return !existed;\r\n }\r\n\r\n unignorePeer(publicKey: string): boolean {\r\n return this.ignoredPeers.delete(publicKey.trim());\r\n }\r\n\r\n listIgnoredPeers(): string[] {\r\n return Array.from(this.ignoredPeers.values()).sort();\r\n }\r\n\r\n private isRateLimited(senderPublicKey: string): boolean {\r\n if (!this.rateLimitEnabled) {\r\n return false;\r\n }\r\n\r\n const now = Date.now();\r\n const current = this.senderWindows.get(senderPublicKey);\r\n\r\n if (!current && this.senderWindows.size >= InboundMessageGuard.MAX_SENDER_ENTRIES) {\r\n this.evictOldestSender();\r\n }\r\n\r\n if (!current || now - current.windowStart > this.rateLimitWindowMs) {\r\n this.senderWindows.set(senderPublicKey, { count: 1, windowStart: now });\r\n return false;\r\n }\r\n\r\n current.count += 1;\r\n return current.count > this.rateLimitMaxMessages;\r\n }\r\n\r\n private evictOldestSender(): void {\r\n let oldestKey: string | null = null;\r\n let oldestTime = Number.POSITIVE_INFINITY;\r\n\r\n for (const [key, value] of this.senderWindows.entries()) {\r\n if (value.windowStart < oldestTime) {\r\n oldestTime = value.windowStart;\r\n oldestKey = key;\r\n }\r\n }\r\n\r\n if (oldestKey) {\r\n this.senderWindows.delete(oldestKey);\r\n }\r\n }\r\n\r\n private isDuplicateEnvelopeId(envelopeId: string): boolean {\r\n if (!this.envelopeDedupEnabled) {\r\n return false;\r\n }\r\n\r\n if (this.envelopeIds.has(envelopeId)) {\r\n return true;\r\n }\r\n\r\n this.envelopeIds.add(envelopeId);\r\n if (this.envelopeIds.size > this.envelopeDedupMaxIds) {\r\n const oldest = this.envelopeIds.values().next().value;\r\n if (oldest !== undefined) {\r\n this.envelopeIds.delete(oldest);\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private isDuplicateContent(senderPublicKey: string, type: string, payload: unknown): boolean {\r\n if (!this.contentDedupEnabled) {\r\n return false;\r\n }\r\n\r\n const hash = createHash('sha256')\r\n .update(senderPublicKey)\r\n .update(type)\r\n .update(JSON.stringify(payload ?? null))\r\n .digest('hex');\r\n\r\n const now = Date.now();\r\n const firstSeen = this.contentDedup.get(hash);\r\n\r\n if (firstSeen !== undefined && now - firstSeen < this.contentDedupWindowMs) {\r\n return true;\r\n }\r\n\r\n this.contentDedup.set(hash, now);\r\n\r\n if (this.contentDedup.size > InboundMessageGuard.MAX_CONTENT_ENTRIES) {\r\n for (const [key, ts] of this.contentDedup.entries()) {\r\n if (now - ts >= this.contentDedupWindowMs) {\r\n this.contentDedup.delete(key);\r\n }\r\n }\r\n if (this.contentDedup.size > InboundMessageGuard.MAX_CONTENT_ENTRIES) {\r\n const oldestKey = this.contentDedup.keys().next().value;\r\n if (oldestKey !== undefined) {\r\n this.contentDedup.delete(oldestKey);\r\n }\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n}\r\n","import type { AgoraIdentity, RelayConfig } from './config';\r\nimport { getDefaultConfigPath, loadAgoraConfigAsync } from './config';\r\nimport type { Envelope } from './message/envelope';\r\nimport type { MessageType } from './message/envelope';\r\nimport { RelayClient } from './relay/client';\r\nimport type { PeerConfig } from './transport/http';\r\nimport { decodeInboundEnvelope, sendToPeer } from './transport/http';\r\nimport { sendViaRelay } from './transport/relay';\r\nimport { shortKey } from './utils';\r\n\r\n/**\r\n * Service config: identity, peers keyed by name, optional relay.\r\n */\r\nexport interface AgoraServiceConfig {\r\n identity: AgoraIdentity;\r\n peers: Map<string, PeerConfig>;\r\n relay?: RelayConfig;\r\n}\r\n\r\nexport interface SendMessageOptions {\r\n peerName: string;\r\n type: MessageType;\r\n payload: unknown;\r\n inReplyTo?: string;\r\n /** Skip relay, send directly via HTTP only. Fails if peer has no URL or is unreachable. */\r\n direct?: boolean;\r\n /** Skip direct HTTP, always use relay even if peer has a URL. */\r\n relayOnly?: boolean;\r\n /** All recipients of this message (for multi-recipient sends). Used in the envelope `to` field. */\r\n allRecipients?: string[];\r\n}\r\n\r\nexport interface SendMessageResult {\r\n ok: boolean;\r\n status: number;\r\n error?: string;\r\n}\r\n\r\nexport interface SendToAllOptions {\r\n /** Recipient identifiers — peer names, public keys, or short refs. */\r\n recipients: string[];\r\n type: MessageType;\r\n payload: unknown;\r\n inReplyTo?: string;\r\n direct?: boolean;\r\n relayOnly?: boolean;\r\n}\r\n\r\nexport interface SendToAllResult {\r\n /** True when at least one recipient succeeded (or recipients is empty). */\r\n ok: boolean;\r\n errors: Array<{ recipient: string; error: string }>;\r\n}\r\n\r\nexport interface ReplyToEnvelopeOptions {\r\n /** The public key of the target (from envelope.sender) */\r\n targetPubkey: string;\r\n /** Message type for the reply */\r\n type: MessageType;\r\n /** Reply payload */\r\n payload: unknown;\r\n /** The envelope ID being replied to (required — this IS a reply) */\r\n inReplyTo: string;\r\n}\r\n\r\nexport interface DecodeInboundResult {\r\n ok: boolean;\r\n envelope?: Envelope;\r\n reason?: string;\r\n}\r\n\r\n/** Handler for relay messages. (envelope, fromPublicKey, fromName?) */\r\nexport type RelayMessageHandlerWithName = (envelope: Envelope, from: string, fromName?: string) => void;\r\n\r\n/** @deprecated Use RelayMessageHandlerWithName. Kept for backward compatibility. */\r\nexport type RelayMessageHandler = (envelope: Envelope) => void;\r\n\r\nexport interface Logger {\r\n debug(message: string): void;\r\n}\r\n\r\nexport interface RelayClientLike {\r\n connect(): Promise<void>;\r\n disconnect(): void;\r\n connected(): boolean;\r\n send(to: string, envelope: Envelope): Promise<{ ok: boolean; error?: string }>;\r\n on(event: 'message', handler: (envelope: Envelope, from: string, fromName?: string) => void): void;\r\n on(event: 'error', handler: (error: Error) => void): void;\r\n}\r\n\r\nexport interface RelayClientFactory {\r\n (opts: {\r\n relayUrl: string;\r\n publicKey: string;\r\n privateKey: string;\r\n name?: string;\r\n pingInterval: number;\r\n maxReconnectDelay: number;\r\n }): RelayClientLike;\r\n}\r\n\r\n/**\r\n * High-level Agora service: send by peer name, decode inbound, relay lifecycle.\r\n */\r\nexport class AgoraService {\r\n private config: AgoraServiceConfig;\r\n private relayClient: RelayClientLike | null = null;\r\n private readonly onRelayMessage: RelayMessageHandlerWithName;\r\n private logger: Logger | null;\r\n private relayClientFactory: RelayClientFactory | null;\r\n\r\n /**\r\n * @param config - Service config (identity, peers, optional relay)\r\n * @param onRelayMessage - Required callback for relay messages. Ensures no messages are lost between init and connect.\r\n * @param logger - Optional debug logger\r\n * @param relayClientFactory - Optional factory for relay client (for testing)\r\n */\r\n constructor(\r\n config: AgoraServiceConfig,\r\n onRelayMessage: RelayMessageHandlerWithName,\r\n logger?: Logger,\r\n relayClientFactory?: RelayClientFactory\r\n ) {\r\n this.config = config;\r\n this.onRelayMessage = onRelayMessage;\r\n this.logger = logger ?? null;\r\n this.relayClientFactory = relayClientFactory ?? null;\r\n }\r\n\r\n private resolvePeer(identifier: string): PeerConfig | undefined {\r\n const direct = this.config.peers.get(identifier);\r\n if (direct) {\r\n return direct;\r\n }\r\n\r\n for (const peer of this.config.peers.values()) {\r\n if (peer.publicKey === identifier || peer.name === identifier) {\r\n return peer;\r\n }\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n /**\r\n * Send a signed message to a named peer.\r\n * Tries HTTP webhook first; falls back to relay if HTTP is unavailable.\r\n */\r\n async sendMessage(options: SendMessageOptions): Promise<SendMessageResult> {\r\n const peer = this.resolvePeer(options.peerName);\r\n if (!peer) {\r\n return {\r\n ok: false,\r\n status: 0,\r\n error: `Unknown peer: ${options.peerName}`,\r\n };\r\n }\r\n\r\n // Try HTTP first (only if peer has a webhook URL and --relay-only not set)\r\n if (peer.url && !options.relayOnly) {\r\n const transportConfig = {\r\n identity: {\r\n publicKey: this.config.identity.publicKey,\r\n privateKey: this.config.identity.privateKey,\r\n },\r\n peers: new Map<string, PeerConfig>([[peer.publicKey, peer]]),\r\n };\r\n\r\n const httpResult = await sendToPeer(\r\n transportConfig,\r\n peer.publicKey,\r\n options.type,\r\n options.payload,\r\n options.inReplyTo,\r\n options.allRecipients\r\n );\r\n\r\n if (httpResult.ok) {\r\n return httpResult;\r\n }\r\n\r\n this.logger?.debug(`HTTP send to ${options.peerName} failed: ${httpResult.error}`);\r\n\r\n // --direct flag: do not fall back to relay\r\n if (options.direct) {\r\n return {\r\n ok: false,\r\n status: httpResult.status,\r\n error: `Direct send to ${options.peerName} failed: ${httpResult.error}`,\r\n };\r\n }\r\n } else if (options.direct && !peer.url) {\r\n // --direct requested but peer has no URL configured\r\n return {\r\n ok: false,\r\n status: 0,\r\n error: `Direct send failed: peer '${options.peerName}' has no URL configured`,\r\n };\r\n }\r\n\r\n // Fall back to relay\r\n if (this.relayClient?.connected() && this.config.relay) {\r\n const relayResult = await sendViaRelay(\r\n {\r\n identity: this.config.identity,\r\n relayUrl: this.config.relay.url,\r\n relayClient: this.relayClient,\r\n },\r\n peer.publicKey,\r\n options.type,\r\n options.payload,\r\n options.inReplyTo,\r\n options.allRecipients\r\n );\r\n\r\n return {\r\n ok: relayResult.ok,\r\n status: 0,\r\n error: relayResult.error,\r\n };\r\n }\r\n\r\n // Both failed\r\n return {\r\n ok: false,\r\n status: 0,\r\n error: peer.url\r\n ? `HTTP send failed and relay not available for peer: ${options.peerName}`\r\n : `No webhook URL and relay not available for peer: ${options.peerName}`,\r\n };\r\n }\r\n\r\n /**\r\n * Send a message to multiple recipients.\r\n * Each envelope carries the full recipient list in its `to` field so every\r\n * receiver knows who else received the message (enables multi-party replies).\r\n * Delivery is attempted per-recipient via HTTP-first + relay fallback.\r\n */\r\n async sendToAll(options: SendToAllOptions): Promise<SendToAllResult> {\r\n const unique = Array.from(new Set(options.recipients.filter(Boolean)));\r\n if (unique.length === 0) {\r\n return { ok: true, errors: [] };\r\n }\r\n\r\n // Resolve all recipient public keys for the envelope `to` field\r\n const allPubkeys = unique.map((id) => {\r\n const peer = this.resolvePeer(id);\r\n return peer?.publicKey ?? id;\r\n });\r\n\r\n const errors: Array<{ recipient: string; error: string }> = [];\r\n for (const recipient of unique) {\r\n const result = await this.sendMessage({\r\n peerName: recipient,\r\n type: options.type,\r\n payload: options.payload,\r\n inReplyTo: options.inReplyTo,\r\n direct: options.direct,\r\n relayOnly: options.relayOnly,\r\n allRecipients: allPubkeys,\r\n });\r\n if (!result.ok) {\r\n errors.push({ recipient, error: result.error ?? 'unknown error' });\r\n }\r\n }\r\n\r\n return { ok: errors.length < unique.length, errors };\r\n }\r\n\r\n /**\r\n * Reply to an envelope from any sender via relay.\r\n * Unlike sendMessage(), this does NOT require the target to be a configured peer.\r\n * Uses the target's public key directly — relay-only (no HTTP, since unknown peers have no URL).\r\n */\r\n async replyToEnvelope(options: ReplyToEnvelopeOptions): Promise<SendMessageResult> {\r\n if (!this.relayClient?.connected() || !this.config.relay) {\r\n return {\r\n ok: false,\r\n status: 0,\r\n error: 'Relay not connected — cannot reply to envelope without relay',\r\n };\r\n }\r\n\r\n this.logger?.debug(\r\n `Replying to envelope via relay: target=${shortKey(options.targetPubkey)} type=${options.type} inReplyTo=${options.inReplyTo}`\r\n );\r\n\r\n const relayResult = await sendViaRelay(\r\n {\r\n identity: this.config.identity,\r\n relayUrl: this.config.relay.url,\r\n relayClient: this.relayClient,\r\n },\r\n options.targetPubkey,\r\n options.type,\r\n options.payload,\r\n options.inReplyTo\r\n );\r\n\r\n return {\r\n ok: relayResult.ok,\r\n status: 0,\r\n error: relayResult.error,\r\n };\r\n }\r\n\r\n /**\r\n * Decode and verify an inbound envelope from a webhook message.\r\n */\r\n async decodeInbound(message: string): Promise<DecodeInboundResult> {\r\n const peersByPubKey = new Map<string, PeerConfig>();\r\n for (const peer of this.config.peers.values()) {\r\n peersByPubKey.set(peer.publicKey, peer);\r\n }\r\n const result = decodeInboundEnvelope(message, peersByPubKey);\r\n if (result.ok) {\r\n return { ok: true, envelope: result.envelope };\r\n }\r\n return { ok: false, reason: result.reason };\r\n }\r\n\r\n getPeers(): string[] {\r\n return Array.from(this.config.peers.keys());\r\n }\r\n\r\n getPeerConfig(name: string): PeerConfig | undefined {\r\n return this.resolvePeer(name);\r\n }\r\n\r\n /**\r\n * Connect to the relay server.\r\n */\r\n async connectRelay(url: string): Promise<void> {\r\n if (this.relayClient) {\r\n return;\r\n }\r\n\r\n const maxReconnectDelay = this.config.relay?.reconnectMaxMs ?? 300000;\r\n let name = this.config.identity.name ?? this.config.relay?.name;\r\n // Never use the short key (id) as the relay display name; treat it as no name\r\n if (name && name === shortKey(this.config.identity.publicKey)) {\r\n name = undefined;\r\n }\r\n const opts = {\r\n relayUrl: url,\r\n publicKey: this.config.identity.publicKey,\r\n privateKey: this.config.identity.privateKey,\r\n name,\r\n pingInterval: 30000,\r\n maxReconnectDelay,\r\n };\r\n\r\n if (this.relayClientFactory) {\r\n this.relayClient = this.relayClientFactory(opts);\r\n } else {\r\n this.relayClient = new RelayClient(opts);\r\n }\r\n\r\n this.relayClient.on('error', (error: Error) => {\r\n this.logger?.debug(`Agora relay error: ${error.message}`);\r\n });\r\n\r\n this.relayClient.on('message', (envelope: Envelope, from: string, fromName?: string) => {\r\n this.onRelayMessage(envelope, from, fromName);\r\n });\r\n\r\n try {\r\n await this.relayClient.connect();\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : String(error);\r\n this.logger?.debug(`Agora relay connect failed (${url}): ${message}`);\r\n this.relayClient = null;\r\n }\r\n }\r\n\r\n async disconnectRelay(): Promise<void> {\r\n if (this.relayClient) {\r\n this.relayClient.disconnect();\r\n this.relayClient = null;\r\n }\r\n }\r\n\r\n isRelayConnected(): boolean {\r\n return this.relayClient?.connected() ?? false;\r\n }\r\n\r\n /**\r\n * Load Agora configuration and return service config (peers as Map).\r\n */\r\n static async loadConfig(path?: string): Promise<AgoraServiceConfig> {\r\n const configPath = path ?? getDefaultConfigPath();\r\n const loaded = await loadAgoraConfigAsync(configPath);\r\n\r\n const peers = new Map<string, PeerConfig>();\r\n for (const p of Object.values(loaded.peers)) {\r\n peers.set(p.publicKey, {\r\n publicKey: p.publicKey,\r\n url: p.url,\r\n token: p.token,\r\n name: p.name,\r\n } satisfies PeerConfig);\r\n }\r\n\r\n return {\r\n identity: loaded.identity,\r\n peers,\r\n relay: loaded.relay,\r\n };\r\n }\r\n}\r\n","/**\r\n * Network reputation query handler.\r\n * Handles incoming reputation_query messages and returns reputation_response.\r\n */\r\n\r\nimport type { ReputationQuery, ReputationResponse, TrustScore } from './types.js';\r\nimport { computeTrustScore, computeTrustScores } from './scoring.js';\r\nimport type { ReputationStore } from './store.js';\r\n\r\n/** Maximum number of verification records to include in a response */\r\nconst MAX_VERIFICATIONS_IN_RESPONSE = 50;\r\n\r\n/**\r\n * Handle an incoming reputation query by reading from the local store,\r\n * computing trust scores, and returning a response.\r\n *\r\n * @param query - The reputation query (agent, optional domain, optional after timestamp)\r\n * @param store - Local reputation store to read from\r\n * @param currentTime - Current timestamp (ms) for score computation\r\n * @returns Reputation response with computed scores and verification records\r\n */\r\nexport async function handleReputationQuery(\r\n query: ReputationQuery,\r\n store: ReputationStore,\r\n currentTime: number\r\n): Promise<ReputationResponse> {\r\n // Get all verifications from the store for score computation\r\n const allVerifications = await store.getVerifications();\r\n\r\n // Filter to verifications targeting the queried agent\r\n let relevantVerifications = allVerifications.filter(v => v.target === query.agent);\r\n\r\n // Apply domain filter if specified\r\n if (query.domain !== undefined) {\r\n relevantVerifications = relevantVerifications.filter(v => v.domain === query.domain);\r\n }\r\n\r\n // Apply after-timestamp filter if specified\r\n if (query.after !== undefined) {\r\n const after = query.after;\r\n relevantVerifications = relevantVerifications.filter(v => v.timestamp > after);\r\n }\r\n\r\n // Compute trust scores\r\n let scores: Record<string, TrustScore>;\r\n\r\n if (query.domain !== undefined) {\r\n const score = computeTrustScore(query.agent, query.domain, allVerifications, currentTime);\r\n scores = { [query.domain]: score };\r\n } else {\r\n const scoreMap = computeTrustScores(query.agent, allVerifications, currentTime);\r\n scores = {};\r\n for (const [domain, score] of scoreMap.entries()) {\r\n scores[domain] = score;\r\n }\r\n }\r\n\r\n // Size-limit verifications to most recent MAX_VERIFICATIONS_IN_RESPONSE\r\n const limitedVerifications = relevantVerifications\r\n .slice()\r\n .sort((a, b) => b.timestamp - a.timestamp)\r\n .slice(0, MAX_VERIFICATIONS_IN_RESPONSE);\r\n\r\n const response: ReputationResponse = {\r\n agent: query.agent,\r\n verifications: limitedVerifications,\r\n scores,\r\n };\r\n\r\n if (query.domain !== undefined) {\r\n response.domain = query.domain;\r\n }\r\n\r\n return response;\r\n}\r\n","/**\r\n * Cross-peer reputation synchronization.\r\n * Pull and merge verification records from trusted peers.\r\n */\r\n\r\nimport type { ReputationQuery, ReputationResponse } from './types.js';\r\nimport { verifyVerificationSignature } from './verification.js';\r\nimport type { ReputationStore } from './store.js';\r\n\r\n/**\r\n * Pull reputation data from a peer and merge it into the local store.\r\n *\r\n * Flow:\r\n * 1. Send `reputation_query` to peer via sendMessage\r\n * 2. Receive `reputation_response` with verification records\r\n * 3. For each record: verify signature, check domain matches, check not duplicate\r\n * 4. Append new records to local store\r\n * 5. Return count of added/skipped\r\n *\r\n * @param agentPublicKey - Public key of the agent whose reputation to sync\r\n * @param domain - Domain to sync reputation for\r\n * @param store - Local reputation store to merge records into\r\n * @param sendMessage - Function that sends a reputation_query and returns the response\r\n * @returns Counts of records added and skipped\r\n */\r\nexport async function syncReputationFromPeer(\r\n agentPublicKey: string,\r\n domain: string,\r\n store: ReputationStore,\r\n sendMessage: (type: string, payload: ReputationQuery) => Promise<ReputationResponse>\r\n): Promise<{ added: number; skipped: number }> {\r\n // Build and send the query\r\n const query: ReputationQuery = {\r\n agent: agentPublicKey,\r\n domain,\r\n };\r\n\r\n const response = await sendMessage('reputation_query', query);\r\n\r\n // Build set of existing record IDs for fast deduplication\r\n const existing = await store.getVerifications();\r\n const existingIds = new Set(existing.map(v => v.id));\r\n\r\n let added = 0;\r\n let skipped = 0;\r\n\r\n for (const record of response.verifications) {\r\n // Skip duplicate records (content-addressed by ID)\r\n if (existingIds.has(record.id)) {\r\n skipped++;\r\n continue;\r\n }\r\n\r\n // Verify cryptographic signature before accepting\r\n const sigResult = verifyVerificationSignature(record);\r\n if (!sigResult.valid) {\r\n skipped++;\r\n continue;\r\n }\r\n\r\n // Ensure the record's domain matches what we requested\r\n if (record.domain !== domain) {\r\n skipped++;\r\n continue;\r\n }\r\n\r\n // Add to local store and track for deduplication within this batch\r\n await store.addVerification(record);\r\n existingIds.add(record.id);\r\n added++;\r\n }\r\n\r\n return { added, skipped };\r\n}\r\n"],"mappings":"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","names":["createHash"]}
1
+ {"version":3,"sources":["../src/identity/seen-keys.ts","../src/registry/capability.ts","../src/registry/peer-store.ts","../src/registry/discovery-service.ts","../src/message/types/peer-discovery.ts","../src/relay/ignored-peers.ts","../src/relay/inbound-message-guard.ts","../src/service.ts","../src/reputation/network.ts","../src/reputation/sync.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\r\nimport { dirname, join } from 'path';\r\nimport { getDefaultConfigPath } from '../config';\r\n\r\nexport const SEEN_KEYS_FILE_NAME = 'seen-keys.json';\r\n\r\n/**\r\n * Default path for the seen-keys store: alongside the Agora config file.\r\n */\r\nexport function getSeenKeysPath(storageDir?: string): string {\r\n if (storageDir) {\r\n return join(storageDir, SEEN_KEYS_FILE_NAME);\r\n }\r\n const configPath = getDefaultConfigPath();\r\n return join(dirname(configPath), SEEN_KEYS_FILE_NAME);\r\n}\r\n\r\nexport interface SeenKeyEntry {\r\n publicKey: string;\r\n firstSeen: number;\r\n lastSeen: number;\r\n seenCount: number;\r\n}\r\n\r\n/**\r\n * Persists all encountered public keys to support reliable identity resolution.\r\n * The full key remains the canonical identity — the shortened suffix is display only.\r\n */\r\nexport class SeenKeyStore {\r\n private keys: Map<string, SeenKeyEntry> = new Map();\r\n private dirty = false;\r\n\r\n constructor(private readonly filePath: string) {\r\n this.load();\r\n }\r\n\r\n /**\r\n * Record a public key sighting. Adds or updates the entry.\r\n */\r\n record(publicKey: string): void {\r\n const now = Date.now();\r\n const existing = this.keys.get(publicKey);\r\n if (existing) {\r\n existing.lastSeen = now;\r\n existing.seenCount++;\r\n } else {\r\n this.keys.set(publicKey, {\r\n publicKey,\r\n firstSeen: now,\r\n lastSeen: now,\r\n seenCount: 1,\r\n });\r\n }\r\n this.dirty = true;\r\n }\r\n\r\n has(publicKey: string): boolean {\r\n return this.keys.has(publicKey);\r\n }\r\n\r\n get(publicKey: string): SeenKeyEntry | undefined {\r\n return this.keys.get(publicKey);\r\n }\r\n\r\n getAll(): SeenKeyEntry[] {\r\n return Array.from(this.keys.values());\r\n }\r\n\r\n /**\r\n * Flush changes to disk. Call periodically or on shutdown.\r\n */\r\n flush(): void {\r\n if (!this.dirty) return;\r\n const dir = dirname(this.filePath);\r\n if (!existsSync(dir)) {\r\n mkdirSync(dir, { recursive: true });\r\n }\r\n const entries = Array.from(this.keys.values());\r\n writeFileSync(this.filePath, JSON.stringify(entries, null, 2), 'utf-8');\r\n this.dirty = false;\r\n }\r\n\r\n private load(): void {\r\n if (!existsSync(this.filePath)) return;\r\n try {\r\n const raw = readFileSync(this.filePath, 'utf-8');\r\n const entries: SeenKeyEntry[] = JSON.parse(raw);\r\n for (const entry of entries) {\r\n if (typeof entry.publicKey === 'string' && entry.publicKey.length > 0) {\r\n this.keys.set(entry.publicKey, entry);\r\n }\r\n }\r\n } catch {\r\n // Corrupt file — start fresh\r\n }\r\n }\r\n}\r\n","import { createHash } from 'node:crypto';\r\n\r\n/**\r\n * A capability describes something an agent can do\r\n */\r\nexport interface Capability {\r\n /** Unique ID (content-addressed hash of name + version + schema) */\r\n id: string;\r\n /** Human-readable name: 'code-review', 'summarization', 'translation' */\r\n name: string;\r\n /** Semantic version */\r\n version: string;\r\n /** What the capability does */\r\n description: string;\r\n /** JSON Schema for expected input */\r\n inputSchema?: object;\r\n /** JSON Schema for expected output */\r\n outputSchema?: object;\r\n /** Discovery tags: ['code', 'typescript', 'review'] */\r\n tags: string[];\r\n}\r\n\r\n/**\r\n * Deterministic JSON serialization for capability hashing.\r\n * Recursively sorts object keys.\r\n */\r\nfunction stableStringify(value: unknown): string {\r\n if (value === null || value === undefined) return JSON.stringify(value);\r\n if (typeof value !== 'object') return JSON.stringify(value);\r\n if (Array.isArray(value)) {\r\n return '[' + value.map(stableStringify).join(',') + ']';\r\n }\r\n const keys = Object.keys(value as Record<string, unknown>).sort();\r\n const pairs = keys.map(k => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k]));\r\n return '{' + pairs.join(',') + '}';\r\n}\r\n\r\n/**\r\n * Compute content-addressed ID for a capability based on name, version, and schemas.\r\n */\r\nfunction computeCapabilityId(name: string, version: string, inputSchema?: object, outputSchema?: object): string {\r\n const data = {\r\n name,\r\n version,\r\n ...(inputSchema !== undefined ? { inputSchema } : {}),\r\n ...(outputSchema !== undefined ? { outputSchema } : {}),\r\n };\r\n const canonical = stableStringify(data);\r\n return createHash('sha256').update(canonical).digest('hex');\r\n}\r\n\r\n/**\r\n * Creates a capability with a content-addressed ID.\r\n * \r\n * @param name - Human-readable capability name\r\n * @param version - Semantic version string\r\n * @param description - Description of what the capability does\r\n * @param options - Optional input/output schemas and tags\r\n * @returns A Capability object with computed ID\r\n */\r\nexport function createCapability(\r\n name: string,\r\n version: string,\r\n description: string,\r\n options: {\r\n inputSchema?: object;\r\n outputSchema?: object;\r\n tags?: string[];\r\n } = {}\r\n): Capability {\r\n const { inputSchema, outputSchema, tags = [] } = options;\r\n \r\n const id = computeCapabilityId(name, version, inputSchema, outputSchema);\r\n \r\n return {\r\n id,\r\n name,\r\n version,\r\n description,\r\n ...(inputSchema !== undefined ? { inputSchema } : {}),\r\n ...(outputSchema !== undefined ? { outputSchema } : {}),\r\n tags,\r\n };\r\n}\r\n\r\n/**\r\n * Validates that a capability has all required fields.\r\n * \r\n * @param capability - The capability to validate\r\n * @returns Object with `valid` boolean and optional `errors` array\r\n */\r\nexport function validateCapability(capability: unknown): { valid: boolean; errors?: string[] } {\r\n const errors: string[] = [];\r\n \r\n if (!capability || typeof capability !== 'object') {\r\n return { valid: false, errors: ['Capability must be an object'] };\r\n }\r\n \r\n const cap = capability as Record<string, unknown>;\r\n \r\n if (!cap.id || typeof cap.id !== 'string') {\r\n errors.push('Missing or invalid field: id (must be a string)');\r\n }\r\n \r\n if (!cap.name || typeof cap.name !== 'string') {\r\n errors.push('Missing or invalid field: name (must be a string)');\r\n }\r\n \r\n if (!cap.version || typeof cap.version !== 'string') {\r\n errors.push('Missing or invalid field: version (must be a string)');\r\n }\r\n \r\n if (!cap.description || typeof cap.description !== 'string') {\r\n errors.push('Missing or invalid field: description (must be a string)');\r\n }\r\n \r\n if (!Array.isArray(cap.tags)) {\r\n errors.push('Missing or invalid field: tags (must be an array)');\r\n } else if (!cap.tags.every(tag => typeof tag === 'string')) {\r\n errors.push('Invalid field: tags (all elements must be strings)');\r\n }\r\n \r\n if (cap.inputSchema !== undefined && (typeof cap.inputSchema !== 'object' || cap.inputSchema === null)) {\r\n errors.push('Invalid field: inputSchema (must be an object)');\r\n }\r\n \r\n if (cap.outputSchema !== undefined && (typeof cap.outputSchema !== 'object' || cap.outputSchema === null)) {\r\n errors.push('Invalid field: outputSchema (must be an object)');\r\n }\r\n \r\n if (errors.length > 0) {\r\n return { valid: false, errors };\r\n }\r\n \r\n return { valid: true };\r\n}\r\n","import type { Peer } from './peer';\n\n/**\n * In-memory store for known peers on the network\n */\nexport class PeerStore {\n private peers: Map<string, Peer> = new Map();\n\n /**\n * Add or update a peer in the store.\n * If a peer with the same publicKey exists, it will be updated.\n * \n * @param peer - The peer to add or update\n */\n addOrUpdatePeer(peer: Peer): void {\n this.peers.set(peer.publicKey, peer);\n }\n\n /**\n * Remove a peer from the store.\n * \n * @param publicKey - The public key of the peer to remove\n * @returns true if the peer was removed, false if it didn't exist\n */\n removePeer(publicKey: string): boolean {\n return this.peers.delete(publicKey);\n }\n\n /**\n * Get a peer by their public key.\n * \n * @param publicKey - The public key of the peer to retrieve\n * @returns The peer if found, undefined otherwise\n */\n getPeer(publicKey: string): Peer | undefined {\n return this.peers.get(publicKey);\n }\n\n /**\n * Find all peers that offer a specific capability by name.\n * \n * @param name - The capability name to search for\n * @returns Array of peers that have a capability with the given name\n */\n findByCapability(name: string): Peer[] {\n const result: Peer[] = [];\n \n for (const peer of this.peers.values()) {\n const hasCapability = peer.capabilities.some(cap => cap.name === name);\n if (hasCapability) {\n result.push(peer);\n }\n }\n \n return result;\n }\n\n /**\n * Find all peers that have capabilities with a specific tag.\n * \n * @param tag - The tag to search for\n * @returns Array of peers that have at least one capability with the given tag\n */\n findByTag(tag: string): Peer[] {\n const result: Peer[] = [];\n \n for (const peer of this.peers.values()) {\n const hasTag = peer.capabilities.some(cap => cap.tags.includes(tag));\n if (hasTag) {\n result.push(peer);\n }\n }\n \n return result;\n }\n\n /**\n * Get all peers in the store.\n * \n * @returns Array of all peers\n */\n allPeers(): Peer[] {\n return Array.from(this.peers.values());\n }\n\n /**\n * Remove peers that haven't been seen within the specified time window.\n * \n * @param maxAgeMs - Maximum age in milliseconds. Peers older than this will be removed.\n * @param currentTime - Current timestamp (ms), defaults to Date.now()\n * @returns Number of peers removed\n */\n prune(maxAgeMs: number, currentTime: number = Date.now()): number {\n const cutoff = currentTime - maxAgeMs;\n let removed = 0;\n \n for (const [publicKey, peer] of this.peers.entries()) {\n if (peer.lastSeen < cutoff) {\n this.peers.delete(publicKey);\n removed++;\n }\n }\n \n return removed;\n }\n}\n","import { createEnvelope, type Envelope } from '../message/envelope';\nimport { PeerStore } from './peer-store';\nimport type { Capability } from './capability';\nimport type { Peer } from './peer';\nimport type {\n CapabilityAnnouncePayload,\n CapabilityQueryPayload,\n CapabilityResponsePayload,\n} from './messages';\n\n/**\n * DiscoveryService manages capability-based peer discovery.\n * It maintains a local index of peer capabilities and handles\n * capability announce, query, and response messages.\n */\nexport class DiscoveryService {\n constructor(\n private peerStore: PeerStore,\n private identity: { publicKey: string; privateKey: string }\n ) {}\n\n /**\n * Announce own capabilities to the network.\n * Creates a capability_announce envelope that can be broadcast to peers.\n * \n * @param capabilities - List of capabilities this agent offers\n * @param metadata - Optional metadata about this agent\n * @returns A signed capability_announce envelope\n */\n announce(\n capabilities: Capability[],\n metadata?: { name?: string; version?: string },\n recipients: string[] = [this.identity.publicKey]\n ): Envelope<CapabilityAnnouncePayload> {\n const payload: CapabilityAnnouncePayload = {\n publicKey: this.identity.publicKey,\n capabilities,\n metadata: metadata ? {\n ...metadata,\n lastSeen: Date.now(),\n } : {\n lastSeen: Date.now(),\n },\n };\n\n return createEnvelope(\n 'capability_announce',\n this.identity.publicKey,\n this.identity.privateKey,\n payload,\n Date.now(),\n undefined,\n recipients\n );\n }\n\n /**\n * Handle an incoming capability_announce message.\n * Updates the peer store with the announced capabilities.\n * \n * @param envelope - The capability_announce envelope to process\n */\n handleAnnounce(envelope: Envelope<CapabilityAnnouncePayload>): void {\n const { payload } = envelope;\n \n const peer: Peer = {\n publicKey: payload.publicKey,\n capabilities: payload.capabilities,\n lastSeen: payload.metadata?.lastSeen || envelope.timestamp,\n metadata: payload.metadata ? {\n name: payload.metadata.name,\n version: payload.metadata.version,\n } : undefined,\n };\n\n this.peerStore.addOrUpdatePeer(peer);\n }\n\n /**\n * Create a capability query payload.\n * \n * @param queryType - Type of query: 'name', 'tag', or 'schema'\n * @param query - The query value (capability name, tag, or schema)\n * @param filters - Optional filters (limit, minTrustScore)\n * @returns A capability_query payload\n */\n query(\n queryType: 'name' | 'tag' | 'schema',\n query: string | object,\n filters?: { limit?: number; minTrustScore?: number }\n ): CapabilityQueryPayload {\n return {\n queryType,\n query,\n filters,\n };\n }\n\n /**\n * Handle an incoming capability_query message.\n * Searches the local peer store and returns matching peers.\n * \n * @param envelope - The capability_query envelope to process\n * @returns A capability_response envelope with matching peers\n */\n handleQuery(\n envelope: Envelope<CapabilityQueryPayload>\n ): Envelope<CapabilityResponsePayload> {\n const { payload } = envelope;\n let peers: Peer[] = [];\n\n // Execute query based on type\n if (payload.queryType === 'name' && typeof payload.query === 'string') {\n peers = this.peerStore.findByCapability(payload.query);\n } else if (payload.queryType === 'tag' && typeof payload.query === 'string') {\n peers = this.peerStore.findByTag(payload.query);\n } else if (payload.queryType === 'schema') {\n // Schema-based matching is deferred to Phase 2b\n // For now, return empty results\n peers = [];\n }\n\n // Apply filters\n const limit = payload.filters?.limit;\n const totalMatches = peers.length;\n \n if (limit !== undefined && limit > 0) {\n peers = peers.slice(0, limit);\n }\n\n // Transform peers to response format\n const responsePeers = peers.map(peer => ({\n publicKey: peer.publicKey,\n capabilities: peer.capabilities,\n metadata: peer.metadata ? {\n name: peer.metadata.name,\n version: peer.metadata.version,\n lastSeen: peer.lastSeen,\n } : {\n lastSeen: peer.lastSeen,\n },\n // Trust score integration deferred to Phase 2b (RFC-001)\n trustScore: undefined,\n }));\n\n const responsePayload: CapabilityResponsePayload = {\n queryId: envelope.id,\n peers: responsePeers,\n totalMatches,\n };\n\n return createEnvelope(\n 'capability_response',\n this.identity.publicKey,\n this.identity.privateKey,\n responsePayload,\n Date.now(),\n envelope.id, // inReplyTo\n [envelope.from]\n );\n }\n\n /**\n * Remove peers that haven't been seen within the specified time window.\n * \n * @param maxAgeMs - Maximum age in milliseconds\n * @returns Number of peers removed\n */\n pruneStale(maxAgeMs: number, currentTime: number = Date.now()): number {\n return this.peerStore.prune(maxAgeMs, currentTime);\n }\n}\n","/**\r\n * Peer discovery message types for the Agora network.\r\n */\r\n\r\n/**\r\n * Request peer list from relay\r\n */\r\nexport interface PeerListRequestPayload {\r\n /** Optional filters */\r\n filters?: {\r\n /** Only peers seen in last N ms */\r\n activeWithin?: number;\r\n /** Maximum peers to return */\r\n limit?: number;\r\n };\r\n}\r\n\r\n/**\r\n * Relay responds with connected peers\r\n */\r\nexport interface PeerListResponsePayload {\r\n /** List of known peers */\r\n peers: Array<{\r\n /** Peer's Ed25519 public key */\r\n publicKey: string;\r\n /** Optional metadata (if peer announced) */\r\n metadata?: {\r\n name?: string;\r\n version?: string;\r\n capabilities?: string[];\r\n };\r\n /** Last seen timestamp (ms) */\r\n lastSeen: number;\r\n }>;\r\n /** Total peer count (may be > peers.length if limited) */\r\n totalPeers: number;\r\n /** Relay's public key (for trust verification) */\r\n relayPublicKey: string;\r\n}\r\n\r\n/**\r\n * Agent recommends another agent\r\n */\r\nexport interface PeerReferralPayload {\r\n /** Referred peer's public key */\r\n publicKey: string;\r\n /** Optional endpoint (if known) */\r\n endpoint?: string;\r\n /** Optional metadata */\r\n metadata?: {\r\n name?: string;\r\n version?: string;\r\n capabilities?: string[];\r\n };\r\n /** Referrer's comment */\r\n comment?: string;\r\n /** Trust hint (RFC-001 integration) */\r\n trustScore?: number;\r\n}\r\n\r\n/**\r\n * Validate PeerListRequestPayload\r\n */\r\nexport function validatePeerListRequest(payload: unknown): { valid: boolean; errors: string[] } {\r\n const errors: string[] = [];\r\n\r\n if (typeof payload !== 'object' || payload === null) {\r\n errors.push('Payload must be an object');\r\n return { valid: false, errors };\r\n }\r\n\r\n const p = payload as Record<string, unknown>;\r\n\r\n if (p.filters !== undefined) {\r\n if (typeof p.filters !== 'object' || p.filters === null) {\r\n errors.push('filters must be an object');\r\n } else {\r\n const filters = p.filters as Record<string, unknown>;\r\n if (filters.activeWithin !== undefined && typeof filters.activeWithin !== 'number') {\r\n errors.push('filters.activeWithin must be a number');\r\n }\r\n if (filters.limit !== undefined && typeof filters.limit !== 'number') {\r\n errors.push('filters.limit must be a number');\r\n }\r\n }\r\n }\r\n\r\n return { valid: errors.length === 0, errors };\r\n}\r\n\r\n/**\r\n * Validate PeerListResponsePayload\r\n */\r\nexport function validatePeerListResponse(payload: unknown): { valid: boolean; errors: string[] } {\r\n const errors: string[] = [];\r\n\r\n if (typeof payload !== 'object' || payload === null) {\r\n errors.push('Payload must be an object');\r\n return { valid: false, errors };\r\n }\r\n\r\n const p = payload as Record<string, unknown>;\r\n\r\n if (!Array.isArray(p.peers)) {\r\n errors.push('peers must be an array');\r\n } else {\r\n p.peers.forEach((peer, index) => {\r\n if (typeof peer !== 'object' || peer === null) {\r\n errors.push(`peers[${index}] must be an object`);\r\n return;\r\n }\r\n const peerObj = peer as Record<string, unknown>;\r\n if (typeof peerObj.publicKey !== 'string') {\r\n errors.push(`peers[${index}].publicKey must be a string`);\r\n }\r\n if (typeof peerObj.lastSeen !== 'number') {\r\n errors.push(`peers[${index}].lastSeen must be a number`);\r\n }\r\n });\r\n }\r\n\r\n if (typeof p.totalPeers !== 'number') {\r\n errors.push('totalPeers must be a number');\r\n }\r\n\r\n if (typeof p.relayPublicKey !== 'string') {\r\n errors.push('relayPublicKey must be a string');\r\n }\r\n\r\n return { valid: errors.length === 0, errors };\r\n}\r\n\r\n/**\r\n * Validate PeerReferralPayload\r\n */\r\nexport function validatePeerReferral(payload: unknown): { valid: boolean; errors: string[] } {\r\n const errors: string[] = [];\r\n\r\n if (typeof payload !== 'object' || payload === null) {\r\n errors.push('Payload must be an object');\r\n return { valid: false, errors };\r\n }\r\n\r\n const p = payload as Record<string, unknown>;\r\n\r\n if (typeof p.publicKey !== 'string') {\r\n errors.push('publicKey must be a string');\r\n }\r\n\r\n if (p.endpoint !== undefined && typeof p.endpoint !== 'string') {\r\n errors.push('endpoint must be a string');\r\n }\r\n\r\n if (p.comment !== undefined && typeof p.comment !== 'string') {\r\n errors.push('comment must be a string');\r\n }\r\n\r\n if (p.trustScore !== undefined && typeof p.trustScore !== 'number') {\r\n errors.push('trustScore must be a number');\r\n }\r\n\r\n return { valid: errors.length === 0, errors };\r\n}\r\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\r\nimport { join, dirname } from 'node:path';\r\nimport { getDefaultConfigPath } from '../config';\r\n\r\nexport const IGNORED_FILE_NAME = 'IGNORED_PEERS.md';\r\n\r\nexport function getIgnoredPeersPath(storageDir?: string): string {\r\n if (storageDir) {\r\n return join(storageDir, IGNORED_FILE_NAME);\r\n }\r\n const configPath = getDefaultConfigPath();\r\n return join(dirname(configPath), IGNORED_FILE_NAME);\r\n}\r\n\r\nexport function loadIgnoredPeers(filePath?: string): string[] {\r\n const path = filePath ?? getIgnoredPeersPath();\r\n if (!existsSync(path)) return [];\r\n\r\n const lines = readFileSync(path, 'utf-8')\r\n .split('\\n')\r\n .map((line) => line.trim())\r\n .filter((line) => line.length > 0 && !line.startsWith('#'));\r\n\r\n return Array.from(new Set(lines));\r\n}\r\n\r\nexport function saveIgnoredPeers(peers: string[], filePath?: string): void {\r\n const path = filePath ?? getIgnoredPeersPath();\r\n const dir = dirname(path);\r\n if (!existsSync(dir)) {\r\n mkdirSync(dir, { recursive: true });\r\n }\r\n\r\n const unique = Array.from(new Set(peers.map((peer) => peer.trim()).filter(Boolean))).sort();\r\n const content = [\r\n '# Ignored peers',\r\n '# One public key per line',\r\n ...unique,\r\n '',\r\n ].join('\\n');\r\n\r\n writeFileSync(path, content, 'utf-8');\r\n}\r\n\r\nexport class IgnoredPeersManager {\r\n private readonly peers: Set<string>;\r\n private readonly filePath: string;\r\n\r\n constructor(filePath?: string) {\r\n this.filePath = filePath ?? getIgnoredPeersPath();\r\n this.peers = new Set(loadIgnoredPeers(this.filePath));\r\n }\r\n\r\n ignorePeer(publicKey: string): boolean {\r\n const normalized = publicKey.trim();\r\n if (!normalized) {\r\n return false;\r\n }\r\n const added = !this.peers.has(normalized);\r\n this.peers.add(normalized);\r\n if (added) {\r\n this.persist();\r\n }\r\n return added;\r\n }\r\n\r\n unignorePeer(publicKey: string): boolean {\r\n const normalized = publicKey.trim();\r\n const removed = this.peers.delete(normalized);\r\n if (removed) {\r\n this.persist();\r\n }\r\n return removed;\r\n }\r\n\r\n listIgnoredPeers(): string[] {\r\n return Array.from(this.peers.values()).sort();\r\n }\r\n\r\n private persist(): void {\r\n saveIgnoredPeers(this.listIgnoredPeers(), this.filePath);\r\n }\r\n}\r\n","import { createHash } from 'node:crypto';\r\nimport type { Envelope } from '../message/envelope';\r\n\r\ninterface SenderWindow {\r\n count: number;\r\n windowStart: number;\r\n}\r\n\r\nexport interface InboundSecurityOptions {\r\n rateLimitEnabled?: boolean;\r\n rateLimitMaxMessages?: number;\r\n rateLimitWindowMs?: number;\r\n envelopeDedupEnabled?: boolean;\r\n envelopeDedupMaxIds?: number;\r\n contentDedupEnabled?: boolean;\r\n contentDedupWindowMs?: number;\r\n ignoredPeers?: string[];\r\n}\r\n\r\n/**\r\n * Shared inbound guard for relay-fed consumers.\r\n *\r\n * Provides:\r\n * - static ignore list checks\r\n * - per-sender rate limiting\r\n * - envelope ID deduplication\r\n * - content-window deduplication\r\n */\r\nexport class InboundMessageGuard {\r\n private readonly senderWindows = new Map<string, SenderWindow>();\r\n private readonly envelopeIds = new Set<string>();\r\n private readonly contentDedup = new Map<string, number>();\r\n private readonly ignoredPeers = new Set<string>();\r\n\r\n private rateLimitEnabled: boolean;\r\n private rateLimitMaxMessages: number;\r\n private rateLimitWindowMs: number;\r\n private envelopeDedupEnabled: boolean;\r\n private envelopeDedupMaxIds: number;\r\n private contentDedupEnabled: boolean;\r\n private contentDedupWindowMs: number;\r\n\r\n private static readonly MAX_SENDER_ENTRIES = 500;\r\n private static readonly MAX_CONTENT_ENTRIES = 5000;\r\n\r\n constructor(options: InboundSecurityOptions = {}) {\r\n this.rateLimitEnabled = options.rateLimitEnabled ?? true;\r\n this.rateLimitMaxMessages = options.rateLimitMaxMessages ?? 10;\r\n this.rateLimitWindowMs = options.rateLimitWindowMs ?? 60_000;\r\n this.envelopeDedupEnabled = options.envelopeDedupEnabled ?? true;\r\n this.envelopeDedupMaxIds = options.envelopeDedupMaxIds ?? 1000;\r\n this.contentDedupEnabled = options.contentDedupEnabled ?? true;\r\n this.contentDedupWindowMs = options.contentDedupWindowMs ?? 1_800_000;\r\n\r\n for (const peer of options.ignoredPeers ?? []) {\r\n const normalized = peer.trim();\r\n if (normalized) {\r\n this.ignoredPeers.add(normalized);\r\n }\r\n }\r\n }\r\n\r\n shouldDrop(envelope: Envelope, senderPublicKey: string): { drop: boolean; reason?: string } {\r\n if (this.ignoredPeers.has(senderPublicKey)) {\r\n return { drop: true, reason: 'ignored_peer' };\r\n }\r\n\r\n if (this.isRateLimited(senderPublicKey)) {\r\n return { drop: true, reason: 'rate_limited' };\r\n }\r\n\r\n if (this.isDuplicateEnvelopeId(envelope.id)) {\r\n return { drop: true, reason: 'duplicate_envelope_id' };\r\n }\r\n\r\n if (this.isDuplicateContent(senderPublicKey, envelope.type, envelope.payload)) {\r\n return { drop: true, reason: 'duplicate_content' };\r\n }\r\n\r\n return { drop: false };\r\n }\r\n\r\n ignorePeer(publicKey: string): boolean {\r\n const normalized = publicKey.trim();\r\n if (!normalized) {\r\n return false;\r\n }\r\n const existed = this.ignoredPeers.has(normalized);\r\n this.ignoredPeers.add(normalized);\r\n return !existed;\r\n }\r\n\r\n unignorePeer(publicKey: string): boolean {\r\n return this.ignoredPeers.delete(publicKey.trim());\r\n }\r\n\r\n listIgnoredPeers(): string[] {\r\n return Array.from(this.ignoredPeers.values()).sort();\r\n }\r\n\r\n private isRateLimited(senderPublicKey: string): boolean {\r\n if (!this.rateLimitEnabled) {\r\n return false;\r\n }\r\n\r\n const now = Date.now();\r\n const current = this.senderWindows.get(senderPublicKey);\r\n\r\n if (!current && this.senderWindows.size >= InboundMessageGuard.MAX_SENDER_ENTRIES) {\r\n this.evictOldestSender();\r\n }\r\n\r\n if (!current || now - current.windowStart > this.rateLimitWindowMs) {\r\n this.senderWindows.set(senderPublicKey, { count: 1, windowStart: now });\r\n return false;\r\n }\r\n\r\n current.count += 1;\r\n return current.count > this.rateLimitMaxMessages;\r\n }\r\n\r\n private evictOldestSender(): void {\r\n let oldestKey: string | null = null;\r\n let oldestTime = Number.POSITIVE_INFINITY;\r\n\r\n for (const [key, value] of this.senderWindows.entries()) {\r\n if (value.windowStart < oldestTime) {\r\n oldestTime = value.windowStart;\r\n oldestKey = key;\r\n }\r\n }\r\n\r\n if (oldestKey) {\r\n this.senderWindows.delete(oldestKey);\r\n }\r\n }\r\n\r\n private isDuplicateEnvelopeId(envelopeId: string): boolean {\r\n if (!this.envelopeDedupEnabled) {\r\n return false;\r\n }\r\n\r\n if (this.envelopeIds.has(envelopeId)) {\r\n return true;\r\n }\r\n\r\n this.envelopeIds.add(envelopeId);\r\n if (this.envelopeIds.size > this.envelopeDedupMaxIds) {\r\n const oldest = this.envelopeIds.values().next().value;\r\n if (oldest !== undefined) {\r\n this.envelopeIds.delete(oldest);\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private isDuplicateContent(senderPublicKey: string, type: string, payload: unknown): boolean {\r\n if (!this.contentDedupEnabled) {\r\n return false;\r\n }\r\n\r\n const hash = createHash('sha256')\r\n .update(senderPublicKey)\r\n .update(type)\r\n .update(JSON.stringify(payload ?? null))\r\n .digest('hex');\r\n\r\n const now = Date.now();\r\n const firstSeen = this.contentDedup.get(hash);\r\n\r\n if (firstSeen !== undefined && now - firstSeen < this.contentDedupWindowMs) {\r\n return true;\r\n }\r\n\r\n this.contentDedup.set(hash, now);\r\n\r\n if (this.contentDedup.size > InboundMessageGuard.MAX_CONTENT_ENTRIES) {\r\n for (const [key, ts] of this.contentDedup.entries()) {\r\n if (now - ts >= this.contentDedupWindowMs) {\r\n this.contentDedup.delete(key);\r\n }\r\n }\r\n if (this.contentDedup.size > InboundMessageGuard.MAX_CONTENT_ENTRIES) {\r\n const oldestKey = this.contentDedup.keys().next().value;\r\n if (oldestKey !== undefined) {\r\n this.contentDedup.delete(oldestKey);\r\n }\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n}\r\n","import type { AgoraIdentity, RelayConfig } from './config';\r\nimport { getDefaultConfigPath, loadAgoraConfigAsync } from './config';\r\nimport type { Envelope } from './message/envelope';\r\nimport type { MessageType } from './message/envelope';\r\nimport { RelayClient } from './relay/client';\r\nimport type { PeerConfig } from './transport/http';\r\nimport { decodeInboundEnvelope, sendToPeer } from './transport/http';\r\nimport { sendViaRelay } from './transport/relay';\r\nimport { shortKey } from './utils';\r\n\r\n/**\r\n * Service config: identity, peers keyed by name, optional relay.\r\n */\r\nexport interface AgoraServiceConfig {\r\n identity: AgoraIdentity;\r\n peers: Map<string, PeerConfig>;\r\n relay?: RelayConfig;\r\n}\r\n\r\nexport interface SendMessageOptions {\r\n peerName: string;\r\n type: MessageType;\r\n payload: unknown;\r\n inReplyTo?: string;\r\n /** Skip relay, send directly via HTTP only. Fails if peer has no URL or is unreachable. */\r\n direct?: boolean;\r\n /** Skip direct HTTP, always use relay even if peer has a URL. */\r\n relayOnly?: boolean;\r\n /** All recipients of this message (for multi-recipient sends). Used in the envelope `to` field. */\r\n allRecipients?: string[];\r\n}\r\n\r\nexport interface SendMessageResult {\r\n ok: boolean;\r\n status: number;\r\n error?: string;\r\n}\r\n\r\nexport interface SendToAllOptions {\r\n /** Recipient identifiers — peer names, public keys, or short refs. */\r\n recipients: string[];\r\n type: MessageType;\r\n payload: unknown;\r\n inReplyTo?: string;\r\n direct?: boolean;\r\n relayOnly?: boolean;\r\n}\r\n\r\nexport interface SendToAllResult {\r\n /** True when at least one recipient succeeded (or recipients is empty). */\r\n ok: boolean;\r\n errors: Array<{ recipient: string; error: string }>;\r\n}\r\n\r\nexport interface ReplyToEnvelopeOptions {\r\n /** The public key of the target (from envelope.sender) */\r\n targetPubkey: string;\r\n /** Message type for the reply */\r\n type: MessageType;\r\n /** Reply payload */\r\n payload: unknown;\r\n /** The envelope ID being replied to (required — this IS a reply) */\r\n inReplyTo: string;\r\n}\r\n\r\nexport interface DecodeInboundResult {\r\n ok: boolean;\r\n envelope?: Envelope;\r\n reason?: string;\r\n}\r\n\r\n/** Handler for relay messages. (envelope, fromPublicKey) */\r\nexport type RelayMessageHandler = (envelope: Envelope, from: string) => void;\r\n\r\nexport interface Logger {\r\n debug(message: string): void;\r\n}\r\n\r\nexport interface RelayClientLike {\r\n connect(): Promise<void>;\r\n disconnect(): void;\r\n connected(): boolean;\r\n send(to: string, envelope: Envelope): Promise<{ ok: boolean; error?: string }>;\r\n on(event: 'message', handler: (envelope: Envelope, from: string) => void): void;\r\n on(event: 'error', handler: (error: Error) => void): void;\r\n}\r\n\r\nexport interface RelayClientFactory {\r\n (opts: {\r\n relayUrl: string;\r\n publicKey: string;\r\n privateKey: string;\r\n name?: string;\r\n pingInterval: number;\r\n maxReconnectDelay: number;\r\n }): RelayClientLike;\r\n}\r\n\r\n/**\r\n * High-level Agora service: send by peer name, decode inbound, relay lifecycle.\r\n */\r\nexport class AgoraService {\r\n private config: AgoraServiceConfig;\r\n private relayClient: RelayClientLike | null = null;\r\n private readonly onRelayMessage: RelayMessageHandler;\r\n private logger: Logger | null;\r\n private relayClientFactory: RelayClientFactory | null;\r\n\r\n /**\r\n * @param config - Service config (identity, peers, optional relay)\r\n * @param onRelayMessage - Required callback for relay messages. Ensures no messages are lost between init and connect.\r\n * @param logger - Optional debug logger\r\n * @param relayClientFactory - Optional factory for relay client (for testing)\r\n */\r\n constructor(\r\n config: AgoraServiceConfig,\r\n onRelayMessage: RelayMessageHandler,\r\n logger?: Logger,\r\n relayClientFactory?: RelayClientFactory\r\n ) {\r\n this.config = config;\r\n this.onRelayMessage = onRelayMessage;\r\n this.logger = logger ?? null;\r\n this.relayClientFactory = relayClientFactory ?? null;\r\n }\r\n\r\n private resolvePeer(identifier: string): PeerConfig | undefined {\r\n const direct = this.config.peers.get(identifier);\r\n if (direct) {\r\n return direct;\r\n }\r\n\r\n for (const peer of this.config.peers.values()) {\r\n if (peer.publicKey === identifier || peer.name === identifier) {\r\n return peer;\r\n }\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n /**\r\n * Send a signed message to a named peer.\r\n * Tries HTTP webhook first; falls back to relay if HTTP is unavailable.\r\n */\r\n async sendMessage(options: SendMessageOptions): Promise<SendMessageResult> {\r\n const peer = this.resolvePeer(options.peerName);\r\n if (!peer) {\r\n return {\r\n ok: false,\r\n status: 0,\r\n error: `Unknown peer: ${options.peerName}`,\r\n };\r\n }\r\n\r\n // Try HTTP first (only if peer has a webhook URL and --relay-only not set)\r\n if (peer.url && !options.relayOnly) {\r\n const transportConfig = {\r\n identity: {\r\n publicKey: this.config.identity.publicKey,\r\n privateKey: this.config.identity.privateKey,\r\n },\r\n peers: new Map<string, PeerConfig>([[peer.publicKey, peer]]),\r\n };\r\n\r\n const httpResult = await sendToPeer(\r\n transportConfig,\r\n peer.publicKey,\r\n options.type,\r\n options.payload,\r\n options.inReplyTo,\r\n options.allRecipients\r\n );\r\n\r\n if (httpResult.ok) {\r\n return httpResult;\r\n }\r\n\r\n this.logger?.debug(`HTTP send to ${options.peerName} failed: ${httpResult.error}`);\r\n\r\n // --direct flag: do not fall back to relay\r\n if (options.direct) {\r\n return {\r\n ok: false,\r\n status: httpResult.status,\r\n error: `Direct send to ${options.peerName} failed: ${httpResult.error}`,\r\n };\r\n }\r\n } else if (options.direct && !peer.url) {\r\n // --direct requested but peer has no URL configured\r\n return {\r\n ok: false,\r\n status: 0,\r\n error: `Direct send failed: peer '${options.peerName}' has no URL configured`,\r\n };\r\n }\r\n\r\n // Fall back to relay\r\n if (this.relayClient?.connected() && this.config.relay) {\r\n const relayResult = await sendViaRelay(\r\n {\r\n identity: this.config.identity,\r\n relayUrl: this.config.relay.url,\r\n relayClient: this.relayClient,\r\n },\r\n peer.publicKey,\r\n options.type,\r\n options.payload,\r\n options.inReplyTo,\r\n options.allRecipients\r\n );\r\n\r\n return {\r\n ok: relayResult.ok,\r\n status: 0,\r\n error: relayResult.error,\r\n };\r\n }\r\n\r\n // Both failed\r\n return {\r\n ok: false,\r\n status: 0,\r\n error: peer.url\r\n ? `HTTP send failed and relay not available for peer: ${options.peerName}`\r\n : `No webhook URL and relay not available for peer: ${options.peerName}`,\r\n };\r\n }\r\n\r\n /**\r\n * Send a message to multiple recipients.\r\n * Each envelope carries the full recipient list in its `to` field so every\r\n * receiver knows who else received the message (enables multi-party replies).\r\n * Delivery is attempted per-recipient via HTTP-first + relay fallback.\r\n */\r\n async sendToAll(options: SendToAllOptions): Promise<SendToAllResult> {\r\n const unique = Array.from(new Set(options.recipients.filter(Boolean)));\r\n if (unique.length === 0) {\r\n return { ok: true, errors: [] };\r\n }\r\n\r\n // Resolve all recipient public keys for the envelope `to` field\r\n const allPubkeys = unique.map((id) => {\r\n const peer = this.resolvePeer(id);\r\n return peer?.publicKey ?? id;\r\n });\r\n\r\n const errors: Array<{ recipient: string; error: string }> = [];\r\n for (const recipient of unique) {\r\n const result = await this.sendMessage({\r\n peerName: recipient,\r\n type: options.type,\r\n payload: options.payload,\r\n inReplyTo: options.inReplyTo,\r\n direct: options.direct,\r\n relayOnly: options.relayOnly,\r\n allRecipients: allPubkeys,\r\n });\r\n if (!result.ok) {\r\n errors.push({ recipient, error: result.error ?? 'unknown error' });\r\n }\r\n }\r\n\r\n return { ok: errors.length < unique.length, errors };\r\n }\r\n\r\n /**\r\n * Reply to an envelope from any sender via relay.\r\n * Unlike sendMessage(), this does NOT require the target to be a configured peer.\r\n * Uses the target's public key directly — relay-only (no HTTP, since unknown peers have no URL).\r\n */\r\n async replyToEnvelope(options: ReplyToEnvelopeOptions): Promise<SendMessageResult> {\r\n if (!this.relayClient?.connected() || !this.config.relay) {\r\n return {\r\n ok: false,\r\n status: 0,\r\n error: 'Relay not connected — cannot reply to envelope without relay',\r\n };\r\n }\r\n\r\n this.logger?.debug(\r\n `Replying to envelope via relay: target=${shortKey(options.targetPubkey)} type=${options.type} inReplyTo=${options.inReplyTo}`\r\n );\r\n\r\n const relayResult = await sendViaRelay(\r\n {\r\n identity: this.config.identity,\r\n relayUrl: this.config.relay.url,\r\n relayClient: this.relayClient,\r\n },\r\n options.targetPubkey,\r\n options.type,\r\n options.payload,\r\n options.inReplyTo\r\n );\r\n\r\n return {\r\n ok: relayResult.ok,\r\n status: 0,\r\n error: relayResult.error,\r\n };\r\n }\r\n\r\n /**\r\n * Decode and verify an inbound envelope from a webhook message.\r\n */\r\n async decodeInbound(message: string): Promise<DecodeInboundResult> {\r\n const peersByPubKey = new Map<string, PeerConfig>();\r\n for (const peer of this.config.peers.values()) {\r\n peersByPubKey.set(peer.publicKey, peer);\r\n }\r\n const result = decodeInboundEnvelope(message, peersByPubKey);\r\n if (result.ok) {\r\n return { ok: true, envelope: result.envelope };\r\n }\r\n return { ok: false, reason: result.reason };\r\n }\r\n\r\n getPeers(): string[] {\r\n return Array.from(this.config.peers.keys());\r\n }\r\n\r\n getPeerConfig(name: string): PeerConfig | undefined {\r\n return this.resolvePeer(name);\r\n }\r\n\r\n /**\r\n * Connect to the relay server.\r\n */\r\n async connectRelay(url: string): Promise<void> {\r\n if (this.relayClient) {\r\n return;\r\n }\r\n\r\n const maxReconnectDelay = this.config.relay?.reconnectMaxMs ?? 300000;\r\n let name = this.config.identity.name ?? this.config.relay?.name;\r\n // Never use the short key (id) as the relay display name; treat it as no name\r\n if (name && name === shortKey(this.config.identity.publicKey)) {\r\n name = undefined;\r\n }\r\n const opts = {\r\n relayUrl: url,\r\n publicKey: this.config.identity.publicKey,\r\n privateKey: this.config.identity.privateKey,\r\n name,\r\n pingInterval: 30000,\r\n maxReconnectDelay,\r\n };\r\n\r\n if (this.relayClientFactory) {\r\n this.relayClient = this.relayClientFactory(opts);\r\n } else {\r\n this.relayClient = new RelayClient(opts);\r\n }\r\n\r\n this.relayClient.on('error', (error: Error) => {\r\n this.logger?.debug(`Agora relay error: ${error.message}`);\r\n });\r\n\r\n this.relayClient.on('message', (envelope: Envelope, from: string) => {\r\n this.onRelayMessage(envelope, from);\r\n });\r\n\r\n try {\r\n await this.relayClient.connect();\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : String(error);\r\n this.logger?.debug(`Agora relay connect failed (${url}): ${message}`);\r\n this.relayClient = null;\r\n }\r\n }\r\n\r\n async disconnectRelay(): Promise<void> {\r\n if (this.relayClient) {\r\n this.relayClient.disconnect();\r\n this.relayClient = null;\r\n }\r\n }\r\n\r\n isRelayConnected(): boolean {\r\n return this.relayClient?.connected() ?? false;\r\n }\r\n\r\n /**\r\n * Load Agora configuration and return service config (peers as Map).\r\n */\r\n static async loadConfig(path?: string): Promise<AgoraServiceConfig> {\r\n const configPath = path ?? getDefaultConfigPath();\r\n const loaded = await loadAgoraConfigAsync(configPath);\r\n\r\n const peers = new Map<string, PeerConfig>();\r\n for (const p of Object.values(loaded.peers)) {\r\n peers.set(p.publicKey, {\r\n publicKey: p.publicKey,\r\n url: p.url,\r\n token: p.token,\r\n name: p.name,\r\n } satisfies PeerConfig);\r\n }\r\n\r\n return {\r\n identity: loaded.identity,\r\n peers,\r\n relay: loaded.relay,\r\n };\r\n }\r\n}\r\n","/**\r\n * Network reputation query handler.\r\n * Handles incoming reputation_query messages and returns reputation_response.\r\n */\r\n\r\nimport type { ReputationQuery, ReputationResponse, TrustScore } from './types.js';\r\nimport { computeTrustScore, computeTrustScores } from './scoring.js';\r\nimport type { ReputationStore } from './store.js';\r\n\r\n/** Maximum number of verification records to include in a response */\r\nconst MAX_VERIFICATIONS_IN_RESPONSE = 50;\r\n\r\n/**\r\n * Handle an incoming reputation query by reading from the local store,\r\n * computing trust scores, and returning a response.\r\n *\r\n * @param query - The reputation query (agent, optional domain, optional after timestamp)\r\n * @param store - Local reputation store to read from\r\n * @param currentTime - Current timestamp (ms) for score computation\r\n * @returns Reputation response with computed scores and verification records\r\n */\r\nexport async function handleReputationQuery(\r\n query: ReputationQuery,\r\n store: ReputationStore,\r\n currentTime: number\r\n): Promise<ReputationResponse> {\r\n // Get all verifications from the store for score computation\r\n const allVerifications = await store.getVerifications();\r\n\r\n // Filter to verifications targeting the queried agent\r\n let relevantVerifications = allVerifications.filter(v => v.target === query.agent);\r\n\r\n // Apply domain filter if specified\r\n if (query.domain !== undefined) {\r\n relevantVerifications = relevantVerifications.filter(v => v.domain === query.domain);\r\n }\r\n\r\n // Apply after-timestamp filter if specified\r\n if (query.after !== undefined) {\r\n const after = query.after;\r\n relevantVerifications = relevantVerifications.filter(v => v.timestamp > after);\r\n }\r\n\r\n // Compute trust scores\r\n let scores: Record<string, TrustScore>;\r\n\r\n if (query.domain !== undefined) {\r\n const score = computeTrustScore(query.agent, query.domain, allVerifications, currentTime);\r\n scores = { [query.domain]: score };\r\n } else {\r\n const scoreMap = computeTrustScores(query.agent, allVerifications, currentTime);\r\n scores = {};\r\n for (const [domain, score] of scoreMap.entries()) {\r\n scores[domain] = score;\r\n }\r\n }\r\n\r\n // Size-limit verifications to most recent MAX_VERIFICATIONS_IN_RESPONSE\r\n const limitedVerifications = relevantVerifications\r\n .slice()\r\n .sort((a, b) => b.timestamp - a.timestamp)\r\n .slice(0, MAX_VERIFICATIONS_IN_RESPONSE);\r\n\r\n const response: ReputationResponse = {\r\n agent: query.agent,\r\n verifications: limitedVerifications,\r\n scores,\r\n };\r\n\r\n if (query.domain !== undefined) {\r\n response.domain = query.domain;\r\n }\r\n\r\n return response;\r\n}\r\n","/**\r\n * Cross-peer reputation synchronization.\r\n * Pull and merge verification records from trusted peers.\r\n */\r\n\r\nimport type { ReputationQuery, ReputationResponse } from './types.js';\r\nimport { verifyVerificationSignature } from './verification.js';\r\nimport type { ReputationStore } from './store.js';\r\n\r\n/**\r\n * Pull reputation data from a peer and merge it into the local store.\r\n *\r\n * Flow:\r\n * 1. Send `reputation_query` to peer via sendMessage\r\n * 2. Receive `reputation_response` with verification records\r\n * 3. For each record: verify signature, check domain matches, check not duplicate\r\n * 4. Append new records to local store\r\n * 5. Return count of added/skipped\r\n *\r\n * @param agentPublicKey - Public key of the agent whose reputation to sync\r\n * @param domain - Domain to sync reputation for\r\n * @param store - Local reputation store to merge records into\r\n * @param sendMessage - Function that sends a reputation_query and returns the response\r\n * @returns Counts of records added and skipped\r\n */\r\nexport async function syncReputationFromPeer(\r\n agentPublicKey: string,\r\n domain: string,\r\n store: ReputationStore,\r\n sendMessage: (type: string, payload: ReputationQuery) => Promise<ReputationResponse>\r\n): Promise<{ added: number; skipped: number }> {\r\n // Build and send the query\r\n const query: ReputationQuery = {\r\n agent: agentPublicKey,\r\n domain,\r\n };\r\n\r\n const response = await sendMessage('reputation_query', query);\r\n\r\n // Build set of existing record IDs for fast deduplication\r\n const existing = await store.getVerifications();\r\n const existingIds = new Set(existing.map(v => v.id));\r\n\r\n let added = 0;\r\n let skipped = 0;\r\n\r\n for (const record of response.verifications) {\r\n // Skip duplicate records (content-addressed by ID)\r\n if (existingIds.has(record.id)) {\r\n skipped++;\r\n continue;\r\n }\r\n\r\n // Verify cryptographic signature before accepting\r\n const sigResult = verifyVerificationSignature(record);\r\n if (!sigResult.valid) {\r\n skipped++;\r\n continue;\r\n }\r\n\r\n // Ensure the record's domain matches what we requested\r\n if (record.domain !== domain) {\r\n skipped++;\r\n continue;\r\n }\r\n\r\n // Add to local store and track for deduplication within this batch\r\n await store.addVerification(record);\r\n existingIds.add(record.id);\r\n added++;\r\n }\r\n\r\n return { added, skipped };\r\n}\r\n"],"mappings":"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","names":["readFileSync","writeFileSync","existsSync","mkdirSync","join","dirname","join","dirname","existsSync","readFileSync","mkdirSync","writeFileSync","createHash"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rookdaemon/agora",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "A coordination network for AI agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",