@recursiv/sdk 0.7.123 → 0.7.152
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/README.md +3 -1
- package/dist/index.d.ts +2 -1
- package/dist/privacy/facts.generated.d.ts +52 -3
- package/dist/privacy/facts.generated.js +59 -3
- package/dist/resources/agents.d.ts +10 -1
- package/dist/resources/agents.js +11 -0
- package/dist/resources/member-experience.d.ts +24 -0
- package/dist/resources/member-experience.js +1 -0
- package/dist/resources/protocols.d.ts +73 -8
- package/dist/resources/protocols.js +7 -2
- package/dist/resources/reports.d.ts +11 -2
- package/dist/resources/reports.js +5 -1
- package/dist/resources/search.d.ts +26 -3
- package/dist/resources/settings.d.ts +9 -0
- package/dist/resources/settings.js +12 -0
- package/dist/types.d.ts +12 -0
- package/llm.md +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
76
76
|
### AI Agents
|
|
77
77
|
| Resource | Key methods |
|
|
78
78
|
|----------|------------|
|
|
79
|
-
| `r.agents` | `create`, `update`, `chat`, `chatStream`, `retryChatStream`, `grantProjectAccess`, `conversations
|
|
79
|
+
| `r.agents` | `create`, `update`, `chat`, `chatStream`, `retryChatStream`, `grantProjectAccess`, `conversations` (each with a `name`), `createConversation` ("New chat" with your own personal agent; send its first message with `chatStream` and its `conversation_id`), `discover` (the app's agent directory: server search and sort, keyless-readable) |
|
|
80
80
|
| `r.e2ee` | `status`, `registerDevice`, `devices`, `revokeDevice` (`{ cascade }` to also revoke devices approved through it; 409 `has_dependents` otherwise), `requestResetCode` and `resetAccount` (reset encrypted chat with an emailed code where `status().accountReset`), `peerChanges` (contacts whose main device a reset replaced since a time), `publishKeyPackages`, `keyPackageStatus`, `claimKeyPackage`, `appendEvent` (`trust_change: true`: after a contact's reset, 409 `peer_not_joined` until this account added their new device), `events`, `acknowledge`, `conversations`, `requestStart` and `cancelStartRequest` (ask the other person to encrypt a chat when they have no device yet: a message with `metadata.kind` `e2ee_start_request`; the live ones are `ConversationDetail.encryption_requests`); device linking by scan: `createLink`, `link`, `scannedLink`, `approveLink`, `completeLink`, `linkProgress`, `cancelLink` (flag-gated E2EE v2 transport; its doorbell is `r.realtime.onE2eeEvent`, which carries a small message's stored ciphertext to the owner's own sockets; link and device changes arrive on `r.realtime.onE2eeLink` and `onE2eeDevices`, a contact's reset on `onE2eePeer`) |
|
|
81
81
|
| `r.brain` | `sendMessage` |
|
|
82
82
|
| `r.commands` | `execute`, `gatePrompt` |
|
|
@@ -104,6 +104,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
104
104
|
| `r.tags` | `create`, `list` |
|
|
105
105
|
| `r.signals` | `batch` (up to 100 engagement signals, such as views, dwell and hides, for the member's own ranking; `keepalive` for a page-leave flush), `record` (one signal) (0.7.71); `clearViewingHistory` ("Clear my viewing history"); typed `SignalMetadata`, an id per event so a retry is stored once, and `duplicates`/`limited`/`rejected` in the answer (0.7.93) |
|
|
106
106
|
| `r.privacy` | `capabilities` (the platform privacy facts the deployed API's build enforces, and its commit); pair with `@recursiv/sdk/privacy` (`PRIVACY_FACTS`, `isFactLive`, `liveFacts`) (unreleased) |
|
|
107
|
+
| `r.reports` | `create`, `list`, `resolve`, `dismiss`, `signAttachments`; 0.7.152 `sealed_answer` reports target the key's exact project id and need `posts:write` on a human-owned, non-agent project key. This intake does not recheck live app membership; the Sealed gateway additionally does. Review applies no account enforcement. Every answer request, including a join, spends report budget. `create` returns the report `id`, optional `existing`, and `details_kept` for an answer: false means its reason was kept but its text did not fit in full. Deploy supporting API before client adoption. |
|
|
107
108
|
| `r.sealed` | Sealed mode: `config`, `attestation`, `send`; for web Sealed's frame (unreleased): `frameToken` (a short-lived `sft_` credential for the frame), `me` (the member's own age view and Sealed adult setting), `report`; pair with `@recursiv/sdk/sealed` (the resource alone) and `@recursiv/sdk/sealed-frame` (below) |
|
|
108
109
|
| `r.curator` | `today` (World Today; a world story can carry `story_key` and `discussion`, its count across networks), `storyPosts` / `memberStoryPosts` (one page of a story's posts, keyless or under the member's key), `storyPostPages` (walk every page), `forYou`, `run`, `refreshIfStale`, `seedDeck` |
|
|
109
110
|
| `r.uploads` | `uploadMedia` — upload post/chat bytes end to end |
|
|
@@ -113,6 +114,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
113
114
|
### Platform
|
|
114
115
|
| Resource | Purpose |
|
|
115
116
|
|----------|---------|
|
|
117
|
+
| `r.settings` experience | `getExperience`, `updateExperience({ version, goals?, interests?, avoid_categories? })`, `undoExperience({ version })` — explicit per-app starting choices with a real category Discover link; requires the member's own project-bound key (unreleased). Does not change whole-feed ranking or privacy settings. |
|
|
116
118
|
| `r.settings` | User preferences (including app-scoped content choices; per-kind notification settings in `notifications.channels`, set one kind with `updateNotifications({ channels: { post_reaction: { push: false } } })`, 0.7.72), sessions (`revokeOtherSessions` keeps this device), password management, email change (`requestConfirmationCode` for accounts with no password), deactivation (`disableAccount({ password })`, 0.7.71; 404 on an API that does not serve it) |
|
|
117
119
|
| `r.billing` | Usage tracking, subscriptions, checkout |
|
|
118
120
|
| `r.appSubscriptions` | Consumer app plans: `checkout`, `status`, `createPortalSession`, `previewTierChange`, `changeTier` |
|
package/dist/index.d.ts
CHANGED
|
@@ -191,6 +191,7 @@ export { OrganizationsResource } from './resources/organizations.js';
|
|
|
191
191
|
export { NotificationsResource } from './resources/notifications.js';
|
|
192
192
|
export type { Notification, NotificationActor, NotificationCategory, NotificationPostPreview, NotificationPostTag, NotificationScopeParams, NotificationUnreadCount, ListNotificationsParams, NotificationListResponse, } from './resources/notifications.js';
|
|
193
193
|
export { SettingsResource } from './resources/settings.js';
|
|
194
|
+
export type { ExperienceGoal, ExperienceCategory, MemberExperience, UpdateMemberExperienceInput } from './resources/member-experience.js';
|
|
194
195
|
export { BillingResource } from './resources/billing.js';
|
|
195
196
|
export { FreeTierResource } from './resources/free-tier.js';
|
|
196
197
|
export { InviteCodesResource } from './resources/invite-codes.js';
|
|
@@ -252,7 +253,7 @@ export { BoostsResource } from './resources/boosts.js';
|
|
|
252
253
|
export type { BoostBalance, BoostBalanceResponse, BoostCampaign, BoostCampaignResponse, BoostEventResult, BoostLedgerEntry, BoostLedgerKind, BoostPlanCredit, BoostRejectionReason, BoostReviewStatus, BoostStatus, BoostTopupCheckout, BoostTopupConfirmation, BoostTopupLimits, } from './resources/boosts.js';
|
|
253
254
|
export { ProtocolsResource } from './resources/protocols.js';
|
|
254
255
|
export { PROTOCOL_ADAPTER_MATURITIES, PROTOCOL_CAPABILITY_OPERATIONS, PROTOCOL_CAPABILITY_STATUSES, SOCIAL_FOLLOWING_SOURCE_STATUSES, viralItemHasVideo, } from './resources/protocols.js';
|
|
255
|
-
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, OpenGroupThreads, OpenGroupThreadsGroup, OpenGroupThreadsParams, 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';
|
|
256
|
+
export type { SocialProfile, SocialBridge, SocialMention, 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, SocialPopularProfile, SocialPopularProfilesParams, SocialPopularProfilesProtocol, SocialPopularProfilesSource, SocialAuthorFeed, OpenGroupThreads, OpenGroupThreadsGroup, OpenGroupThreadsParams, 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';
|
|
256
257
|
export { InboxResource } from './resources/inbox.js';
|
|
257
258
|
export { NetworkResource } from './resources/network.js';
|
|
258
259
|
export { SimulatorResource } from './resources/simulator.js';
|
|
@@ -47,7 +47,7 @@ export declare const PRIVACY_FACTS: readonly [{
|
|
|
47
47
|
readonly enforcement: "server";
|
|
48
48
|
readonly capability: "chat_tier";
|
|
49
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.";
|
|
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, except the report a member chooses to file (below). 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. Two routes the Sealed frame uses relay nothing, and answer only while Sealed in a browser is switched on: GET /sealed/me answers the member's own age view and adult setting, and POST /sealed/reports reads the report a member chooses to file (a reason, and details they write) and files it as any report is filed.";
|
|
51
51
|
}, {
|
|
52
52
|
readonly id: "sealed.dest_tinfoil_only";
|
|
53
53
|
readonly status: "enforced";
|
|
@@ -61,7 +61,7 @@ export declare const PRIVACY_FACTS: readonly [{
|
|
|
61
61
|
readonly enforcement: "server";
|
|
62
62
|
readonly capability: "sealed_history";
|
|
63
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.";
|
|
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, except a report the member chooses to file at POST /sealed/reports, whose reason and the details they write (which may quote the exchange they report) are kept as any report is. GET /sealed/me, which answers the member's own age view and adult setting, writes no row and keeps nothing it answers; it counts the member's requests for a minute in the shared counters, as the attestation relay does.";
|
|
65
65
|
}, {
|
|
66
66
|
readonly id: "sealed.backup_excluded";
|
|
67
67
|
readonly status: "planned";
|
|
@@ -75,7 +75,7 @@ export declare const PRIVACY_FACTS: readonly [{
|
|
|
75
75
|
readonly enforcement: "server";
|
|
76
76
|
readonly capability: "chat_tier";
|
|
77
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.";
|
|
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. The one exception is the report a member chooses to file at POST /sealed/reports, which answers only while Sealed in a browser is switched on: it is filed as any report is, so a report whose reason is child sexual abuse material, intimate images shared without consent or child safety may page Minds staff by email and on the safety rota's pager webhook, carrying the report's ids and the reason's category, never the details the member wrote.";
|
|
79
79
|
}, {
|
|
80
80
|
readonly id: "ai_prefs.agent_enabled";
|
|
81
81
|
readonly status: "planned";
|
|
@@ -307,4 +307,53 @@ export declare const PRIVACY_FACTS: readonly [{
|
|
|
307
307
|
readonly capability: "sealed_history";
|
|
308
308
|
readonly step: "S29";
|
|
309
309
|
readonly says: "Exporting a Sealed chat writes a file on the phone and sends nothing.";
|
|
310
|
+
}, {
|
|
311
|
+
readonly id: "sealed.attest_before_send_browser";
|
|
312
|
+
readonly status: "planned";
|
|
313
|
+
readonly enforcement: "device";
|
|
314
|
+
readonly capability: "chat_tier";
|
|
315
|
+
readonly step: "S28";
|
|
316
|
+
readonly says: "In a browser, the Sealed frame seals a Sealed request only to the key of a router it checked against a fresh challenge of its own, with the phone's checks: 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. The checks and the sealing are the phone's own Sealed core, built for the browser and run in a Worker inside the frame. 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.";
|
|
317
|
+
}, {
|
|
318
|
+
readonly id: "sealed.frame_separate_origin";
|
|
319
|
+
readonly status: "planned";
|
|
320
|
+
readonly enforcement: "server_and_client";
|
|
321
|
+
readonly capability: "chat_tier";
|
|
322
|
+
readonly step: "S29";
|
|
323
|
+
readonly says: "In a browser, Sealed runs only in the Sealed frame, a page of its own loaded from sealed.minds.com, so the Minds page it sits in (such as minds.com or build.minds.com) cannot read what is typed or answered there. The frame tells that page only fixed signals (that it is ready, its state, that it needs a new token, where to go, and that a sign-out clear finished), never words, answers, chat names or counts, and its content security policy, enforced and never report-only, lets only Minds' own pages embed it and lets it connect only to its own origin and the Minds API.";
|
|
324
|
+
}, {
|
|
325
|
+
readonly id: "sealed.frame_token_scoped";
|
|
326
|
+
readonly status: "planned";
|
|
327
|
+
readonly enforcement: "server_and_client";
|
|
328
|
+
readonly capability: "chat_tier";
|
|
329
|
+
readonly step: "S28";
|
|
330
|
+
readonly says: "The Sealed frame never holds the member's app key. The page around it mints a Sealed frame token from that key and hands the frame only that token, which lasts at most 15 minutes, works only on the Sealed routes the frame uses and only from the frame's own origin, cannot mint another, and stops working on its next request once the key it was minted from is revoked.";
|
|
331
|
+
}, {
|
|
332
|
+
readonly id: "sealed.history_browser_only";
|
|
333
|
+
readonly status: "planned";
|
|
334
|
+
readonly enforcement: "device";
|
|
335
|
+
readonly capability: "sealed_history";
|
|
336
|
+
readonly step: "S29";
|
|
337
|
+
readonly says: "In a browser, Sealed chats are saved only in that browser, in the storage it keeps for sealed.minds.com (the Sealed frame's origin), under the account; Minds keeps none of them, apart from what a member chooses to put in a report.";
|
|
338
|
+
}, {
|
|
339
|
+
readonly id: "sealed.signout_clears_browser";
|
|
340
|
+
readonly status: "planned";
|
|
341
|
+
readonly enforcement: "client";
|
|
342
|
+
readonly capability: "sealed_history";
|
|
343
|
+
readonly step: "S29";
|
|
344
|
+
readonly says: "Signing out of Minds in a browser clears the account's Sealed chats from that browser when the Sealed frame or its sign-out page answers that they are gone; sign-out waits at most 1.5 seconds for that answer. If neither answers in time but the sign-out page had noted the clear in that browser, the clear finishes the next time the Sealed frame loads there. If the sign-out page could not load, or was stopped before it noted the clear, nothing is noted: the chats can stay in that browser until the Sealed frame next loads there with no one signed in to Minds, which clears every account's Sealed chats in it, or until the member clears the site's data.";
|
|
345
|
+
}, {
|
|
346
|
+
readonly id: "sealed.frame_no_telemetry";
|
|
347
|
+
readonly status: "planned";
|
|
348
|
+
readonly enforcement: "client";
|
|
349
|
+
readonly capability: "chat_tier";
|
|
350
|
+
readonly step: "S29";
|
|
351
|
+
readonly says: "The Sealed frame runs no analytics or error-reporting code, and its content security policy names no address for violation reports: besides its own origin, the only place it connects to is the Minds API.";
|
|
352
|
+
}, {
|
|
353
|
+
readonly id: "sealed.web_switch_enforced";
|
|
354
|
+
readonly status: "planned";
|
|
355
|
+
readonly enforcement: "server";
|
|
356
|
+
readonly capability: "chat_tier";
|
|
357
|
+
readonly step: "S28";
|
|
358
|
+
readonly says: "While Sealed in a browser is switched off on the server (SEALED_GATEWAY_WEB, off unless set), the Sealed gateway mints no frame token and accepts none, and refuses every Sealed request that carries an Origin header, as a web page's requests do, whatever its credential, except GET /sealed/config, which reports the switch. Turning it off stops each frame token on its next request.";
|
|
310
359
|
}];
|
|
@@ -56,7 +56,7 @@ export const PRIVACY_FACTS = [
|
|
|
56
56
|
enforcement: "server",
|
|
57
57
|
capability: "chat_tier",
|
|
58
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.",
|
|
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, except the report a member chooses to file (below). 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. Two routes the Sealed frame uses relay nothing, and answer only while Sealed in a browser is switched on: GET /sealed/me answers the member's own age view and adult setting, and POST /sealed/reports reads the report a member chooses to file (a reason, and details they write) and files it as any report is filed.",
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
id: "sealed.dest_tinfoil_only",
|
|
@@ -72,7 +72,7 @@ export const PRIVACY_FACTS = [
|
|
|
72
72
|
enforcement: "server",
|
|
73
73
|
capability: "sealed_history",
|
|
74
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.",
|
|
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, except a report the member chooses to file at POST /sealed/reports, whose reason and the details they write (which may quote the exchange they report) are kept as any report is. GET /sealed/me, which answers the member's own age view and adult setting, writes no row and keeps nothing it answers; it counts the member's requests for a minute in the shared counters, as the attestation relay does.",
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
id: "sealed.backup_excluded",
|
|
@@ -88,7 +88,7 @@ export const PRIVACY_FACTS = [
|
|
|
88
88
|
enforcement: "server",
|
|
89
89
|
capability: "chat_tier",
|
|
90
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.",
|
|
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. The one exception is the report a member chooses to file at POST /sealed/reports, which answers only while Sealed in a browser is switched on: it is filed as any report is, so a report whose reason is child sexual abuse material, intimate images shared without consent or child safety may page Minds staff by email and on the safety rota's pager webhook, carrying the report's ids and the reason's category, never the details the member wrote.",
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
id: "ai_prefs.agent_enabled",
|
|
@@ -354,4 +354,60 @@ export const PRIVACY_FACTS = [
|
|
|
354
354
|
step: "S29",
|
|
355
355
|
says: "Exporting a Sealed chat writes a file on the phone and sends nothing.",
|
|
356
356
|
},
|
|
357
|
+
{
|
|
358
|
+
id: "sealed.attest_before_send_browser",
|
|
359
|
+
status: "planned",
|
|
360
|
+
enforcement: "device",
|
|
361
|
+
capability: "chat_tier",
|
|
362
|
+
step: "S28",
|
|
363
|
+
says: "In a browser, the Sealed frame seals a Sealed request only to the key of a router it checked against a fresh challenge of its own, with the phone's checks: 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. The checks and the sealing are the phone's own Sealed core, built for the browser and run in a Worker inside the frame. 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.",
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "sealed.frame_separate_origin",
|
|
367
|
+
status: "planned",
|
|
368
|
+
enforcement: "server_and_client",
|
|
369
|
+
capability: "chat_tier",
|
|
370
|
+
step: "S29",
|
|
371
|
+
says: "In a browser, Sealed runs only in the Sealed frame, a page of its own loaded from sealed.minds.com, so the Minds page it sits in (such as minds.com or build.minds.com) cannot read what is typed or answered there. The frame tells that page only fixed signals (that it is ready, its state, that it needs a new token, where to go, and that a sign-out clear finished), never words, answers, chat names or counts, and its content security policy, enforced and never report-only, lets only Minds' own pages embed it and lets it connect only to its own origin and the Minds API.",
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: "sealed.frame_token_scoped",
|
|
375
|
+
status: "planned",
|
|
376
|
+
enforcement: "server_and_client",
|
|
377
|
+
capability: "chat_tier",
|
|
378
|
+
step: "S28",
|
|
379
|
+
says: "The Sealed frame never holds the member's app key. The page around it mints a Sealed frame token from that key and hands the frame only that token, which lasts at most 15 minutes, works only on the Sealed routes the frame uses and only from the frame's own origin, cannot mint another, and stops working on its next request once the key it was minted from is revoked.",
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: "sealed.history_browser_only",
|
|
383
|
+
status: "planned",
|
|
384
|
+
enforcement: "device",
|
|
385
|
+
capability: "sealed_history",
|
|
386
|
+
step: "S29",
|
|
387
|
+
says: "In a browser, Sealed chats are saved only in that browser, in the storage it keeps for sealed.minds.com (the Sealed frame's origin), under the account; Minds keeps none of them, apart from what a member chooses to put in a report.",
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
id: "sealed.signout_clears_browser",
|
|
391
|
+
status: "planned",
|
|
392
|
+
enforcement: "client",
|
|
393
|
+
capability: "sealed_history",
|
|
394
|
+
step: "S29",
|
|
395
|
+
says: "Signing out of Minds in a browser clears the account's Sealed chats from that browser when the Sealed frame or its sign-out page answers that they are gone; sign-out waits at most 1.5 seconds for that answer. If neither answers in time but the sign-out page had noted the clear in that browser, the clear finishes the next time the Sealed frame loads there. If the sign-out page could not load, or was stopped before it noted the clear, nothing is noted: the chats can stay in that browser until the Sealed frame next loads there with no one signed in to Minds, which clears every account's Sealed chats in it, or until the member clears the site's data.",
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: "sealed.frame_no_telemetry",
|
|
399
|
+
status: "planned",
|
|
400
|
+
enforcement: "client",
|
|
401
|
+
capability: "chat_tier",
|
|
402
|
+
step: "S29",
|
|
403
|
+
says: "The Sealed frame runs no analytics or error-reporting code, and its content security policy names no address for violation reports: besides its own origin, the only place it connects to is the Minds API.",
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "sealed.web_switch_enforced",
|
|
407
|
+
status: "planned",
|
|
408
|
+
enforcement: "server",
|
|
409
|
+
capability: "chat_tier",
|
|
410
|
+
step: "S28",
|
|
411
|
+
says: "While Sealed in a browser is switched off on the server (SEALED_GATEWAY_WEB, off unless set), the Sealed gateway mints no frame token and accepts none, and refuses every Sealed request that carries an Origin header, as a web page's requests do, whatever its credential, except GET /sealed/config, which reports the switch. Turning it off stops each frame token on its next request.",
|
|
412
|
+
},
|
|
357
413
|
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
|
-
import type { PaginatedResponse, SingleResponse, DeleteResponse, Agent, AgentDetail, CreateAgentInput, CreatedAgent, UpdateAgentInput, UpdatedAgent, AgentChatInput, AgentChatRetryInput, AgentChatResult, AgentConversation, AgentConversationMessage, PaginationParams, StreamChunk } from '../types.js';
|
|
2
|
+
import type { PaginatedResponse, SingleResponse, DeleteResponse, Agent, AgentDetail, CreateAgentInput, CreatedAgent, UpdateAgentInput, UpdatedAgent, AgentChatInput, AgentChatRetryInput, AgentChatResult, AgentConversation, AgentConversationMessage, CreatedAgentConversation, PaginationParams, StreamChunk } from '../types.js';
|
|
3
3
|
export interface AgentMessage {
|
|
4
4
|
id: string;
|
|
5
5
|
networkId: string;
|
|
@@ -241,6 +241,15 @@ export declare class AgentsResource {
|
|
|
241
241
|
chat(id: string, input: AgentChatInput): Promise<SingleResponse<AgentChatResult>>;
|
|
242
242
|
/** List an agent's conversations */
|
|
243
243
|
conversations(id: string, params?: PaginationParams): Promise<PaginatedResponse<AgentConversation>>;
|
|
244
|
+
/**
|
|
245
|
+
* Start a new chat with the caller's own personal agent (`id`): an empty thread in this key's app. Send its first
|
|
246
|
+
* message with `chatStream(id, { message, conversation_id })`; `conversations()` then lists it with its `name`.
|
|
247
|
+
* When the member already has an empty chat with the agent there, that one comes back with `created: false`, so a
|
|
248
|
+
* second tap (or a resent request) never makes another blank thread. An id that names no agent, or an agent outside
|
|
249
|
+
* the network of this key and its app, is 404 `agent_not_found` (the same answer for both); in the caller's network,
|
|
250
|
+
* any agent that is not the caller's own personal agent, or a key acting as an agent, is 403 `not_owner`.
|
|
251
|
+
*/
|
|
252
|
+
createConversation(id: string): Promise<SingleResponse<CreatedAgentConversation>>;
|
|
244
253
|
/**
|
|
245
254
|
* Read the messages in one of an agent's conversations, oldest first.
|
|
246
255
|
*
|
package/dist/resources/agents.js
CHANGED
|
@@ -103,6 +103,17 @@ export class AgentsResource {
|
|
|
103
103
|
conversations(id, params) {
|
|
104
104
|
return this.client.get(`/agents/${id}/conversations`, params);
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Start a new chat with the caller's own personal agent (`id`): an empty thread in this key's app. Send its first
|
|
108
|
+
* message with `chatStream(id, { message, conversation_id })`; `conversations()` then lists it with its `name`.
|
|
109
|
+
* When the member already has an empty chat with the agent there, that one comes back with `created: false`, so a
|
|
110
|
+
* second tap (or a resent request) never makes another blank thread. An id that names no agent, or an agent outside
|
|
111
|
+
* the network of this key and its app, is 404 `agent_not_found` (the same answer for both); in the caller's network,
|
|
112
|
+
* any agent that is not the caller's own personal agent, or a key acting as an agent, is 403 `not_owner`.
|
|
113
|
+
*/
|
|
114
|
+
createConversation(id) {
|
|
115
|
+
return this.client.post(`/agents/${id}/conversations`, {});
|
|
116
|
+
}
|
|
106
117
|
/**
|
|
107
118
|
* Read the messages in one of an agent's conversations, oldest first.
|
|
108
119
|
*
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** A starting intent; private_ai records interest without changing chat protection or providers. */
|
|
2
|
+
export type ExperienceGoal = 'build' | 'create' | 'explore' | 'connect' | 'private_ai' | 'earn';
|
|
3
|
+
export type ExperienceCategory = 'news' | 'technology' | 'science' | 'business' | 'sports' | 'entertainment' | 'gaming' | 'life' | 'politics' | 'ai' | 'crypto' | 'finance' | 'space' | 'nature' | 'health' | 'fitness' | 'music' | 'film-tv' | 'art' | 'photography' | 'books' | 'food' | 'travel' | 'animals' | 'education' | 'comedy';
|
|
4
|
+
export interface MemberExperience {
|
|
5
|
+
goals: ExperienceGoal[];
|
|
6
|
+
interests: ExperienceCategory[];
|
|
7
|
+
/** These categories cannot become the saved starting point; this is not a whole-feed filter. */
|
|
8
|
+
avoid_categories: ExperienceCategory[];
|
|
9
|
+
version: number;
|
|
10
|
+
can_undo: boolean;
|
|
11
|
+
applied_scope: 'saved_starting_point';
|
|
12
|
+
/** A trusted relative route into existing category results; null without a non-avoided interest. */
|
|
13
|
+
discovery: {
|
|
14
|
+
href: string;
|
|
15
|
+
category: ExperienceCategory;
|
|
16
|
+
} | null;
|
|
17
|
+
}
|
|
18
|
+
export interface UpdateMemberExperienceInput {
|
|
19
|
+
/** Required current version from getExperience; a stale save is refused with 409 stale_version. */
|
|
20
|
+
version: number;
|
|
21
|
+
goals?: ExperienceGoal[];
|
|
22
|
+
interests?: ExperienceCategory[];
|
|
23
|
+
avoid_categories?: ExperienceCategory[];
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1188,6 +1188,19 @@ export interface SocialProfilePage {
|
|
|
1188
1188
|
items: SocialProfile[];
|
|
1189
1189
|
cursor: string | null;
|
|
1190
1190
|
}
|
|
1191
|
+
/**
|
|
1192
|
+
* One person a Nostr post's text names (SocialPost.mentions, 0.7.143): at most eight a post, in the order the text first
|
|
1193
|
+
* names them, each only when the server holds their signed metadata, it neither hides nor withholds them, and neither the
|
|
1194
|
+
* app's moderation nor the member's own blocks and mutes hide them (anyone else stays as the text writes them). Show `@displayName` in place of each of `references`, the exact `nostr:`
|
|
1195
|
+
* references in `text` naming this person (at most eight). `handle` is the NIP-05 name their domain confirmed, else
|
|
1196
|
+
* their npub; `pubkey` (hex) opens their profile (`protocol: 'nostr'`, `actor: pubkey`).
|
|
1197
|
+
*/
|
|
1198
|
+
export interface SocialMention {
|
|
1199
|
+
pubkey: string;
|
|
1200
|
+
displayName: string;
|
|
1201
|
+
handle: string;
|
|
1202
|
+
references: string[];
|
|
1203
|
+
}
|
|
1191
1204
|
/** Public source data read on demand; never a canonical Minds post ID. */
|
|
1192
1205
|
export interface SocialPost {
|
|
1193
1206
|
protocol: string;
|
|
@@ -1248,6 +1261,11 @@ export interface SocialPost {
|
|
|
1248
1261
|
* (a Mastodon sensitive flag or content warning, a Bluesky label, a Nostr content-warning) is never returned at all.
|
|
1249
1262
|
*/
|
|
1250
1263
|
sensitive?: boolean;
|
|
1264
|
+
/**
|
|
1265
|
+
* Nostr (0.7.143): the people the text names with NIP-27 references (`nostr:npub1…`, `nostr:nprofile1…`) that the server
|
|
1266
|
+
* can name (SocialMention). Absent when it names nobody the server can name, and on an older server.
|
|
1267
|
+
*/
|
|
1268
|
+
mentions?: SocialMention[];
|
|
1251
1269
|
/** At most one independently filtered quoted post. */
|
|
1252
1270
|
quote?: {
|
|
1253
1271
|
state: 'available';
|
|
@@ -1794,24 +1812,71 @@ export interface ViralReadResponse {
|
|
|
1794
1812
|
export interface SocialReadOptions {
|
|
1795
1813
|
retry?: boolean;
|
|
1796
1814
|
}
|
|
1815
|
+
/**
|
|
1816
|
+
* `protocol: 'all'`: every network's reviewed famous accounts (AT Protocol, the Fediverse, Nostr) in one ranking, then
|
|
1817
|
+
* the provider-ranked ones. A network's own list (`protocol: 'bluesky'` and the rest) leads with that network's famous
|
|
1818
|
+
* accounts too.
|
|
1819
|
+
*/
|
|
1820
|
+
export type SocialPopularProfilesProtocol = 'all' | 'bluesky' | 'activitypub' | 'nostr' | (string & {});
|
|
1821
|
+
export interface SocialPopularProfilesParams {
|
|
1822
|
+
protocol: SocialPopularProfilesProtocol;
|
|
1823
|
+
/** 1 to 50; the server's default is 25. */
|
|
1824
|
+
limit?: number;
|
|
1825
|
+
/** The previous page's `cursor`. A `protocol: 'all'` cursor holds its list across an hourly refresh for a few hours, then is refused (start again). */
|
|
1826
|
+
cursor?: string;
|
|
1827
|
+
/** `protocol: 'all'` only: one of the page's `categories` ('news', 'tech', 'sports' ...). */
|
|
1828
|
+
category?: string;
|
|
1829
|
+
/** The member's Politics choice, as Discover sends it: `hide` (Hide politics) leaves out political accounts; any other choice is the default mix. */
|
|
1830
|
+
compass?: string;
|
|
1831
|
+
}
|
|
1832
|
+
/** One popular account: the profile, the member's Minds subscription, and on `protocol: 'all'` its catalog category. */
|
|
1833
|
+
export interface SocialPopularProfile extends SocialProfile {
|
|
1834
|
+
/** `protocol: 'all'` only: the account's category in the reviewed catalog, null for a provider-ranked account. */
|
|
1835
|
+
category?: string | null;
|
|
1836
|
+
}
|
|
1797
1837
|
/** Provider-ranked accounts. `source` says whether this page is the app's hourly index snapshot or a live read. */
|
|
1798
1838
|
export interface SocialPopularProfiles {
|
|
1799
|
-
items:
|
|
1839
|
+
items: SocialPopularProfile[];
|
|
1800
1840
|
cursor: string | null;
|
|
1841
|
+
/** `protocol: 'all'` only: the categories `category` accepts, best known first. */
|
|
1842
|
+
categories?: string[];
|
|
1801
1843
|
provenance: {
|
|
1844
|
+
/**
|
|
1845
|
+
* Who ranked the list: the network's provider, or, when the reviewed famous accounts lead it (`famous_accounts`),
|
|
1846
|
+
* the ranking in plain words (`fame and recent activity`, URL `urn:famous-accounts`, not a page): no company name,
|
|
1847
|
+
* so an app can show it as is.
|
|
1848
|
+
*/
|
|
1802
1849
|
provider: {
|
|
1803
1850
|
name: string;
|
|
1804
1851
|
url: string;
|
|
1805
1852
|
};
|
|
1806
|
-
/**
|
|
1853
|
+
/**
|
|
1854
|
+
* The ranker's own label: the provider's (`suggested_users` ...), or `famous_accounts` when the platform's reviewed
|
|
1855
|
+
* catalog leads the list (always on `protocol: 'all'`); never a Minds popularity score.
|
|
1856
|
+
*/
|
|
1807
1857
|
ranking: string;
|
|
1808
1858
|
fetchedAt: string;
|
|
1809
1859
|
coverage: 'provider_ranked_window';
|
|
1860
|
+
/**
|
|
1861
|
+
* `famous_accounts` on a network's list (0.7.151): the provider ranking its other accounts come from, with that
|
|
1862
|
+
* provider's own label and read time. Absent when the provider ranked the whole list.
|
|
1863
|
+
*/
|
|
1864
|
+
sources?: SocialPopularProfilesSource[];
|
|
1810
1865
|
source: 'index' | 'live';
|
|
1811
1866
|
/** Provider read time of the served snapshot; null for live reads. */
|
|
1812
1867
|
snapshotAt: string | null;
|
|
1813
1868
|
};
|
|
1814
1869
|
}
|
|
1870
|
+
/** A provider ranking a platform-ranked list drew accounts from: its own label and read time. */
|
|
1871
|
+
export interface SocialPopularProfilesSource {
|
|
1872
|
+
provider: {
|
|
1873
|
+
name: string;
|
|
1874
|
+
url: string;
|
|
1875
|
+
};
|
|
1876
|
+
ranking: string;
|
|
1877
|
+
fetchedAt: string;
|
|
1878
|
+
coverage: 'provider_ranked_window';
|
|
1879
|
+
}
|
|
1815
1880
|
export interface SocialConversation {
|
|
1816
1881
|
focus: SocialPost;
|
|
1817
1882
|
/** Oldest available ancestor first, ending with the immediate parent. */
|
|
@@ -2070,12 +2135,12 @@ export declare class ProtocolsResource {
|
|
|
2070
2135
|
* no items; a 401 or 403 always throws (a session or key failure is never reported as off).
|
|
2071
2136
|
*/
|
|
2072
2137
|
getPublicViral(params?: ViralReadParams, options?: SocialReadOptions): Promise<SingleResponse<ViralReadResponse>>;
|
|
2073
|
-
/**
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2138
|
+
/**
|
|
2139
|
+
* Popular public accounts with explicit provenance; served from the app's index when a fresh snapshot exists.
|
|
2140
|
+
* `protocol: 'all'` reads the famous accounts of every network in one list, paged for infinite scroll, optionally
|
|
2141
|
+
* narrowed to a `category`; `compass: 'hide'` leaves political accounts out of any list.
|
|
2142
|
+
*/
|
|
2143
|
+
getPublicPopularProfiles(params: SocialPopularProfilesParams): Promise<SingleResponse<SocialPopularProfiles>>;
|
|
2079
2144
|
/** Read this connected account's current native following; never infer it from a public count. */
|
|
2080
2145
|
nativeFollowState(input: NativeFollowSourceInput): Promise<SingleResponse<NativeFollowState>>;
|
|
2081
2146
|
/**
|
|
@@ -315,9 +315,14 @@ export class ProtocolsResource {
|
|
|
315
315
|
async getPublicViral(params = {}, options) {
|
|
316
316
|
return viralRead(await this.client.get('/protocols/social/viral', viralQuery(params), ...readOptions(options)));
|
|
317
317
|
}
|
|
318
|
-
/**
|
|
318
|
+
/**
|
|
319
|
+
* Popular public accounts with explicit provenance; served from the app's index when a fresh snapshot exists.
|
|
320
|
+
* `protocol: 'all'` reads the famous accounts of every network in one list, paged for infinite scroll, optionally
|
|
321
|
+
* narrowed to a `category`; `compass: 'hide'` leaves political accounts out of any list.
|
|
322
|
+
*/
|
|
319
323
|
getPublicPopularProfiles(params) {
|
|
320
|
-
return this.client.get('/protocols/social/popular-profiles', { protocol: params.protocol, limit: params.limit, cursor: params.cursor
|
|
324
|
+
return this.client.get('/protocols/social/popular-profiles', { protocol: params.protocol, limit: params.limit, cursor: params.cursor,
|
|
325
|
+
...(params.category !== undefined ? { category: params.category } : {}), ...(params.compass !== undefined ? { compass: params.compass } : {}) });
|
|
321
326
|
}
|
|
322
327
|
/** Read this connected account's current native following; never infer it from a public count. */
|
|
323
328
|
nativeFollowState(input) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
2
|
import type { PaginatedResponse, SingleResponse } from '../types.js';
|
|
3
|
-
export type ReportTargetType = 'post' | 'user' | 'community' | 'message' | 'source_actor' | 'source_post';
|
|
3
|
+
export type ReportTargetType = 'post' | 'user' | 'community' | 'message' | 'sealed_answer' | 'source_actor' | 'source_post';
|
|
4
4
|
export type ReportStatus = 'pending' | 'in_review' | 'escalated' | 'resolved' | 'dismissed';
|
|
5
5
|
export interface CreateReportInput {
|
|
6
6
|
/** External sources require protocols.reportSource so evidence is verified. */
|
|
7
7
|
target_type: Exclude<ReportTargetType, 'source_actor' | 'source_post'>;
|
|
8
|
+
/** For sealed_answer, the authenticated project key's project id; it does not accuse the reporter. Requires supporting API. */
|
|
8
9
|
target_id: string;
|
|
9
10
|
reason: string;
|
|
10
11
|
details?: string;
|
|
@@ -32,9 +33,13 @@ export declare class ReportsResource {
|
|
|
32
33
|
private client;
|
|
33
34
|
constructor(client: HttpClient);
|
|
34
35
|
/**
|
|
35
|
-
* Submit a content report. Targets a post, user, community, or
|
|
36
|
+
* Submit a content report. Targets a post, user, community, message, or Sealed answer
|
|
36
37
|
* by ID, with a short reason and optional free-text details. Reporter
|
|
37
38
|
* is the authenticated user.
|
|
39
|
+
* A sealed_answer targets the key's own project and needs posts:write on a human-owned,
|
|
40
|
+
* non-agent project key. This intake does not recheck live app membership; the Sealed
|
|
41
|
+
* gateway's report route does. Review applies no account enforcement.
|
|
42
|
+
* Deploy supporting API before client adoption.
|
|
38
43
|
*
|
|
39
44
|
* Sent once, never retried automatically: past the member's report budget
|
|
40
45
|
* the server answers 429 (`RateLimitError`, `retryAfter` = seconds until
|
|
@@ -42,6 +47,10 @@ export declare class ReportsResource {
|
|
|
42
47
|
*/
|
|
43
48
|
create(input: CreateReportInput): Promise<SingleResponse<{
|
|
44
49
|
success: true;
|
|
50
|
+
id?: string;
|
|
51
|
+
existing?: boolean;
|
|
52
|
+
/** Sealed answers: false when a join kept the reason but the selected exchange did not fit in full. */
|
|
53
|
+
details_kept?: boolean;
|
|
45
54
|
}>>;
|
|
46
55
|
/** List recent reports in the caller's project/network (admin only). */
|
|
47
56
|
list(): Promise<PaginatedResponse<ReportRecord>>;
|
|
@@ -4,9 +4,13 @@ export class ReportsResource {
|
|
|
4
4
|
this.client = client;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Submit a content report. Targets a post, user, community, or
|
|
7
|
+
* Submit a content report. Targets a post, user, community, message, or Sealed answer
|
|
8
8
|
* by ID, with a short reason and optional free-text details. Reporter
|
|
9
9
|
* is the authenticated user.
|
|
10
|
+
* A sealed_answer targets the key's own project and needs posts:write on a human-owned,
|
|
11
|
+
* non-agent project key. This intake does not recheck live app membership; the Sealed
|
|
12
|
+
* gateway's report route does. Review applies no account enforcement.
|
|
13
|
+
* Deploy supporting API before client adoption.
|
|
10
14
|
*
|
|
11
15
|
* Sent once, never retried automatically: past the member's report budget
|
|
12
16
|
* the server answers 429 (`RateLimitError`, `retryAfter` = seconds until
|
|
@@ -62,12 +62,23 @@ export interface PublicSearchPage<T = Record<string, unknown>> {
|
|
|
62
62
|
};
|
|
63
63
|
/** The `media` tab only: what the web has for the words. Absent where the app does not get it (`ladder.unavailable` names `media`). */
|
|
64
64
|
media?: SearchMedia;
|
|
65
|
+
/**
|
|
66
|
+
* A posts tab's deep stage, page 1 only (server 0.7.137): searches related to what the words mean, best first, for
|
|
67
|
+
* related-topic chips ("hockey": NHL, Stanley Cup). Absent when the server has none in time, and from older servers.
|
|
68
|
+
*/
|
|
69
|
+
related_queries?: Array<{
|
|
70
|
+
query: string;
|
|
71
|
+
}>;
|
|
65
72
|
}
|
|
66
73
|
/** The kinds of a search's media items, in the order the server lists them. */
|
|
67
74
|
export declare const SEARCH_MEDIA_KINDS: readonly ["about", "video", "article", "image", "audio"];
|
|
68
75
|
export type SearchMediaKind = (typeof SEARCH_MEDIA_KINDS)[number];
|
|
69
|
-
/**
|
|
70
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Where a media item was found. `eventregistry` (0.7.133): a news article from the thousands the server's news lanes
|
|
78
|
+
* read each day (Event Registry and outlet feeds), matched by its headline: show its headline, `description` (a short
|
|
79
|
+
* excerpt), `publisher` (the outlet), `published_at`, `thumbnail` and link to `url`, never more of the article.
|
|
80
|
+
*/
|
|
81
|
+
export type SearchMediaSource = 'brave' | 'wikipedia' | 'peertube' | 'openverse' | 'eventregistry';
|
|
71
82
|
/**
|
|
72
83
|
* How a video may play in place: YouTube's own player for `id` (use youtube-nocookie.com and follow its player rules:
|
|
73
84
|
* at least 200x200, nothing drawn over it, YouTube shown as the source), or a PeerTube instance's own embed page.
|
|
@@ -107,7 +118,11 @@ export interface SearchMediaItem {
|
|
|
107
118
|
*/
|
|
108
119
|
sensitive?: true;
|
|
109
120
|
}
|
|
110
|
-
|
|
121
|
+
/**
|
|
122
|
+
* The reads behind the media section. `news_index` (0.7.133): today's articles about the words from those the server's
|
|
123
|
+
* news lanes already read; `eventregistry`: a live per-query Event Registry read, where the server has one.
|
|
124
|
+
*/
|
|
125
|
+
export type SearchMediaProvider = 'brave_web' | 'brave_images' | 'wikipedia' | 'peertube' | 'openverse_images' | 'openverse_audio' | 'news_index' | 'eventregistry';
|
|
111
126
|
/**
|
|
112
127
|
* What each provider did for this answer; `timeout`: it is still reading, ask again shortly for more; `budget`: a
|
|
113
128
|
* spending limit held it back (the day's Brave calls, or this caller's own calls a day or fresh answers an hour, past
|
|
@@ -226,6 +241,14 @@ export interface SearchRelated {
|
|
|
226
241
|
slug: string;
|
|
227
242
|
count: number;
|
|
228
243
|
}>;
|
|
244
|
+
/**
|
|
245
|
+
* Searches related to the query's subject, best first, for related-topic chips ("hockey": NHL, Stanley Cup, ice hockey):
|
|
246
|
+
* from the server's understanding of the query (server 0.7.137), so a query whose posts carry no topic still has them.
|
|
247
|
+
* Absent when the server has none for the query, and from older servers.
|
|
248
|
+
*/
|
|
249
|
+
queries?: Array<{
|
|
250
|
+
query: string;
|
|
251
|
+
}>;
|
|
229
252
|
}
|
|
230
253
|
/** One blended search answer. Only answered sections are present; an answered section may be empty. */
|
|
231
254
|
export interface SearchAnswer {
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
2
|
import type { ChangePasswordInput, ConfirmationCodePurpose, ConfirmationCodeSent, DeletionStatus, DisableAccountInput, LoginHistoryEntry, PaginatedResponse, RequestEmailChangeInput, RevokeOtherSessionsInput, RevokeOtherSessionsResult, Session, SingleResponse, SuccessResponse, UpdateAppearanceInput, UpdateContentPreferencesInput, UpdateNotificationChannelsInput, UpdatePrivacyInput, UserPreferences } from '../types.js';
|
|
3
|
+
import type { MemberExperience, UpdateMemberExperienceInput } from './member-experience.js';
|
|
3
4
|
export declare class SettingsResource {
|
|
4
5
|
private client;
|
|
5
6
|
constructor(client: HttpClient);
|
|
6
7
|
/** Get user preferences */
|
|
7
8
|
getPreferences(): Promise<SingleResponse<UserPreferences>>;
|
|
9
|
+
/** Read your explicit goals and category starting point in this app; requires your own project-bound key. */
|
|
10
|
+
getExperience(): Promise<SingleResponse<MemberExperience>>;
|
|
11
|
+
/** Save explicit choices at the current version. This changes the saved starting point, not whole-feed ranking. */
|
|
12
|
+
updateExperience(input: UpdateMemberExperienceInput): Promise<SingleResponse<MemberExperience>>;
|
|
13
|
+
/** Restore the previous choices if this version is still current; a stale Undo is refused. */
|
|
14
|
+
undoExperience(input: {
|
|
15
|
+
version: number;
|
|
16
|
+
}): Promise<SingleResponse<MemberExperience>>;
|
|
8
17
|
/**
|
|
9
18
|
* Download a portable ZIP containing the caller's profile, posts, social
|
|
10
19
|
* graph, authored messages, account activity, preferences, and fetchable
|
|
@@ -8,6 +8,18 @@ export class SettingsResource {
|
|
|
8
8
|
getPreferences() {
|
|
9
9
|
return this.client.get('/settings/preferences');
|
|
10
10
|
}
|
|
11
|
+
/** Read your explicit goals and category starting point in this app; requires your own project-bound key. */
|
|
12
|
+
getExperience() {
|
|
13
|
+
return this.client.get('/settings/preferences/experience');
|
|
14
|
+
}
|
|
15
|
+
/** Save explicit choices at the current version. This changes the saved starting point, not whole-feed ranking. */
|
|
16
|
+
updateExperience(input) {
|
|
17
|
+
return this.client.patch('/settings/preferences/experience', input);
|
|
18
|
+
}
|
|
19
|
+
/** Restore the previous choices if this version is still current; a stale Undo is refused. */
|
|
20
|
+
undoExperience(input) {
|
|
21
|
+
return this.client.post('/settings/preferences/experience/undo', input);
|
|
22
|
+
}
|
|
11
23
|
/**
|
|
12
24
|
* Download a portable ZIP containing the caller's profile, posts, social
|
|
13
25
|
* graph, authored messages, account activity, preferences, and fetchable
|
package/dist/types.d.ts
CHANGED
|
@@ -1378,6 +1378,13 @@ export interface AgentChatResult {
|
|
|
1378
1378
|
export interface AgentConversation {
|
|
1379
1379
|
id: string;
|
|
1380
1380
|
type: string;
|
|
1381
|
+
/**
|
|
1382
|
+
* The conversation's own name (a group's, the Asks thread's), else, for a one-to-one chat, its title: the first 60
|
|
1383
|
+
* characters of the first message the agent did not send, looked for in the chat's first 20 messages, made by the
|
|
1384
|
+
* server with no model call and gone when that message is. Null when no such message is among the first 20 (before
|
|
1385
|
+
* anyone but the agent has written in it, for one); absent from a server that predates it.
|
|
1386
|
+
*/
|
|
1387
|
+
name?: string | null;
|
|
1381
1388
|
members: {
|
|
1382
1389
|
id: string;
|
|
1383
1390
|
name: string;
|
|
@@ -1404,6 +1411,11 @@ export interface AgentConversationMessage {
|
|
|
1404
1411
|
} | null;
|
|
1405
1412
|
created_at: string;
|
|
1406
1413
|
}
|
|
1414
|
+
/** A new chat with the caller's own personal agent, as `agents.createConversation` returns it. */
|
|
1415
|
+
export interface CreatedAgentConversation extends AgentConversation {
|
|
1416
|
+
/** False when the member's empty chat with the agent in this app was handed back instead of a second blank one. */
|
|
1417
|
+
created: boolean;
|
|
1418
|
+
}
|
|
1407
1419
|
export interface CreateTagInput {
|
|
1408
1420
|
name: string;
|
|
1409
1421
|
description?: string;
|
package/llm.md
CHANGED
|
@@ -161,6 +161,12 @@ const { content, conversationId } = await r.agents.chatStreamText(agent.id, {
|
|
|
161
161
|
conversation_id: existingConvId, // optional
|
|
162
162
|
})
|
|
163
163
|
|
|
164
|
+
// "New chat" with the member's own personal agent: an empty thread in this key's app (201), or the member's existing
|
|
165
|
+
// empty one there (200, created: false). 404 agent_not_found for no agent or another network's; 403 not_owner otherwise.
|
|
166
|
+
const { data: chat } = await r.agents.createConversation(myAgentId) // { id, type, name: null, members, created, ... }
|
|
167
|
+
await r.agents.chatStreamText(myAgentId, { message: 'Plan a weekend in Lisbon', conversation_id: chat.id })
|
|
168
|
+
const { data: chats } = await r.agents.conversations(myAgentId) // chats[].name: own name, else first 60 chars of the first message, else null
|
|
169
|
+
|
|
164
170
|
// The app's agent directory, searched and sorted on the server (0.7.71). Keyless works for visitors:
|
|
165
171
|
// new Recursiv({ anonymous: true, inheritEnvironmentApiKey: false }) on the app's API host (public profiles only).
|
|
166
172
|
const { data: found, meta } = await r.agents.discover({ q: 'space', sort: 'popular', limit: 20 }) // sort: popular | recent | name
|
|
@@ -173,7 +179,7 @@ await r.agents.grantProjectAccess(agent.id, {
|
|
|
173
179
|
})
|
|
174
180
|
```
|
|
175
181
|
|
|
176
|
-
Methods: `list`, `get`, `create`, `update`, `delete`, `chat`, `chatStream`, `chatStreamText`, `retryChatStream`, `retryChatStreamText` ("Try again": `{ conversation_id, retry_message_id }` re-answers your latest message without sending it twice), `conversations
|
|
182
|
+
Methods: `list`, `get`, `create`, `update`, `delete`, `chat`, `chatStream`, `chatStreamText`, `retryChatStream`, `retryChatStreamText` ("Try again": `{ conversation_id, retry_message_id }` re-answers your latest message without sending it twice), `conversations` (each with `name`), `createConversation` ("New chat" with your own personal agent; send with `chatStream`'s `conversation_id`), `discover` (search and sort the app's published agents, cursor paged, keyless-readable), `listDiscoverable`, `leaderboard`, `grantProjectAccess`, `revokeProjectAccess`, `resetRequestCount`, `getByClaimToken`, `claim`, `orchestratorStatus`
|
|
177
183
|
|
|
178
184
|
### r.brain — AI assistant messaging
|
|
179
185
|
|
|
@@ -744,7 +750,7 @@ for (const section of answer.order) render(section, answer.sections[section])
|
|
|
744
750
|
const { data: web } = await r.search.query({ q: 'hockey', sections: ['media'], own_images: shownPictures })
|
|
745
751
|
```
|
|
746
752
|
|
|
747
|
-
`media` (0.7.123): videos (YouTube ids as `embed`, PeerTube embed pages), news articles, pictures, sound and one `about` card (Wikipedia), from Brave Search, Wikipedia, PeerTube and Openverse with strict safe search. It answers only when `sections` names it (the default sections leave it out: each answer may spend a paid web search). Every picture passes the same vision check a post's picture does: a visitor sees only pictures judged safe, and a member who turned Show NSFW on gets a suggestive one marked `sensitive` (blur it). Every `thumbnail` is on the API's signed media proxy; play a YouTube embed through youtube-nocookie.com by its player's rules and show each `attribution`. `own_images` (how many pictures the page already shows) lets the server ask for the web's pictures only when fewer than six. Only the networks the server offers it to get it (others: `omitted` `unavailable`); `providers` says what each source did (`timeout`: ask again shortly). Visitors read it as `publicDiscovery.search` `tab: 'media'` (`PublicSearchPage.media`).
|
|
753
|
+
`media` (0.7.123): videos (YouTube ids as `embed`, PeerTube embed pages), news articles (0.7.133: also today's articles about the words from the server's news lanes, `source: 'eventregistry'`, `providers.news_index`; show the headline, `description` excerpt, `publisher`, date, `thumbnail` and link only), pictures, sound and one `about` card (Wikipedia), from Brave Search, Wikipedia, PeerTube and Openverse with strict safe search. It answers only when `sections` names it (the default sections leave it out: each answer may spend a paid web search). Every picture passes the same vision check a post's picture does: a visitor sees only pictures judged safe, and a member who turned Show NSFW on gets a suggestive one marked `sensitive` (blur it). Every `thumbnail` is on the API's signed media proxy; play a YouTube embed through youtube-nocookie.com by its player's rules and show each `attribution`. `own_images` (how many pictures the page already shows) lets the server ask for the web's pictures only when fewer than six. Only the networks the server offers it to get it (others: `omitted` `unavailable`); `providers` says what each source did (`timeout`: ask again shortly). Visitors read it as `publicDiscovery.search` `tab: 'media'` (`PublicSearchPage.media`).
|
|
748
754
|
|
|
749
755
|
`trending` joins when the answer is thin or old, so a search never dead-ends. A thin answer steps down the search ladder (0.7.79): the word a typo meant, the known words with the rest dropped ("wiggly memes" searches "memes"), what is popular in the category the words are about, then on the app. Every item says how in `via` (`SearchVia`) and `answer.ladder` (`SearchLadder`) says what happened: label it honestly ("No exact matches for X", "Did you mean Y?", "Popular in Comedy right now"); `ladder.unavailable` names sections the key cannot read. Visitors read the same ladder keyless through tRPC `publicDiscovery.search` (`PublicSearchPage`). `top_posts` is floored as `r.posts.search`'s relevance page is; `people` rows carry `followers_count`, `posts_count`, `empty` and `verified` (see `r.profiles.search`); `groups` rows carry `description` (one line) and `last_active_at`, to tell groups of one name apart. Page further with `r.posts.search`. Needs `posts:read`; `people` needs `users:read`, `groups` `communities:read`; `open_network` and `stories` need `users:read` plus the caller's own app key on its network, `trending` that app key. What the key cannot have is in `omitted` (`scope` / `unavailable`).
|
|
750
756
|
|
|
@@ -897,12 +903,14 @@ await r.appSubscriptions.changeTier({ tier: 'pro', proration_date: quote.prorati
|
|
|
897
903
|
- `r.judgments` (0.7.71) — Jev's labels of one post. `get({ kind: 'post' | 'network_post', id })`: switch on `data.view` — `'full'` for the post's author and staff (`judgments` with answers, `safety` verdict, `viral.withheld`; staff also `reviewer_ids`, `review_note`), `'summary'` for other members (`category`, `politics` band in words with its `judgment_id`; never a probability or cell). `data.status === 'unavailable'` (returned, not thrown): hide label actions. `getPublic(projectId, { kind, id })` on an anonymous client: `view: 'category'` only. `dispute(judgmentId, { question_key: 'placement', proposed: 'cm' | … | 'not_political', note? })` (safety: `question_key: 'safety'`, author only): the author's dispute is an appeal; a repeat answers `existing: true`. Staff: `review(judgmentId, { outcome: 'uphold' } | { outcome: 'override', proposed, note?, apply_to_shared? })`, sent once (never retried): a compass override answers 202 `{ status: 'awaiting_reviewer', agreeing, needed }` until a second reviewer (or an agreeing Jev re-judge) proposes the same; a safety override to `'none'` that would free other posts throws 409 `shared_words` (`isJudgmentSharedWordsError(e)`, `e.details.shared_posts`) until resent with `apply_to_shared: true`. Budgets (60 reads/hour, 25 of one author's posts, 20 disputes/day) throw `RateLimitError` with `scope: 'action'` at once. Example: `const { data } = await r.judgments.get({ kind: 'post', id }); if (data.status === 'ok' && data.view === 'summary' && data.politics) await r.judgments.dispute(data.politics.judgment_id, { question_key: 'placement', proposed: 'not_political' })`
|
|
898
904
|
- `r.inviteCodes` / `r.inviteCodesAdmin` — Invite code management
|
|
899
905
|
- `r.organizationSettings` / `r.organizationSecurity` — Org configuration
|
|
906
|
+
- `r.settings` explicit starting choices (unreleased): `const { data: current } = await r.settings.getExperience(); const { data: saved } = await r.settings.updateExperience({ version: current.version, goals: ['explore'], interests: ['technology'] });` Open `saved.discovery?.href` for existing category results; this does not change whole-feed ranking or privacy. `undoExperience({ version: saved.version })` restores the last choices and refuses a stale version. Own project-bound member key only, `settings:read` / `settings:write`; goal ids: build, create, explore, connect, private_ai, earn. Categories are canonical browse ids; no chat text is saved.
|
|
900
907
|
- Native follows: `nativeFollowState({ accountId, protocol, nativeId })`, `prepareNativeFollow({ clientMutationId, accountId, protocol, nativeId, action: "follow" | "unfollow" })`, `submitNativeFollow(id, { approve: true, event? })`, `nativeFollowStatus(id)`. Show the acting identity and native following/requested/unfollowed state separately from the Minds feed subscription. Nostr must sign the complete review event without dropping other contacts; stale lists require a fresh review. Keep mutation IDs and reconcile ambiguous outcomes without replay. Requires current follow scopes and API `NATIVE_SOCIAL_FOLLOW_PROJECTS` plus source controls.
|
|
901
908
|
- Native reposts: `nativeRepostState({ accountId, protocol, nativeId })`, `prepareNativeRepost({ clientMutationId, accountId, protocol, nativeId, action: "repost" | "unrepost" })`, `submitNativeRepost(id, { approve: true, event? })`, `nativeRepostStatus(id)`. Requires `NATIVE_SOCIAL_REPOST_PROJECTS`, account capability and explicit approval; Nostr signs the exact review (kind 6 or own kind 5). Unknown attempts only reconcile. Bluesky connections need the new repost scope; ActivityPub uses write:statuses. Native likes: `nativeLikeState({ accountId, protocol, nativeId })`, `prepareNativeLike({ clientMutationId, accountId, protocol, nativeId, action: "like" | "unlike" })`, `submitNativeLike(id, { approve: true, event? })`, and `nativeLikeStatus(id)`. Display the selected account, source and action before approval. Only Nostr supplies `event`: sign the exact public `signing.event` in the review; never transmit a private key. Preserve mutation IDs on ambiguous responses and refresh unknown results instead of resending. Read capabilities before offering actions; old reply-only connections need reconnecting for likes. Nostr results describe the connected relay, not every public relay. The app-specific `NATIVE_SOCIAL_LIKE_PROJECTS` switch and source controls must be enabled on the API.
|
|
902
909
|
- Delegated social accounts: `r.protocols.nativeSocialAccounts()`, `startNativeSocialConnection({ protocol, identifier })`, `finishNativeSocialConnection({ protocol, state, code?, iss?, error? })`, `disconnectNativeSocialAccount(id)`. Example: `const { data } = await r.protocols.startNativeSocialConnection({ protocol: "bluesky", identifier: "alice.bsky.social" })`; open `data.authorizationUrl`, then complete with the same signed-in app user. Never log callback codes, send passwords, or auto-retry OAuth callbacks. Shared replies (off until schema/API deployment and live verification): `prepareNativeReply({ clientMutationId, text, parent: { protocol, nativeId, readRef? }, accountIds, media?, linkedPostAccountIds?, recoverFrom?: { batchId, deliveryIds } })`, `submitNativeReply(id, { approve: true, deliveryIds })`, `nativeReplyStatus(id)`, `nativeReplyHistory({ limit?, cursor? })`, `prepareNativeReplyRemoval(id, { deliveryIds })`, `removeNativeReply(id, { approve: true, removals })`. Review account, destination, `kind`, source, exact `wireText` and every reviewed `media` item with its alt text; connection is not publication approval. `media` takes the same `NativeMediaInput[]` as `prepareNativePost` and needs the account's `publish_media` capability. `linkedPostAccountIds` explicitly selects different-network standalone posts with a source link, never matching threaded replies or a same-network denial fallback. Preserve mutation IDs, show per-destination results and reconcile uncertain attempts without reposting. To review selected unsent or definitively rejected deliveries again, use recoverFrom with a new mutation ID, the same source/accounts and freshly reviewed text; preserve that ID on uncertain preparation. Only saved recoverable destinations qualify, current gates still apply, and replacedBy disables old sends atomically. Never restart unknown, accepted or published deliveries. The new preview still needs explicit approval. Saved delegated drafts/results: `const { data: history } = await r.protocols.nativeReplyHistory({ limit: 20 })`; use its opaque cursor for the next page and `nativeReplyStatus(item.id)` for details. This is a private read, not a write or provider refresh, and includes Nostr results marked type=nostr. Use nativeNostrReplyResult(item.id) to check that recorded event on its relay without signing or resending. Discard page state on owner/app changes. See README for examples and the callback, revocation and removal contracts.
|
|
903
910
|
- `r.protocols` — Private app safety: `sourceControls` returns current `enabled` plus paged saved choices; `setSourceControl`, `onSourceControlChange` and `reportSource` share app scope. Example: `const choices = new Map(); const stop = r.protocols.onSourceControlChange(change => choices.set(JSON.stringify([change.source, change.control]), change.enabled));` then `await r.protocols.setSourceControl({ source: { protocol: "bluesky", kind: "actor", nativeId: "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa" }, control: "mute", enabled: true, scope: "minds" });` and call `stop(); choices.clear()` on account change/unmount. Notifications follow an exact server confirmation, never an uncertain response. These preferences and reports apply in Minds, not on the remote network. Open network ids (0.7.87, readers first): `import { SOCIAL_PROTOCOL_IDS, isSocialProtocolId } from '@recursiv/sdk'` lists every network a post, account or `SocialSourceRef` may name: bluesky, activitypub, nostr and farcaster (a Farcaster account by its FID, "3"; a cast by `<fid>:0x<40 lowercase hex>`). Listing an id serves nothing: read only what `publicNetworks()` lists, and skip an id this SDK does not know. Writes stay narrower: a native reply, follow, like or repost takes only `NATIVE_WRITE_PROTOCOLS` (bluesky, activitypub, nostr; `isNativeWriteProtocol(post.protocol)` before offering one), and a feed top's `networks` always names those three while a Farcaster lane may be absent (`networks.farcaster ?? []`). Keyless post links: `const visitor = new Recursiv({ anonymous: true, inheritEnvironmentApiKey: false }); const { data: thread } = await visitor.protocols.getSharedConversation(projectId, { protocol: 'nostr', post: publicEventId })`; `sharedNetworks(projectId)` lists public conversation capabilities. Only configured apps; app moderation and rate limits apply, no member state or write authority. Member public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; a Fediverse community a groups read lists under `open_network` (0.7.109): `const { data: page } = await r.protocols.getOpenGroupThreads({ group: entry.actor, limit: 20 })` reads its newest threads read-only (`page.group`, `page.items`); `limit` is 1 to 25, `compass: 'hide'` leaves out the threads in politics, and the next page passes `cursor: page.cursor` while it is set (404 while the app does not offer groups from other networks); private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. `getFollowingFeed` sources: a slow account is `late` (status `late`, or `ok` with `late: true` while its held first page is shown; older APIs say `unavailable` with `late: true`), never a failure: read the next page for its posts (`lateNetworks` names its network). Example after selecting a returned source post: `const { data: thread } = await r.protocols.getPublicConversation({ protocol: post.protocol, post: post.nativeId, readRef: post.readRef })`. Render `focus`, oldest-first `ancestors` and direct `replies`; `public_provider_window` never promises every reply, and `limited` marks known omissions. The author's own thread (0.7.81): `thread` holds the focus author's continuation below the post, oldest first (a "1/" post's 2/, 3/, 4/ ..., at most 20 parts, each held to the same moderation and member controls as a reply); its first part is not repeated in `replies`, so render `focus`, `thread`, then `replies` (`thread ?? []`: older APIs omit it, and a `replies: 'none'` read answers none). A card (a post shared into a chat, a link preview) asks for the post alone, 0.7.78: `getPublicConversation({ protocol, post, replies: 'none' })` (also `getSharedConversation` and `getConnectedSocialConversation`) answers `focus` with no `ancestors` or `replies` and `repliesStatus: 'not_requested'`; a Nostr note answers from one relay read instead of a whole thread's. An older API answers 400 `invalid_input`: read without it. Requires both `users:read` and `posts:read` on the current human app key. Protocol discovery/settings: `const { data: adapters } = await r.protocols.list()` returns bounded `{ id, name, icon, color, maturity, capabilities, limitations }` metadata; `supported` means implemented, not configured, healthy, authorized, or enabled. Methods: `list`, `status`, `listCandidates`, `getCandidate`, `search`, `getSettings`, `refresh`, `updateSettings`.
|
|
904
911
|
- Native Nostr: `const { data: preview } = await r.protocols.prepareNativeNostr(identityId, { action: "publish", content: draft })`; review and sign the exact public event, then `submitNativeNostr(identityId, { intent: preview.intent, event, approve: true })`. Preparation/submission never auto-retry. After uncertainty, use `getNativeNostrPost(identityId, preview.postId ?? preview.eventId)` before a user-selected retry of the identical intent/event: deletion checks the original note's `postId`, publication checks `eventId`. Check `nativeNostrAccount()` enablement first; keys stay with the signer. For replies, after `nativeNostrAccount()` advertises `reply_text_note`, use `prepareNativeNostr(identityId, { action: "reply", content: draft, parentId: post.nativeId })`. Review returned parent/root/participants/relay and exact event, sign with the connected client signer, then `submitNativeNostr(identityId, { intent, event, approve: true })`. Requires users:read, posts:read and posts:write. Kind1 public notes only; reply enablement and shared UI/live proof remain pending. Do not inherit arbitrary notification tags or relay hints. Signing mutations never auto-retry; check `getNativeNostrPost(identityId, eventId)` after uncertainty before a user-selected identical-event retry.
|
|
905
912
|
- Native networks (0.7.89): every eligible member is on ActivityPub, AT Protocol and Nostr by rule. `const { data } = await r.protocols.networks()` lists `{ network, state, reason, mode, handle, engageDefault }` (read-only; `ready` = set up on their first post or interaction, show "Will be set up when you first post or interact"). `activateNetwork(network)` = "Set up now" (`pending` + `retryAt` over budget; 409 `network_off` after the member's off switch). `setNetwork(network, { enabled: false, removePast: true })` turns it off and removes what was shared; `{ enabled: true }` turns it back on (403 `network_locked`, 409 `not_rolled_out`/`age_band_required`). Needs users:read (+ users:write to change). A linked outside account is a Settings choice, never a step in a main flow.
|
|
913
|
+
- Famous accounts (0.7.151): `const { data } = await r.protocols.getPublicPopularProfiles({ protocol: 'all', limit: 24 })` reads the reviewed famous accounts of AT Protocol, the Fediverse and Nostr in one ranking (most famous and recently active first: followers plus readers of the person's Wikipedia article, categories interleaved, politics at most one in four and rotated across sides), then the providers' own suggestions. Page with `cursor` for infinite scroll (it holds its list across an hourly refresh for a few hours; on 400 `invalid_input` start again); `null` is the end. `category` (one of `data.categories`) narrows the list; `compass: 'hide'` (the member's Hide politics) leaves political accounts out of any list. Items carry `category` on `protocol: 'all'`. A list the catalog leads says so: `provenance.ranking` is `famous_accounts` (provider named in plain words, `fame and recent activity`, with no company name, so an app can show it as is), and on a network's own list `provenance.sources` keeps the provider's own label and read time for the accounts after the catalog's. A `protocol: 'all'` cursor is also refused after a deploy changed the catalog. Needs users:read and posts:read; 503 until the app's index has built the list.
|
|
906
914
|
- Hosted Bluesky account on the app's own PDS: `const { data } = await r.protocols.createHostedIdentity({ protocol: 'atproto', handle: 'alice', email })`. Only the label is sent; `data.handle` carries the domain and `data.password` is returned this once, never stored. Never auto-retried. Either the account is finished or it is removed again. 409 `handle_unavailable` = choose another label; 503 `handle_dns_unavailable`/`handle_unresolvable`/`handle_unverified` = operator setup, retry later unchanged.
|
|
907
915
|
- Anonymous discovery: `const visitor = new Recursiv({ anonymous: true, inheritEnvironmentApiKey: false }); const { data } = await visitor.protocols.getSharedTrends(projectId, { protocol: 'nostr', limit: 20, discovery: true })`. Use `sharedNetworks(projectId)` capabilities; open a returned topic with `getSharedTrendFeed(projectId, { protocol: 'bluesky', topic: trend.nativeId, cursor })`. Configured apps only; no account authority or writes. App moderation and per-IP/app rate limits apply. Categories and Jev layouts reuse cached judgments without initiating paid inference; handle partial categories and finite provider windows. Category depth: `r.posts.list({ category: 'science', sort: 'hot', discovery: true })` pages the hot window by offset and then by keyset (`meta.next` is `{ offset }`, `{ cursor }` or `null`; pass a cursor back as `cursor`); when a stage ends, `meta.fallover` lists the next feeds in order (`category_older`, `category_network`, `category` with `via`, `for_you`, `all`; `feed` with `ref` is declared in 0.7.60 but not sent yet, so skip kinds you cannot open). One call for a category's modules: `const { data } = await r.discovery.category({ category: 'science' })` returns `stories`, `network`, `clips`, `people`, `groups`, each `{ items, ms, reason? }`. Watch's Minds videos: `const { data, meta } = await r.discovery.videos({ limit: 12 })` returns public video posts in hot order from a list shared per app (rebuilt about once a minute), each re-checked per call for the viewer; page on `meta.next_cursor`. With no category about one row in four is an open-network clip (0.7.78): `isWatchNetworkClip(row)` tells `{ kind: 'social', id, post: SocialPost }` from a `Post`; open and react to it as the network's post (its `id` is never a Minds id).
|
|
908
916
|
- Progressive Discover feed (0.7.71): `const feed = r.discovery.createProgressiveFeed({ networks, category, recommend, top: { ref: 'builtin:all' }, seen, seenClusters }); const round = await feed.next(part => draw(part))` opens one `createFeed` session per source (Minds with the radar and suggestions, one per network with `public_trends`) and hands each source's page on as a `DiscoveryPart` (`{ source, round, modules, composed?, mixed? }`) the moment it lands, instead of a page that waits for its slowest network (up to 15 s); a round waits `DISCOVERY_STRAGGLER_MS` past its first answer and rejects only when every source failed. The first load is held until Minds and one other network answered (or `FIRST_SCREEN_MS`, 2.5 s, after the first answer; `firstScreenMs: VISITOR_FIRST_SCREEN_MS` for a visitor) and arrives as one `composed: true` part from `composeFirstScreen`: the top's first post when it is a playable video, else a picture lead from the best 8 by engagement (never a story, rail, video strip, column, `isSensitivePost` post or `linksAdultSite` porn link; 0.7.80: never a post with no picture or clip and no engagement while the screen holds one with either, which then leads: media and engagement, else media, else engagement, as `mergeDiscoveryArrival` leads a feed's top and the feed after a line), then every network's posts by engagement (`DiscoveryEngagementMerge`) with row 2 from another network than the lead's when one is left (`FIRST_SCREEN_NETWORK_ROWS`), a story as row 3 and a rail as row 5, nothing dropped. Merge later `mixed` parts with `mergeDiscoveryArrival(rows, arriving, reachedIds)`. `top`/`seen`/`seenClusters`/`bucket` reach every session; the top is read once per open. Also `retryFailed()`, `onStatus(listener)`, `setNetworks(catalog)`; settings `perSource`, `firstScreen`, `onLanded`. For you: `createProgressiveFeed({ createFeed: o => r.discovery.createPersonalFeed(o) }, options, { perSource: false })`. A session's `seen` now also leaves seen network posts out of trend pages, previews and topic pages; in a category a topic page with nothing new ends its topic and after `DISCOVERY_EMPTY_TOPIC_PAGES` (3) in a row the network's other topics are skipped. 0.7.84: a featured module carries `theme` only when its lead window was themed so and most of its own posts were judged into that category (a category feed's posts, by the read's category filter); a feed with no category titles every module by its kind alone ("Videos", not "Videos in Technology" over an unjudged Nostr clip). A post is shown once by every identity (`discoveryItemIdentities(item)`: its key, an open-network post's network id, and each media file by address and by content hash, so a second note carrying the same clip is the same post) across a session's pages, a progressive feed's sources (`withoutSeenItems` takes identities or keys), `mergeDiscoveryArrival` and gallery pooling; `withoutRepeatedDiscoveryItems(rows, shown)` does the same for an app's own rows.
|
|
@@ -912,6 +920,7 @@ await r.appSubscriptions.changeTier({ tier: 'pro', proration_date: quote.prorati
|
|
|
912
920
|
- Political stories (0.7.67): every radar story (`getPublicRadar`, `getSharedRadar`, a story read) and every World Today story (`r.curator.today()`) carries `political`: true when a quarter of its judged posts are politics, its theme or topic is politics, or its headline or summary names an election, a war or troop deployment, a court or justice, a legislature, or a party or politician. `compass: 'hide'` leaves those out whatever their theme; `r.curator.today({ compass: 'hide' })` does the same to the front page, the next story leads, and `meta.compass_filter` counts what was left out. A story from `r.feeds.stories()` or a `r.feeds.top()` lead carries `political` too, and both reads, and `r.curator.forYouWorld()`, leave political stories out under `compass: 'hide'` whatever their theme. An older server omits `political`: decide by `theme` as before.
|
|
913
921
|
- Hide politics as a floor (0.7.72): the radar, `r.curator.today()`, `getPublicTrends`, keyless `visitor.protocols.getSharedTrends(projectId, { protocol, compass: 'hide' })` (visitors send `all` or `hide`, and a side or `mix` only where the catalog lists `compass_sides`, 0.7.116; an older API answers 400 to `compass` there), the viral read, `r.discovery.videos` and a visitor's Discover posts apply `compass: 'hide'` for every caller. Where the operator does not serve the caller the full filter, the answer's `meta.compass_filter` is `{ status: 'applied', basis: 'flags', ... }`: left out by the server's own `political` flags and browse categories, no judgment read. Treat `applied` with or without `basis` as filtered. Choose sides and Mix sides still answer `unavailable` until the operator serves them, and the catalog's `compass_filter` capability still says when to offer Politics in Filters; `compass_sides` (0.7.116) says the viewer may choose sides and Mix too (a visitor only where the operator serves visitors the whole filter).
|
|
914
922
|
- Post language: a `SocialPost` may carry `language`, the lower-case primary subtag the post declares (`'de'`, `'en'`; Bluesky `langs[0]`, Mastodon `language`, a Nostr NIP-32 `ISO-639-1` label). It is absent when the post declares none (most Nostr notes) and on posts from the source index; it is never guessed and no post is dropped for it. Order by it (the viewer's language first, undeclared next), never filter on it.
|
|
923
|
+
- Nostr mentions (0.7.143): a Nostr `SocialPost` (and its available `quote.post`) may carry `mentions`, the people its `text` names with NIP-27 references that the server can name: `{ pubkey, displayName, handle, references }`, at most eight, in the order the text first names them. Draw `@displayName` in place of each string in `references` (the exact `nostr:npub1…`/`nostr:nprofile1…` references in `text`), and open the person with `protocol: 'nostr'`, `actor: pubkey`. `handle` is their NIP-05 name once its domain confirmed it, else their npub. A person the server has not read, whose metadata hides them, or whom the app's moderation or the member's own blocks and mutes hide, is left out: show that reference as the text writes it. The server never follows an nprofile's relay hints. A quoted note's author is named by the newer of what the read relays hold and what the server last read of them (a key only when neither has any), so an author who hides themselves withholds the quote at once.
|
|
915
924
|
- `listCandidates({ protocol?, limit?, cursor? })` and `getCandidate(id)` are read-only quarantine review, not published-post search. Both require `admin` scope, a project-bound key, a live admin role, and existing project-admin access. The key supplies app/network scope; neither method accepts another project or network. They never invoke refresh, change settings, or publish.
|
|
916
925
|
- Lists return `{ data: ProtocolQuarantinedCandidate[], meta: { limit, has_more, next_cursor } }`; use the opaque `next_cursor`, not offset pagination. The API defaults `limit` to 20 and permits at most 50. Details return `{ data: ProtocolQuarantinedCandidate }`. Every candidate has `state: 'quarantined'` and `content_format: 'plain'`; render its bounded `content` as text, respect `content_truncated`, and distinguish upstream `published_at` from local `observed_at` / `created_at`.
|
|
917
926
|
- Safe source IDs are preserved byte-exact. `external_id: null` means unsafe or malformed provenance was withheld, not an invented ID; `author.native_id` may likewise be absent or withheld. Preserve nulls in both list and detail views, and use the candidate's own `id` for `getCandidate`.
|
|
@@ -1012,6 +1021,8 @@ Pagination: pass `limit` and `offset`. Check `meta.has_more`.
|
|
|
1012
1021
|
|
|
1013
1022
|
Auto-retry on 429/5xx built in (exponential backoff, default `maxRetries: 2`), except a 429 with `X-RateLimit-Scope: action` (one member action past its budget): never retried, `RateLimitError.scope === 'action'`, `resetAt` = when it reopens; show it on that action, do not pause the app. A read that holds its own deadline can ask once: `r.posts.list({ ...params, retry: false })` and `r.curator.forYou({ ...params, retry: false })` (0.7.59) throw the first 5xx or 429 with no retry and no `Retry-After` sleep; `retry` is never sent. It rides in the params so a reader that wraps `posts.list` and forwards its params keeps it. Discover's feed sessions (`discovery.createFeed`, `createPersonalFeed`, a `DiscoveryFeedSession` over your own `posts.list` wrapper) read Minds posts this way, as they read the open networks, and report a busy read in `failedSources`; a wrapper that rebuilds the params from chosen fields must copy `retry` too. Member actions with a per-member budget are sent once and never auto-retried: `reports.create`, `moderation.appeal` and `protocols.reportSource` throw the 429 at once (`RateLimitError`, `message` is member-facing, `retryAfter` = seconds until the window opens; a budget refusal carries `X-RateLimit-Scope: action` and no `Retry-After` header).
|
|
1014
1023
|
|
|
1024
|
+
Sealed answer reports (0.7.152; deploy the supporting API before client adoption): `await r.reports.create({ target_type: 'sealed_answer', target_id: appProjectId, reason: 'harmful', details: 'The exchange the member chose to show staff' })`. Use a human-owned, non-agent project key with `posts:write` and its exact project id. This intake does not recheck live app membership; `/sealed/reports` additionally applies the gateway's membership guard. These queue items do not accuse the reporter or hold their unrelated posts/account deletion; a reviewer can close them without account enforcement. Older `user` reports remain account reports and are not reclassified. Existing native clients must adopt this target after the API/SDK rollout; source support alone does not fix them. Every answer request, including a join, spends the shared report budget. Read `data.id`, `data.existing` and `data.details_kept`: false means the reason was retained but the selected text did not fit in full; show a successful partial receipt, not a failure or automatic resend.
|
|
1025
|
+
|
|
1015
1026
|
### Idempotency
|
|
1016
1027
|
|
|
1017
1028
|
The REST API (`/api/v1/*`) supports the `Idempotency-Key` header on POST/PUT/PATCH requests. Include a unique key per mutation to prevent duplicate side effects on retries. The key is scoped per-user and cached for 24 hours. Max key length: 256 characters. The SDK's built-in retry handles 429/5xx automatically, but idempotency keys protect against client-side retries too.
|