@recursiv/sdk 0.7.26 → 0.7.27

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
@@ -110,7 +110,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
110
110
  | `r.admin` | User management, stats, content reports (`listReports`, `resolveReport`, `dismissReport`) |
111
111
  | `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
112
112
  | `r.goals` | Org goals with task rollups (group dispatcher tasks) |
113
- | `r.protocols` | Public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; 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` |
113
+ | `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; 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` |
114
114
 
115
115
  Full reference: see [llm.md](./llm.md) (ships with this package) or the type definitions in `dist/`.
116
116
 
@@ -459,3 +459,114 @@ configured. After an uncertain submission, check
459
459
  `getNativeNostrPost(identityId, preview.postId ?? preview.eventId)`: deletion uses
460
460
  the original note's `postId`, while publication uses the new `eventId`. Any
461
461
  user-selected retry must keep the same intent and signed event.
462
+
463
+ When the deployed API advertises `reply_text_note`, the same method can prepare
464
+ `{ action: 'reply', content: draftText, parentId: selectedPost.nativeId }`. Review
465
+ the returned parent, root, participants, relay and exact event before asking the
466
+ signer to sign. The API constructs verified NIP-10 references; callers cannot
467
+ inject tags, participant lists, relay URLs or signing keys. Reply prepare/submit
468
+ also require `posts:read`. This capability remains off until the shared reply UI
469
+ and live delivery are verified. Only public kind-1 notes are supported; additional
470
+ participant selection is unfinished. `textTruncated` labels an abbreviated source
471
+ preview. Preparation and submission never automatically retry; check the event ID
472
+ after an uncertain result before choosing a retry of the identical signed event.
473
+
474
+ ### Delegated Bluesky and ActivityPub accounts
475
+
476
+ These methods require the same signed-in human app key throughout. The API must
477
+ enable the app and deploy the authorization schema before use.
478
+
479
+ ```ts
480
+ const { data: connection } = await r.protocols.startNativeSocialConnection({
481
+ protocol: 'bluesky', identifier: 'alice.bsky.social',
482
+ })
483
+ // Navigate to connection.authorizationUrl. On the configured app callback:
484
+ const { data: account } = await r.protocols.finishNativeSocialConnection({
485
+ protocol: 'bluesky', state, code, iss,
486
+ })
487
+ const { data: accounts } = await r.protocols.nativeSocialAccounts()
488
+ await r.protocols.disconnectNativeSocialAccount(account.id)
489
+ ```
490
+
491
+ ActivityPub uses an operator-approved HTTPS server origin as `identifier` and
492
+ requires Mastodon-compatible OAuth metadata with S256 PKCE. The server configures
493
+ the exact callback path `/social/connect/callback/:protocol`; the app must remove
494
+ its query from browser history immediately and never log codes. No password or
495
+ native token is sent through these SDK methods. Start and callback are single-use
496
+ requests and are never automatically retried. If a callback result is uncertain,
497
+ refresh the account list before starting again.
498
+
499
+ Connection does not approve publication. The account's `capabilities` lists only
500
+ implemented actions, currently empty for these adapters. Local disconnect takes
501
+ effect even when `providerRevocation` is `unconfirmed`; the user can also revoke
502
+ Minds from their network's authorized-app settings.
503
+
504
+ ### One reviewed reply across selected accounts
505
+
506
+ The shared reply API is off by default. Deploy its required schema/API and finish
507
+ live authorization and delivery verification before enabling it. This example
508
+ prepares a preview; it does not publish anything:
509
+
510
+ ```ts
511
+ const { data: preview } = await r.protocols.prepareNativeReply({
512
+ clientMutationId: crypto.randomUUID(), text: draftText,
513
+ parent: { protocol: 'bluesky', nativeId: selectedPost.nativeId },
514
+ accountIds: [blueskyAccount.id, activityPubAccount.id],
515
+ linkedPostAccountIds: [activityPubAccount.id],
516
+ })
517
+ // Review each delivery's account, protocol, kind, parent and exact wireText.
518
+ // After the user approves selected delivery IDs:
519
+ await r.protocols.submitNativeReply(preview.id, { approve: true, deliveryIds: approvedDeliveryIds })
520
+ ```
521
+
522
+ Use `linkedPostAccountIds` only after the user selects a standalone post with the
523
+ source link on that different network. It is not a matching threaded reply, and
524
+ a denied same-network reply never falls back automatically. Verified cross-network
525
+ thread mappings are not implemented here. Nostr uses the connected client signer
526
+ described above. Preserve the mutation
527
+ ID while recovering an uncertain preparation; read `nativeReplyStatus(preview.id)` to
528
+ show each destination's result. Repeated submit reconciles an attempted delivery
529
+ without publishing it again. Removal uses a fresh `prepareNativeReplyRemoval`
530
+ preview and explicit `removeNativeReply` approval for the selected own posts.
531
+
532
+ For a selected unsent or definitively rejected destination, the same prepare
533
+ method accepts `recoverFrom: { batchId: preview.id, deliveryIds: selectedIds }`
534
+ with a **new** mutation ID, the same source/accounts and freshly reviewed text.
535
+ Preserve that new ID if preparation is uncertain. The server creates the fresh
536
+ review and disables only those old destinations atomically; `replacedBy` points
537
+ to the new batch. `recoverable` describes saved eligibility, while current account,
538
+ source and safety checks still apply. Sending, unknown, accepted or published
539
+ destinations cannot be resent this way. The fresh preview still needs explicit
540
+ approval; completed destinations retain their outcomes.
541
+
542
+ `nativeReplyHistory` pages through one private history containing Bluesky/ActivityPub
543
+ drafts and delivery results, plus Nostr replies and linked posts once submitted. It also includes unsent or expired preparations. The
544
+ summary contains a bounded preview of the user's own text and source references,
545
+ not old remote parent text or approval material. Opening a result with
546
+ `nativeReplyStatus(id)` does not send anything. Reading remains available when
547
+ new replies are disabled; it does not refresh credentials or contact a network.
548
+ Nostr items have `type: "nostr"`. `nativeNostrReplyResult(id)` checks the saved event
549
+ on its recorded relay without signing or resending. It requires the same connected
550
+ identity and preserves historical publication/deletion evidence separately from
551
+ `lastReadBack`. A relay absence is not proof of global deletion. Signatures and
552
+ intents are never retained in history.
553
+
554
+ ```ts
555
+ const { data: history } = await r.protocols.nativeReplyHistory({ limit: 20 })
556
+ // Read a saved result without submitting it.
557
+ const firstResult = history.items[0]
558
+ if (firstResult) {
559
+ if ('type' in firstResult && firstResult.type === 'nostr') {
560
+ await r.protocols.nativeNostrReplyResult(firstResult.id)
561
+ } else await r.protocols.nativeReplyStatus(firstResult.id)
562
+ }
563
+ if (history.cursor) {
564
+ const { data: next } = await r.protocols.nativeReplyHistory({ limit: 20, cursor: history.cursor })
565
+ console.log(next.items.length)
566
+ }
567
+ ```
568
+
569
+ Keep the cursor opaque and discard it when the app or signed-in user changes.
570
+ History order is fixed by creation time, so later delivery updates do not move
571
+ older entries across page boundaries. Reopen each result for its current saved
572
+ state and obtain a fresh review before a new write.
package/dist/index.d.ts CHANGED
@@ -160,7 +160,7 @@ export { EnvVarsResource } from './resources/env-vars.js';
160
160
  export type { QueryDatabaseInput, QueryResult } from './resources/databases.js';
161
161
  export { GitHubResource } from './resources/github.js';
162
162
  export { DeploymentsResource } from './resources/deployments.js';
163
- export type { PrepareProtocolServersInput, PreparedProtocolServers, ProtocolServerPreparation } from './resources/deployments.js';
163
+ export type { PrepareProtocolServersInput, PreparedProtocolServers, ProtocolServerPreparation, ProtocolServerUpgradePreview, ProtocolServerUpgradeInput, ProtocolServerUpgradeStatus } from './resources/deployments.js';
164
164
  export { IntegrationsResource } from './resources/integrations.js';
165
165
  export type { ExecuteToolInput, ExecuteToolResult } from './resources/integrations.js';
166
166
  export { ProjectBrainResource } from './resources/project-brain.js';
@@ -189,7 +189,7 @@ export type { UploadMediaInput, UploadUrlInput, UploadUrlResult } from './resour
189
189
  export { WalletResource } from './resources/wallet.js';
190
190
  export { ProtocolsResource } from './resources/protocols.js';
191
191
  export { PROTOCOL_ADAPTER_MATURITIES, PROTOCOL_CAPABILITY_OPERATIONS, PROTOCOL_CAPABILITY_STATUSES, } from './resources/protocols.js';
192
- export type { SocialProfile, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialNetwork, SocialPost, SocialConversation, SocialTrend, SocialTrendProvenance, SocialTrends, SocialTrendFeed, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SearchPublicProfilesParams, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, NativeNostrPrepareInput, NativeNostrPrepared, NativeNostrAccount, NativeNostrPostState, NativeNostrReceipt, HostedPostPreview, ConnectHostedIdentityInput, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
192
+ export type { SocialProfile, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialNetwork, SocialPost, SocialConversation, SocialTrend, SocialTrendProvenance, SocialTrends, SocialTrendFeed, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SearchPublicProfilesParams, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, NativeNostrPrepareInput, NativeNostrPrepared, NativeNostrAccount, NativeNostrPostState, NativeNostrReceipt, NativeSocialProtocol, FederationIdentity, FederationIdentityView, NativeSocialAccount, NativeSocialCallbackInput, NativeReplyPrepareInput, NativeReplyReceipt, NativeReplyBatch, NativeReplyHistory, NativeDelegatedReplyHistoryItem, NativeNostrReplyResult, NativeNostrReplyHistoryItem, NativeReplyRemovalReceipt, NativeReplyRemovalPreview, SocialSourceRef, SocialSourceControl, SocialSourceControlChange, HostedPostPreview, ConnectHostedIdentityInput, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
193
193
  export { InboxResource } from './resources/inbox.js';
194
194
  export { NetworkResource } from './resources/network.js';
195
195
  export { SimulatorResource } from './resources/simulator.js';
@@ -62,6 +62,12 @@ export interface PreparedProtocolServers {
62
62
  runtime_status: 'unverified';
63
63
  }
64
64
  export interface ProtocolServerPreparation {
65
+ /** Non-secret locator for recovering inspection after an operator/client restart. */
66
+ upgrade?: {
67
+ revision: string;
68
+ image: string;
69
+ phase: ProtocolServerUpgradeStatus['phase'];
70
+ };
65
71
  /** Provider process observation only; protocol interoperability remains unverified. */
66
72
  provider_status: 'running' | 'stopped' | 'transitioning' | 'unhealthy' | 'unknown';
67
73
  id: string;
@@ -87,6 +93,32 @@ export interface HostedServiceScope {
87
93
  export interface HostedServiceOperation extends HostedServiceScope {
88
94
  request_hash: string;
89
95
  }
96
+ export interface ProtocolServerUpgradePreview {
97
+ id: string;
98
+ provider_service_uuid: string;
99
+ revision: string;
100
+ from_image: string;
101
+ to_image: string;
102
+ restart_services: ['pds', 'activitypub', 'nostr'];
103
+ provider_status: 'running' | 'stopped';
104
+ operation: 'preview';
105
+ runtime_status: 'unverified';
106
+ storage_identity_verification_required: true;
107
+ }
108
+ export interface ProtocolServerUpgradeInput extends HostedServiceOperation {
109
+ image: string;
110
+ revision: string;
111
+ }
112
+ export interface ProtocolServerUpgradeStatus {
113
+ revision: string;
114
+ phase: 'applying' | 'configuration_saved' | 'restarting' | 'restart_requested';
115
+ runtime_status: 'unverified';
116
+ automatic_retry_allowed: false;
117
+ configuration_matches?: boolean;
118
+ provider_status?: ProtocolServerPreparation['provider_status'];
119
+ restart_services?: ['pds', 'activitypub', 'nostr'];
120
+ identity_verification_required?: true;
121
+ }
90
122
  export interface HostedServiceReservation {
91
123
  id: string;
92
124
  provider_service_uuid: string | null;
@@ -115,6 +147,16 @@ export declare class DeploymentsResource {
115
147
  project_id: string;
116
148
  network_id: string;
117
149
  }): Promise<SingleResponse<ProtocolServerPreparation>>;
150
+ /** Read-only staging image proposal; does not update or restart servers.
151
+ * Requires an unbound owner key and the server-configured approved image digest. */
152
+ previewProtocolServerUpgrade(input: HostedServiceOperation & {
153
+ image: string;
154
+ }): Promise<SingleResponse<ProtocolServerUpgradePreview>>;
155
+ /** Apply a reviewed staging upgrade once. Never automatically retry a lost response.
156
+ * Acceptance does not establish live identity, storage contents or interoperability. */
157
+ upgradeProtocolServers(input: ProtocolServerUpgradeInput): Promise<SingleResponse<ProtocolServerUpgradeStatus>>;
158
+ /** Read the saved upgrade and provider configuration; never replays a write. */
159
+ getProtocolServerUpgrade(input: ProtocolServerUpgradeInput): Promise<SingleResponse<ProtocolServerUpgradeStatus>>;
118
160
  /** Queue a single activation after verifying the saved provider configuration and secrets.
119
161
  * A 202 response is not evidence of live runtime health. */
120
162
  startProtocolServers(input: {
@@ -26,6 +26,20 @@ export class DeploymentsResource {
26
26
  getProtocolServerPreparation(scope) {
27
27
  return this.client.get('/deployments/protocol-servers', { project_id: scope.project_id, network_id: scope.network_id });
28
28
  }
29
+ /** Read-only staging image proposal; does not update or restart servers.
30
+ * Requires an unbound owner key and the server-configured approved image digest. */
31
+ previewProtocolServerUpgrade(input) {
32
+ return this.client.post('/deployments/protocol-servers/upgrade-preview', input);
33
+ }
34
+ /** Apply a reviewed staging upgrade once. Never automatically retry a lost response.
35
+ * Acceptance does not establish live identity, storage contents or interoperability. */
36
+ upgradeProtocolServers(input) {
37
+ return this.client.post('/deployments/protocol-servers/upgrade', input, { retry: false });
38
+ }
39
+ /** Read the saved upgrade and provider configuration; never replays a write. */
40
+ getProtocolServerUpgrade(input) {
41
+ return this.client.post('/deployments/protocol-servers/upgrade-status', input);
42
+ }
29
43
  /** Queue a single activation after verifying the saved provider configuration and secrets.
30
44
  * A 202 response is not evidence of live runtime health. */
31
45
  startProtocolServers(input) {
@@ -1,6 +1,6 @@
1
1
  import type { HttpClient } from '../client.js';
2
2
  import type { SingleResponse } from '../types.js';
3
- export type ModerationTargetType = 'post' | 'user' | 'message' | 'community' | 'agent';
3
+ export type ModerationTargetType = 'post' | 'user' | 'message' | 'community' | 'agent' | 'source_actor' | 'source_post';
4
4
  export type ModerationActionType = 'down_rank' | 'label' | 'remove' | 'suspend' | 'ban';
5
5
  export type ModerationAppealStatus = 'none' | 'pending' | 'upheld' | 'overturned';
6
6
  export interface ModerationPrinciple {
@@ -53,7 +53,8 @@ export interface OwnModerationLog {
53
53
  actions: OwnModerationAction[];
54
54
  }
55
55
  export interface ApplyModerationActionInput {
56
- target_type: ModerationTargetType;
56
+ /** Source actions are resolved through the scoped report queue. */
57
+ target_type: Exclude<ModerationTargetType, 'source_actor' | 'source_post'>;
57
58
  target_id: string;
58
59
  action: ModerationActionType;
59
60
  label?: string;
@@ -162,10 +162,199 @@ export interface HostedNostrIdentityProof {
162
162
  export type NativeNostrPrepareInput = {
163
163
  action: 'publish';
164
164
  content: string;
165
+ } | {
166
+ action: 'reply';
167
+ content: string;
168
+ parentId: string;
169
+ } | {
170
+ action: 'linked_post';
171
+ content: string;
172
+ source: {
173
+ protocol: NativeSocialProtocol;
174
+ nativeId: string;
175
+ readRef?: string;
176
+ };
165
177
  } | {
166
178
  action: 'delete';
167
179
  postId: string;
168
180
  };
181
+ export type NativeSocialProtocol = 'bluesky' | 'activitypub';
182
+ export interface NativeSocialAccount {
183
+ id: string;
184
+ protocol: NativeSocialProtocol;
185
+ nativeId: string;
186
+ serverUrl: string;
187
+ handle: string;
188
+ connectedAt: string;
189
+ capabilities: string[];
190
+ }
191
+ /** A Minds-hosted ActivityPub identity for this app user. Keys never leave the platform. */
192
+ export interface FederationIdentity {
193
+ identifier: string;
194
+ actorUri: string;
195
+ handle: string;
196
+ enabled: boolean;
197
+ origin: string;
198
+ }
199
+ export interface FederationIdentityView {
200
+ enabled: boolean;
201
+ origin: string | null;
202
+ identity: FederationIdentity | null;
203
+ }
204
+ export interface SocialSourceRef {
205
+ protocol: 'bluesky' | 'activitypub' | 'nostr';
206
+ kind: 'actor' | 'post';
207
+ nativeId: string;
208
+ }
209
+ export interface SocialSourceControl {
210
+ id: string;
211
+ source: SocialSourceRef;
212
+ control: 'block' | 'mute' | 'hide';
213
+ scope: 'minds';
214
+ createdAt: string;
215
+ }
216
+ export type SocialSourceControlChange = Omit<SocialSourceControl, 'id' | 'createdAt'> & {
217
+ enabled: boolean;
218
+ };
219
+ export interface NativeReplyPrepareInput {
220
+ clientMutationId: string;
221
+ text: string;
222
+ parent: {
223
+ protocol: SocialSourceRef['protocol'];
224
+ nativeId: string;
225
+ readRef?: string;
226
+ };
227
+ accountIds: string[];
228
+ /** Selected different-network accounts to review as standalone posts with a source link. */
229
+ linkedPostAccountIds?: string[];
230
+ /** Fresh review of selected unsent or definitively rejected deliveries. The original selected reviews are atomically fenced; preserve this mutation ID on retry. */
231
+ recoverFrom?: {
232
+ batchId: string;
233
+ deliveryIds: string[];
234
+ };
235
+ }
236
+ export interface NativeReplyReceipt {
237
+ state: 'published' | 'accepted' | 'unknown' | 'rejected';
238
+ nativeId: string | null;
239
+ sourceUrl: string | null;
240
+ providerId: string | null;
241
+ acknowledged: boolean;
242
+ readBack: 'matched' | 'absent' | 'unavailable';
243
+ }
244
+ export interface NativeReplyRemovalReceipt {
245
+ /** Absence is observed on the owning server; this is not global erasure. */
246
+ state: 'absent' | 'accepted' | 'unknown' | 'rejected';
247
+ acknowledged: boolean;
248
+ readBack: 'absent' | 'present' | 'unavailable';
249
+ }
250
+ export interface NativeReplyRemovalPreview {
251
+ id: string;
252
+ previews: Array<{
253
+ deliveryId: string;
254
+ removalId: string;
255
+ expiresAt: string;
256
+ protocol: NativeSocialProtocol;
257
+ nativeId: string;
258
+ providerId: string;
259
+ sourceUrl: string;
260
+ revision: string | null;
261
+ text: string | null;
262
+ additionalContent: boolean;
263
+ account: {
264
+ nativeId: string;
265
+ handle: string;
266
+ serverUrl: string;
267
+ } | null;
268
+ }>;
269
+ errors: Record<string, {
270
+ code: string;
271
+ message: string;
272
+ }>;
273
+ }
274
+ export interface NativeReplyBatch {
275
+ id: string;
276
+ clientMutationId: string;
277
+ expiresAt: string;
278
+ deliveries: Array<{
279
+ id: string;
280
+ grantId: string;
281
+ protocol: NativeSocialProtocol;
282
+ kind: 'reply' | 'linked_post';
283
+ state: 'prepared' | 'sending' | NativeReplyReceipt['state'];
284
+ attemptedAt: string | null;
285
+ /** Saved attempt eligibility only; source, account authority and app/provider gates are checked again during preparation. */
286
+ recoverable?: boolean;
287
+ /** Newer batch that replaced this destination's approval. This old destination cannot publish. */
288
+ replacedBy?: string;
289
+ account: {
290
+ nativeId: string;
291
+ handle: string;
292
+ serverUrl: string;
293
+ } | null;
294
+ parent: {
295
+ protocol: SocialSourceRef['protocol'];
296
+ nativeId: string;
297
+ sourceUrl: string;
298
+ authorId: string;
299
+ authorHandle: string;
300
+ text: string;
301
+ textTruncated?: boolean;
302
+ };
303
+ text: string;
304
+ wireText: string;
305
+ receipt: NativeReplyReceipt | null;
306
+ removal: {
307
+ id: string;
308
+ expiresAt: string;
309
+ attemptedAt: string | null;
310
+ receipt: NativeReplyRemovalReceipt | null;
311
+ } | null;
312
+ }>;
313
+ }
314
+ export interface NativeSocialCallbackInput {
315
+ protocol: NativeSocialProtocol;
316
+ state: string;
317
+ code?: string;
318
+ iss?: string;
319
+ error?: string;
320
+ }
321
+ export interface NativeDelegatedReplyHistoryItem {
322
+ id: string;
323
+ createdAt: string;
324
+ text: string;
325
+ textTruncated: boolean;
326
+ source: Pick<NativeReplyBatch['deliveries'][number]['parent'], 'protocol' | 'nativeId' | 'sourceUrl'>;
327
+ deliveries: Array<Pick<NativeReplyBatch['deliveries'][number], 'id' | 'protocol' | 'kind' | 'state' | 'attemptedAt' | 'account' | 'recoverable' | 'replacedBy'> & {
328
+ removalReceipt: NativeReplyRemovalReceipt | null;
329
+ }>;
330
+ }
331
+ export interface NativeNostrReplyResult {
332
+ id: string;
333
+ createdAt: string;
334
+ text: string;
335
+ source: {
336
+ protocol: SocialSourceRef['protocol'];
337
+ nativeId: string;
338
+ sourceUrl: string;
339
+ };
340
+ identityId: string;
341
+ pubkey: string;
342
+ relay: string;
343
+ eventId: string;
344
+ action: 'reply' | 'linked_post';
345
+ receipt: NativeNostrReceipt;
346
+ checkedAt: string | null;
347
+ lastReadBack: NativeNostrReceipt['readBack'];
348
+ }
349
+ export interface NativeNostrReplyHistoryItem extends NativeNostrReplyResult {
350
+ type: 'nostr';
351
+ textTruncated: boolean;
352
+ }
353
+ export interface NativeReplyHistory {
354
+ /** One private history, including client-signed Nostr replies once submitted. */
355
+ items: Array<NativeDelegatedReplyHistoryItem | NativeNostrReplyHistoryItem>;
356
+ cursor: string | null;
357
+ }
169
358
  export interface NativeNostrPrepared {
170
359
  intent: string;
171
360
  event: {
@@ -175,17 +364,42 @@ export interface NativeNostrPrepared {
175
364
  tags: string[][];
176
365
  };
177
366
  eventId: string;
178
- action: 'publish' | 'delete';
367
+ action: 'publish' | 'reply' | 'linked_post' | 'delete';
179
368
  identityId: string;
180
369
  pubkey: string;
181
370
  relay: string;
182
371
  expiresAt: string;
183
372
  postId?: string;
373
+ parent?: {
374
+ protocol: 'nostr';
375
+ nativeId: string;
376
+ authorId: string;
377
+ authorHandle: string;
378
+ sourceUrl: string;
379
+ text: string;
380
+ textTruncated: boolean;
381
+ root: {
382
+ nativeId: string;
383
+ authorId: string;
384
+ sourceUrl: string;
385
+ };
386
+ participants: string[];
387
+ tags: string[][];
388
+ };
389
+ source?: {
390
+ protocol: NativeSocialProtocol;
391
+ nativeId: string;
392
+ authorId: string;
393
+ authorHandle: string;
394
+ sourceUrl: string;
395
+ text: string;
396
+ textTruncated?: boolean;
397
+ };
184
398
  }
185
399
  export interface NativeNostrAccount {
186
400
  enabled: boolean;
187
401
  identity: HostedProtocolIdentity | null;
188
- capabilities: Array<'publish_original_text' | 'request_own_post_deletion'>;
402
+ capabilities: Array<'publish_original_text' | 'request_own_post_deletion' | 'reply_text_note' | 'publish_linked_text'>;
189
403
  signing?: 'client';
190
404
  }
191
405
  export interface NativeNostrPostState {
@@ -202,7 +416,7 @@ export interface NativeNostrReceipt {
202
416
  eventId: string;
203
417
  postId: string;
204
418
  relay: string;
205
- action: 'publish' | 'delete';
419
+ action: 'publish' | 'reply' | 'linked_post' | 'delete';
206
420
  state: 'published' | 'deletion_requested' | 'unknown' | 'rejected';
207
421
  acknowledged: boolean;
208
422
  readBack: 'found' | 'absent' | 'unavailable';
@@ -380,10 +594,13 @@ export interface SocialFollowingFeed {
380
594
  export declare class ProtocolsResource {
381
595
  private client;
382
596
  private subscriptionListeners;
597
+ private sourceControlListeners;
383
598
  constructor(client: HttpClient);
384
599
  /** Confirmed changes from this SDK instance only. Unsubscribe when a view unmounts or its account changes. */
385
600
  onSubscriptionChange(listener: (change: SocialSubscriptionChange) => void): () => void;
386
601
  private subscriptionChanged;
602
+ /** Confirmed safety choices from this authenticated SDK instance. Unsubscribe when its account or view changes. */
603
+ onSourceControlChange(listener: (change: SocialSourceControlChange) => void): () => void;
387
604
  publicNetworks(): Promise<SingleResponse<SocialNetwork[]>>;
388
605
  /** Query the live public network. It does not import users or posts. */
389
606
  searchPublicProfiles(params: SearchPublicProfilesParams): Promise<SingleResponse<SocialProfilePage>>;
@@ -419,17 +636,113 @@ export declare class ProtocolsResource {
419
636
  limit?: number;
420
637
  cursor?: string;
421
638
  }): Promise<SingleResponse<SocialTrendFeed>>;
639
+ /** Prepare one exact shared reply preview. Keep the mutation ID across uncertain responses. */
640
+ prepareNativeReply(input: NativeReplyPrepareInput): Promise<SingleResponse<NativeReplyBatch>>;
641
+ /** Send only explicitly approved delivery IDs from this preview. Repeated submissions reconcile; they do not replay a write. */
642
+ submitNativeReply(id: string, input: {
643
+ approve: true;
644
+ deliveryIds: string[];
645
+ }): Promise<SingleResponse<NativeReplyBatch & {
646
+ errors: Record<string, {
647
+ code: string;
648
+ message: string;
649
+ }>;
650
+ }>>;
651
+ /** Read saved delivery results after refresh. This never publishes or contacts a source network. */
652
+ nativeReplyStatus(id: string): Promise<SingleResponse<NativeReplyBatch>>;
653
+ /** Page through this app user's saved delegated drafts/results. This never publishes, refreshes credentials or queries a source network. */
654
+ nativeReplyHistory(params?: {
655
+ limit?: number;
656
+ cursor?: string;
657
+ }): Promise<SingleResponse<NativeReplyHistory>>;
658
+ /** Review the current own posts before removal. A new preview replaces the previous approval. */
659
+ prepareNativeReplyRemoval(id: string, input: {
660
+ deliveryIds: string[];
661
+ }): Promise<SingleResponse<NativeReplyRemovalPreview>>;
662
+ /** Remove only selected, freshly reviewed destinations. Repeating approval never repeats a native delete. */
663
+ removeNativeReply(id: string, input: {
664
+ approve: true;
665
+ removals: Array<{
666
+ deliveryId: string;
667
+ removalId: string;
668
+ }>;
669
+ }): Promise<SingleResponse<NativeReplyBatch & {
670
+ errors: Record<string, {
671
+ code: string;
672
+ message: string;
673
+ }>;
674
+ }>>;
675
+ /** List this app user's delegated accounts. A connection is not approval to post. */
676
+ nativeSocialAccounts(): Promise<SingleResponse<{
677
+ enabled: boolean;
678
+ accounts: NativeSocialAccount[];
679
+ }>>;
680
+ /** Private source controls in this app, with bounded pagination. */
681
+ sourceControls(params?: {
682
+ limit?: number;
683
+ cursor?: string;
684
+ }): Promise<SingleResponse<{
685
+ enabled: boolean;
686
+ items: SocialSourceControl[];
687
+ cursor: string | null;
688
+ }>>;
689
+ /** Block/mute an actor or hide a post in Minds. This does not impersonate a native network block. */
690
+ setSourceControl(input: {
691
+ source: SocialSourceRef;
692
+ control: 'block' | 'mute' | 'hide';
693
+ enabled: boolean;
694
+ scope: 'minds';
695
+ }): Promise<SingleResponse<SocialSourceControlChange>>;
696
+ /** File a report in the app's existing moderation queue, using server-read public source evidence. */
697
+ reportSource(input: {
698
+ source: SocialSourceRef;
699
+ readRef?: string;
700
+ reason: string;
701
+ details?: string;
702
+ scope: 'minds';
703
+ }): Promise<SingleResponse<{
704
+ id: string;
705
+ status: string;
706
+ existing: boolean;
707
+ scope: 'minds';
708
+ }>>;
709
+ /** Open the returned authorization URL in the user's browser; never submit passwords or tokens. */
710
+ startNativeSocialConnection(input: {
711
+ protocol: NativeSocialProtocol;
712
+ identifier: string;
713
+ }): Promise<SingleResponse<{
714
+ protocol: NativeSocialProtocol;
715
+ authorizationUrl: string;
716
+ }>>;
717
+ /** Complete a single-use callback with the same signed-in app user. Remove its query from browser history; do not log codes. */
718
+ finishNativeSocialConnection(input: NativeSocialCallbackInput): Promise<SingleResponse<NativeSocialAccount>>;
719
+ /** Disable this app's saved authority even when the provider cannot acknowledge revocation. */
720
+ disconnectNativeSocialAccount(id: string): Promise<SingleResponse<{
721
+ disconnected: true;
722
+ providerRevocation: 'acknowledged' | 'unconfirmed';
723
+ }>>;
724
+ /** Current Minds-hosted ActivityPub state for this app user; never provisions. */
725
+ federationIdentity(): Promise<SingleResponse<FederationIdentityView>>;
726
+ /** Explicit opt-in; the platform provisions once and never rotates keys or re-enables a disabled identity on retry. */
727
+ enableFederationIdentity(): Promise<SingleResponse<FederationIdentityView>>;
728
+ /** Stops serving and signing for the identity; its stable URL and keys are retained by the platform. */
729
+ disableFederationIdentity(): Promise<SingleResponse<{
730
+ disabled: true;
731
+ identity: FederationIdentity;
732
+ }>>;
422
733
  /** Native writes are off unless enabled for this exact app; public readers are independent. */
423
734
  nativeNostrAccount(): Promise<SingleResponse<NativeNostrAccount>>;
424
735
  /** Prepare an exact event for review. A timeout never automatically creates another signing request. */
425
736
  prepareNativeNostr(id: string, input: NativeNostrPrepareInput): Promise<SingleResponse<NativeNostrPrepared>>;
426
- /** Pass only the public signed event. After a timeout, read the prepared postId (delete) or eventId (publish).
737
+ /** Pass only the public signed event. After a timeout, read the prepared postId (delete) or eventId (publish/reply/linked_post).
427
738
  * Any user-selected retry keeps the same intent/event. */
428
739
  submitNativeNostr(id: string, input: {
429
740
  intent: string;
430
741
  event: HostedNostrIdentityProof;
431
742
  approve: true;
432
743
  }): Promise<SingleResponse<NativeNostrReceipt>>;
744
+ /** Check only the event bound to this private saved result; never sign, prepare or resend it. */
745
+ nativeNostrReplyResult(id: string): Promise<SingleResponse<NativeNostrReplyResult>>;
433
746
  getNativeNostrPost(id: string, postId: string): Promise<SingleResponse<NativeNostrPostState>>;
434
747
  /** Save a private reading subscription after resolving the immutable native ID. */
435
748
  subscribeToProfile(input: {
@@ -37,6 +37,7 @@ function hostedIdentityPath(id) {
37
37
  export class ProtocolsResource {
38
38
  client;
39
39
  subscriptionListeners = new Set();
40
+ sourceControlListeners = new Set();
40
41
  constructor(client) {
41
42
  this.client = client;
42
43
  }
@@ -54,6 +55,11 @@ export class ProtocolsResource {
54
55
  catch { /* Other mounted views still receive the change. */ }
55
56
  }
56
57
  }
58
+ /** Confirmed safety choices from this authenticated SDK instance. Unsubscribe when its account or view changes. */
59
+ onSourceControlChange(listener) {
60
+ this.sourceControlListeners.add(listener);
61
+ return () => { this.sourceControlListeners.delete(listener); };
62
+ }
57
63
  publicNetworks() {
58
64
  return this.client.get('/protocols/social/networks');
59
65
  }
@@ -90,6 +96,98 @@ export class ProtocolsResource {
90
96
  getPublicTrendFeed(params) {
91
97
  return this.client.get('/protocols/social/trend-feed', { protocol: params.protocol, topic: params.topic, limit: params.limit, cursor: params.cursor });
92
98
  }
99
+ /** Prepare one exact shared reply preview. Keep the mutation ID across uncertain responses. */
100
+ prepareNativeReply(input) {
101
+ return this.client.post('/protocols/social/replies/prepare', { clientMutationId: input.clientMutationId, text: input.text,
102
+ parent: { protocol: input.parent.protocol, nativeId: input.parent.nativeId,
103
+ ...(input.parent.readRef !== undefined ? { readRef: input.parent.readRef } : {}) }, accountIds: input.accountIds,
104
+ ...(input.linkedPostAccountIds !== undefined ? { linkedPostAccountIds: input.linkedPostAccountIds } : {}),
105
+ ...(input.recoverFrom !== undefined ? { recoverFrom: { batchId: input.recoverFrom.batchId, deliveryIds: input.recoverFrom.deliveryIds } } : {}) }, { retry: false });
106
+ }
107
+ /** Send only explicitly approved delivery IDs from this preview. Repeated submissions reconcile; they do not replay a write. */
108
+ submitNativeReply(id, input) {
109
+ hostedIdentityPath(id);
110
+ return this.client.post(`/protocols/social/replies/${encodeURIComponent(id)}/submit`, { approve: input.approve, deliveryIds: input.deliveryIds }, { retry: false });
111
+ }
112
+ /** Read saved delivery results after refresh. This never publishes or contacts a source network. */
113
+ nativeReplyStatus(id) {
114
+ hostedIdentityPath(id);
115
+ return this.client.get(`/protocols/social/replies/${encodeURIComponent(id)}`);
116
+ }
117
+ /** Page through this app user's saved delegated drafts/results. This never publishes, refreshes credentials or queries a source network. */
118
+ nativeReplyHistory(params = {}) {
119
+ return this.client.get('/protocols/social/replies', { limit: params.limit, cursor: params.cursor });
120
+ }
121
+ /** Review the current own posts before removal. A new preview replaces the previous approval. */
122
+ prepareNativeReplyRemoval(id, input) {
123
+ hostedIdentityPath(id);
124
+ return this.client.post(`/protocols/social/replies/${encodeURIComponent(id)}/removals/prepare`, { deliveryIds: input.deliveryIds }, { retry: false });
125
+ }
126
+ /** Remove only selected, freshly reviewed destinations. Repeating approval never repeats a native delete. */
127
+ removeNativeReply(id, input) {
128
+ hostedIdentityPath(id);
129
+ return this.client.post(`/protocols/social/replies/${encodeURIComponent(id)}/removals/submit`, { approve: input.approve,
130
+ removals: input.removals.map(value => ({ deliveryId: value.deliveryId, removalId: value.removalId })) }, { retry: false });
131
+ }
132
+ /** List this app user's delegated accounts. A connection is not approval to post. */
133
+ nativeSocialAccounts() {
134
+ return this.client.get('/protocols/social/accounts');
135
+ }
136
+ /** Private source controls in this app, with bounded pagination. */
137
+ sourceControls(params = {}) {
138
+ return this.client.get('/protocols/social/controls', { limit: params.limit, cursor: params.cursor });
139
+ }
140
+ /** Block/mute an actor or hide a post in Minds. This does not impersonate a native network block. */
141
+ async setSourceControl(input) {
142
+ const choice = { source: { protocol: input.source.protocol, kind: input.source.kind, nativeId: input.source.nativeId },
143
+ control: input.control, enabled: input.enabled, scope: input.scope };
144
+ const response = await this.client.post('/protocols/social/controls', choice, { retry: false });
145
+ const saved = response.data;
146
+ if (!saved || saved.scope !== choice.scope || saved.enabled !== choice.enabled || saved.control !== choice.control
147
+ || saved.source?.protocol !== choice.source.protocol || saved.source.kind !== choice.source.kind || saved.source.nativeId !== choice.source.nativeId) {
148
+ throw new ValidationError({ type: 'validation_error', code: 'source_control_unconfirmed', message: 'The saved safety choice could not be confirmed. Check your safety settings before trying again.' });
149
+ }
150
+ for (const listener of [...this.sourceControlListeners]) {
151
+ // A broken view must not turn a confirmed preference into a failed write.
152
+ try {
153
+ listener({ ...choice, source: { ...choice.source } });
154
+ }
155
+ catch { /* Continue notifying the other views. */ }
156
+ }
157
+ return response;
158
+ }
159
+ /** File a report in the app's existing moderation queue, using server-read public source evidence. */
160
+ reportSource(input) {
161
+ return this.client.post('/protocols/social/reports', { source: { protocol: input.source.protocol, kind: input.source.kind, nativeId: input.source.nativeId },
162
+ readRef: input.readRef, reason: input.reason, details: input.details, scope: input.scope }, { retry: false });
163
+ }
164
+ /** Open the returned authorization URL in the user's browser; never submit passwords or tokens. */
165
+ startNativeSocialConnection(input) {
166
+ return this.client.post('/protocols/social/oauth/start', { protocol: input.protocol, identifier: input.identifier }, { retry: false });
167
+ }
168
+ /** Complete a single-use callback with the same signed-in app user. Remove its query from browser history; do not log codes. */
169
+ finishNativeSocialConnection(input) {
170
+ const { protocol, state, code, iss, error } = input;
171
+ return this.client.post('/protocols/social/oauth/callback', { protocol, state,
172
+ ...(code !== undefined ? { code } : {}), ...(iss !== undefined ? { iss } : {}), ...(error !== undefined ? { error } : {}) }, { retry: false });
173
+ }
174
+ /** Disable this app's saved authority even when the provider cannot acknowledge revocation. */
175
+ disconnectNativeSocialAccount(id) {
176
+ hostedIdentityPath(id);
177
+ return this.client.delete(`/protocols/social/accounts/${encodeURIComponent(id)}`);
178
+ }
179
+ /** Current Minds-hosted ActivityPub state for this app user; never provisions. */
180
+ federationIdentity() {
181
+ return this.client.get('/protocols/social/federation/identity');
182
+ }
183
+ /** Explicit opt-in; the platform provisions once and never rotates keys or re-enables a disabled identity on retry. */
184
+ enableFederationIdentity() {
185
+ return this.client.post('/protocols/social/federation/identity', { consent: true }, { retry: false });
186
+ }
187
+ /** Stops serving and signing for the identity; its stable URL and keys are retained by the platform. */
188
+ disableFederationIdentity() {
189
+ return this.client.delete('/protocols/social/federation/identity');
190
+ }
93
191
  /** Native writes are off unless enabled for this exact app; public readers are independent. */
94
192
  nativeNostrAccount() {
95
193
  return this.client.get('/protocols/social/nostr/account');
@@ -97,10 +195,15 @@ export class ProtocolsResource {
97
195
  /** Prepare an exact event for review. A timeout never automatically creates another signing request. */
98
196
  prepareNativeNostr(id, input) {
99
197
  hostedIdentityPath(id);
100
- const body = input.action === 'publish' ? { action: input.action, content: input.content } : { action: input.action, postId: input.postId };
198
+ const body = input.action === 'publish' ? { action: input.action, content: input.content }
199
+ : input.action === 'reply' ? { action: input.action, content: input.content, parentId: input.parentId }
200
+ : input.action === 'linked_post' ? { action: input.action, content: input.content,
201
+ source: { protocol: input.source.protocol, nativeId: input.source.nativeId,
202
+ ...(input.source.readRef !== undefined ? { readRef: input.source.readRef } : {}) } }
203
+ : { action: input.action, postId: input.postId };
101
204
  return this.client.post(`/protocols/social/nostr/${encodeURIComponent(id)}/prepare`, body, { retry: false });
102
205
  }
103
- /** Pass only the public signed event. After a timeout, read the prepared postId (delete) or eventId (publish).
206
+ /** Pass only the public signed event. After a timeout, read the prepared postId (delete) or eventId (publish/reply/linked_post).
104
207
  * Any user-selected retry keeps the same intent/event. */
105
208
  submitNativeNostr(id, input) {
106
209
  hostedIdentityPath(id);
@@ -109,6 +212,11 @@ export class ProtocolsResource {
109
212
  intent: input.intent, event: { id: eventId, pubkey, sig, kind, content, created_at, tags }, approve: input.approve,
110
213
  }, { retry: false });
111
214
  }
215
+ /** Check only the event bound to this private saved result; never sign, prepare or resend it. */
216
+ nativeNostrReplyResult(id) {
217
+ hostedIdentityPath(id);
218
+ return this.client.get(`/protocols/social/nostr/replies/${encodeURIComponent(id)}`);
219
+ }
112
220
  getNativeNostrPost(id, postId) {
113
221
  hostedIdentityPath(id);
114
222
  if (!/^[0-9a-f]{64}$/.test(postId))
@@ -1,9 +1,10 @@
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';
4
- export type ReportStatus = 'pending' | 'resolved' | 'dismissed';
3
+ export type ReportTargetType = 'post' | 'user' | 'community' | 'message' | 'source_actor' | 'source_post';
4
+ export type ReportStatus = 'pending' | 'in_review' | 'escalated' | 'resolved' | 'dismissed';
5
5
  export interface CreateReportInput {
6
- target_type: ReportTargetType;
6
+ /** External sources require protocols.reportSource so evidence is verified. */
7
+ target_type: Exclude<ReportTargetType, 'source_actor' | 'source_post'>;
7
8
  target_id: string;
8
9
  reason: string;
9
10
  details?: string;
package/llm.md CHANGED
@@ -619,8 +619,9 @@ if (subscription.active) {
619
619
  - `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)
620
620
  - `r.inviteCodes` / `r.inviteCodesAdmin` — Invite code management
621
621
  - `r.organizationSettings` / `r.organizationSecurity` — Org configuration
622
- - `r.protocols` — Public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. 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. 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`.
623
- - 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.
622
+ - 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, 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 and exact `wireText`; connection is not publication approval. `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.
623
+ - `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. Public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. 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. 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`.
624
+ - 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.
624
625
  - Public trends: require `public_trends` from `publicNetworks()`, then `getPublicTrends({ protocol, limit?, cursor? })`; render `kind: topics` as source topics and `kind: posts` as source posts. With `trend_feed`, open only a returned current topic using `getPublicTrendFeed({ protocol, topic: trend.nativeId, limit?, cursor? })`. Show provider, observedAt and provider_ranked_window coverage; null rankingWindow is unknown, not a daily/global ranking. Preserve source order, canonical IDs and readRef, and opaque cursors. Topic catalogs do not paginate; Mastodon trends stop at 200 source positions. Both methods require users:read plus posts:read on the current human app key; Nostr trends are unsupported. Propagate errors; trend_unavailable means refresh the topic list. No ingestion or native writes.
625
626
  - `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.
626
627
  - 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`.
@@ -795,3 +796,12 @@ catch (e) { if (e.status === 404) { /* private or doesn't exist */ } }
795
796
  9. **`r.memory` rejects secrets.** Facts containing patterns like `sk_`, `api_key=`, `password=`, or private keys are rejected on write. Store secrets in environment variables, not memory.
796
797
  10. **Memory is network-scoped.** Facts and decisions are isolated per network (tenant). Two teams on different networks cannot see each other's memory.
797
798
  11. **`r.memory.context()` has a token budget.** Default is 2000 tokens (~8000 chars). Pass `max_tokens` to control how much context is returned.
799
+
800
+ Nostr replies and linked posts are saved in the same `protocols.nativeReplyHistory()`
801
+ feed after signed submission. Items with `type: 'nostr'` use
802
+ `protocols.nativeNostrReplyResult(id)` for a read-only check of the saved event and
803
+ its author's deletion requests on the recorded relay. Other history items use
804
+ `nativeReplyStatus(id)`. Do not turn an unknown result into a fresh preparation or
805
+ publication; history stores no signature, signing key or intent. Keep the acting
806
+ user/app-bound result IDs and discard UI state after an account switch. The last
807
+ read-back observation is separate from retained publication/deletion evidence.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recursiv/sdk",
3
- "version": "0.7.26",
3
+ "version": "0.7.27",
4
4
  "description": "TypeScript SDK for the Recursiv API",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "type": "module",