@recursiv/sdk 0.7.82 → 0.7.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +128 -1
  2. package/dist/assistant/fixedCardData.d.ts +91 -0
  3. package/dist/assistant/fixedCardData.js +372 -0
  4. package/dist/assistant/fixedCards.d.ts +35 -0
  5. package/dist/assistant/fixedCards.js +904 -0
  6. package/dist/assistant/hiddenText.d.ts +24 -0
  7. package/dist/assistant/hiddenText.js +263 -0
  8. package/dist/assistant/index.d.ts +30 -0
  9. package/dist/assistant/index.js +30 -0
  10. package/dist/assistant/render.d.ts +31 -0
  11. package/dist/assistant/render.js +434 -0
  12. package/dist/assistant/subjectBundle.d.ts +30 -0
  13. package/dist/assistant/subjectBundle.js +535 -0
  14. package/dist/assistant/turnCaps.d.ts +3 -0
  15. package/dist/assistant/turnCaps.js +141 -0
  16. package/dist/assistant/types.d.ts +569 -0
  17. package/dist/assistant/types.js +26 -0
  18. package/dist/assistant-surfaces.d.ts +42 -0
  19. package/dist/assistant-surfaces.js +113 -0
  20. package/dist/index.d.ts +15 -6
  21. package/dist/index.js +8 -1
  22. package/dist/privacy/facts.generated.d.ts +79 -0
  23. package/dist/privacy/facts.generated.js +93 -0
  24. package/dist/privacy/index.d.ts +48 -0
  25. package/dist/privacy/index.js +42 -0
  26. package/dist/realtime.d.ts +10 -3
  27. package/dist/realtime.js +11 -2
  28. package/dist/resources/agents.d.ts +6 -1
  29. package/dist/resources/agents.js +12 -2
  30. package/dist/resources/discovery-merge.d.ts +17 -1
  31. package/dist/resources/discovery-merge.js +84 -4
  32. package/dist/resources/discovery-progressive.d.ts +4 -3
  33. package/dist/resources/discovery-progressive.js +11 -8
  34. package/dist/resources/discovery.d.ts +9 -1
  35. package/dist/resources/discovery.js +62 -15
  36. package/dist/resources/e2ee.d.ts +56 -0
  37. package/dist/resources/e2ee.js +21 -0
  38. package/dist/resources/feeds.d.ts +15 -6
  39. package/dist/resources/feeds.js +2 -1
  40. package/dist/resources/integrations.d.ts +3 -0
  41. package/dist/resources/link-preview.d.ts +42 -1
  42. package/dist/resources/link-preview.js +36 -2
  43. package/dist/resources/network.d.ts +6 -0
  44. package/dist/resources/privacy.d.ts +13 -0
  45. package/dist/resources/privacy.js +14 -0
  46. package/dist/resources/project-brain.d.ts +8 -0
  47. package/dist/resources/protocols.d.ts +99 -11
  48. package/dist/resources/protocols.js +45 -0
  49. package/dist/resources/sealed.d.ts +127 -7
  50. package/dist/resources/sealed.js +33 -6
  51. package/dist/resources/settings.d.ts +3 -1
  52. package/dist/resources/settings.js +3 -1
  53. package/dist/resources/users.d.ts +11 -2
  54. package/dist/resources/users.js +1 -1
  55. package/dist/social-protocol-ids.d.ts +28 -0
  56. package/dist/social-protocol-ids.js +30 -0
  57. package/dist/types.d.ts +71 -0
  58. package/llm.md +45 -4
  59. package/package.json +11 -1
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Server-driven kill switches for the assistant's surfaces (George plan step G7a).
3
+ *
4
+ * A flag compiled into a phone app cannot be switched off once that build ships, so every place an app shows the
5
+ * assistant asks the server first. The switches are the app's project setting `assistant.surfaces`, and they arrive
6
+ * inside responses the app already fetches: `users.me()` (the boot read) and `network.getConfig()` both carry
7
+ * `data.assistant.surfaces`. An app's owner or admin changes them with
8
+ * `projectBrain.updateSettings(projectId, { assistant: { surfaces: { post_button: true } } })`.
9
+ *
10
+ * Fail closed: show a surface only when {@link assistantSurfaceOn} says so, which is only for an explicit `true`. A
11
+ * response from an older server (no `assistant`), a read that failed, a malformed value and an id this SDK does not
12
+ * know are all off.
13
+ *
14
+ * The server keeps the same list (packages/server/src/features/assistant/surfaces.ts); a contract test holds them equal.
15
+ */
16
+ /**
17
+ * Every assistant surface an app may show, one switch each. All default off. Step numbers are the George plan's §8.
18
+ * New ids are appended; an id is never renamed.
19
+ */
20
+ export const ASSISTANT_SURFACE_IDS = [
21
+ // The G glyph on a post (feed, post page, network or bridged post, clip, article, story), with its long-press item
22
+ // and screen-reader action. G3.
23
+ 'post_button',
24
+ // The answer sheet: the answer streamed in place (bottom sheet on phones, right rail on desktop), its chips and
25
+ // follow-ups. G11 (G3's interim sheet rides it too).
26
+ 'answer_sheet',
27
+ // "Ask about these results" on a question-shaped search, and the Cmd-K ask. G4; answers G23a.
28
+ 'search_row',
29
+ // The glyph in the top bar that opens the assistant tab. G4.
30
+ 'top_bar',
31
+ // The assistant's one row in the Chat inbox. G4.
32
+ 'inbox_row',
33
+ // Mentioning the assistant in a reply (the `ai:george` chip), raw `@askgeorge` routing, and the push that an answer
34
+ // is ready. G22.
35
+ 'mentions',
36
+ // Asking about picked messages in an ordinary (not encrypted) DM or group chat, and mentioning the assistant in a
37
+ // small closed group chat. G20.
38
+ 'chats',
39
+ // Asking the Sealed assistant about one message in an encrypted chat, on the phone only. G21.
40
+ 'e2ee_asks',
41
+ // "Catch me up" in a group's (community's) header. G24.
42
+ 'groups_catch_up',
43
+ // "Translate" under a post in another language. G23a (on-device G25).
44
+ 'translate',
45
+ // The compose chip: check tone, shorten, fix spelling, translate, draft a reply. G23a.
46
+ 'compose',
47
+ // "About this account": the facts card on a profile, with no model call. G23a.
48
+ 'profile_facts',
49
+ // "Why am I seeing this" on a feed post, and asking the assistant to explain it. G28.
50
+ 'why_seeing',
51
+ // The opt-in daily brief and routines. G30.
52
+ 'daily_brief',
53
+ // Context notes the assistant drafts and people rate. G31.
54
+ 'notes',
55
+ // The assistant tab itself: thread list, new chat, the chat screen. G13.
56
+ 'george_tab',
57
+ // Sealed chats inside the assistant tab, on the phone. G15b.
58
+ 'sealed_in_tab',
59
+ // "Summarise replies" on a thread with 10 or more replies. G11.
60
+ 'thread_summary',
61
+ // Ask and Quote on selected text (web selection popover G11; phone apps G32).
62
+ 'selection',
63
+ // "What's going on" on a topic bar or Today story. G23a.
64
+ 'topic_catch_up',
65
+ // "Describe" on a picture in the media viewer. G23b, only after the upload checks are live.
66
+ 'describe_image',
67
+ // "Suggest alt text" for your own picture in compose. G23b, only after the upload checks are live.
68
+ 'alt_text',
69
+ // "Tell the assistant what you want more or less of" in feed settings. G29.
70
+ 'feed_steering',
71
+ // Sharing an answer as your own post. G27, not before 2026-11-04.
72
+ 'share_answer',
73
+ ];
74
+ const SURFACE_IDS = new Set(ASSISTANT_SURFACE_IDS);
75
+ /** True for a surface id this SDK knows. */
76
+ export function isAssistantSurfaceId(value) {
77
+ return typeof value === 'string' && SURFACE_IDS.has(value);
78
+ }
79
+ function isPlainObject(value) {
80
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
81
+ }
82
+ /**
83
+ * The surfaces map inside `settings`, which may be a response's `data` (carrying `assistant.surfaces`) or its
84
+ * `assistant` block (carrying `surfaces`). Null when there is none to read.
85
+ */
86
+ function surfacesIn(settings) {
87
+ if (!isPlainObject(settings))
88
+ return null;
89
+ if (Object.hasOwn(settings, 'assistant')) {
90
+ const assistant = settings.assistant;
91
+ return isPlainObject(assistant) && isPlainObject(assistant.surfaces) ? assistant.surfaces : null;
92
+ }
93
+ return isPlainObject(settings.surfaces) ? settings.surfaces : null;
94
+ }
95
+ /**
96
+ * Whether the app may show surface `id`: true only when `settings` holds an explicit `true` for it. `settings` is a
97
+ * response's `data` (`(await sdk.users.me()).data`, `(await sdk.network.getConfig()).data`) or its `assistant` block.
98
+ * Anything else is off: no settings (a read that failed, an older server), a malformed value, a non-boolean, an id
99
+ * this SDK does not know.
100
+ */
101
+ export function assistantSurfaceOn(settings, id) {
102
+ if (!isAssistantSurfaceId(id))
103
+ return false;
104
+ const surfaces = surfacesIn(settings);
105
+ return !!surfaces && Object.hasOwn(surfaces, id) && surfaces[id] === true;
106
+ }
107
+ /** Every known surface with its switch, read from `settings` by the same rule as {@link assistantSurfaceOn}. */
108
+ export function assistantSurfaces(settings) {
109
+ const out = {};
110
+ for (const id of ASSISTANT_SURFACE_IDS)
111
+ out[id] = assistantSurfaceOn(settings, id);
112
+ return out;
113
+ }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from './resources/discovery-learning.js';
2
2
  import { DiscoveryResource } from './resources/discovery.js';
3
3
  export * from './resources/discovery.js';
4
4
  export * from './feed-definition.js';
5
+ export * from './social-protocol-ids.js';
5
6
  import { AuthResource } from './resources/auth.js';
6
7
  import { PostsResource } from './resources/posts.js';
7
8
  import { UsersResource } from './resources/users.js';
@@ -67,6 +68,7 @@ import { FeedsResource } from './resources/feeds.js';
67
68
  import { AccountsResource } from './resources/accounts.js';
68
69
  import { SignalsResource } from './resources/signals.js';
69
70
  import { SealedResource } from './resources/sealed.js';
71
+ import { PrivacyResource } from './resources/privacy.js';
70
72
  import { RealtimeClient } from './realtime.js';
71
73
  import type { RecursivConfig } from './types.js';
72
74
  export declare class Recursiv {
@@ -144,6 +146,8 @@ export declare class Recursiv {
144
146
  readonly signals: SignalsResource;
145
147
  /** Sealed mode's config and transport: targets, models, allowance, the attestation relay and the sealed send (no crypto; flag-gated). */
146
148
  readonly sealed: SealedResource;
149
+ /** Which platform privacy facts the deployed API's build enforces (pair with `@recursiv/sdk/privacy`). */
150
+ readonly privacy: PrivacyResource;
147
151
  readonly realtime: RealtimeClient;
148
152
  constructor(config?: Partial<RecursivConfig>);
149
153
  /**
@@ -168,7 +172,7 @@ export { CommunitiesResource } from './resources/communities.js';
168
172
  export { ChatResource, CHAT_ATTACHMENT_REF_PREFIX, chatAttachmentKeysIn, chatAttachmentRefsIn } from './resources/chat.js';
169
173
  export type { ChatAttachmentRef, ChatAttachmentUploadInput, ChatAttachmentUploadUrl, ChatAttachmentUploadUrlInput, SignedChatAttachment, } from './resources/chat.js';
170
174
  export { E2eeResource } from './resources/e2ee.js';
171
- export type { E2eeAccountReset, E2eeDevice, E2eeDevicesRealtimeEvent, E2eeEnvelope, E2eeEvent, E2eeHasDependentsDetails, E2eeKeyPackageStatus, E2eeLink, E2eeLinkCancelledBy, E2eeLinkProgress, E2eeLinkRealtimeEvent, E2eeLinkState, E2eeObjectDownload, E2eeObjectUpload, E2eeRealtimeEvent, E2eeResetCodeSent, E2eeResetErrorCode, E2eeRevokeResponse, E2eeStartRequest, E2eeStatus, E2eeStatusReason, } from './resources/e2ee.js';
175
+ export type { E2eeAccountReset, E2eeDevice, E2eeDevicesRealtimeEvent, E2eeEnvelope, E2eeEvent, E2eeHasDependentsDetails, E2eeKeyPackageStatus, E2eeLink, E2eeLinkCancelledBy, E2eeLinkProgress, E2eeLinkRealtimeEvent, E2eeLinkState, E2eeObjectDownload, E2eeObjectUpload, E2eePeerChange, E2eePeerErrorCode, E2eePeerNotJoinedDetails, E2eePeerRealtimeEvent, E2eeRealtimeEvent, E2eeResetCodeSent, E2eeResetErrorCode, E2eeRevokeResponse, E2eeStartRequest, E2eeStatus, E2eeStatusReason, } from './resources/e2ee.js';
172
176
  export { ProjectsResource } from './resources/projects.js';
173
177
  export { AgentsResource, SUPPORTED_MODELS, AGENT_DIRECTORY_SORTS, AGENT_DIRECTORY_MAX_LIMIT } from './resources/agents.js';
174
178
  export { EvidenceResource } from './resources/evidence.js';
@@ -218,15 +222,15 @@ export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
218
222
  export type { PublicSearchPage, PublicSearchTab, SearchAnswer, SearchGroup, SearchLadder, SearchOmitReason, SearchParams, SearchPerson, SearchPost, SearchRelated, SearchResponse, SearchSection, SearchStage, SearchStory, SearchTopic, SearchVia, } from './resources/search.js';
219
223
  export { BOOKMARK_MERGE_MAX_ITEMS, BookmarkCollectionsResource, BookmarksResource } from './resources/bookmarks.js';
220
224
  export { FEED_TOP_NETWORKS, FeedsResource } from './resources/feeds.js';
221
- export type { FeedStoriesParams, FeedStoriesResponse, FeedStory, FeedTopExplain, FeedTopItem, FeedTopMeta, FeedTopNetwork, FeedTopNetworkCard, FeedTopNetworkEntry, FeedTopParams, FeedTopResponse, FeedTopTarget, PublicFeedTopMeta, PublicFeedTopParams, PublicFeedTopResponse, } from './resources/feeds.js';
225
+ export type { FeedStoriesParams, FeedStoriesResponse, FeedStory, FeedTopExplain, FeedTopItem, FeedTopMeta, FeedTopNetwork, FeedTopNetworkCard, FeedTopNetworkEntry, FeedTopNetworkMap, FeedTopParams, FeedTopResponse, FeedTopTarget, PublicFeedTopMeta, PublicFeedTopParams, PublicFeedTopResponse, } from './resources/feeds.js';
222
226
  export type { BookmarkCollection, BookmarkCollectionInput, BookmarkCollectionKind, BookmarkCollectionPatch, BookmarkCollectionsList, BookmarkCollectionTarget, BookmarkCollectionVisibility, BookmarkedPost, BookmarkInput, BookmarkItem, BookmarkKey, BookmarkKeyRow, BookmarkKeysPage, BookmarkKind, BookmarkMergeItem, BookmarkMergeResult, BookmarkProtocol, BookmarkRequestOptions, BookmarksPage, FiledBookmark, RemovedBookmarkCollection, SavedBookmark, UnfiledBookmark, UnsavedBookmark, } from './resources/bookmarks.js';
223
227
  export { AccountsResource } from './resources/accounts.js';
224
228
  export { SIGNAL_BATCH_MAX, SignalsResource } from './resources/signals.js';
225
229
  export type { SignalBatchResult, SignalEventInput, SignalEventType, SignalNotRecordedReason, SignalRecordResult, SignalSendOptions, } from './resources/signals.js';
226
230
  export { SEALED_ALLOWANCE_REMAINING_HEADER, SEALED_REFUSAL_HEADER, SealedResource, SealedTransportError, } from './resources/sealed.js';
227
- export type { SealedAllowance, SealedAttestationInput, SealedConfig, SealedModel, SealedSendInput, SealedTarget, SealedTargetKind, SealedTransportErrorCode, } from './resources/sealed.js';
231
+ export type { SealedAllowance, SealedAttestationInput, SealedConfig, SealedModel, SealedPlan, SealedPurpose, SealedQuotaDetails, SealedSendInput, SealedTarget, SealedTargetKind, SealedTransportErrorCode, } from './resources/sealed.js';
228
232
  export type { SiblingAccount, SiblingAccountsResponse, SwitchAccountResult, } from './resources/accounts.js';
229
- export type { LinkPreview, LinkPreviewResponse, } from './resources/link-preview.js';
233
+ export type { LinkPreview, LinkPreviewResponse, LinkPreviewSource, } from './resources/link-preview.js';
230
234
  export type { AgentCapabilityConsentRequirement, AgentCapabilityDataSensitivity, AgentCapabilityDomain, AgentCapabilityExportBoundary, AgentCapabilityRetentionClass, AgentCapabilitySubdomain, AgentCapabilitySubject, AgentCapabilityTrustTier, AgentToolAudience, AgentToolScope, BrowserUseRunTaskInput, ProjectBrainSettings, ProjectToolBundleClassification, ProjectToolBundleKey, ProjectToolPermission, ProjectToolPermissionOverrides, UpdateProjectSettingsInput, } from './resources/project-brain.js';
231
235
  export { OrganizationSettingsResource } from './resources/organization-settings.js';
232
236
  export { ProjectSettingsResource } from './resources/project-settings.js';
@@ -238,8 +242,8 @@ export type { LinkedTokenWallet, PayoutState, TokenBalance, TokenLedgerEntry } f
238
242
  export { BoostsResource } from './resources/boosts.js';
239
243
  export type { BoostBalance, BoostBalanceResponse, BoostCampaign, BoostCampaignResponse, BoostEventResult, BoostLedgerEntry, BoostLedgerKind, BoostPlanCredit, BoostRejectionReason, BoostReviewStatus, BoostStatus, BoostTopupCheckout, BoostTopupConfirmation, BoostTopupLimits, } from './resources/boosts.js';
240
244
  export { ProtocolsResource } from './resources/protocols.js';
241
- export { PROTOCOL_ADAPTER_MATURITIES, PROTOCOL_CAPABILITY_OPERATIONS, PROTOCOL_CAPABILITY_STATUSES, viralItemHasVideo, } from './resources/protocols.js';
242
- export type { SocialProfile, SocialBridge, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialRadar, SocialRadarCluster, SocialRadarMember, SocialRadarParams, SocialRadarPosts, SocialRadarStory, SocialRadarStoryArchive, ViralBaseline, ViralItem, ViralItemFields, ViralItemSpread, ViralItemVelocity, ViralLabel, ViralMindsCard, ViralMindsItem, ViralNetwork, ViralOpenProtocol, ViralProtocol, ViralProvider, ViralReadMeta, ViralReadParams, ViralReadResponse, ViralReadStatus, ViralRef, ViralSocialItem, SocialNetwork, SocialPost, SocialConversation, SocialTrend, SocialTrendProvenance, SocialTrends, SocialTrendSourceIndex, SocialTrendFeed, SocialPopularProfiles, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SearchPublicProfilesParams, SearchPublicPostsParams, SocialPostSearch, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, NativeNostrPrepareInput, NativeNostrPrepared, NativeNostrAccount, NativeNostrPostState, NativeNostrReceipt, NativeSocialProtocol, FederationIdentity, FederationIdentityView, NativeSocialAccount, NativeSocialCallbackInput, NativeReplyPrepareInput, NativePostPrepareInput, NativeMediaInput, NativeMediaItem, NativePostBatch, NativePostQuote, NativePostHistory, NativeDelegatedPostHistoryItem, NativeNostrPostHistoryItem, NativeFollowSourceInput, NativeFollowPrepareInput, NativeFollowTarget, NativeFollowReceipt, NativeFollowState, NativeFollowAccount, NativeFollowStates, NativeFollowReview, NativeLikeSourceInput, NativeRepostSourceInput, NativeLikePrepareInput, NativeRepostPrepareInput, NativeLikeTarget, NativeRepostTarget, NativeLikeReceipt, NativeRepostReceipt, NativeLikeState, NativeRepostState, NativeLikeReview, NativeRepostReview, NativeReplyReceipt, NativeReplyBatch, NativeReplyHistory, NativeDelegatedReplyHistoryItem, NativeNostrReplyResult, NativeNostrReplyHistoryItem, NativeReplyRemovalReceipt, NativeReplyRemovalPreview, SocialSourceRef, SocialSourceControl, SocialSourceControlDisplay, SocialSourceControlChange, HostedPostPreview, ConnectHostedIdentityInput, CreateHostedIdentityInput, CreatedHostedProtocolIdentity, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolIngestionActivityReason, ProtocolIngestionCircuitHealth, ProtocolIngestionDeadLetterGroup, ProtocolIngestionHealthPosition, ProtocolIngestionHealthReport, ProtocolIngestionHealthRuntime, ProtocolIngestionHealthSection, ProtocolIngestionLaneActivity, ProtocolIngestionLaneCurrentJob, ProtocolIngestionLaneDeadLetters, ProtocolIngestionLaneHealth, ProtocolIngestionLaneJobs, ProtocolIngestionReplayResult, ProtocolIngestionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, ReplayProtocolIngestionJobInput, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
245
+ export { PROTOCOL_ADAPTER_MATURITIES, PROTOCOL_CAPABILITY_OPERATIONS, PROTOCOL_CAPABILITY_STATUSES, SOCIAL_FOLLOWING_SOURCE_STATUSES, viralItemHasVideo, } from './resources/protocols.js';
246
+ export type { SocialProfile, SocialBridge, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialRadar, SocialRadarCluster, SocialRadarMember, SocialRadarParams, SocialRadarSourceKind, SocialRadarPosts, SocialRadarStory, SocialRadarStoryArchive, ViralBaseline, ViralItem, ViralItemFields, ViralItemSpread, ViralItemVelocity, ViralLabel, ViralMindsCard, ViralMindsItem, ViralNetwork, ViralOpenProtocol, ViralProtocol, ViralProvider, ViralReadMeta, ViralReadParams, ViralReadResponse, ViralReadStatus, ViralRef, ViralSocialItem, SocialNetwork, SocialPost, SocialConversation, SocialTrend, SocialTrendProvenance, SocialTrends, SocialTrendSourceIndex, SocialTrendFeed, SocialPopularProfiles, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SocialFollowingSourceStatus, SearchPublicProfilesParams, SearchPublicPostsParams, SocialPostSearch, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, NativeNostrPrepareInput, NativeNostrPrepared, NativeNostrAccount, NativeNostrPostState, NativeNostrReceipt, NativeSocialProtocol, FederationIdentity, FederationIdentityView, NativeSocialAccount, NativeNetwork, NativeNetworkState, NativeNetworkPresence, NativeNetworkActivation, NativeNetworkOff, SetNativeNetworkInput, NativeSocialCallbackInput, NativeReplyPrepareInput, NativePostPrepareInput, NativeMediaInput, NativeMediaItem, NativePostBatch, NativePostQuote, NativePostHistory, NativeDelegatedPostHistoryItem, NativeNostrPostHistoryItem, NativeFollowSourceInput, NativeFollowPrepareInput, NativeFollowTarget, NativeFollowReceipt, NativeFollowState, NativeFollowAccount, NativeFollowStates, NativeFollowReview, NativeLikeSourceInput, NativeRepostSourceInput, NativeLikePrepareInput, NativeRepostPrepareInput, NativeLikeTarget, NativeRepostTarget, NativeLikeReceipt, NativeRepostReceipt, NativeLikeState, NativeRepostState, NativeLikeReview, NativeRepostReview, NativeReplyReceipt, NativeReplyBatch, NativeReplyHistory, NativeDelegatedReplyHistoryItem, NativeNostrReplyResult, NativeNostrReplyHistoryItem, NativeReplyRemovalReceipt, NativeReplyRemovalPreview, SocialSourceRef, SocialSourceControl, SocialSourceControlDisplay, SocialSourceControlChange, HostedPostPreview, ConnectHostedIdentityInput, CreateHostedIdentityInput, CreatedHostedProtocolIdentity, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolIngestionActivityReason, ProtocolIngestionCircuitHealth, ProtocolIngestionDeadLetterGroup, ProtocolIngestionHealthPosition, ProtocolIngestionHealthReport, ProtocolIngestionHealthRuntime, ProtocolIngestionHealthSection, ProtocolIngestionLaneActivity, ProtocolIngestionLaneCurrentJob, ProtocolIngestionLaneDeadLetters, ProtocolIngestionLaneHealth, ProtocolIngestionLaneJobs, ProtocolIngestionReplayResult, ProtocolIngestionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, ReplayProtocolIngestionJobInput, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
243
247
  export { InboxResource } from './resources/inbox.js';
244
248
  export { NetworkResource } from './resources/network.js';
245
249
  export { SimulatorResource } from './resources/simulator.js';
@@ -270,3 +274,8 @@ export { parseSSE } from './lib/sse.js';
270
274
  export * from './lib/webhooks.js';
271
275
  export type * from './types.js';
272
276
  export type { PrepareHostedServiceInput, HostedServiceScope, HostedServiceOperation, HostedServiceReservation, HostedServiceObservation } from './resources/deployments.js';
277
+ export { ASSISTANT_SURFACE_IDS, assistantSurfaceOn, assistantSurfaces, isAssistantSurfaceId, } from './assistant-surfaces.js';
278
+ export type { AssistantSettings, AssistantSettingsUpdate, AssistantSurfaceId, AssistantSurfaces, } from './assistant-surfaces.js';
279
+ export type { CurrentUser } from './resources/users.js';
280
+ export { PrivacyResource } from './resources/privacy.js';
281
+ export type { LiveFactOptions, PrivacyCapabilities } from './privacy/index.js';
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export * from './resources/discovery-learning.js';
2
2
  import { DiscoveryResource } from './resources/discovery.js';
3
3
  export * from './resources/discovery.js';
4
4
  export * from './feed-definition.js';
5
+ export * from './social-protocol-ids.js';
5
6
  import { HttpClient } from './client.js';
6
7
  import { AuthResource } from './resources/auth.js';
7
8
  import { PostsResource } from './resources/posts.js';
@@ -68,6 +69,7 @@ import { FeedsResource } from './resources/feeds.js';
68
69
  import { AccountsResource } from './resources/accounts.js';
69
70
  import { SignalsResource } from './resources/signals.js';
70
71
  import { SealedResource } from './resources/sealed.js';
72
+ import { PrivacyResource } from './resources/privacy.js';
71
73
  import { RealtimeClient } from './realtime.js';
72
74
  export class Recursiv {
73
75
  /** Shared transport, kept so the client name can be set after construction. */
@@ -145,6 +147,8 @@ export class Recursiv {
145
147
  signals;
146
148
  /** Sealed mode's config and transport: targets, models, allowance, the attestation relay and the sealed send (no crypto; flag-gated). */
147
149
  sealed;
150
+ /** Which platform privacy facts the deployed API's build enforces (pair with `@recursiv/sdk/privacy`). */
151
+ privacy;
148
152
  realtime;
149
153
  constructor(config) {
150
154
  const client = new HttpClient(config);
@@ -214,6 +218,7 @@ export class Recursiv {
214
218
  this.accounts = new AccountsResource(client);
215
219
  this.signals = new SignalsResource(client);
216
220
  this.sealed = new SealedResource(client);
221
+ this.privacy = new PrivacyResource(client);
217
222
  this.realtime = new RealtimeClient(client);
218
223
  this.#http = client;
219
224
  }
@@ -283,7 +288,7 @@ export { UploadsResource } from './resources/uploads.js';
283
288
  export { WalletResource } from './resources/wallet.js';
284
289
  export { BoostsResource } from './resources/boosts.js';
285
290
  export { ProtocolsResource } from './resources/protocols.js';
286
- export { PROTOCOL_ADAPTER_MATURITIES, PROTOCOL_CAPABILITY_OPERATIONS, PROTOCOL_CAPABILITY_STATUSES, viralItemHasVideo, } from './resources/protocols.js';
291
+ export { PROTOCOL_ADAPTER_MATURITIES, PROTOCOL_CAPABILITY_OPERATIONS, PROTOCOL_CAPABILITY_STATUSES, SOCIAL_FOLLOWING_SOURCE_STATUSES, viralItemHasVideo, } from './resources/protocols.js';
287
292
  export { InboxResource } from './resources/inbox.js';
288
293
  export { NetworkResource } from './resources/network.js';
289
294
  export { SimulatorResource } from './resources/simulator.js';
@@ -302,3 +307,5 @@ export { MediaResource } from './resources/media.js';
302
307
  export { RecursivError, AuthenticationError, AuthorizationError, LegacyHandoffError, NotFoundError, ValidationError, InsufficientCreditsError, RateLimitError, ConflictError, isActionRateLimitScope, EmailNotSentError, } from './errors.js';
303
308
  export { parseSSE } from './lib/sse.js';
304
309
  export * from './lib/webhooks.js';
310
+ export { ASSISTANT_SURFACE_IDS, assistantSurfaceOn, assistantSurfaces, isAssistantSurfaceId, } from './assistant-surfaces.js';
311
+ export { PrivacyResource } from './resources/privacy.js';
@@ -0,0 +1,79 @@
1
+ export declare const PRIVACY_REGISTRY_VERSION: 0;
2
+ export declare const PRIVACY_FACTS: readonly [{
3
+ readonly id: "agents.tool_mode.chat_only";
4
+ readonly status: "planned";
5
+ readonly enforcement: "server";
6
+ readonly capability: "web_tools";
7
+ readonly step: "S5";
8
+ readonly says: "An agent in chat_only tool mode is given no tools, so it opens no web page.";
9
+ }, {
10
+ readonly id: "agents.turn_stamp";
11
+ readonly status: "enforced";
12
+ readonly enforcement: "server";
13
+ readonly capability: null;
14
+ readonly step: "S7";
15
+ readonly says: "Every reply a model writes in an agent chat through the platform's agent turn records, in metadata.ai, the model, its maker and the route, a model swapped in and whether for usage limits or after a failure, and the tools it called.";
16
+ }, {
17
+ readonly id: "agents.personal_context_sections";
18
+ readonly status: "planned";
19
+ readonly enforcement: "server";
20
+ readonly capability: "activity";
21
+ readonly step: "S26";
22
+ readonly says: "A personal agent's turn carries no follows, upvotes, groups or views of the member unless the member turned that part of their activity on.";
23
+ }, {
24
+ readonly id: "sealed.attest_before_send";
25
+ readonly status: "enforced";
26
+ readonly enforcement: "device";
27
+ readonly capability: "chat_tier";
28
+ readonly step: "S28";
29
+ readonly says: "The phone seals a Sealed request only to the key of a router it checked against a fresh challenge of its own: the router's report, signed by its AMD hardware, must answer that challenge with debug mode off and firmware at or above a floor, and its software must be a release Tinfoil signed and listed in a public log. If any check fails there is no key to seal to, so nothing is sealed or sent. How long a checked router is used before it is checked again is up to the app.";
30
+ }, {
31
+ readonly id: "sealed.attest_second_signer";
32
+ readonly status: "planned";
33
+ readonly enforcement: "device";
34
+ readonly capability: "chat_tier";
35
+ readonly step: "S28";
36
+ readonly says: "The phone also requires Recursiv's own signature over the router's release, so a release Tinfoil signed alone is refused.";
37
+ }, {
38
+ readonly id: "sealed.no_standard_fallback";
39
+ readonly status: "enforced";
40
+ readonly enforcement: "client";
41
+ readonly capability: "chat_tier";
42
+ readonly step: "S28";
43
+ readonly says: "The SDK sends a Sealed request only to the Sealed route, marked Sealed, and only once: a refusal, an error or an answer that is not sealed goes back to the app, and the SDK never sends the request again, to Sealed or to any other route.";
44
+ }, {
45
+ readonly id: "sealed.gateway_opaque";
46
+ readonly status: "enforced";
47
+ readonly enforcement: "server";
48
+ readonly capability: "chat_tier";
49
+ readonly step: "S28";
50
+ readonly says: "The Sealed gateway passes each Sealed request and answer on as bytes it does not read: it never parses, stores or logs a request or answer body. It refuses a request that carries no EHBP encapsulated key, the mark of a sealed body, and a success answer that carries no well-formed EHBP response nonce, the mark of a sealed answer; it cannot tell from the bytes themselves whether a body is sealed.";
51
+ }, {
52
+ readonly id: "sealed.dest_tinfoil_only";
53
+ readonly status: "enforced";
54
+ readonly enforcement: "server";
55
+ readonly capability: "chat_tier";
56
+ readonly step: "S28";
57
+ readonly says: "The Sealed gateway sends Sealed requests, and asks for attestation documents, only to the router origins its configuration lists, named exactly, and it lists only https origins of Tinfoil router hosts (inference.tinfoil.sh or router-N.tinfoil.sh). It refuses any other server a client names and follows no redirect.";
58
+ }, {
59
+ readonly id: "sealed.no_server_history";
60
+ readonly status: "enforced";
61
+ readonly enforcement: "server";
62
+ readonly capability: "sealed_history";
63
+ readonly step: "S29";
64
+ readonly says: "The platform keeps no Sealed chat history: no Sealed route writes a conversation or message row, and the gateway stores no request or answer body.";
65
+ }, {
66
+ readonly id: "sealed.backup_excluded";
67
+ readonly status: "planned";
68
+ readonly enforcement: "device";
69
+ readonly capability: "sealed_history";
70
+ readonly step: "S29";
71
+ readonly says: "Sealed chats saved on a phone are left out of the phone's backups.";
72
+ }, {
73
+ readonly id: "sealed.no_push";
74
+ readonly status: "enforced";
75
+ readonly enforcement: "server";
76
+ readonly capability: "chat_tier";
77
+ readonly step: "S29";
78
+ readonly says: "No Sealed route sends a push notification or writes a notification: a Sealed answer goes back only on the request that asked for it.";
79
+ }];
@@ -0,0 +1,93 @@
1
+ // Generated by scripts/privacy-claims.mjs from docs/privacy-claims.json. Do not edit: CI refuses a hand edit.
2
+ // Change the registry, then run: node scripts/privacy-claims.mjs --write
3
+ export const PRIVACY_REGISTRY_VERSION = 0;
4
+ export const PRIVACY_FACTS = [
5
+ {
6
+ id: "agents.tool_mode.chat_only",
7
+ status: "planned",
8
+ enforcement: "server",
9
+ capability: "web_tools",
10
+ step: "S5",
11
+ says: "An agent in chat_only tool mode is given no tools, so it opens no web page.",
12
+ },
13
+ {
14
+ id: "agents.turn_stamp",
15
+ status: "enforced",
16
+ enforcement: "server",
17
+ capability: null,
18
+ step: "S7",
19
+ says: "Every reply a model writes in an agent chat through the platform's agent turn records, in metadata.ai, the model, its maker and the route, a model swapped in and whether for usage limits or after a failure, and the tools it called.",
20
+ },
21
+ {
22
+ id: "agents.personal_context_sections",
23
+ status: "planned",
24
+ enforcement: "server",
25
+ capability: "activity",
26
+ step: "S26",
27
+ says: "A personal agent's turn carries no follows, upvotes, groups or views of the member unless the member turned that part of their activity on.",
28
+ },
29
+ {
30
+ id: "sealed.attest_before_send",
31
+ status: "enforced",
32
+ enforcement: "device",
33
+ capability: "chat_tier",
34
+ step: "S28",
35
+ says: "The phone seals a Sealed request only to the key of a router it checked against a fresh challenge of its own: the router's report, signed by its AMD hardware, must answer that challenge with debug mode off and firmware at or above a floor, and its software must be a release Tinfoil signed and listed in a public log. If any check fails there is no key to seal to, so nothing is sealed or sent. How long a checked router is used before it is checked again is up to the app.",
36
+ },
37
+ {
38
+ id: "sealed.attest_second_signer",
39
+ status: "planned",
40
+ enforcement: "device",
41
+ capability: "chat_tier",
42
+ step: "S28",
43
+ says: "The phone also requires Recursiv's own signature over the router's release, so a release Tinfoil signed alone is refused.",
44
+ },
45
+ {
46
+ id: "sealed.no_standard_fallback",
47
+ status: "enforced",
48
+ enforcement: "client",
49
+ capability: "chat_tier",
50
+ step: "S28",
51
+ says: "The SDK sends a Sealed request only to the Sealed route, marked Sealed, and only once: a refusal, an error or an answer that is not sealed goes back to the app, and the SDK never sends the request again, to Sealed or to any other route.",
52
+ },
53
+ {
54
+ id: "sealed.gateway_opaque",
55
+ status: "enforced",
56
+ enforcement: "server",
57
+ capability: "chat_tier",
58
+ step: "S28",
59
+ says: "The Sealed gateway passes each Sealed request and answer on as bytes it does not read: it never parses, stores or logs a request or answer body. It refuses a request that carries no EHBP encapsulated key, the mark of a sealed body, and a success answer that carries no well-formed EHBP response nonce, the mark of a sealed answer; it cannot tell from the bytes themselves whether a body is sealed.",
60
+ },
61
+ {
62
+ id: "sealed.dest_tinfoil_only",
63
+ status: "enforced",
64
+ enforcement: "server",
65
+ capability: "chat_tier",
66
+ step: "S28",
67
+ says: "The Sealed gateway sends Sealed requests, and asks for attestation documents, only to the router origins its configuration lists, named exactly, and it lists only https origins of Tinfoil router hosts (inference.tinfoil.sh or router-N.tinfoil.sh). It refuses any other server a client names and follows no redirect.",
68
+ },
69
+ {
70
+ id: "sealed.no_server_history",
71
+ status: "enforced",
72
+ enforcement: "server",
73
+ capability: "sealed_history",
74
+ step: "S29",
75
+ says: "The platform keeps no Sealed chat history: no Sealed route writes a conversation or message row, and the gateway stores no request or answer body.",
76
+ },
77
+ {
78
+ id: "sealed.backup_excluded",
79
+ status: "planned",
80
+ enforcement: "device",
81
+ capability: "sealed_history",
82
+ step: "S29",
83
+ says: "Sealed chats saved on a phone are left out of the phone's backups.",
84
+ },
85
+ {
86
+ id: "sealed.no_push",
87
+ status: "enforced",
88
+ enforcement: "server",
89
+ capability: "chat_tier",
90
+ step: "S29",
91
+ says: "No Sealed route sends a push notification or writes a notification: a Sealed answer goes back only on the request that asked for it.",
92
+ },
93
+ ];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `@recursiv/sdk/privacy`: the platform privacy facts, generated from the claims registry (docs/privacy-claims.json
3
+ * in recursivlabs/recursiv). Each fact says one thing the platform does with member data and whether this SDK release
4
+ * marks it enforced, which it does only once every test that proves it is real and runs in CI.
5
+ *
6
+ * An app shows a privacy sentence only when the facts it rests on are live: enforced here AND listed by the API it
7
+ * talks to (`sdk.privacy.capabilities()`), so neither a newer SDK nor a newer API can make a claim on its own. A fact
8
+ * held on the phone (`enforcement: 'device'`) needs a third check, the app's own: neither this SDK nor the API can see
9
+ * which native module the phone runs, so the app names the device facts its installed module carries (`deviceFacts`).
10
+ *
11
+ * The types live here, not in the SDK's shared types, so this entry point stands alone.
12
+ */
13
+ import { PRIVACY_FACTS, PRIVACY_REGISTRY_VERSION } from './facts.generated.js';
14
+ export { PRIVACY_FACTS, PRIVACY_REGISTRY_VERSION };
15
+ export type PrivacyFact = (typeof PRIVACY_FACTS)[number];
16
+ export type PrivacyFactId = PrivacyFact['id'];
17
+ /** `planned`: the platform does not yet prove it. `enforced`: every test that proves it is real and runs in CI. */
18
+ export type PrivacyFactStatus = 'planned' | 'enforced';
19
+ /** Where a fact holds: on the server, on the phone (the native module), in the SDK, or in both server and client. */
20
+ export type PrivacyFactEnforcement = 'server' | 'device' | 'client' | 'server_and_client';
21
+ /** GET /privacy/capabilities: the facts the deployed API's build marks enforced, and the commit it runs. */
22
+ export interface PrivacyCapabilities {
23
+ /** The commit the API was built from, or `unknown`. */
24
+ api_commit: string;
25
+ /** Fact ids, sorted. An id this SDK release does not know is never live here. */
26
+ facts: string[];
27
+ }
28
+ /** The fact with this id, or undefined for an id this SDK release does not know. */
29
+ export declare function privacyFact(id: string): PrivacyFact | undefined;
30
+ /** Whether this SDK release marks the fact enforced. An unknown id is not. */
31
+ export declare function isFactEnforced(id: string): boolean;
32
+ export interface LiveFactOptions {
33
+ /**
34
+ * The device facts (`enforcement: 'device'`) the app's installed native module carries, as the app knows from the
35
+ * module revision it ships (for Sealed, @recursiv/e2ee-react-native). A device fact is live only when it is named
36
+ * here too. Left out, no device fact is live. A fact says what the module does, not what the app does with it: for
37
+ * `sealed.attest_before_send` the app chooses how long a checked router is reused, so copy that says the router is
38
+ * checked before every send needs the app to check before every send.
39
+ */
40
+ deviceFacts?: Iterable<string>;
41
+ }
42
+ /**
43
+ * Enforced in this SDK release AND listed by the deployed API, and for a device fact also named in `deviceFacts`.
44
+ * Missing capabilities mean nothing is live.
45
+ */
46
+ export declare function isFactLive(id: string, capabilities: PrivacyCapabilities | null | undefined, options?: LiveFactOptions): boolean;
47
+ /** Every fact that is live for this API (and, for device facts, this app's native module), in registry order. */
48
+ export declare function liveFacts(capabilities: PrivacyCapabilities | null | undefined, options?: LiveFactOptions): PrivacyFactId[];
@@ -0,0 +1,42 @@
1
+ /**
2
+ * `@recursiv/sdk/privacy`: the platform privacy facts, generated from the claims registry (docs/privacy-claims.json
3
+ * in recursivlabs/recursiv). Each fact says one thing the platform does with member data and whether this SDK release
4
+ * marks it enforced, which it does only once every test that proves it is real and runs in CI.
5
+ *
6
+ * An app shows a privacy sentence only when the facts it rests on are live: enforced here AND listed by the API it
7
+ * talks to (`sdk.privacy.capabilities()`), so neither a newer SDK nor a newer API can make a claim on its own. A fact
8
+ * held on the phone (`enforcement: 'device'`) needs a third check, the app's own: neither this SDK nor the API can see
9
+ * which native module the phone runs, so the app names the device facts its installed module carries (`deviceFacts`).
10
+ *
11
+ * The types live here, not in the SDK's shared types, so this entry point stands alone.
12
+ */
13
+ import { PRIVACY_FACTS, PRIVACY_REGISTRY_VERSION } from './facts.generated.js';
14
+ export { PRIVACY_FACTS, PRIVACY_REGISTRY_VERSION };
15
+ const BY_ID = new Map(PRIVACY_FACTS.map((fact) => [fact.id, fact]));
16
+ /** The fact with this id, or undefined for an id this SDK release does not know. */
17
+ export function privacyFact(id) {
18
+ return BY_ID.get(id);
19
+ }
20
+ /** Whether this SDK release marks the fact enforced. An unknown id is not. */
21
+ export function isFactEnforced(id) {
22
+ return BY_ID.get(id)?.status === 'enforced';
23
+ }
24
+ /**
25
+ * Enforced in this SDK release AND listed by the deployed API, and for a device fact also named in `deviceFacts`.
26
+ * Missing capabilities mean nothing is live.
27
+ */
28
+ export function isFactLive(id, capabilities, options = {}) {
29
+ const fact = BY_ID.get(id);
30
+ if (!fact || fact.status !== 'enforced')
31
+ return false;
32
+ if (!Array.isArray(capabilities?.facts) || !capabilities.facts.includes(id))
33
+ return false;
34
+ if (fact.enforcement === 'device')
35
+ return new Set(options.deviceFacts ?? []).has(id);
36
+ return true;
37
+ }
38
+ /** Every fact that is live for this API (and, for device facts, this app's native module), in registry order. */
39
+ export function liveFacts(capabilities, options = {}) {
40
+ const deviceFacts = [...(options.deviceFacts ?? [])];
41
+ return PRIVACY_FACTS.filter((fact) => isFactLive(fact.id, capabilities, { deviceFacts })).map((fact) => fact.id);
42
+ }
@@ -1,6 +1,6 @@
1
1
  import type { Socket } from 'socket.io-client';
2
2
  import { HttpClient } from './client.js';
3
- import type { E2eeDevicesRealtimeEvent, E2eeLinkRealtimeEvent, E2eeRealtimeEvent } from './resources/e2ee.js';
3
+ import type { E2eeDevicesRealtimeEvent, E2eeLinkRealtimeEvent, E2eePeerRealtimeEvent, E2eeRealtimeEvent } from './resources/e2ee.js';
4
4
  import type { ChatConversationEvent, ChatMembersEvent } from './types.js';
5
5
  export interface ChatMessageEvent {
6
6
  id: string;
@@ -150,8 +150,8 @@ export declare class RealtimeClient {
150
150
  *
151
151
  * Server events include `notification`, `feed_update`,
152
152
  * `conversation_created`, `chat_message`, `chat_typing`, `agent_thinking`,
153
- * `chat:members`, `chat:conversation`, `e2ee:event`, `e2ee:link` and
154
- * `e2ee:devices`.
153
+ * `chat:members`, `chat:conversation`, `e2ee:event`, `e2ee:link`,
154
+ * `e2ee:devices` and `e2ee:peer`.
155
155
  */
156
156
  on<T = unknown>(event: string, listener: (payload: T) => void): () => void;
157
157
  /** Join a conversation room now, and again after every reconnect, until you leave it. */
@@ -199,4 +199,11 @@ export declare class RealtimeClient {
199
199
  * `{ deviceId, reason: 'revoked' }`.
200
200
  */
201
201
  onE2eeDevices(callback: (event: E2eeDevicesRealtimeEvent) => void): () => void;
202
+ /**
203
+ * Someone this account shares an encrypted conversation with reset encrypted
204
+ * chat: `{ userId, reason: 'root_changed', conversationId }`, once per shared
205
+ * conversation. Look at their devices again (`r.e2ee.devices({ user_id })`);
206
+ * catch up with `r.e2ee.peerChanges()` after time in the background.
207
+ */
208
+ onE2eePeer(callback: (event: E2eePeerRealtimeEvent) => void): () => void;
202
209
  }
package/dist/realtime.js CHANGED
@@ -369,8 +369,8 @@ export class RealtimeClient {
369
369
  *
370
370
  * Server events include `notification`, `feed_update`,
371
371
  * `conversation_created`, `chat_message`, `chat_typing`, `agent_thinking`,
372
- * `chat:members`, `chat:conversation`, `e2ee:event`, `e2ee:link` and
373
- * `e2ee:devices`.
372
+ * `chat:members`, `chat:conversation`, `e2ee:event`, `e2ee:link`,
373
+ * `e2ee:devices` and `e2ee:peer`.
374
374
  */
375
375
  on(event, listener) {
376
376
  // Every call is its own subscription, even for a function that is already
@@ -473,4 +473,13 @@ export class RealtimeClient {
473
473
  onE2eeDevices(callback) {
474
474
  return this.on('e2ee:devices', callback);
475
475
  }
476
+ /**
477
+ * Someone this account shares an encrypted conversation with reset encrypted
478
+ * chat: `{ userId, reason: 'root_changed', conversationId }`, once per shared
479
+ * conversation. Look at their devices again (`r.e2ee.devices({ user_id })`);
480
+ * catch up with `r.e2ee.peerChanges()` after time in the background.
481
+ */
482
+ onE2eePeer(callback) {
483
+ return this.on('e2ee:peer', callback);
484
+ }
476
485
  }
@@ -234,7 +234,10 @@ export declare class AgentsResource {
234
234
  }>;
235
235
  /** Delete an agent */
236
236
  delete(id: string): Promise<DeleteResponse>;
237
- /** Send a message to an agent (async — agent responds asynchronously) */
237
+ /**
238
+ * Send a message to an agent (async — agent responds asynchronously).
239
+ * `turn: { safe: true }` makes it a safe turn in the caller's Asks thread (see `AgentChatTurn`).
240
+ */
238
241
  chat(id: string, input: AgentChatInput): Promise<SingleResponse<AgentChatResult>>;
239
242
  /** List an agent's conversations */
240
243
  conversations(id: string, params?: PaginationParams): Promise<PaginatedResponse<AgentConversation>>;
@@ -398,6 +401,8 @@ export declare class AgentsResource {
398
401
  *
399
402
  * Requires `ReadableStream` support (Node.js, modern browsers).
400
403
  * For React Native, use `chatStreamText()` instead.
404
+ * `turn: { safe: true }` streams a safe turn in the caller's Asks thread (see `AgentChatTurn`); a refusal throws a
405
+ * `RecursivError` whose code is a `SafeTurnRefusalCode`, before anything streams.
401
406
  */
402
407
  chatStream(id: string, input: AgentChatInput): AsyncGenerator<StreamChunk>;
403
408
  /**
@@ -1,4 +1,4 @@
1
- import { RecursivError } from '../errors.js';
1
+ import { RateLimitError, rateLimitDetailsFromResponse, RecursivError } from '../errors.js';
2
2
  import { parseSSE } from '../lib/sse.js';
3
3
  // ── Agent directory (GET /agents/discover) ──
4
4
  /** The orders the agent directory serves (server: features/agents/agentDirectory.ts). */
@@ -83,7 +83,10 @@ export class AgentsResource {
83
83
  delete(id) {
84
84
  return this.client.delete(`/agents/${id}`);
85
85
  }
86
- /** Send a message to an agent (async — agent responds asynchronously) */
86
+ /**
87
+ * Send a message to an agent (async — agent responds asynchronously).
88
+ * `turn: { safe: true }` makes it a safe turn in the caller's Asks thread (see `AgentChatTurn`).
89
+ */
87
90
  chat(id, input) {
88
91
  return this.client.post(`/agents/${id}/chat`, input);
89
92
  }
@@ -224,6 +227,8 @@ export class AgentsResource {
224
227
  *
225
228
  * Requires `ReadableStream` support (Node.js, modern browsers).
226
229
  * For React Native, use `chatStreamText()` instead.
230
+ * `turn: { safe: true }` streams a safe turn in the caller's Asks thread (see `AgentChatTurn`); a refusal throws a
231
+ * `RecursivError` whose code is a `SafeTurnRefusalCode`, before anything streams.
227
232
  */
228
233
  async *chatStream(id, input) {
229
234
  yield* this.streamChunks(id, input);
@@ -286,6 +291,11 @@ export class AgentsResource {
286
291
  code: 'stream_error',
287
292
  });
288
293
  }
294
+ // A 429 is a RateLimitError, as on every other request: with its scope and reset (a safe turn's daily cap is
295
+ // `scope: 'action'`, see SafeTurnRefusalCode).
296
+ if (response.status === 429 && errorBody?.error && typeof errorBody.error === 'object') {
297
+ throw new RateLimitError(errorBody.error, rateLimitDetailsFromResponse(response.headers, errorBody.error));
298
+ }
289
299
  throw new RecursivError(response.status, errorBody.error);
290
300
  }
291
301
  return response;