@recursiv/sdk 0.7.82 → 0.7.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -1
- package/dist/assistant/fixedCardData.d.ts +91 -0
- package/dist/assistant/fixedCardData.js +372 -0
- package/dist/assistant/fixedCards.d.ts +35 -0
- package/dist/assistant/fixedCards.js +904 -0
- package/dist/assistant/hiddenText.d.ts +24 -0
- package/dist/assistant/hiddenText.js +263 -0
- package/dist/assistant/index.d.ts +30 -0
- package/dist/assistant/index.js +30 -0
- package/dist/assistant/render.d.ts +31 -0
- package/dist/assistant/render.js +434 -0
- package/dist/assistant/subjectBundle.d.ts +30 -0
- package/dist/assistant/subjectBundle.js +535 -0
- package/dist/assistant/turnCaps.d.ts +3 -0
- package/dist/assistant/turnCaps.js +141 -0
- package/dist/assistant/types.d.ts +569 -0
- package/dist/assistant/types.js +26 -0
- package/dist/assistant-surfaces.d.ts +42 -0
- package/dist/assistant-surfaces.js +113 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.js +8 -1
- package/dist/privacy/facts.generated.d.ts +79 -0
- package/dist/privacy/facts.generated.js +93 -0
- package/dist/privacy/index.d.ts +48 -0
- package/dist/privacy/index.js +42 -0
- package/dist/realtime.d.ts +10 -3
- package/dist/realtime.js +11 -2
- package/dist/resources/agents.d.ts +6 -1
- package/dist/resources/agents.js +12 -2
- package/dist/resources/discovery-merge.d.ts +17 -1
- package/dist/resources/discovery-merge.js +84 -4
- package/dist/resources/discovery-progressive.d.ts +4 -3
- package/dist/resources/discovery-progressive.js +11 -8
- package/dist/resources/discovery.d.ts +9 -1
- package/dist/resources/discovery.js +62 -15
- package/dist/resources/e2ee.d.ts +56 -0
- package/dist/resources/e2ee.js +21 -0
- package/dist/resources/feeds.d.ts +15 -6
- package/dist/resources/feeds.js +2 -1
- package/dist/resources/integrations.d.ts +3 -0
- package/dist/resources/link-preview.d.ts +42 -1
- package/dist/resources/link-preview.js +36 -2
- package/dist/resources/network.d.ts +6 -0
- package/dist/resources/privacy.d.ts +13 -0
- package/dist/resources/privacy.js +14 -0
- package/dist/resources/project-brain.d.ts +8 -0
- package/dist/resources/protocols.d.ts +99 -11
- package/dist/resources/protocols.js +45 -0
- package/dist/resources/sealed.d.ts +127 -7
- package/dist/resources/sealed.js +33 -6
- package/dist/resources/settings.d.ts +3 -1
- package/dist/resources/settings.js +3 -1
- package/dist/resources/users.d.ts +11 -2
- package/dist/resources/users.js +1 -1
- package/dist/social-protocol-ids.d.ts +28 -0
- package/dist/social-protocol-ids.js +30 -0
- package/dist/types.d.ts +71 -0
- package/llm.md +45 -4
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
75
75
|
| Resource | Key methods |
|
|
76
76
|
|----------|------------|
|
|
77
77
|
| `r.agents` | `create`, `update`, `chat`, `chatStream`, `retryChatStream`, `grantProjectAccess`, `conversations`, `discover` (the app's agent directory: server search and sort, keyless-readable) |
|
|
78
|
-
| `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`), `publishKeyPackages`, `keyPackageStatus`, `claimKeyPackage`, `appendEvent
|
|
78
|
+
| `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`) |
|
|
79
79
|
| `r.brain` | `sendMessage` |
|
|
80
80
|
| `r.commands` | `execute`, `gatePrompt` |
|
|
81
81
|
| `r.projectBrain` | `tasks`, `decisions`, `milestones`, `completeTask`, `usage` |
|
|
@@ -101,6 +101,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
101
101
|
| `r.chat` | `send`, `dm`, `createGroup` (`encrypted: true` for an end-to-end encrypted group, 0.7.59), `messages` (cursor paging), `acceptRequest`, `declineRequest`, `leave`, `deleteConversation`; a private group's admin: `addMembers`, `removeMember`, `updateGroup` (0.7.59); photos and voice notes: `uploadAttachment`, `signAttachments` (private chat storage, 0.7.82) |
|
|
102
102
|
| `r.tags` | `create`, `list` |
|
|
103
103
|
| `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) |
|
|
104
|
+
| `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) |
|
|
104
105
|
| `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` |
|
|
105
106
|
| `r.uploads` | `uploadMedia` — upload post/chat bytes end to end |
|
|
106
107
|
| `r.judgments` | Jev's labels of a post (0.7.71): `get({ kind, id })` (the author and staff get every judgment and the safety verdict; other members the category and the compass band in words; `status: 'unavailable'` is returned, not thrown), keyless `getPublic(projectId, { kind, id })` (the category only), `dispute(judgmentId, { question_key, proposed, note? })` (the author's dispute is an appeal; a repeat returns `existing: true`), staff `review(judgmentId, { outcome, proposed?, note?, apply_to_shared? })` (sent once; 202 `awaiting_reviewer` until a compass label's second reviewer, or an agreeing Jev re-judge; `isJudgmentSharedWordsError` for the 409) |
|
|
@@ -121,6 +122,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
121
122
|
| `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` |
|
|
122
123
|
| `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
|
|
123
124
|
| `r.goals` | Org goals with task rollups (group dispatcher tasks) |
|
|
125
|
+
| `r.linkPreview` | `get(url)` — a public link's Open Graph card (`data: null` when it has none). For a chat link pass `{ messageId }` or `{ conversationId }` on a signed-in key with `chat:read`: the link is fetched only when it is in the named message and a person wrote that message, or the caller wrote it in that conversation, else `refused: 'ai_link'`. A source with no id rejects (`ValidationError`). Not in encrypted chats: the server cannot read them, so leave `source` out there and apply the app's own rule |
|
|
124
126
|
| `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?, replies?: 'none' })` (`replies: 'none'`, 0.7.78: the post alone for a card, with `repliesStatus: 'not_requested'`; `getPublicConversation` and `getConnectedSocialConversation` take it too). 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` |
|
|
125
127
|
|
|
126
128
|
Full reference: see [llm.md](./llm.md) (ships with this package) or the type definitions in `dist/`.
|
|
@@ -360,6 +362,112 @@ export async function POST(req: NextRequest) {
|
|
|
360
362
|
|
|
361
363
|
Server-side only — the `server-only` guard fails the build if it's ever imported into a client component. Keep it out of `middleware.ts` as well (edge bundle size); gate routes with a plain cookie-presence check on a shared constant.
|
|
362
364
|
|
|
365
|
+
## Assistant subjects (`@recursiv/sdk/assistant`)
|
|
366
|
+
|
|
367
|
+
Two pure functions for asking an assistant about a post: no network, clock, randomness or Node-only API, so a server, a React Native phone and a web app build the same model input from the same data (unreleased).
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { buildSubjectBundle, renderBundleForModel } from '@recursiv/sdk/assistant'
|
|
371
|
+
|
|
372
|
+
const { bundle, refused, excluded } = buildSubjectBundle({
|
|
373
|
+
mode: 'standard', // or 'sealed'
|
|
374
|
+
asker: { id: me.id, blocked_ids, muted_ids }, // a server also passes blocked_by_ids
|
|
375
|
+
post, parent, quoted, replies, // data you already hold; nothing is fetched
|
|
376
|
+
})
|
|
377
|
+
if (!bundle) return showUnavailable() // make no model call; never show the asker `audit`
|
|
378
|
+
// boundary: fresh per call, e.g. 32 hex digits from crypto.getRandomValues
|
|
379
|
+
const { system, user, references } = renderBundleForModel(bundle, 'explain', { boundary })
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
- `buildSubjectBundle` leaves out posts and replies whose authors turned asks off (`ai_ask_allowed`: `'all'`, `'sealed_only'` read only in Sealed, `'off'`), authors the asker blocked or muted and authors who blocked the asker, known-minor authors (unless the asker is the author), posts under a child-safety hold and posts a moderator suppressed. Pictures go as alt text and the stored caption only, never bytes or addresses. Hidden text (zero-width, bidi and tag characters, HTML comments and hidden elements, invisible markdown) is stripped, and the top 20 replies and every field are capped. Every safety flag fails closed, and an id list it cannot read throws.
|
|
383
|
+
- `known_minor`, `child_safety_hold`, `assistant_suppressed`, media `held: true` and `blocked_by_ids` are server-side inputs: never send them to another member's client (leave held media out of that payload, and the post's stored caption, `ai_caption`, whenever any of its media is held: the caption may describe the held picture, and a client builder cannot see media it was never sent). A client builder passes each post's `assist_readable` (`'all'`, `'sealed_only'`, `'none'`), the server's verdict for that viewer, which does not say why. The one exception is a picture's `held: false`, the payload's "not held" mark beside `checked_at` (below).
|
|
384
|
+
- `refused` and `excluded` are what the asker may be told: blocks, minors, holds and suppression all read `'unavailable'`, so they never say who blocked the asker, whose account is a minor's or that a post is held. `audit` holds the detail for the server's log only.
|
|
385
|
+
- `renderBundleForModel(bundle, intent, { boundary, language? })` spotlights the bundle for `explain`, `summarise`, `fact_context`, `translate` or `describe`: the content sits between two lines carrying your per-call random boundary, every member-written line starts with `| `, each item is labelled with its id and handle, and `references` lists the only ids, handles and addresses an answer may use (never a picture address; write them only as plain links, never render an image the model wrote). Render only bundles built from your own reads.
|
|
386
|
+
|
|
387
|
+
#### Post pictures as pictures
|
|
388
|
+
|
|
389
|
+
Pass each media item's `id`, the payload's `checked_at` (when Minds last matched the picture against the child-safety hash lists) and its `held: false` mark into `buildSubjectBundle`, then list the candidates, run your own checks on them, and render with the ones that passed:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { type BundlePicture, bundlePictures, renderBundleForModel } from '@recursiv/sdk/assistant'
|
|
393
|
+
|
|
394
|
+
const max = Math.max(0, 3 - phonePictures.length) // 3 pictures per message in all; the composer caps phone pictures at 3
|
|
395
|
+
const listed = bundlePictures(bundle, 'explain', { now: Date.now(), max })
|
|
396
|
+
// Your checks load each listed picture from your image cache (never fetched), prepare it as sealed-full-scope-v1 2.2
|
|
397
|
+
// says (longest side at most 1536 px, re-encoded as JPEG, metadata stripped), check the prepared bytes, and return the
|
|
398
|
+
// pictures that passed, each with the prepared bytes they checked. Send those bytes: a second read of the cache could
|
|
399
|
+
// return a picture nobody checked, and the raw cached file still carries its metadata.
|
|
400
|
+
const passed: { picture: BundlePicture; prepared_jpeg: string }[] = await runYourPictureChecks(listed)
|
|
401
|
+
const same = (a: BundlePicture, b: BundlePicture) =>
|
|
402
|
+
a.item_id === b.item_id && a.media_id === b.media_id && a.number === b.number
|
|
403
|
+
const { system, user_parts, pictures } = renderBundleForModel(bundle, 'explain', {
|
|
404
|
+
boundary,
|
|
405
|
+
pictures: {
|
|
406
|
+
now: Date.now(), // a fresh clock: a picture that went stale during your checks goes as alt text
|
|
407
|
+
max,
|
|
408
|
+
allowed: passed.map((x) => x.picture),
|
|
409
|
+
cached: (p) => passed.find((x) => same(x.picture, p))?.prepared_jpeg ?? null,
|
|
410
|
+
},
|
|
411
|
+
})
|
|
412
|
+
// user_parts: the opening line and the content's text, one { type: 'image_url', image_url: { url: 'data:image/jpeg;base64,…' } }
|
|
413
|
+
// per picture, then the closing line and the task. Send `system` and `user_parts`: with a picture attached, `user` and
|
|
414
|
+
// `text` name it but cannot carry it.
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
- A picture goes as a picture only when it is on the asked-about post, its parent or its quote (never a reply's), its input says `held: false` exactly (2.1: "checked within the last 24 hours and not held"; absent or null is not enough, since a phone cannot see why media is held), its `checked_at` is under 24 hours old by `now` (up to 5 minutes ahead still counts), it has a plain media id, it is on `allowed`, and `cached` returns the base64 of a JPEG (no line breaks): the prepared bytes your checks passed (shrunk, re-encoded and stripped of metadata before the checks), never a second read of the cache. The SDK checks only that the bytes look like a JPEG; it cannot tell a prepared picture from a raw one. At most `max` (3 when absent, never more), the asked-about post first, and none for `translate`. Anything else, including a missing or unreadable `checked_at`, goes as alt text and the stored caption only, exactly as without `pictures`.
|
|
418
|
+
- The renderer re-checks each approved picture at its own `now` and attaches nothing that is not on `allowed`: a picture that went stale between the two calls, or that your checks left out, goes as alt text and no other picture takes its place. Read the clock again for the render call: a later `now` can only take pictures away.
|
|
419
|
+
- The pictures sit inside the delimited content block, after its text. `references` never holds a picture's address or bytes. The content says which picture is attached ("picture 1 is attached: image 1 of 1"), and the instructions tell the model that words in a picture are data and never to say who a person in a picture is.
|
|
420
|
+
|
|
421
|
+
#### What a Sealed message may carry
|
|
422
|
+
|
|
423
|
+
`sealedTurnCaps(input)` is the "never all three" rule for Sealed (something about the member, someone else's content, a way out): one pure function the Sealed tab, the post sheet, the encrypted-chat ask and Both all decide through.
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { sealedTurnCaps } from '@recursiv/sdk/assistant'
|
|
427
|
+
|
|
428
|
+
const caps = sealedTurnCaps({
|
|
429
|
+
mode: 'sealed', // or 'both'
|
|
430
|
+
search: true, // "Search the web" on for this chat
|
|
431
|
+
others_content: ['public_post'], // required; also 'members_only_post', 'chat_messages', 'encrypted_chat'; [] = own words
|
|
432
|
+
temporary: false, // required: a temporary chat?
|
|
433
|
+
searched_earlier: false, // required: did an earlier message in this chat search?
|
|
434
|
+
phone_pictures_earlier: false, // required: did an earlier message in this chat carry a phone picture to Sealed?
|
|
435
|
+
earlier_messages: true, // required: does the chat have earlier messages or answers?
|
|
436
|
+
memory: true, // Settings switch
|
|
437
|
+
memory_in_chat: true, // the header chip: counts only in the member's own chat
|
|
438
|
+
memory_tick: false, // "Use what George remembers": the only thing that counts with others' content
|
|
439
|
+
about_you: false,
|
|
440
|
+
phone_pictures: 0, // required: pictures from the phone on this message, 0 to 3
|
|
441
|
+
page_opens_limited: false, // Tinfoil ask 3; leave false until a server switch says otherwise
|
|
442
|
+
})
|
|
443
|
+
// caps: { search, history, memory, about_you, phone_pictures, suggest_memory, reasons: ['search.off_subject'] }
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
- `others_content`, `phone_pictures`, `temporary`, `searched_earlier`, `phone_pictures_earlier` and `earlier_messages` are required: left out or null, they throw rather than read as "none" (so a searching message never drops the chat so far without its `search.question_only` line, or a picture without its `search.off_mix` line). `phone_pictures` is 0 to 3 (3 pictures per message in all); more throws, so cap them in the composer.
|
|
447
|
+
- `mode: 'both'` with `'encrypted_chat'` in `others_content` throws: an encrypted-chat ask is Sealed only (G21), and Both's Standard half would put the decrypted messages on Minds and the AI company. Offer Sealed alone on that surface.
|
|
448
|
+
- A message that searches never carries memory, About you or a picture from the phone; those stay and search turns off (`search.off_mix`). Someone else's public post (in the message or earlier in the chat) stops search until `page_opens_limited` (`search.off_subject`); members-only posts and picked chat messages never search (`search.off_members`), nor do encrypted-chat messages (`search.e2ee`). A message that searches carries only the question (and, after ask 3, the post it asks about): `history` is false, with `search.question_only` when the chat has earlier messages. Both never searches (`both.no_search`) and never carries a phone picture (`both.no_photo`). An encrypted-chat ask carries no phone picture either (one message of words; no line, since that surface has no Photo item).
|
|
449
|
+
- Memory goes only when on and on for this chat: the chip in the member's own chat, and only the tick with someone else's content (the chip alone never counts there). Never in a temporary chat or with encrypted-chat messages; About you follows the same rules without needing memory on. `suggest_memory` is false whenever the message holds anyone else's words, searches, carries a picture from the phone (it may be a screenshot of someone else's words), follows a search or a phone picture earlier in the chat, or is temporary. `reasons` are copy ids for what the member turned on that this message leaves off.
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
### Fixed election and crisis cards
|
|
453
|
+
|
|
454
|
+
Before a model answers, check the member's own words for a fixed card. Pure, no model call, the same card on a server and a phone (unreleased):
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import { fixedCardAnswer, matchFixedCard } from '@recursiv/sdk/assistant'
|
|
458
|
+
|
|
459
|
+
const match = matchFixedCard(question, { country: 'US', locale: 'en-US' }) // country: ISO 3166-1 alpha-2, if known
|
|
460
|
+
if (match) {
|
|
461
|
+
show(match.card) // { kind, id, title, lines, phones, links, as_of }
|
|
462
|
+
if (fixedCardAnswer(match.kind, Date.now()) === 'card_only') return // election before 2026-11-04T12:00Z: no model call
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
- `crisis`: self-harm or suicide risk (first person, plans, means, worry about someone else) or immediate danger. The card gives the country's crisis line and emergency number, or the generic card (the US line, labelled, and findahelpline.com). Danger from someone else, or a medical emergency, leads with the emergency number (the police first where that is a separate number, as in Brazil, Chile and Peru: id `crisis.XX.danger`); worry about someone else gets a line addressed to the helper (`crisis.XX.other`). It always shows first; the turn may go on.
|
|
467
|
+
- Matching runs in time linear in the message. Past 20,000 characters (after invisible ones are dropped), the start and the end are read.
|
|
468
|
+
- `election`: only clear logistics (how, where or when to vote, registering, deadlines, polling places, mail or absentee ballots, voter ID, checking registration or a ballot). Never candidates, results, polls, fraud claims or votes on a post or poll. The US card points to vote.gov and CanIVote.org and the state election office; elsewhere the country's election authority, or a generic line.
|
|
469
|
+
- English and Spanish. Every number and address has a cited source and an `as_of` date (`CRISIS_LINES`, `ELECTION_SITES`); a country with no checked entry gets the generic card, never a guessed number. `fixedCardFor(kind, options)` gives a card without a message.
|
|
470
|
+
|
|
363
471
|
## Error handling
|
|
364
472
|
|
|
365
473
|
```typescript
|
|
@@ -481,6 +589,25 @@ A signed-out read holds only clips whose video the visitor may see, and counts t
|
|
|
481
589
|
`mediaFilter.withheld`. A network without a video window answers 404 `unsupported_protocol`;
|
|
482
590
|
an API that predates `media` answers 400 `invalid_input`, so fall back to its trends.
|
|
483
591
|
|
|
592
|
+
### Native networks
|
|
593
|
+
|
|
594
|
+
Every eligible member is on ActivityPub, AT Protocol and Nostr by rule; an identity is
|
|
595
|
+
set up lazily, on their first public post, their first interaction on that network, or
|
|
596
|
+
"Set up now". `protocols.networks()` reads each network's `state` (`ready`, `on`,
|
|
597
|
+
`pending`, `off`, `error`, `needs_action`, `locked`) with its `reason` and `handle`, and
|
|
598
|
+
never sets anything up. `protocols.activateNetwork('atproto')` sets one up now (over the
|
|
599
|
+
setup budget it answers `pending` with `retryAt`). `protocols.setNetwork('activitypub',
|
|
600
|
+
{ enabled: false, removePast: true })` is the member's off switch, and
|
|
601
|
+
`setNetwork(network, { enabled: true })` turns it back on with the same identity.
|
|
602
|
+
|
|
603
|
+
```ts
|
|
604
|
+
const { data } = await r.protocols.networks();
|
|
605
|
+
for (const network of data.networks) {
|
|
606
|
+
if (network.state === 'ready') show(network.network, 'Will be set up when you first post or interact');
|
|
607
|
+
}
|
|
608
|
+
await r.protocols.setNetwork('nostr', { enabled: false, removePast: true });
|
|
609
|
+
```
|
|
610
|
+
|
|
484
611
|
### Native Nostr signing
|
|
485
612
|
|
|
486
613
|
Use `protocols.nativeNostrAccount()` to check the app's explicit native-write
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The numbers and addresses the fixed election and crisis cards show (George plan G6b, risks 3 and 22). Every one was
|
|
3
|
+
* checked against the source cited beside it on FIXED_CARD_DATA_AS_OF; nothing here is filled in from memory, and a
|
|
4
|
+
* country with no checked entry gets the generic card, never a guessed number or address.
|
|
5
|
+
*
|
|
6
|
+
* Crisis lines come from findahelpline.com (ThroughLine's maintained directory, the per-country source the plan
|
|
7
|
+
* names) and, where the service publishes its own page, from that page too. Emergency numbers are findahelpline.com's
|
|
8
|
+
* per-country `emergencyNumber`; where that reaches an ambulance alone, the police number is added from a government
|
|
9
|
+
* page. Election sites are each country's own election authority, or the government's page that points to it.
|
|
10
|
+
*
|
|
11
|
+
* To change an entry: check it at its source, update `sources`, the comment above the entry and
|
|
12
|
+
* FIXED_CARD_DATA_AS_OF, and keep the contract test passing (it holds every number and address to the comment above
|
|
13
|
+
* its entry). Plain, frozen data only, so a phone and the server show the same card.
|
|
14
|
+
*/
|
|
15
|
+
/** The day every number and address below was last checked against its sources. */
|
|
16
|
+
export declare const FIXED_CARD_DATA_AS_OF = "2026-10-08";
|
|
17
|
+
/** Words in each card language. */
|
|
18
|
+
export interface LocalizedText {
|
|
19
|
+
readonly en: string;
|
|
20
|
+
readonly es: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CrisisLineData {
|
|
23
|
+
/** The service's own name, shown as written. */
|
|
24
|
+
readonly name: string;
|
|
25
|
+
/** The number as the service writes it. */
|
|
26
|
+
readonly number: string;
|
|
27
|
+
/** What a phone dials: digits, keeping a leading `*` (Chile's *4141). */
|
|
28
|
+
readonly dial: string;
|
|
29
|
+
/** The number to text, when the service takes texts; null when it is calls only. */
|
|
30
|
+
readonly text: string | null;
|
|
31
|
+
/** Open every hour of every day. Every line chosen below is. */
|
|
32
|
+
readonly always_open: boolean;
|
|
33
|
+
/** What to do after dialling, in each card language, when the service needs it (Peru's option 5). */
|
|
34
|
+
readonly after_dialling: LocalizedText | null;
|
|
35
|
+
/** One more line for a card in that language only (the 988 Lifeline's Spanish service). */
|
|
36
|
+
readonly extra_line: Partial<LocalizedText> | null;
|
|
37
|
+
/** Where `number`, `text` and `always_open` were checked. */
|
|
38
|
+
readonly sources: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
/** One emergency number and where it was checked. */
|
|
41
|
+
export interface EmergencyNumberData {
|
|
42
|
+
readonly number: string;
|
|
43
|
+
readonly sources: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
export interface CrisisCountryData {
|
|
46
|
+
readonly name: LocalizedText;
|
|
47
|
+
/** The first line is the one the card leads with. */
|
|
48
|
+
readonly lines: readonly CrisisLineData[];
|
|
49
|
+
/**
|
|
50
|
+
* findahelpline.com's emergency number for the country: one that reaches every service, or, where `police` is set,
|
|
51
|
+
* the ambulance alone.
|
|
52
|
+
*/
|
|
53
|
+
readonly emergency: string;
|
|
54
|
+
readonly emergency_sources: readonly string[];
|
|
55
|
+
/**
|
|
56
|
+
* Set only where `emergency` reaches an ambulance alone (Brazil's SAMU, Chile's 131, Peru's SAMU): the police
|
|
57
|
+
* number, which a card about danger from someone else leads with. A card always shows both.
|
|
58
|
+
*/
|
|
59
|
+
readonly police?: EmergencyNumberData;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Per-country crisis lines, keyed by ISO 3166-1 alpha-2 code. Each line is free and open all day, every day, by its
|
|
63
|
+
* source. Kept to countries where that could be checked. The comment above each entry names every number in it and
|
|
64
|
+
* where it was checked; the contract test holds each number to its comment.
|
|
65
|
+
*/
|
|
66
|
+
export declare const CRISIS_LINES: Readonly<Record<string, CrisisCountryData>>;
|
|
67
|
+
/** The directory the generic crisis card points to when the member's country is unknown or has no entry above. */
|
|
68
|
+
export declare const CRISIS_DIRECTORY: {
|
|
69
|
+
readonly name: "findahelpline.com";
|
|
70
|
+
readonly url: "https://findahelpline.com";
|
|
71
|
+
readonly sources: readonly ["https://findahelpline.com"];
|
|
72
|
+
};
|
|
73
|
+
export interface ElectionSiteData {
|
|
74
|
+
/** Who runs it, as they name themselves. */
|
|
75
|
+
readonly name: string;
|
|
76
|
+
readonly url: string;
|
|
77
|
+
/** Where the address was checked. */
|
|
78
|
+
readonly sources: readonly string[];
|
|
79
|
+
}
|
|
80
|
+
export interface ElectionCountryData {
|
|
81
|
+
readonly name: LocalizedText;
|
|
82
|
+
readonly sites: readonly ElectionSiteData[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Official election sites, keyed by ISO 3166-1 alpha-2 code: each country's national election authority, or the
|
|
86
|
+
* government page for voters. A country not listed gets the generic line, never a guessed address. Some of these
|
|
87
|
+
* hosts refuse scripted requests (403), so they were checked by hand on the date above.
|
|
88
|
+
*/
|
|
89
|
+
export declare const ELECTION_SITES: Readonly<Record<string, ElectionCountryData>>;
|
|
90
|
+
/** Common names for a country that are not its ISO 3166-1 alpha-2 code. */
|
|
91
|
+
export declare const COUNTRY_ALIASES: Readonly<Record<string, string>>;
|