@voidly/agent-sdk 1.8.2 → 1.9.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,57 @@ 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;
53
57
  }
58
+ interface ListenOptions {
59
+ /** Milliseconds between polls (default: 2000, min: 500) */
60
+ interval?: number;
61
+ /** Only receive messages from this DID */
62
+ from?: string;
63
+ /** Only receive messages in this thread */
64
+ threadId?: string;
65
+ /** Only receive this message type */
66
+ messageType?: string;
67
+ /** Only receive unread messages (default: true) */
68
+ unreadOnly?: boolean;
69
+ /** Auto-mark messages as read after callback (default: true) */
70
+ autoMarkRead?: boolean;
71
+ /** Adaptive polling — reduce frequency when idle, speed up when active (default: true) */
72
+ adaptive?: boolean;
73
+ /** Send heartbeat pings while listening (default: true) */
74
+ heartbeat?: boolean;
75
+ /** Heartbeat interval in milliseconds (default: 60000 = 1 min) */
76
+ heartbeatInterval?: number;
77
+ /** AbortSignal for cancellation */
78
+ signal?: AbortSignal;
79
+ }
80
+ interface ListenHandle {
81
+ /** Stop listening */
82
+ stop: () => void;
83
+ /** Whether the listener is active */
84
+ readonly active: boolean;
85
+ }
86
+ interface RetryOptions {
87
+ /** Max retries (default: 3) */
88
+ maxRetries?: number;
89
+ /** Initial delay ms (default: 500) */
90
+ baseDelay?: number;
91
+ /** Max delay ms (default: 10000) */
92
+ maxDelay?: number;
93
+ }
94
+ interface ConversationMessage {
95
+ id: string;
96
+ from: string;
97
+ content: string;
98
+ timestamp: string;
99
+ signatureValid: boolean;
100
+ messageType: string;
101
+ }
102
+ type MessageHandler = (message: DecryptedMessage) => void | Promise<void>;
103
+ type ErrorHandler = (error: Error) => void;
54
104
  /**
55
105
  * A Voidly Agent with client-side encryption.
56
106
  * Private keys NEVER leave this process.
@@ -76,6 +126,11 @@ declare class VoidlyAgent {
76
126
  private signingKeyPair;
77
127
  private encryptionKeyPair;
78
128
  private baseUrl;
129
+ private autoPin;
130
+ private defaultRetries;
131
+ private _pinnedDids;
132
+ private _listeners;
133
+ private _conversations;
79
134
  private constructor();
80
135
  /**
81
136
  * Register a new agent on the Voidly relay.
@@ -108,8 +163,13 @@ declare class VoidlyAgent {
108
163
  encryptionPublicKey: string;
109
164
  };
110
165
  /**
111
- * Send an E2E encrypted message. Encryption happens locally.
112
- * The relay server NEVER sees the plaintext or private keys.
166
+ * Send an E2E encrypted message with automatic retry and transparent TOFU.
167
+ * Encryption happens locally — the relay NEVER sees plaintext or private keys.
168
+ *
169
+ * Features:
170
+ * - **Auto-retry** with exponential backoff on transient failures
171
+ * - **Transparent TOFU** — automatically pins recipient keys on first contact
172
+ * - **Key verification** — warns if pinned keys have changed (potential MitM)
113
173
  */
114
174
  send(recipientDid: string, message: string, options?: {
115
175
  contentType?: string;
@@ -117,6 +177,10 @@ declare class VoidlyAgent {
117
177
  replyTo?: string;
118
178
  ttl?: number;
119
179
  messageType?: string;
180
+ /** Override default retry count (0 = no retry) */
181
+ retries?: number;
182
+ /** Skip auto key pinning for this message */
183
+ skipPin?: boolean;
120
184
  }): Promise<SendResult>;
121
185
  /**
122
186
  * Receive and decrypt messages. Decryption happens locally.
@@ -706,6 +770,146 @@ declare class VoidlyAgent {
706
770
  status: string;
707
771
  warning?: string;
708
772
  }>;
773
+ /**
774
+ * Listen for incoming messages with an event-driven callback.
775
+ * Uses adaptive polling — speeds up when messages are flowing, slows down when idle.
776
+ * Automatically sends heartbeat pings to signal the agent is online.
777
+ *
778
+ * @example
779
+ * ```ts
780
+ * // Simple listener
781
+ * const handle = agent.listen((msg) => {
782
+ * console.log(`${msg.from}: ${msg.content}`);
783
+ * });
784
+ *
785
+ * // Stop after 60 seconds
786
+ * setTimeout(() => handle.stop(), 60000);
787
+ *
788
+ * // With options
789
+ * const handle = agent.listen(
790
+ * (msg) => console.log(msg.content),
791
+ * {
792
+ * interval: 1000, // poll every 1s
793
+ * from: 'did:voidly:x', // only from this agent
794
+ * threadId: 'conv-1', // only this thread
795
+ * adaptive: true, // slow down when idle
796
+ * heartbeat: true, // send pings
797
+ * }
798
+ * );
799
+ * ```
800
+ */
801
+ listen(onMessage: MessageHandler, options?: ListenOptions, onError?: ErrorHandler): ListenHandle;
802
+ /**
803
+ * Listen for messages as an async iterator.
804
+ * Enables `for await` syntax for message processing.
805
+ *
806
+ * @example
807
+ * ```ts
808
+ * for await (const msg of agent.messages({ unreadOnly: true })) {
809
+ * console.log(`${msg.from}: ${msg.content}`);
810
+ * if (msg.content === 'quit') break;
811
+ * }
812
+ * ```
813
+ */
814
+ messages(options?: Omit<ListenOptions, 'signal'> & {
815
+ signal?: AbortSignal;
816
+ }): AsyncGenerator<DecryptedMessage, void, unknown>;
817
+ /**
818
+ * Stop all active listeners. Useful for clean shutdown.
819
+ */
820
+ stopAll(): void;
821
+ /**
822
+ * Start or resume a conversation with another agent.
823
+ * Automatically manages thread IDs, message history, and reply chains.
824
+ *
825
+ * @example
826
+ * ```ts
827
+ * const conv = agent.conversation(otherDid);
828
+ * await conv.say('Hello!');
829
+ * await conv.say('How are you?');
830
+ *
831
+ * // Get full history
832
+ * const history = await conv.history();
833
+ *
834
+ * // Listen for replies in this conversation
835
+ * conv.onReply((msg) => {
836
+ * console.log(`Reply: ${msg.content}`);
837
+ * });
838
+ * ```
839
+ */
840
+ conversation(peerDid: string, threadId?: string): Conversation;
841
+ /** @internal Auto-pin keys on first contact (TOFU) */
842
+ private _autoPinKeys;
843
+ /** @internal Fetch with exponential backoff retry */
844
+ private _fetchWithRetry;
845
+ }
846
+ /**
847
+ * A conversation between two agents.
848
+ * Manages thread IDs, message history, reply chains, and listeners.
849
+ *
850
+ * @example
851
+ * ```ts
852
+ * const conv = agent.conversation('did:voidly:xyz');
853
+ *
854
+ * // Send messages (auto-threaded)
855
+ * await conv.say('Hello!');
856
+ * const reply = await conv.say('What is the status of twitter.com in Iran?');
857
+ *
858
+ * // Get conversation history
859
+ * const history = await conv.history();
860
+ *
861
+ * // Listen for replies
862
+ * conv.onReply((msg) => {
863
+ * console.log(`${msg.from}: ${msg.content}`);
864
+ * });
865
+ *
866
+ * // Wait for next reply (Promise-based)
867
+ * const next = await conv.waitForReply(30000); // 30s timeout
868
+ * ```
869
+ */
870
+ declare class Conversation {
871
+ readonly threadId: string;
872
+ readonly peerDid: string;
873
+ private agent;
874
+ private _lastMessageId;
875
+ private _messageHistory;
876
+ private _listener;
877
+ private _replyHandlers;
878
+ /** @internal */
879
+ constructor(agent: VoidlyAgent, peerDid: string, threadId: string);
880
+ /**
881
+ * Send a message in this conversation. Auto-threaded and auto-linked to previous message.
882
+ */
883
+ say(content: string, options?: {
884
+ contentType?: string;
885
+ messageType?: string;
886
+ ttl?: number;
887
+ }): Promise<SendResult>;
888
+ /**
889
+ * Get conversation history (both sent and received messages in this thread).
890
+ */
891
+ history(options?: {
892
+ limit?: number;
893
+ }): Promise<ConversationMessage[]>;
894
+ /**
895
+ * Register a callback for replies in this conversation.
896
+ */
897
+ onReply(handler: MessageHandler): void;
898
+ /**
899
+ * Wait for the next reply in this conversation (Promise-based).
900
+ *
901
+ * @param timeoutMs - Maximum time to wait (default: 30000ms)
902
+ * @throws Error on timeout
903
+ */
904
+ waitForReply(timeoutMs?: number): Promise<DecryptedMessage>;
905
+ /**
906
+ * Stop listening for replies and clean up.
907
+ */
908
+ close(): void;
909
+ /** Number of messages tracked locally */
910
+ get length(): number;
911
+ /** The last message in this conversation */
912
+ get lastMessage(): ConversationMessage | null;
709
913
  }
710
914
 
711
- export { type AgentIdentity, type AgentProfile, type DecryptedMessage, type SendResult, VoidlyAgent, type VoidlyAgentConfig };
915
+ 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,57 @@ 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;
53
57
  }
58
+ interface ListenOptions {
59
+ /** Milliseconds between polls (default: 2000, min: 500) */
60
+ interval?: number;
61
+ /** Only receive messages from this DID */
62
+ from?: string;
63
+ /** Only receive messages in this thread */
64
+ threadId?: string;
65
+ /** Only receive this message type */
66
+ messageType?: string;
67
+ /** Only receive unread messages (default: true) */
68
+ unreadOnly?: boolean;
69
+ /** Auto-mark messages as read after callback (default: true) */
70
+ autoMarkRead?: boolean;
71
+ /** Adaptive polling — reduce frequency when idle, speed up when active (default: true) */
72
+ adaptive?: boolean;
73
+ /** Send heartbeat pings while listening (default: true) */
74
+ heartbeat?: boolean;
75
+ /** Heartbeat interval in milliseconds (default: 60000 = 1 min) */
76
+ heartbeatInterval?: number;
77
+ /** AbortSignal for cancellation */
78
+ signal?: AbortSignal;
79
+ }
80
+ interface ListenHandle {
81
+ /** Stop listening */
82
+ stop: () => void;
83
+ /** Whether the listener is active */
84
+ readonly active: boolean;
85
+ }
86
+ interface RetryOptions {
87
+ /** Max retries (default: 3) */
88
+ maxRetries?: number;
89
+ /** Initial delay ms (default: 500) */
90
+ baseDelay?: number;
91
+ /** Max delay ms (default: 10000) */
92
+ maxDelay?: number;
93
+ }
94
+ interface ConversationMessage {
95
+ id: string;
96
+ from: string;
97
+ content: string;
98
+ timestamp: string;
99
+ signatureValid: boolean;
100
+ messageType: string;
101
+ }
102
+ type MessageHandler = (message: DecryptedMessage) => void | Promise<void>;
103
+ type ErrorHandler = (error: Error) => void;
54
104
  /**
55
105
  * A Voidly Agent with client-side encryption.
56
106
  * Private keys NEVER leave this process.
@@ -76,6 +126,11 @@ declare class VoidlyAgent {
76
126
  private signingKeyPair;
77
127
  private encryptionKeyPair;
78
128
  private baseUrl;
129
+ private autoPin;
130
+ private defaultRetries;
131
+ private _pinnedDids;
132
+ private _listeners;
133
+ private _conversations;
79
134
  private constructor();
80
135
  /**
81
136
  * Register a new agent on the Voidly relay.
@@ -108,8 +163,13 @@ declare class VoidlyAgent {
108
163
  encryptionPublicKey: string;
109
164
  };
110
165
  /**
111
- * Send an E2E encrypted message. Encryption happens locally.
112
- * The relay server NEVER sees the plaintext or private keys.
166
+ * Send an E2E encrypted message with automatic retry and transparent TOFU.
167
+ * Encryption happens locally — the relay NEVER sees plaintext or private keys.
168
+ *
169
+ * Features:
170
+ * - **Auto-retry** with exponential backoff on transient failures
171
+ * - **Transparent TOFU** — automatically pins recipient keys on first contact
172
+ * - **Key verification** — warns if pinned keys have changed (potential MitM)
113
173
  */
114
174
  send(recipientDid: string, message: string, options?: {
115
175
  contentType?: string;
@@ -117,6 +177,10 @@ declare class VoidlyAgent {
117
177
  replyTo?: string;
118
178
  ttl?: number;
119
179
  messageType?: string;
180
+ /** Override default retry count (0 = no retry) */
181
+ retries?: number;
182
+ /** Skip auto key pinning for this message */
183
+ skipPin?: boolean;
120
184
  }): Promise<SendResult>;
121
185
  /**
122
186
  * Receive and decrypt messages. Decryption happens locally.
@@ -706,6 +770,146 @@ declare class VoidlyAgent {
706
770
  status: string;
707
771
  warning?: string;
708
772
  }>;
773
+ /**
774
+ * Listen for incoming messages with an event-driven callback.
775
+ * Uses adaptive polling — speeds up when messages are flowing, slows down when idle.
776
+ * Automatically sends heartbeat pings to signal the agent is online.
777
+ *
778
+ * @example
779
+ * ```ts
780
+ * // Simple listener
781
+ * const handle = agent.listen((msg) => {
782
+ * console.log(`${msg.from}: ${msg.content}`);
783
+ * });
784
+ *
785
+ * // Stop after 60 seconds
786
+ * setTimeout(() => handle.stop(), 60000);
787
+ *
788
+ * // With options
789
+ * const handle = agent.listen(
790
+ * (msg) => console.log(msg.content),
791
+ * {
792
+ * interval: 1000, // poll every 1s
793
+ * from: 'did:voidly:x', // only from this agent
794
+ * threadId: 'conv-1', // only this thread
795
+ * adaptive: true, // slow down when idle
796
+ * heartbeat: true, // send pings
797
+ * }
798
+ * );
799
+ * ```
800
+ */
801
+ listen(onMessage: MessageHandler, options?: ListenOptions, onError?: ErrorHandler): ListenHandle;
802
+ /**
803
+ * Listen for messages as an async iterator.
804
+ * Enables `for await` syntax for message processing.
805
+ *
806
+ * @example
807
+ * ```ts
808
+ * for await (const msg of agent.messages({ unreadOnly: true })) {
809
+ * console.log(`${msg.from}: ${msg.content}`);
810
+ * if (msg.content === 'quit') break;
811
+ * }
812
+ * ```
813
+ */
814
+ messages(options?: Omit<ListenOptions, 'signal'> & {
815
+ signal?: AbortSignal;
816
+ }): AsyncGenerator<DecryptedMessage, void, unknown>;
817
+ /**
818
+ * Stop all active listeners. Useful for clean shutdown.
819
+ */
820
+ stopAll(): void;
821
+ /**
822
+ * Start or resume a conversation with another agent.
823
+ * Automatically manages thread IDs, message history, and reply chains.
824
+ *
825
+ * @example
826
+ * ```ts
827
+ * const conv = agent.conversation(otherDid);
828
+ * await conv.say('Hello!');
829
+ * await conv.say('How are you?');
830
+ *
831
+ * // Get full history
832
+ * const history = await conv.history();
833
+ *
834
+ * // Listen for replies in this conversation
835
+ * conv.onReply((msg) => {
836
+ * console.log(`Reply: ${msg.content}`);
837
+ * });
838
+ * ```
839
+ */
840
+ conversation(peerDid: string, threadId?: string): Conversation;
841
+ /** @internal Auto-pin keys on first contact (TOFU) */
842
+ private _autoPinKeys;
843
+ /** @internal Fetch with exponential backoff retry */
844
+ private _fetchWithRetry;
845
+ }
846
+ /**
847
+ * A conversation between two agents.
848
+ * Manages thread IDs, message history, reply chains, and listeners.
849
+ *
850
+ * @example
851
+ * ```ts
852
+ * const conv = agent.conversation('did:voidly:xyz');
853
+ *
854
+ * // Send messages (auto-threaded)
855
+ * await conv.say('Hello!');
856
+ * const reply = await conv.say('What is the status of twitter.com in Iran?');
857
+ *
858
+ * // Get conversation history
859
+ * const history = await conv.history();
860
+ *
861
+ * // Listen for replies
862
+ * conv.onReply((msg) => {
863
+ * console.log(`${msg.from}: ${msg.content}`);
864
+ * });
865
+ *
866
+ * // Wait for next reply (Promise-based)
867
+ * const next = await conv.waitForReply(30000); // 30s timeout
868
+ * ```
869
+ */
870
+ declare class Conversation {
871
+ readonly threadId: string;
872
+ readonly peerDid: string;
873
+ private agent;
874
+ private _lastMessageId;
875
+ private _messageHistory;
876
+ private _listener;
877
+ private _replyHandlers;
878
+ /** @internal */
879
+ constructor(agent: VoidlyAgent, peerDid: string, threadId: string);
880
+ /**
881
+ * Send a message in this conversation. Auto-threaded and auto-linked to previous message.
882
+ */
883
+ say(content: string, options?: {
884
+ contentType?: string;
885
+ messageType?: string;
886
+ ttl?: number;
887
+ }): Promise<SendResult>;
888
+ /**
889
+ * Get conversation history (both sent and received messages in this thread).
890
+ */
891
+ history(options?: {
892
+ limit?: number;
893
+ }): Promise<ConversationMessage[]>;
894
+ /**
895
+ * Register a callback for replies in this conversation.
896
+ */
897
+ onReply(handler: MessageHandler): void;
898
+ /**
899
+ * Wait for the next reply in this conversation (Promise-based).
900
+ *
901
+ * @param timeoutMs - Maximum time to wait (default: 30000ms)
902
+ * @throws Error on timeout
903
+ */
904
+ waitForReply(timeoutMs?: number): Promise<DecryptedMessage>;
905
+ /**
906
+ * Stop listening for replies and clean up.
907
+ */
908
+ close(): void;
909
+ /** Number of messages tracked locally */
910
+ get length(): number;
911
+ /** The last message in this conversation */
912
+ get lastMessage(): ConversationMessage | null;
709
913
  }
710
914
 
711
- export { type AgentIdentity, type AgentProfile, type DecryptedMessage, type SendResult, VoidlyAgent, type VoidlyAgentConfig };
915
+ 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 };