@voidly/agent-sdk 1.8.2 → 2.0.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.d.mts CHANGED
@@ -50,7 +50,63 @@ interface SendResult {
50
50
  }
51
51
  interface VoidlyAgentConfig {
52
52
  baseUrl?: string;
53
+ /** Enable transparent TOFU — auto-pin keys on first contact (default: true) */
54
+ autoPin?: boolean;
55
+ /** Default retry attempts for send() (default: 3) */
56
+ retries?: number;
57
+ /** Fallback relays — if primary fails, try these in order */
58
+ fallbackRelays?: string[];
59
+ /** Enable message padding to resist traffic analysis (default: true) */
60
+ padding?: boolean;
61
+ /** Enable sealed sender — hide sender DID from relay metadata (default: false) */
62
+ sealedSender?: boolean;
53
63
  }
64
+ interface ListenOptions {
65
+ /** Milliseconds between polls (default: 2000, min: 500) */
66
+ interval?: number;
67
+ /** Only receive messages from this DID */
68
+ from?: string;
69
+ /** Only receive messages in this thread */
70
+ threadId?: string;
71
+ /** Only receive this message type */
72
+ messageType?: string;
73
+ /** Only receive unread messages (default: true) */
74
+ unreadOnly?: boolean;
75
+ /** Auto-mark messages as read after callback (default: true) */
76
+ autoMarkRead?: boolean;
77
+ /** Adaptive polling — reduce frequency when idle, speed up when active (default: true) */
78
+ adaptive?: boolean;
79
+ /** Send heartbeat pings while listening (default: true) */
80
+ heartbeat?: boolean;
81
+ /** Heartbeat interval in milliseconds (default: 60000 = 1 min) */
82
+ heartbeatInterval?: number;
83
+ /** AbortSignal for cancellation */
84
+ signal?: AbortSignal;
85
+ }
86
+ interface ListenHandle {
87
+ /** Stop listening */
88
+ stop: () => void;
89
+ /** Whether the listener is active */
90
+ readonly active: boolean;
91
+ }
92
+ interface RetryOptions {
93
+ /** Max retries (default: 3) */
94
+ maxRetries?: number;
95
+ /** Initial delay ms (default: 500) */
96
+ baseDelay?: number;
97
+ /** Max delay ms (default: 10000) */
98
+ maxDelay?: number;
99
+ }
100
+ interface ConversationMessage {
101
+ id: string;
102
+ from: string;
103
+ content: string;
104
+ timestamp: string;
105
+ signatureValid: boolean;
106
+ messageType: string;
107
+ }
108
+ type MessageHandler = (message: DecryptedMessage) => void | Promise<void>;
109
+ type ErrorHandler = (error: Error) => void;
54
110
  /**
55
111
  * A Voidly Agent with client-side encryption.
56
112
  * Private keys NEVER leave this process.
@@ -76,6 +132,16 @@ declare class VoidlyAgent {
76
132
  private signingKeyPair;
77
133
  private encryptionKeyPair;
78
134
  private baseUrl;
135
+ private autoPin;
136
+ private defaultRetries;
137
+ private fallbackRelays;
138
+ private paddingEnabled;
139
+ private sealedSender;
140
+ private _pinnedDids;
141
+ private _listeners;
142
+ private _conversations;
143
+ private _offlineQueue;
144
+ private _ratchetStates;
79
145
  private constructor();
80
146
  /**
81
147
  * Register a new agent on the Voidly relay.
@@ -108,8 +174,17 @@ declare class VoidlyAgent {
108
174
  encryptionPublicKey: string;
109
175
  };
110
176
  /**
111
- * Send an E2E encrypted message. Encryption happens locally.
112
- * The relay server NEVER sees the plaintext or private keys.
177
+ * Send an E2E encrypted message with hardened security.
178
+ * Encryption happens locally — the relay NEVER sees plaintext or private keys.
179
+ *
180
+ * Security features:
181
+ * - **Message padding** — ciphertext padded to power-of-2 boundary (traffic analysis resistance)
182
+ * - **Hash ratchet** — per-conversation forward secrecy (compromise key[n] can't derive key[n-1])
183
+ * - **Sealed sender** — optionally hide sender DID from relay metadata
184
+ * - **Auto-retry** with exponential backoff on transient failures
185
+ * - **Multi-relay fallback** — try backup relays if primary is down
186
+ * - **Offline queue** — queue messages if all relays fail
187
+ * - **Transparent TOFU** — auto-pin recipient keys on first contact
113
188
  */
114
189
  send(recipientDid: string, message: string, options?: {
115
190
  contentType?: string;
@@ -117,6 +192,14 @@ declare class VoidlyAgent {
117
192
  replyTo?: string;
118
193
  ttl?: number;
119
194
  messageType?: string;
195
+ /** Override default retry count (0 = no retry) */
196
+ retries?: number;
197
+ /** Skip auto key pinning for this message */
198
+ skipPin?: boolean;
199
+ /** Force sealed sender for this message */
200
+ sealedSender?: boolean;
201
+ /** Disable padding for this message */
202
+ noPadding?: boolean;
120
203
  }): Promise<SendResult>;
121
204
  /**
122
205
  * Receive and decrypt messages. Decryption happens locally.
@@ -706,6 +789,168 @@ declare class VoidlyAgent {
706
789
  status: string;
707
790
  warning?: string;
708
791
  }>;
792
+ /**
793
+ * Listen for incoming messages with an event-driven callback.
794
+ * Uses adaptive polling — speeds up when messages are flowing, slows down when idle.
795
+ * Automatically sends heartbeat pings to signal the agent is online.
796
+ *
797
+ * @example
798
+ * ```ts
799
+ * // Simple listener
800
+ * const handle = agent.listen((msg) => {
801
+ * console.log(`${msg.from}: ${msg.content}`);
802
+ * });
803
+ *
804
+ * // Stop after 60 seconds
805
+ * setTimeout(() => handle.stop(), 60000);
806
+ *
807
+ * // With options
808
+ * const handle = agent.listen(
809
+ * (msg) => console.log(msg.content),
810
+ * {
811
+ * interval: 1000, // poll every 1s
812
+ * from: 'did:voidly:x', // only from this agent
813
+ * threadId: 'conv-1', // only this thread
814
+ * adaptive: true, // slow down when idle
815
+ * heartbeat: true, // send pings
816
+ * }
817
+ * );
818
+ * ```
819
+ */
820
+ listen(onMessage: MessageHandler, options?: ListenOptions, onError?: ErrorHandler): ListenHandle;
821
+ /**
822
+ * Listen for messages as an async iterator.
823
+ * Enables `for await` syntax for message processing.
824
+ *
825
+ * @example
826
+ * ```ts
827
+ * for await (const msg of agent.messages({ unreadOnly: true })) {
828
+ * console.log(`${msg.from}: ${msg.content}`);
829
+ * if (msg.content === 'quit') break;
830
+ * }
831
+ * ```
832
+ */
833
+ messages(options?: Omit<ListenOptions, 'signal'> & {
834
+ signal?: AbortSignal;
835
+ }): AsyncGenerator<DecryptedMessage, void, unknown>;
836
+ /**
837
+ * Stop all active listeners. Useful for clean shutdown.
838
+ */
839
+ stopAll(): void;
840
+ /**
841
+ * Start or resume a conversation with another agent.
842
+ * Automatically manages thread IDs, message history, and reply chains.
843
+ *
844
+ * @example
845
+ * ```ts
846
+ * const conv = agent.conversation(otherDid);
847
+ * await conv.say('Hello!');
848
+ * await conv.say('How are you?');
849
+ *
850
+ * // Get full history
851
+ * const history = await conv.history();
852
+ *
853
+ * // Listen for replies in this conversation
854
+ * conv.onReply((msg) => {
855
+ * console.log(`Reply: ${msg.content}`);
856
+ * });
857
+ * ```
858
+ */
859
+ conversation(peerDid: string, threadId?: string): Conversation;
860
+ /** @internal Auto-pin keys on first contact (TOFU) */
861
+ private _autoPinKeys;
862
+ /** @internal Fetch with exponential backoff retry */
863
+ private _fetchWithRetry;
864
+ /**
865
+ * Drain the offline message queue — retry sending queued messages.
866
+ * Call this when connectivity is restored.
867
+ * Returns: number of messages successfully sent.
868
+ */
869
+ drainQueue(): Promise<{
870
+ sent: number;
871
+ failed: number;
872
+ remaining: number;
873
+ }>;
874
+ /** Number of messages waiting in the offline queue */
875
+ get queueLength(): number;
876
+ /**
877
+ * Returns what the relay can and cannot see about this agent.
878
+ * Call this to understand your threat model. Total transparency.
879
+ */
880
+ threatModel(): {
881
+ relayCanSee: string[];
882
+ relayCannotSee: string[];
883
+ protections: string[];
884
+ gaps: string[];
885
+ };
886
+ }
887
+ /**
888
+ * A conversation between two agents.
889
+ * Manages thread IDs, message history, reply chains, and listeners.
890
+ *
891
+ * @example
892
+ * ```ts
893
+ * const conv = agent.conversation('did:voidly:xyz');
894
+ *
895
+ * // Send messages (auto-threaded)
896
+ * await conv.say('Hello!');
897
+ * const reply = await conv.say('What is the status of twitter.com in Iran?');
898
+ *
899
+ * // Get conversation history
900
+ * const history = await conv.history();
901
+ *
902
+ * // Listen for replies
903
+ * conv.onReply((msg) => {
904
+ * console.log(`${msg.from}: ${msg.content}`);
905
+ * });
906
+ *
907
+ * // Wait for next reply (Promise-based)
908
+ * const next = await conv.waitForReply(30000); // 30s timeout
909
+ * ```
910
+ */
911
+ declare class Conversation {
912
+ readonly threadId: string;
913
+ readonly peerDid: string;
914
+ private agent;
915
+ private _lastMessageId;
916
+ private _messageHistory;
917
+ private _listener;
918
+ private _replyHandlers;
919
+ /** @internal */
920
+ constructor(agent: VoidlyAgent, peerDid: string, threadId: string);
921
+ /**
922
+ * Send a message in this conversation. Auto-threaded and auto-linked to previous message.
923
+ */
924
+ say(content: string, options?: {
925
+ contentType?: string;
926
+ messageType?: string;
927
+ ttl?: number;
928
+ }): Promise<SendResult>;
929
+ /**
930
+ * Get conversation history (both sent and received messages in this thread).
931
+ */
932
+ history(options?: {
933
+ limit?: number;
934
+ }): Promise<ConversationMessage[]>;
935
+ /**
936
+ * Register a callback for replies in this conversation.
937
+ */
938
+ onReply(handler: MessageHandler): void;
939
+ /**
940
+ * Wait for the next reply in this conversation (Promise-based).
941
+ *
942
+ * @param timeoutMs - Maximum time to wait (default: 30000ms)
943
+ * @throws Error on timeout
944
+ */
945
+ waitForReply(timeoutMs?: number): Promise<DecryptedMessage>;
946
+ /**
947
+ * Stop listening for replies and clean up.
948
+ */
949
+ close(): void;
950
+ /** Number of messages tracked locally */
951
+ get length(): number;
952
+ /** The last message in this conversation */
953
+ get lastMessage(): ConversationMessage | null;
709
954
  }
710
955
 
711
- export { type AgentIdentity, type AgentProfile, type DecryptedMessage, type SendResult, VoidlyAgent, type VoidlyAgentConfig };
956
+ export { type AgentIdentity, type AgentProfile, Conversation, type ConversationMessage, type DecryptedMessage, type ErrorHandler, type ListenHandle, type ListenOptions, type MessageHandler, type RetryOptions, type SendResult, VoidlyAgent, type VoidlyAgentConfig };
package/dist/index.d.ts CHANGED
@@ -50,7 +50,63 @@ interface SendResult {
50
50
  }
51
51
  interface VoidlyAgentConfig {
52
52
  baseUrl?: string;
53
+ /** Enable transparent TOFU — auto-pin keys on first contact (default: true) */
54
+ autoPin?: boolean;
55
+ /** Default retry attempts for send() (default: 3) */
56
+ retries?: number;
57
+ /** Fallback relays — if primary fails, try these in order */
58
+ fallbackRelays?: string[];
59
+ /** Enable message padding to resist traffic analysis (default: true) */
60
+ padding?: boolean;
61
+ /** Enable sealed sender — hide sender DID from relay metadata (default: false) */
62
+ sealedSender?: boolean;
53
63
  }
64
+ interface ListenOptions {
65
+ /** Milliseconds between polls (default: 2000, min: 500) */
66
+ interval?: number;
67
+ /** Only receive messages from this DID */
68
+ from?: string;
69
+ /** Only receive messages in this thread */
70
+ threadId?: string;
71
+ /** Only receive this message type */
72
+ messageType?: string;
73
+ /** Only receive unread messages (default: true) */
74
+ unreadOnly?: boolean;
75
+ /** Auto-mark messages as read after callback (default: true) */
76
+ autoMarkRead?: boolean;
77
+ /** Adaptive polling — reduce frequency when idle, speed up when active (default: true) */
78
+ adaptive?: boolean;
79
+ /** Send heartbeat pings while listening (default: true) */
80
+ heartbeat?: boolean;
81
+ /** Heartbeat interval in milliseconds (default: 60000 = 1 min) */
82
+ heartbeatInterval?: number;
83
+ /** AbortSignal for cancellation */
84
+ signal?: AbortSignal;
85
+ }
86
+ interface ListenHandle {
87
+ /** Stop listening */
88
+ stop: () => void;
89
+ /** Whether the listener is active */
90
+ readonly active: boolean;
91
+ }
92
+ interface RetryOptions {
93
+ /** Max retries (default: 3) */
94
+ maxRetries?: number;
95
+ /** Initial delay ms (default: 500) */
96
+ baseDelay?: number;
97
+ /** Max delay ms (default: 10000) */
98
+ maxDelay?: number;
99
+ }
100
+ interface ConversationMessage {
101
+ id: string;
102
+ from: string;
103
+ content: string;
104
+ timestamp: string;
105
+ signatureValid: boolean;
106
+ messageType: string;
107
+ }
108
+ type MessageHandler = (message: DecryptedMessage) => void | Promise<void>;
109
+ type ErrorHandler = (error: Error) => void;
54
110
  /**
55
111
  * A Voidly Agent with client-side encryption.
56
112
  * Private keys NEVER leave this process.
@@ -76,6 +132,16 @@ declare class VoidlyAgent {
76
132
  private signingKeyPair;
77
133
  private encryptionKeyPair;
78
134
  private baseUrl;
135
+ private autoPin;
136
+ private defaultRetries;
137
+ private fallbackRelays;
138
+ private paddingEnabled;
139
+ private sealedSender;
140
+ private _pinnedDids;
141
+ private _listeners;
142
+ private _conversations;
143
+ private _offlineQueue;
144
+ private _ratchetStates;
79
145
  private constructor();
80
146
  /**
81
147
  * Register a new agent on the Voidly relay.
@@ -108,8 +174,17 @@ declare class VoidlyAgent {
108
174
  encryptionPublicKey: string;
109
175
  };
110
176
  /**
111
- * Send an E2E encrypted message. Encryption happens locally.
112
- * The relay server NEVER sees the plaintext or private keys.
177
+ * Send an E2E encrypted message with hardened security.
178
+ * Encryption happens locally — the relay NEVER sees plaintext or private keys.
179
+ *
180
+ * Security features:
181
+ * - **Message padding** — ciphertext padded to power-of-2 boundary (traffic analysis resistance)
182
+ * - **Hash ratchet** — per-conversation forward secrecy (compromise key[n] can't derive key[n-1])
183
+ * - **Sealed sender** — optionally hide sender DID from relay metadata
184
+ * - **Auto-retry** with exponential backoff on transient failures
185
+ * - **Multi-relay fallback** — try backup relays if primary is down
186
+ * - **Offline queue** — queue messages if all relays fail
187
+ * - **Transparent TOFU** — auto-pin recipient keys on first contact
113
188
  */
114
189
  send(recipientDid: string, message: string, options?: {
115
190
  contentType?: string;
@@ -117,6 +192,14 @@ declare class VoidlyAgent {
117
192
  replyTo?: string;
118
193
  ttl?: number;
119
194
  messageType?: string;
195
+ /** Override default retry count (0 = no retry) */
196
+ retries?: number;
197
+ /** Skip auto key pinning for this message */
198
+ skipPin?: boolean;
199
+ /** Force sealed sender for this message */
200
+ sealedSender?: boolean;
201
+ /** Disable padding for this message */
202
+ noPadding?: boolean;
120
203
  }): Promise<SendResult>;
121
204
  /**
122
205
  * Receive and decrypt messages. Decryption happens locally.
@@ -706,6 +789,168 @@ declare class VoidlyAgent {
706
789
  status: string;
707
790
  warning?: string;
708
791
  }>;
792
+ /**
793
+ * Listen for incoming messages with an event-driven callback.
794
+ * Uses adaptive polling — speeds up when messages are flowing, slows down when idle.
795
+ * Automatically sends heartbeat pings to signal the agent is online.
796
+ *
797
+ * @example
798
+ * ```ts
799
+ * // Simple listener
800
+ * const handle = agent.listen((msg) => {
801
+ * console.log(`${msg.from}: ${msg.content}`);
802
+ * });
803
+ *
804
+ * // Stop after 60 seconds
805
+ * setTimeout(() => handle.stop(), 60000);
806
+ *
807
+ * // With options
808
+ * const handle = agent.listen(
809
+ * (msg) => console.log(msg.content),
810
+ * {
811
+ * interval: 1000, // poll every 1s
812
+ * from: 'did:voidly:x', // only from this agent
813
+ * threadId: 'conv-1', // only this thread
814
+ * adaptive: true, // slow down when idle
815
+ * heartbeat: true, // send pings
816
+ * }
817
+ * );
818
+ * ```
819
+ */
820
+ listen(onMessage: MessageHandler, options?: ListenOptions, onError?: ErrorHandler): ListenHandle;
821
+ /**
822
+ * Listen for messages as an async iterator.
823
+ * Enables `for await` syntax for message processing.
824
+ *
825
+ * @example
826
+ * ```ts
827
+ * for await (const msg of agent.messages({ unreadOnly: true })) {
828
+ * console.log(`${msg.from}: ${msg.content}`);
829
+ * if (msg.content === 'quit') break;
830
+ * }
831
+ * ```
832
+ */
833
+ messages(options?: Omit<ListenOptions, 'signal'> & {
834
+ signal?: AbortSignal;
835
+ }): AsyncGenerator<DecryptedMessage, void, unknown>;
836
+ /**
837
+ * Stop all active listeners. Useful for clean shutdown.
838
+ */
839
+ stopAll(): void;
840
+ /**
841
+ * Start or resume a conversation with another agent.
842
+ * Automatically manages thread IDs, message history, and reply chains.
843
+ *
844
+ * @example
845
+ * ```ts
846
+ * const conv = agent.conversation(otherDid);
847
+ * await conv.say('Hello!');
848
+ * await conv.say('How are you?');
849
+ *
850
+ * // Get full history
851
+ * const history = await conv.history();
852
+ *
853
+ * // Listen for replies in this conversation
854
+ * conv.onReply((msg) => {
855
+ * console.log(`Reply: ${msg.content}`);
856
+ * });
857
+ * ```
858
+ */
859
+ conversation(peerDid: string, threadId?: string): Conversation;
860
+ /** @internal Auto-pin keys on first contact (TOFU) */
861
+ private _autoPinKeys;
862
+ /** @internal Fetch with exponential backoff retry */
863
+ private _fetchWithRetry;
864
+ /**
865
+ * Drain the offline message queue — retry sending queued messages.
866
+ * Call this when connectivity is restored.
867
+ * Returns: number of messages successfully sent.
868
+ */
869
+ drainQueue(): Promise<{
870
+ sent: number;
871
+ failed: number;
872
+ remaining: number;
873
+ }>;
874
+ /** Number of messages waiting in the offline queue */
875
+ get queueLength(): number;
876
+ /**
877
+ * Returns what the relay can and cannot see about this agent.
878
+ * Call this to understand your threat model. Total transparency.
879
+ */
880
+ threatModel(): {
881
+ relayCanSee: string[];
882
+ relayCannotSee: string[];
883
+ protections: string[];
884
+ gaps: string[];
885
+ };
886
+ }
887
+ /**
888
+ * A conversation between two agents.
889
+ * Manages thread IDs, message history, reply chains, and listeners.
890
+ *
891
+ * @example
892
+ * ```ts
893
+ * const conv = agent.conversation('did:voidly:xyz');
894
+ *
895
+ * // Send messages (auto-threaded)
896
+ * await conv.say('Hello!');
897
+ * const reply = await conv.say('What is the status of twitter.com in Iran?');
898
+ *
899
+ * // Get conversation history
900
+ * const history = await conv.history();
901
+ *
902
+ * // Listen for replies
903
+ * conv.onReply((msg) => {
904
+ * console.log(`${msg.from}: ${msg.content}`);
905
+ * });
906
+ *
907
+ * // Wait for next reply (Promise-based)
908
+ * const next = await conv.waitForReply(30000); // 30s timeout
909
+ * ```
910
+ */
911
+ declare class Conversation {
912
+ readonly threadId: string;
913
+ readonly peerDid: string;
914
+ private agent;
915
+ private _lastMessageId;
916
+ private _messageHistory;
917
+ private _listener;
918
+ private _replyHandlers;
919
+ /** @internal */
920
+ constructor(agent: VoidlyAgent, peerDid: string, threadId: string);
921
+ /**
922
+ * Send a message in this conversation. Auto-threaded and auto-linked to previous message.
923
+ */
924
+ say(content: string, options?: {
925
+ contentType?: string;
926
+ messageType?: string;
927
+ ttl?: number;
928
+ }): Promise<SendResult>;
929
+ /**
930
+ * Get conversation history (both sent and received messages in this thread).
931
+ */
932
+ history(options?: {
933
+ limit?: number;
934
+ }): Promise<ConversationMessage[]>;
935
+ /**
936
+ * Register a callback for replies in this conversation.
937
+ */
938
+ onReply(handler: MessageHandler): void;
939
+ /**
940
+ * Wait for the next reply in this conversation (Promise-based).
941
+ *
942
+ * @param timeoutMs - Maximum time to wait (default: 30000ms)
943
+ * @throws Error on timeout
944
+ */
945
+ waitForReply(timeoutMs?: number): Promise<DecryptedMessage>;
946
+ /**
947
+ * Stop listening for replies and clean up.
948
+ */
949
+ close(): void;
950
+ /** Number of messages tracked locally */
951
+ get length(): number;
952
+ /** The last message in this conversation */
953
+ get lastMessage(): ConversationMessage | null;
709
954
  }
710
955
 
711
- export { type AgentIdentity, type AgentProfile, type DecryptedMessage, type SendResult, VoidlyAgent, type VoidlyAgentConfig };
956
+ export { type AgentIdentity, type AgentProfile, Conversation, type ConversationMessage, type DecryptedMessage, type ErrorHandler, type ListenHandle, type ListenOptions, type MessageHandler, type RetryOptions, type SendResult, VoidlyAgent, type VoidlyAgentConfig };