@recursiv/sdk 0.7.73 → 0.7.77

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 CHANGED
@@ -93,7 +93,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
93
93
  ### Social
94
94
  | Resource | Key methods |
95
95
  |----------|------------|
96
- | `r.posts` | `create`, `update`, `list`, `liked`, `bookmarked`, `bookmark`, `unbookmark`, `search`, `replies`, `react` |
96
+ | `r.posts` | `create`, `update`, `list`, `get`, `getMany(ids)` (up to 25 posts by id per request, no thread; a `Map` of each id's post or `NotFoundError`; 0.7.77), `liked`, `bookmarked`, `bookmark`, `unbookmark`, `search`, `replies`, `react` |
97
97
  | `r.bookmarks` | `save`, `unsave`, `posts`, `list`, `keys`, `merge` — the member's saved posts, open-network posts and articles, synced across devices; `collections`: `list`, `create`, `update`, `remove`, `add`, `removeItem`, `items`, `posts` — private named collections of saves (0.7.68, where `network.getConfig()` advertises `capabilities.bookmark_collections`) |
98
98
  | `r.feeds` | `top` (what the member is most likely to engage with, rotated by what they saw, with open-network lanes and the lead story), `stories` (the story tail), `publicTop` (a visitor's top, keyless and edge-cacheable) |
99
99
  | `r.communities` | `create`, `join`, `search`, `members` |
@@ -117,9 +117,10 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
117
117
  | `r.github` | GitHub integration |
118
118
  | `r.integrations` | External service connections |
119
119
  | `r.admin` | User management, stats, content reports (`listReports`, `resolveReport`, `dismissReport`) |
120
+ | `r.moderation` | Moderation actions: `publicLog`, `mine`, `appeal`; staff `apply`, `resolveAppeal`, `list({ target_type?, target_id? })` (one target's recorded actions, newest first, at most 100; none named = the network's latest; 0.7.74) and `reverse(actionId)` (sent once: a repeat answers 404 `action_not_found`; 0.7.74); agent grants `listAgentGrants`, `grantAgent`, `revokeAgent` |
120
121
  | `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
121
122
  | `r.goals` | Org goals with task rollups (group dispatcher tasks) |
122
- | `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices; filter with `control`, each item may carry a `display` label), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Native follows: `nativeFollowStates` (every connected account's state for one source, one read), `nativeFollowState`, `prepareNativeFollow`, `submitNativeFollow`, `nativeFollowStatus` (native following or pending approval, distinct from a Minds feed subscription; preserve the complete signed Nostr list). Native reposts and undo: `nativeRepostState`, `prepareNativeRepost`, `submitNativeRepost`, `nativeRepostStatus` (public posts, explicit approval, connected account, no uncertain retries). Native likes and unlikes: `nativeLikeState`, `prepareNativeLike`, `submitNativeLike`, `nativeLikeStatus` (connected authority, app enablement and explicit approval; resume pending actions instead of duplicating them). Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Keyless shared post links: `sharedNetworks(projectId)`, `getSharedConversation(projectId, { protocol, post, readRef? })`. Member public reading: `searchPublicPosts` (selected account required for Bluesky/ActivityPub; public Nostr search), `getConnectedSocialConversation` (preserve result readerAccountId/readRef), `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; radar stories: `getPublicRadarStory` (one story by id with its posts; one that left the radar within its minimum lifetime, or within the last half hour, still answers live with `liveUntil` and the story that continues it; one that has left for longer comes with its archived copy and the live story it continues as; the id may be a World Today edition story's, and the answer then names it in `editionStory` under the story's radar id), keyless `getSharedRadarStory(projectId, { cluster })` and `getSharedRadarPosts(projectId, { cluster })` (keyless reads carry images and videos only from outlets and members marked `visitorMedia`; signed-in reads keep all media, so a share card uses only those); the Viral read (fastest-moving posts across Bluesky, the Fediverse, Nostr and Minds, each against its own network's pace; 0.7.59): `getPublicViral({ network?, category?, media?: 'video', limit?: 5 | 10 | 20 | 40, cursor?, compass? })` under the member's key and keyless `getSharedViral(projectId, params)` (visitors: `compass` `all` or `hide` only; a visitor's `media: 'video'` read can carry items whose video the server withheld, so filter with the exported `viralItemHasVideo` before a video player; `item.post` narrows on `item.protocol`). `status: 'off'` resolves with no items until the app enables the read, and `getSharedViral` also resolves an older API's 401 `missing_api_key` as off; `getPublicViral` throws every 401 and 403, so a session failure is never read as off; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. `list` returns bounded adapter `maturity`, `limitations`, and exhaustive `supported`/`planned`/`unsupported` implementation capabilities (not provider health or enablement); settings, candidate refresh/status, search, and read-only quarantined candidate review via `listCandidates` / `getCandidate` |
123
+ | `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices; filter with `control`, each item may carry a `display` label), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Native follows: `nativeFollowStates` (every connected account's state for one source, one read), `nativeFollowState`, `prepareNativeFollow`, `submitNativeFollow`, `nativeFollowStatus` (native following or pending approval, distinct from a Minds feed subscription; preserve the complete signed Nostr list). Native reposts and undo: `nativeRepostState`, `prepareNativeRepost`, `submitNativeRepost`, `nativeRepostStatus` (public posts, explicit approval, connected account, no uncertain retries). Native likes and unlikes: `nativeLikeState`, `prepareNativeLike`, `submitNativeLike`, `nativeLikeStatus` (connected authority, app enablement and explicit approval; resume pending actions instead of duplicating them). Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Keyless shared post links: `sharedNetworks(projectId)`, `getSharedConversation(projectId, { protocol, post, readRef? })`. Member public reading: `searchPublicPosts` (selected account required for Bluesky/ActivityPub; public Nostr search), `getConnectedSocialConversation` (preserve result readerAccountId/readRef), `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; radar stories: `getPublicRadarStory` (one story by id with its posts; one that left the radar within its minimum lifetime, or within the last half hour, still answers live with `liveUntil` and the story that continues it; one that has left for longer comes with its archived copy and the live story it continues as; the id may be a World Today edition story's, and the answer then names it in `editionStory` under the story's radar id; a story a World Today story seeded carries `volume`, the first page of the posts that story counts across Minds and the open networks, whose `cursor` goes on with `curator.storyPosts` or `curator.memberStoryPosts`), keyless `getSharedRadarStory(projectId, { cluster })` and `getSharedRadarPosts(projectId, { cluster })` (keyless reads carry images and videos only from outlets and members marked `visitorMedia`; signed-in reads keep all media, so a share card uses only those); the Viral read (fastest-moving posts across Bluesky, the Fediverse, Nostr and Minds, each against its own network's pace; 0.7.59): `getPublicViral({ network?, category?, media?: 'video', limit?: 5 | 10 | 20 | 40, cursor?, compass? })` under the member's key and keyless `getSharedViral(projectId, params)` (visitors: `compass` `all` or `hide` only; a visitor's `media: 'video'` read can carry items whose video the server withheld, so filter with the exported `viralItemHasVideo` before a video player; `item.post` narrows on `item.protocol`). `status: 'off'` resolves with no items until the app enables the read, and `getSharedViral` also resolves an older API's 401 `missing_api_key` as off; `getPublicViral` throws every 401 and 403, so a session failure is never read as off; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. `list` returns bounded adapter `maturity`, `limitations`, and exhaustive `supported`/`planned`/`unsupported` implementation capabilities (not provider health or enablement); settings, candidate refresh/status, search, and read-only quarantined candidate review via `listCandidates` / `getCandidate` |
123
124
 
124
125
  Full reference: see [llm.md](./llm.md) (ships with this package) or the type definitions in `dist/`.
125
126
 
package/dist/index.d.ts CHANGED
@@ -158,8 +158,8 @@ export { RealtimeClient } from './realtime.js';
158
158
  export type { RealtimeConnectOptions } from './realtime.js';
159
159
  export { AuthResource } from './resources/auth.js';
160
160
  export type { AuthSession, CurrentSession, SignUpInput, SignInInput, UsernameSignInInput, AppContext, ResetPasswordInput, TwoFactorConfirmation, TwoFactorEnrollment, TwoFactorChallenge, CompleteTwoFactorSignInInput, OAuthClientInfo, SendOtpInput, VerifyOtpInput, CreateApiKeyInput, ApiKeyResult, LegacyHandoffOptions, LegacyHandoffSession, } from './resources/auth.js';
161
- export { PostsResource } from './resources/posts.js';
162
- export type { CategoryOrder, CategoryOrderMeta, CategoryOrderReason } from './resources/posts.js';
161
+ export { POSTS_BATCH_MAX, PostsResource } from './resources/posts.js';
162
+ export type { CategoryOrder, CategoryOrderMeta, CategoryOrderReason, PostBatch, PostBatchItem } from './resources/posts.js';
163
163
  export { UsersResource } from './resources/users.js';
164
164
  export { CommunitiesResource } from './resources/communities.js';
165
165
  export { ChatResource } from './resources/chat.js';
@@ -208,7 +208,7 @@ export type { ReportTargetType, ReportStatus, CreateReportInput, ReportRecord, R
208
208
  export { JudgmentsResource, isJudgmentSharedWordsError } from './resources/judgments.js';
209
209
  export type { JudgmentSubjectKind, JudgmentSource, JudgmentOverrideMethod, PostLabelsStatus, PostLabelsQuery, PostLabelsSubject, PostLabelsCategory, PostLabelsPolitics, PostJudgmentRecord, PostJudgment, PostSafetyAttribution, PostLabelsCategoryView, PostLabelsSummaryView, PostLabelsFullView, PostLabels, DisputeJudgmentInput, JudgmentDispute, ReviewJudgmentInput, JudgmentReviewUpheld, JudgmentReviewAwaitingReviewer, JudgmentReviewOverridden, JudgmentReview, JudgmentSharedWordsDetails, JudgmentSharedWordsError, } from './resources/judgments.js';
210
210
  export { ModerationResource } from './resources/moderation.js';
211
- export type { ModerationTargetType, ModerationActionType, ModerationAppealStatus, ModerationPrinciple, PublicModerationAction, PublicModerationLog, OwnModerationAppeal, OwnModerationAction, OwnModerationLog, ApplyModerationActionInput, AgentModerationMode, AgentModerationGrant, } from './resources/moderation.js';
211
+ export type { ModerationTargetType, ModerationActionType, ModerationAppealStatus, ModerationPrinciple, PublicModerationAction, PublicModerationLog, OwnModerationAppeal, OwnModerationAction, OwnModerationLog, ApplyModerationActionInput, ModerationAction, ListModerationActionsParams, RecordedModerationTargetType, AgentModerationMode, AgentModerationGrant, } from './resources/moderation.js';
212
212
  export { LinkPreviewResource } from './resources/link-preview.js';
213
213
  export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
214
214
  export type { SearchAnswer, SearchGroup, SearchOmitReason, SearchParams, SearchPerson, SearchRelated, SearchResponse, SearchSection, SearchStory, SearchTopic } from './resources/search.js';
package/dist/index.js CHANGED
@@ -229,7 +229,7 @@ export class Recursiv {
229
229
  export { HttpClient } from './client.js';
230
230
  export { RealtimeClient } from './realtime.js';
231
231
  export { AuthResource } from './resources/auth.js';
232
- export { PostsResource } from './resources/posts.js';
232
+ export { POSTS_BATCH_MAX, PostsResource } from './resources/posts.js';
233
233
  export { UsersResource } from './resources/users.js';
234
234
  export { CommunitiesResource } from './resources/communities.js';
235
235
  export { ChatResource } from './resources/chat.js';
@@ -23,10 +23,10 @@ export interface WatchVideosParams {
23
23
  /** A position in the list to start a first read at (0 to 400); a later page sends `cursor`. */
24
24
  offset?: number;
25
25
  /**
26
- * A Politics choice, spelled as `posts.list` takes it. Only Hide politics (`hide`) is applied here: the videos whose
27
- * browse category is politics (or whose compass judgment places them in politics) leave the page and the next ones
28
- * fill it, with or without compass judgments on the server. A side or Mix is answered unfiltered, marked
29
- * `unavailable`. The page says what it did in `meta.compass_filter`.
26
+ * A Politics choice, spelled as `posts.list` takes it. Only Hide politics (`hide`) is applied here, strictly: the
27
+ * videos whose browse category is politics, whose compass judgment calls them political or maybe, or whose title or
28
+ * words name politics leave the page and the next ones fill it, with or without compass judgments on the server. A
29
+ * side or Mix is answered unfiltered, marked `unavailable`. The page says what it did in `meta.compass_filter`.
30
30
  */
31
31
  compass?: string;
32
32
  }
@@ -63,6 +63,30 @@ export interface ApplyModerationActionInput {
63
63
  actor_type?: 'human' | 'ai' | 'system';
64
64
  source_report_id?: string;
65
65
  }
66
+ /** Where an action can be recorded: `source_item` is one open-network post withheld from promotion surfaces. */
67
+ export type RecordedModerationTargetType = ModerationTargetType | 'source_item';
68
+ /** One recorded action as staff read it (GET /moderation/actions): who did what to which target, and why. */
69
+ export interface ModerationAction {
70
+ id: string;
71
+ target_type: RecordedModerationTargetType;
72
+ target_id: string;
73
+ action: ModerationActionType;
74
+ label: string | null;
75
+ reason: string | null;
76
+ /** The moderator or agent who acted; null for a system or classifier action. */
77
+ actor_id: string | null;
78
+ actor_type: 'human' | 'ai' | 'system';
79
+ source_report_id: string | null;
80
+ expires_at: string | null;
81
+ /** Null while the action is in force. */
82
+ reversed_at: string | null;
83
+ created_at: string;
84
+ }
85
+ /** Both fields, for one target's history; neither, for the network's latest actions. */
86
+ export interface ListModerationActionsParams {
87
+ target_type?: RecordedModerationTargetType;
88
+ target_id?: string;
89
+ }
66
90
  /** What a granted agent may do on the report queue: shadow reads it; act also resolves and dismisses. */
67
91
  export type AgentModerationMode = 'shadow' | 'act';
68
92
  /** A network admin's grant letting one AI agent work the report queue. */
@@ -88,6 +112,23 @@ export declare class ModerationResource {
88
112
  success: true;
89
113
  id: string;
90
114
  }>>;
115
+ /**
116
+ * Admin (0.7.74): recorded actions, newest first (at most 100), inside the caller's
117
+ * network and a bound key's app. Name a target (both fields) for its history,
118
+ * e.g. to reverse a ban still in force; name none for the latest actions.
119
+ */
120
+ list(params?: ListModerationActionsParams): Promise<SingleResponse<ModerationAction[]>>;
121
+ /**
122
+ * Admin (0.7.74): reverse an action; the target's state is derived again from what is
123
+ * still in force (an account is unbanned when its last ban goes) and the
124
+ * public log shows the reversal. Sent once, never retried: a repeat of a
125
+ * reversal that landed answers 404 `action_not_found` (`NotFoundError`), as
126
+ * does an action already reversed. A reviewed label override answers 409
127
+ * `use_judgment_review`.
128
+ */
129
+ reverse(actionId: string): Promise<SingleResponse<{
130
+ success: true;
131
+ }>>;
91
132
  /**
92
133
  * List decisions affecting the caller or content they own. This remains
93
134
  * reachable through a valid key while the account is suspended or banned.
@@ -15,6 +15,28 @@ export class ModerationResource {
15
15
  apply(input) {
16
16
  return this.client.post('/moderation/actions', input);
17
17
  }
18
+ /**
19
+ * Admin (0.7.74): recorded actions, newest first (at most 100), inside the caller's
20
+ * network and a bound key's app. Name a target (both fields) for its history,
21
+ * e.g. to reverse a ban still in force; name none for the latest actions.
22
+ */
23
+ list(params = {}) {
24
+ return this.client.get('/moderation/actions', {
25
+ target_type: params.target_type,
26
+ target_id: params.target_id,
27
+ });
28
+ }
29
+ /**
30
+ * Admin (0.7.74): reverse an action; the target's state is derived again from what is
31
+ * still in force (an account is unbanned when its last ban goes) and the
32
+ * public log shows the reversal. Sent once, never retried: a repeat of a
33
+ * reversal that landed answers 404 `action_not_found` (`NotFoundError`), as
34
+ * does an action already reversed. A reviewed label override answers 409
35
+ * `use_judgment_review`.
36
+ */
37
+ reverse(actionId) {
38
+ return this.client.post(`/moderation/actions/${encodeURIComponent(actionId)}/reverse`, undefined, { retry: false });
39
+ }
18
40
  /**
19
41
  * List decisions affecting the caller or content they own. This remains
20
42
  * reachable through a valid key while the account is suspended or banned.
@@ -1,4 +1,5 @@
1
1
  import type { HttpClient } from '../client.js';
2
+ import { RecursivError } from '../errors.js';
2
3
  import type { PaginatedResponse, SingleResponse, DeleteResponse, Post, PostDetail, CreatePostInput, CreatedPost, UpdatePostInput, ListPostsParams, ListPostRepliesParams, PostRepliesPage, SearchPostsParams, SearchPostsResponse, ReactionType } from '../types.js';
3
4
  import type { BookmarkedPost, BookmarksPage, SavedBookmark, UnsavedBookmark } from './bookmarks.js';
4
5
  /** How GET /posts/categories ordered its answer: the member's own order, the app's popularity order, or the fixed order. */
@@ -14,6 +15,21 @@ export interface CategoryOrderMeta {
14
15
  /** Whether For You can personalize for this member yet; absent when the member's order was not read. */
15
16
  personalized?: boolean;
16
17
  }
18
+ /** The most ids one GET /posts/batch request carries: `posts.getMany` sends one request per this many (0.7.77). */
19
+ export declare const POSTS_BATCH_MAX = 25;
20
+ /** A post `posts.getMany` read: the post as `posts.get` serves it to the caller, without its thread (no `replies`). */
21
+ export type PostBatchItem = Omit<PostDetail, 'replies' | 'replies_next_cursor'>;
22
+ /**
23
+ * What `posts.getMany` found for each id it read: the post, or the error that id's read answered. A `NotFoundError`
24
+ * (status 404) is a post that is gone, was never there, or is not for this caller, as `posts.get` answers it.
25
+ */
26
+ export type PostBatch = Map<string, {
27
+ data: PostBatchItem;
28
+ error?: undefined;
29
+ } | {
30
+ data?: undefined;
31
+ error: RecursivError;
32
+ }>;
17
33
  export declare class PostsResource {
18
34
  private client;
19
35
  constructor(client: HttpClient);
@@ -54,6 +70,15 @@ export declare class PostsResource {
54
70
  }): Promise<PaginatedResponse<Post>>;
55
71
  /** Get a single post with its newest replies; `replies_next_cursor` is set when the thread continues. */
56
72
  get(id: string): Promise<SingleResponse<PostDetail>>;
73
+ /**
74
+ * Several posts by id, one request per POSTS_BATCH_MAX ids (GET /posts/batch; 0.7.77), each id answered on its own:
75
+ * the post as `get` serves it to the caller, `viewer` and the caller's own vote and repost included, without its
76
+ * thread (the server reads no replies and no chain for it, so a batch of six answers about as fast as one post), or
77
+ * the error that read answered (`NotFoundError` for one that is gone or not for this caller). Each id is asked once
78
+ * (blank ones are dropped) and the Map is keyed by the ids as given. A request that fails as a whole throws, as an
79
+ * API without this route does (404): read those ids one by one with `get`.
80
+ */
81
+ getMany(ids: readonly string[]): Promise<PostBatch>;
57
82
  /** One page of a post's replies, newest first. Start from `replies_next_cursor` on `get`, then follow `meta.next_cursor`. */
58
83
  replies(id: string, params?: ListPostRepliesParams): Promise<PostRepliesPage>;
59
84
  /** Create a new post */
@@ -1,3 +1,13 @@
1
+ import { errorBodyFromResponse, NotFoundError, RecursivError } from '../errors.js';
2
+ /** The most ids one GET /posts/batch request carries: `posts.getMany` sends one request per this many (0.7.77). */
3
+ export const POSTS_BATCH_MAX = 25;
4
+ /** One entry's error as the error `posts.get` throws for it. */
5
+ function postBatchError(error) {
6
+ const status = error && typeof error === 'object' && typeof error.status === 'number'
7
+ ? error.status : 404;
8
+ const body = errorBodyFromResponse(status, undefined, { error });
9
+ return status === 404 ? new NotFoundError(body) : new RecursivError(status, body);
10
+ }
1
11
  export class PostsResource {
2
12
  client;
3
13
  constructor(client) {
@@ -48,6 +58,35 @@ export class PostsResource {
48
58
  get(id) {
49
59
  return this.client.get(`/posts/${id}`);
50
60
  }
61
+ /**
62
+ * Several posts by id, one request per POSTS_BATCH_MAX ids (GET /posts/batch; 0.7.77), each id answered on its own:
63
+ * the post as `get` serves it to the caller, `viewer` and the caller's own vote and repost included, without its
64
+ * thread (the server reads no replies and no chain for it, so a batch of six answers about as fast as one post), or
65
+ * the error that read answered (`NotFoundError` for one that is gone or not for this caller). Each id is asked once
66
+ * (blank ones are dropped) and the Map is keyed by the ids as given. A request that fails as a whole throws, as an
67
+ * API without this route does (404): read those ids one by one with `get`.
68
+ */
69
+ async getMany(ids) {
70
+ const unique = [...new Set(ids.filter((id) => typeof id === 'string' && id.trim()))];
71
+ const found = new Map();
72
+ for (let start = 0; start < unique.length; start += POSTS_BATCH_MAX) {
73
+ const chunk = unique.slice(start, start + POSTS_BATCH_MAX);
74
+ const answer = await this.client.get('/posts/batch', { ids: chunk.join(',') });
75
+ // A uuid's case is not part of it: the server answers each id once, by its first spelling.
76
+ const entries = new Map((Array.isArray(answer?.data) ? answer.data : [])
77
+ .filter((entry) => entry && typeof entry.id === 'string')
78
+ .map((entry) => [entry.id.trim().toLowerCase(), entry]));
79
+ for (const id of chunk) {
80
+ const entry = entries.get(id.trim().toLowerCase());
81
+ if (!entry) {
82
+ throw new RecursivError(502, { type: 'api_error', code: 'invalid_response',
83
+ message: 'GET /posts/batch did not answer every id it was asked; read them one by one with posts.get' });
84
+ }
85
+ found.set(id, entry.data && !entry.error ? { data: entry.data } : { error: postBatchError(entry.error) });
86
+ }
87
+ }
88
+ return found;
89
+ }
51
90
  /** One page of a post's replies, newest first. Start from `replies_next_cursor` on `get`, then follow `meta.next_cursor`. */
52
91
  replies(id, params) {
53
92
  return this.client.get(`/posts/${encodeURIComponent(id)}/replies`, params ? { limit: params.limit, cursor: params.cursor } : undefined);
@@ -1,5 +1,6 @@
1
1
  import type { HttpClient } from '../client.js';
2
2
  import type { CompassFilterMeta, DiscoveryLayout, PaginatedResponse, PaginationParams, SingleResponse, StoryOutlets } from '../types.js';
3
+ import type { CuratorStoryPostsPage } from './curator.js';
3
4
  /** Canonical protocol operation names returned by the adapter registry. */
4
5
  export declare const PROTOCOL_CAPABILITY_OPERATIONS: readonly ["discover", "stream", "backfill", "search", "resolveIdentity", "fetchProfile", "fetchThread", "publish", "reply", "react", "repost", "follow", "delete", "health", "checkpoint"];
5
6
  export declare const PROTOCOL_CAPABILITY_STATUSES: readonly ["supported", "planned", "unsupported"];
@@ -1509,6 +1510,14 @@ export interface SocialRadarStory {
1509
1510
  * seeded, and `id` is its radar id, the address to link to. A server that cannot resolve edition story ids answers 400.
1510
1511
  */
1511
1512
  editionStory?: string;
1513
+ /**
1514
+ * The posts the World Today story that seeded this story counts across Minds, Bluesky, the Fediverse and Nostr: the
1515
+ * first page of the reader's pool, exactly as `curator.storyPosts` (signed out) or `curator.memberStoryPosts` (signed
1516
+ * in) serves it for `volume.story_key`, whose `cursor` continues there (or walk it with `curator.storyPostPages`).
1517
+ * `volume.discussion` is the count the pages list toward. Absent while the server does not serve World Story Volume
1518
+ * for the app, and for a story no World Today story seeded or whose pool holds no post for this reader.
1519
+ */
1520
+ volume?: CuratorStoryPostsPage;
1512
1521
  }
1513
1522
  /**
1514
1523
  * The Viral read (`getPublicViral`, `getSharedViral`): what is moving fastest on Bluesky, the Fediverse, Nostr and
package/llm.md CHANGED
@@ -630,6 +630,8 @@ const { data: results } = await r.posts.search({ query: 'shipped', community_id:
630
630
 
631
631
  Methods: `list`, `get`, `create`, `update`, `delete`, `search`, `replies(id, { limit, cursor })` (after `replies_next_cursor` on `get`), `react`, `unreact`, `liked`, `bookmarked`, `bookmark`, `unbookmark`
632
632
 
633
+ Several posts by id (0.7.77): `const found = await r.posts.getMany(ids)` reads up to `POSTS_BATCH_MAX` (25) per request (`GET /posts/batch`) and returns a `Map` keyed by id: `found.get(id)?.data` is the post as `get` serves it to the member (`viewer`, `user_reaction`, `reposted_by_viewer_id`), without its thread (no `replies`; no thread is read, so a story page's six posts come back in about the time of one), and `found.get(id)?.error` is a `NotFoundError` for one that is gone or not for this member. Each id is asked once. The call throws when the request fails as a whole, as on an API without the route (404): read those ids one by one with `get`.
634
+
633
635
  Feed definitions (types only; no route serves one yet): `FeedDefinition` is `{ v: 1, sources, ranking: { mode, range? }, filters?, exclude? }`, with 1-`FEED_SOURCES_MAX` (50) sources read as one union (`FEED_SOURCE_KINDS`; `FEED_SOURCE_KEY[kind]` names each kind's one field, e.g. `{ kind: 'tag', slug: 'rust' }`, `{ kind: 'account', ref: 'did:plc:…' }`). `filters.politics` stores only `'hide'`; viewer settings, blocks, mutes and compass Mix/Sides never go in a definition. Built-in feeds: `builtinCategoryFeedRef('science')` → `'builtin:category:science'`, plus `'builtin:all'` and `'builtin:for_you'`.
634
636
 
635
637
  `search` ranks relevance x recency x engagement (recency falls as a power of age: this month outranks years ago) through the promotion safety floor; `sort: 'new'` lists newest first; `media: 'video'` (0.7.69) keeps only posts with a video that plays (relevance only), in the page shape Watch reads, so a player can play a query. Page with `meta.next_cursor` as `cursor`: later relevance pages are slices of the ranking the first page kept for ten minutes (nothing skipped or repeated while it lives; after that, the page continues past the last post's score boundary), newest-first pages continue by (created_at, id). A cursor is bound to its search and sort: another search's, or the other sort's, is 400 `invalid_cursor`. The first relevance page carries `latest` (up to three of the newest matches not on the page, one per author, for a Latest strip); the last page carries `fall_over.lanes` (`new`, `open_network`, `tag`): draw a way on, not a wall.
@@ -820,6 +822,7 @@ const { data: quote } = await r.appSubscriptions.previewTierChange({ tier: 'pro'
820
822
  await r.appSubscriptions.changeTier({ tier: 'pro', proration_date: quote.prorationDate })
821
823
  ```
822
824
  - `r.admin` — Admin operations (user management, stats, network settings); content reports: `listReports({ status? })`, `resolveReport(id)`, `dismissReport(id)` (project-bound keys see only their app's reports)
825
+ - `r.moderation` — Moderation actions. Staff (admin key), 0.7.74: `const { data: actions } = await r.moderation.list({ target_type: 'user', target_id: userId })` reads one target's recorded actions (`ModerationAction`: `id`, `target_type` incl. `source_item`, `action`, `label`, `reason`, `actor_id`, `actor_type`, `source_report_id`, `expires_at`, `reversed_at`, `created_at`), newest first, at most 100, inside the caller's network and a project-bound key's app; `list()` with no target reads the network's latest. Unban = reverse each ban still in force: `for (const a of actions.filter(a => a.action === 'ban' && !a.reversed_at)) await r.moderation.reverse(a.id)`; the target's state is derived again from what is still in force. `reverse` is sent once, never retried: 404 `action_not_found` (`NotFoundError`) means already reversed or not in the caller's scope; 409 `use_judgment_review` means a reviewed label override (change it with `r.judgments.review`). An older SDK has neither method. Also `apply`, `publicLog`, `mine`, `appeal`, `resolveAppeal`, and agent grants `listAgentGrants`, `grantAgent(agentId, 'shadow' | 'act')`, `revokeAgent`.
823
826
  - `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' })`
824
827
  - `r.inviteCodes` / `r.inviteCodesAdmin` — Invite code management
825
828
  - `r.organizationSettings` / `r.organizationSecurity` — Org configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recursiv/sdk",
3
- "version": "0.7.73",
3
+ "version": "0.7.77",
4
4
  "description": "TypeScript SDK for the Recursiv API",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "type": "module",