@recursiv/sdk 0.7.82 → 0.7.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +128 -1
  2. package/dist/assistant/fixedCardData.d.ts +91 -0
  3. package/dist/assistant/fixedCardData.js +372 -0
  4. package/dist/assistant/fixedCards.d.ts +35 -0
  5. package/dist/assistant/fixedCards.js +904 -0
  6. package/dist/assistant/hiddenText.d.ts +24 -0
  7. package/dist/assistant/hiddenText.js +263 -0
  8. package/dist/assistant/index.d.ts +30 -0
  9. package/dist/assistant/index.js +30 -0
  10. package/dist/assistant/render.d.ts +31 -0
  11. package/dist/assistant/render.js +434 -0
  12. package/dist/assistant/subjectBundle.d.ts +30 -0
  13. package/dist/assistant/subjectBundle.js +535 -0
  14. package/dist/assistant/turnCaps.d.ts +3 -0
  15. package/dist/assistant/turnCaps.js +141 -0
  16. package/dist/assistant/types.d.ts +569 -0
  17. package/dist/assistant/types.js +26 -0
  18. package/dist/assistant-surfaces.d.ts +42 -0
  19. package/dist/assistant-surfaces.js +113 -0
  20. package/dist/index.d.ts +15 -6
  21. package/dist/index.js +8 -1
  22. package/dist/privacy/facts.generated.d.ts +79 -0
  23. package/dist/privacy/facts.generated.js +93 -0
  24. package/dist/privacy/index.d.ts +48 -0
  25. package/dist/privacy/index.js +42 -0
  26. package/dist/realtime.d.ts +10 -3
  27. package/dist/realtime.js +11 -2
  28. package/dist/resources/agents.d.ts +6 -1
  29. package/dist/resources/agents.js +12 -2
  30. package/dist/resources/discovery-merge.d.ts +17 -1
  31. package/dist/resources/discovery-merge.js +84 -4
  32. package/dist/resources/discovery-progressive.d.ts +4 -3
  33. package/dist/resources/discovery-progressive.js +11 -8
  34. package/dist/resources/discovery.d.ts +9 -1
  35. package/dist/resources/discovery.js +62 -15
  36. package/dist/resources/e2ee.d.ts +56 -0
  37. package/dist/resources/e2ee.js +21 -0
  38. package/dist/resources/feeds.d.ts +15 -6
  39. package/dist/resources/feeds.js +2 -1
  40. package/dist/resources/integrations.d.ts +3 -0
  41. package/dist/resources/link-preview.d.ts +42 -1
  42. package/dist/resources/link-preview.js +36 -2
  43. package/dist/resources/network.d.ts +6 -0
  44. package/dist/resources/privacy.d.ts +13 -0
  45. package/dist/resources/privacy.js +14 -0
  46. package/dist/resources/project-brain.d.ts +8 -0
  47. package/dist/resources/protocols.d.ts +99 -11
  48. package/dist/resources/protocols.js +45 -0
  49. package/dist/resources/sealed.d.ts +127 -7
  50. package/dist/resources/sealed.js +33 -6
  51. package/dist/resources/settings.d.ts +3 -1
  52. package/dist/resources/settings.js +3 -1
  53. package/dist/resources/users.d.ts +11 -2
  54. package/dist/resources/users.js +1 -1
  55. package/dist/social-protocol-ids.d.ts +28 -0
  56. package/dist/social-protocol-ids.js +30 -0
  57. package/dist/types.d.ts +71 -0
  58. package/llm.md +45 -4
  59. package/package.json +11 -1
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Text a reader cannot see is text a model must not be given: an instruction hidden in a zero-width run, in Unicode
3
+ * tag characters, behind a direction override, in an HTML comment or a hidden element, or in markdown that renders as
4
+ * nothing. Pure string work (no DOM, no Node APIs), so a phone builds the same bundle the server does.
5
+ */
6
+ /** The longest raw string read. Hidden padding past it is never scanned. */
7
+ export declare const HIDDEN_TEXT_SCAN_LIMIT = 100000;
8
+ /**
9
+ * `text` without controls (but tab and newline) and default-ignorable characters, so a zero-width run inside a word
10
+ * cannot split it. Line breaks become '\n'. Markup is left as written: a member's own message is read in full.
11
+ */
12
+ export declare function removeInvisibleCharacters(text: string): string;
13
+ /**
14
+ * `text` as a reader sees it: no controls, no default-ignorable characters (zero-width, bidi, tags, variation
15
+ * selectors), no HTML comments or hidden elements, no invisible markdown, and pictures as their alt text. Line breaks
16
+ * become '\n'. Spacing is left to the caller.
17
+ */
18
+ export declare function stripHiddenText(text: string): string;
19
+ /** Every web address in `text`, in order, each once. */
20
+ export declare function findUrls(text: string): string[];
21
+ /** `text` with each web address in it passed through `replace`; the punctuation after one is kept. */
22
+ export declare function mapUrls(text: string, replace: (url: string) => string): string;
23
+ /** Every @handle in `text` (without the @), in order, each once. */
24
+ export declare function findHandles(text: string): string[];
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Text a reader cannot see is text a model must not be given: an instruction hidden in a zero-width run, in Unicode
3
+ * tag characters, behind a direction override, in an HTML comment or a hidden element, or in markdown that renders as
4
+ * nothing. Pure string work (no DOM, no Node APIs), so a phone builds the same bundle the server does.
5
+ */
6
+ /**
7
+ * Every character Unicode calls default-ignorable (DerivedCoreProperties.txt, Default_Ignorable_Code_Point), as
8
+ * ranges: soft hyphen, grapheme joiner, Arabic letter mark, Hangul fillers, Khmer inherent vowels, Mongolian variation
9
+ * selectors, zero-width spaces and joiners, direction marks, embeddings and isolates, word joiner and invisible
10
+ * operators, variation selectors, the BOM and the tag characters. Written out because Hermes is not known to take
11
+ * `\p{Default_Ignorable_Code_Point}` (the same list as minds' lib/chatRecipientTargets.ts).
12
+ */
13
+ const DEFAULT_IGNORABLE = [
14
+ [0x00ad, 0x00ad], [0x034f, 0x034f], [0x061c, 0x061c], [0x115f, 0x1160], [0x17b4, 0x17b5],
15
+ [0x180b, 0x180f], [0x200b, 0x200f], [0x202a, 0x202e], [0x2060, 0x206f], [0x3164, 0x3164],
16
+ [0xfe00, 0xfe0f], [0xfeff, 0xfeff], [0xffa0, 0xffa0], [0xfff0, 0xfff8], [0x1bca0, 0x1bca3],
17
+ [0x1d173, 0x1d17a], [0xe0000, 0xe0fff],
18
+ ];
19
+ function isDefaultIgnorable(point) {
20
+ return DEFAULT_IGNORABLE.some(([from, to]) => point >= from && point <= to);
21
+ }
22
+ const INVISIBLE = new RegExp(`[${DEFAULT_IGNORABLE.map(([from, to]) => `\\u{${from.toString(16)}}-\\u{${to.toString(16)}}`).join('')}]`, 'gu');
23
+ /** C0 and C1 controls and DEL, except tab and newline. */
24
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: removing control characters is the point
25
+ const CONTROLS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g;
26
+ /** Line breaks in every spelling, read as one newline. */
27
+ const LINE_BREAKS = /\r\n?|[\u0085\u2028\u2029]/g;
28
+ /**
29
+ * A numeric character reference (the semicolon is optional to a browser), and the named ones that stand for an
30
+ * invisible character. Minds shows text after one entity decode, so `&#xE0069;` is as invisible as the tag character.
31
+ */
32
+ const NUMERIC_REFERENCE = /&#(?:[xX]([0-9A-Fa-f]{1,8})|([0-9]{1,10}));?/g;
33
+ const INVISIBLE_NAMED_REFERENCE = /&(?:shy;?|(?:zwj|zwnj|lrm|rlm|ZeroWidthSpace|NegativeVeryThinSpace|NegativeThinSpace|NegativeMediumSpace|NegativeThickSpace|NoBreak|ApplyFunction|af|InvisibleTimes|it|InvisibleComma|ic);)/g;
34
+ /** A C0 control or DEL a reference may spell, except tab, newline and carriage return (and NUL, shown as U+FFFD). */
35
+ function isReferencedControl(point) {
36
+ return (point >= 0x01 && point <= 0x08) || point === 0x0b || point === 0x0c || (point >= 0x0e && point <= 0x1f) || point === 0x7f;
37
+ }
38
+ /** Character references to invisible characters and controls are dropped; every other reference is left as written. */
39
+ function removeInvisibleReferences(text) {
40
+ if (!text.includes('&'))
41
+ return text;
42
+ return text
43
+ .replace(NUMERIC_REFERENCE, (reference, hex, decimal) => {
44
+ const point = hex !== undefined ? Number.parseInt(hex, 16) : Number.parseInt(decimal, 10);
45
+ return isDefaultIgnorable(point) || isReferencedControl(point) ? '' : reference;
46
+ })
47
+ .replace(INVISIBLE_NAMED_REFERENCE, '');
48
+ }
49
+ /** Elements whose content a browser never shows as text. */
50
+ const HIDDEN_ELEMENTS = new Set([
51
+ 'script', 'style', 'template', 'noscript', 'head', 'title', 'iframe', 'object', 'embed', 'svg', 'datalist',
52
+ 'audio', 'video', 'meta', 'link', 'source', 'track',
53
+ ]);
54
+ /** Elements with no closing tag. */
55
+ const VOID_ELEMENTS = new Set([
56
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr',
57
+ ]);
58
+ /** `hidden` as an attribute: after a space or a solidus (`<div/hidden>`), which a browser reads as a separator. */
59
+ const HIDDEN_ATTRIBUTE = /(?:^|[\s/])hidden(?:[\s=/]|$)/i;
60
+ const HIDDEN_INPUT = /\btype\s*=\s*["']?hidden\b/i;
61
+ /**
62
+ * A style attribute's value, quoted or not: `style=display:none` is valid HTML. A quote left open runs to the end of
63
+ * the tag, so an unclosed value is still read.
64
+ */
65
+ const STYLE_ATTRIBUTE = /(?:^|[\s/])style\s*=\s*(?:"([^"]*)(?:"|$)|'([^']*)(?:'|$)|([^\s"'<>`]+))/gi;
66
+ /** A declaration that hides what it styles. */
67
+ const HIDDEN_DECLARATION = /display\s*:\s*none|visibility\s*:\s*(?:hidden|collapse)|font-size\s*:\s*0(?:\.0+)?(?:px|pt|em|rem|%)?\s*(?:;|!|$)|opacity\s*:\s*0(?:\.0+)?\s*(?:;|!|$)|color\s*:\s*transparent/i;
68
+ /** An opening tag. Attributes stop at the next angle bracket, so the scan stays linear. */
69
+ const OPEN_TAG = /<([A-Za-z][A-Za-z0-9-]{0,31})\b([^<>]{0,4000})>/g;
70
+ /** An opening or closing tag, to find where a hidden element ends. */
71
+ const ANY_TAG = /<(\/?)([A-Za-z][A-Za-z0-9-]{0,31})\b[^<>]{0,4000}>/g;
72
+ const IMG_TAG = /<img\b([^<>]{0,4000})>/gi;
73
+ const ALT_ATTRIBUTE = /\balt\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/i;
74
+ function removeHtmlComments(text) {
75
+ let out = '';
76
+ let from = 0;
77
+ for (;;) {
78
+ const start = text.indexOf('<!--', from);
79
+ if (start < 0)
80
+ return out + text.slice(from);
81
+ out += text.slice(from, start);
82
+ const end = text.indexOf('-->', start + 4);
83
+ // An unclosed comment hides everything after it.
84
+ if (end < 0)
85
+ return out;
86
+ from = end + 3;
87
+ }
88
+ }
89
+ /** Whether any style attribute on the tag hides it. Every one is read, so a second attribute cannot slip by. */
90
+ function hasHiddenStyle(attributes) {
91
+ if (!/style/i.test(attributes))
92
+ return false;
93
+ for (const match of attributes.matchAll(STYLE_ATTRIBUTE)) {
94
+ if (HIDDEN_DECLARATION.test(match[1] ?? match[2] ?? match[3] ?? ''))
95
+ return true;
96
+ }
97
+ return false;
98
+ }
99
+ function isHiddenElement(name, attributes) {
100
+ return HIDDEN_ELEMENTS.has(name)
101
+ || HIDDEN_ATTRIBUTE.test(attributes)
102
+ || hasHiddenStyle(attributes)
103
+ || (name === 'input' && HIDDEN_INPUT.test(attributes));
104
+ }
105
+ /** Where the element opened before `from` ends: after its matching closing tag, or the end of the text. */
106
+ function closingEnd(text, from, name) {
107
+ ANY_TAG.lastIndex = from;
108
+ let depth = 1;
109
+ for (let match = ANY_TAG.exec(text); match; match = ANY_TAG.exec(text)) {
110
+ if (match[2].toLowerCase() !== name)
111
+ continue;
112
+ if (match[1])
113
+ depth -= 1;
114
+ else if (!/\/\s*>$/.test(match[0]))
115
+ depth += 1;
116
+ if (depth === 0)
117
+ return ANY_TAG.lastIndex;
118
+ }
119
+ // An unclosed hidden element hides everything after it.
120
+ return text.length;
121
+ }
122
+ function removeHiddenElements(text) {
123
+ if (!text.includes('<'))
124
+ return text;
125
+ let out = '';
126
+ let last = 0;
127
+ OPEN_TAG.lastIndex = 0;
128
+ for (let match = OPEN_TAG.exec(text); match; match = OPEN_TAG.exec(text)) {
129
+ const name = match[1].toLowerCase();
130
+ const attributes = match[2];
131
+ if (!isHiddenElement(name, attributes))
132
+ continue;
133
+ out += text.slice(last, match.index);
134
+ const selfClosing = VOID_ELEMENTS.has(name) || /\/\s*$/.test(attributes);
135
+ last = selfClosing ? OPEN_TAG.lastIndex : closingEnd(text, OPEN_TAG.lastIndex, name);
136
+ OPEN_TAG.lastIndex = last;
137
+ }
138
+ return out + text.slice(last);
139
+ }
140
+ const imageLabel = (alt) => {
141
+ const words = (alt ?? '').replace(/\s+/g, ' ').trim();
142
+ return words ? `[image: ${words}]` : '';
143
+ };
144
+ /** An HTML picture is its alt text, never its address. */
145
+ function replaceHtmlImages(text) {
146
+ if (!text.includes('<'))
147
+ return text;
148
+ return text.replace(IMG_TAG, (_tag, attributes) => {
149
+ const alt = ALT_ATTRIBUTE.exec(attributes);
150
+ return imageLabel(alt ? (alt[1] ?? alt[2] ?? alt[3]) : undefined);
151
+ });
152
+ }
153
+ /** How deep parentheses inside a link target or a web address are read as part of it. */
154
+ const PAREN_DEPTH = 4;
155
+ /** `character`, or a run of them in parentheses, nested up to PAREN_DEPTH deep: `a(1).jpg`, `Foo_(bar)`. */
156
+ function balanced(character) {
157
+ let pattern = `(?:${character})`;
158
+ for (let level = 0; level < PAREN_DEPTH; level += 1)
159
+ pattern = `(?:${character}|\\((?:${pattern})*\\))`;
160
+ return pattern;
161
+ }
162
+ /**
163
+ * A markdown link target: `<anything on one line>`, or a run with no spaces whose parentheses balance, as CommonMark
164
+ * reads `![alt](https://cdn.example/a(1).jpg)`. A backslash escapes the character after it.
165
+ */
166
+ const TARGET = String.raw `<[^<>\n]{0,4000}>|${balanced(String.raw `[^\s()\\]|\\\S`)}{0,4000}`;
167
+ /** A markdown link title, in any of its three quotings. */
168
+ const TITLE = String.raw `(?:"[^"\n]{0,1000}"|'[^'\n]{0,1000}'|\([^()\n]{0,1000}\))`;
169
+ /** `(target "title")` after a link's or picture's brackets. The bounded classes keep every scan linear. */
170
+ const DESTINATION = String.raw `\([ \t]*(?:${TARGET})(?:[ \t]+${TITLE})?[ \t]*\)`;
171
+ /**
172
+ * Markdown that renders as nothing, or as a picture: link reference definitions (also the `[//]: # (note)` comment
173
+ * idiom; footnotes `[^1]:` show and stay), empty links, link titles (shown only on hover), and pictures, which become
174
+ * their alt text with no address.
175
+ *
176
+ * A reference definition is only a whole line of `[label]:`, one destination and an optional quoted title, as
177
+ * CommonMark has it. "[EDIT]: the vote moved to Tuesday" is words a reader sees, and stays.
178
+ */
179
+ const REFERENCE_DEFINITION = /^ {0,3}\[(?!\^)[^[\]\n]{1,999}\]:[ \t]*(?:<[^<>\n]{0,4000}>|[^\s<]\S{0,4000})(?:[ \t]+(?:"[^"\n]{0,1000}"|'[^'\n]{0,1000}'|\([^()\n]{0,1000}\)))?[ \t]*(?:\n|$)/gm;
180
+ const MARKDOWN_IMAGE = new RegExp(`!\\[([^[\\]\\n]{0,1000})\\]${DESTINATION}`, 'g');
181
+ const MARKDOWN_IMAGE_REFERENCE = /!\[([^[\]\n]{0,1000})\]\[[^[\]\n]{0,1000}\]/g;
182
+ const EMPTY_LINK = new RegExp(`\\[[ \\t]*\\]${DESTINATION}`, 'g');
183
+ const TITLED_LINK = new RegExp(`\\[([^[\\]\\n]{1,1000})\\]\\([ \\t]*(${TARGET})[ \\t]+${TITLE}[ \\t]*\\)`, 'g');
184
+ function removeInvisibleMarkdown(text) {
185
+ if (!text.includes('['))
186
+ return text;
187
+ return text
188
+ .replace(REFERENCE_DEFINITION, '')
189
+ .replace(MARKDOWN_IMAGE, (_image, alt) => imageLabel(alt))
190
+ .replace(MARKDOWN_IMAGE_REFERENCE, (_image, alt) => imageLabel(alt))
191
+ .replace(EMPTY_LINK, '')
192
+ .replace(TITLED_LINK, '[$1]($2)');
193
+ }
194
+ /** The longest raw string read. Hidden padding past it is never scanned. */
195
+ export const HIDDEN_TEXT_SCAN_LIMIT = 100_000;
196
+ /**
197
+ * `text` without controls (but tab and newline) and default-ignorable characters, so a zero-width run inside a word
198
+ * cannot split it. Line breaks become '\n'. Markup is left as written: a member's own message is read in full.
199
+ */
200
+ export function removeInvisibleCharacters(text) {
201
+ if (typeof text !== 'string' || !text)
202
+ return '';
203
+ return text.replace(LINE_BREAKS, '\n').replace(CONTROLS, '').replace(INVISIBLE, '');
204
+ }
205
+ /**
206
+ * `text` as a reader sees it: no controls, no default-ignorable characters (zero-width, bidi, tags, variation
207
+ * selectors), no HTML comments or hidden elements, no invisible markdown, and pictures as their alt text. Line breaks
208
+ * become '\n'. Spacing is left to the caller.
209
+ */
210
+ export function stripHiddenText(text) {
211
+ if (typeof text !== 'string' || !text)
212
+ return '';
213
+ let out = text.length > HIDDEN_TEXT_SCAN_LIMIT ? text.slice(0, HIDDEN_TEXT_SCAN_LIMIT) : text;
214
+ out = out.replace(LINE_BREAKS, '\n').replace(CONTROLS, '').replace(INVISIBLE, '');
215
+ out = removeInvisibleReferences(out);
216
+ out = removeHtmlComments(out);
217
+ out = removeHiddenElements(out);
218
+ out = replaceHtmlImages(out);
219
+ out = removeInvisibleMarkdown(out);
220
+ return out;
221
+ }
222
+ /**
223
+ * Web addresses as they appear in text, without trailing punctuation. Parentheses that balance are part of one
224
+ * (`https://cdn.example/a(1).jpg`); a closing one with no opening one ends it, as in "(see https://a.example)".
225
+ */
226
+ const URL_CHARACTER = /[^\s<>"'`()[\]{}|\\^]/.source;
227
+ const URL_PATTERN = new RegExp(`https?://${balanced(URL_CHARACTER)}+`, 'gi');
228
+ /** @handles as they appear in text, a federated server included; never the middle of an email address. */
229
+ const HANDLE_PATTERN = /(^|[^A-Za-z0-9_@./])@([A-Za-z0-9_](?:[A-Za-z0-9_.-]{0,62}[A-Za-z0-9_])?(?:@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)?)/g;
230
+ /** A web address as matched, without the punctuation that ends a sentence after it. */
231
+ const trimUrl = (match) => match.replace(/[.,;:!?*_~]+$/, '');
232
+ const HAS_HOST = /^https?:\/\/[^/?#\s]/i;
233
+ /** Every web address in `text`, in order, each once. */
234
+ export function findUrls(text) {
235
+ if (typeof text !== 'string' || !text)
236
+ return [];
237
+ const found = new Set();
238
+ for (const match of text.match(URL_PATTERN) ?? []) {
239
+ const url = trimUrl(match);
240
+ if (HAS_HOST.test(url))
241
+ found.add(url);
242
+ }
243
+ return [...found];
244
+ }
245
+ /** `text` with each web address in it passed through `replace`; the punctuation after one is kept. */
246
+ export function mapUrls(text, replace) {
247
+ if (typeof text !== 'string' || !text.includes('://'))
248
+ return text;
249
+ return text.replace(URL_PATTERN, (match) => {
250
+ const url = trimUrl(match);
251
+ return HAS_HOST.test(url) ? `${replace(url)}${match.slice(url.length)}` : match;
252
+ });
253
+ }
254
+ /** Every @handle in `text` (without the @), in order, each once. */
255
+ export function findHandles(text) {
256
+ if (typeof text !== 'string' || !text)
257
+ return [];
258
+ const found = new Set();
259
+ HANDLE_PATTERN.lastIndex = 0;
260
+ for (let match = HANDLE_PATTERN.exec(text); match; match = HANDLE_PATTERN.exec(text))
261
+ found.add(match[2]);
262
+ return [...found];
263
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `@recursiv/sdk/assistant`: what an assistant may read about a post, and how it is handed to a model.
3
+ *
4
+ * - `buildSubjectBundle(input)` applies the reading rules (author choices, blocks and mutes both ways, known minors,
5
+ * child-safety holds, moderator suppression, captions instead of pictures, hidden-text stripping, limits) to data the
6
+ * caller already holds, and says what it left out, as counts. The reasons the asker may see never say who blocked
7
+ * them, whose account is a minor's or that a post is held; the detail is in `audit`, for the server's log only.
8
+ * - `renderBundleForModel(bundle, intent, { boundary, pictures? })` spotlights the bundle as model input. With
9
+ * `pictures` (the caller's clock, the pictures it approved and its image cache), pictures on the asked-about post,
10
+ * parent or quote that Minds checked in the last 24 hours also go as image parts; `bundlePictures` lists them first,
11
+ * so a phone can run its own checks and approve only those that pass.
12
+ * - `sealedTurnCaps(input)` decides what one Sealed message may carry: search, the chat so far, memory, About you and
13
+ * pictures from the phone ("never all three", sealed-full-scope-v1 3.2 rule 5, 4.3 and 2.6), with a copy id for
14
+ * whatever it turns off.
15
+ *
16
+ * - `matchFixedCard(text, { country, locale })` finds the fixed election or crisis card a member's message calls for,
17
+ * with no model call: plain phrase lists in English and Spanish over normalised text, and fixed cards whose every
18
+ * number and address has a cited source. `fixedCardAnswer` says whether the model may answer after the card.
19
+ *
20
+ * All are pure (no network, clock, randomness or Node-only API), so the server, a Sealed phone and an app build the
21
+ * same input from the same data, and show the same card. Nothing here sends anything: a clock, where one matters, is
22
+ * an argument.
23
+ */
24
+ export * from './types.js';
25
+ export { buildSubjectBundle, isPictureAddress, resolveAskChoice, SUBJECT_BUNDLE_LIMITS } from './subjectBundle.js';
26
+ export { BUNDLE_PICTURE_LIMITS, bundlePictures, MEMBER_LINE_PREFIX, renderBundleForModel } from './render.js';
27
+ export { sealedTurnCaps } from './turnCaps.js';
28
+ export { findHandles, findUrls, HIDDEN_TEXT_SCAN_LIMIT, stripHiddenText } from './hiddenText.js';
29
+ export { ELECTION_CARD_ONLY_UNTIL, FIXED_CARD_SCAN_LIMIT, fixedCardAnswer, fixedCardFor, matchFixedCard, normaliseForMatching, } from './fixedCards.js';
30
+ export { CRISIS_DIRECTORY, CRISIS_LINES, type CrisisCountryData, type CrisisLineData, ELECTION_SITES, type ElectionCountryData, type ElectionSiteData, type EmergencyNumberData, FIXED_CARD_DATA_AS_OF, type LocalizedText, } from './fixedCardData.js';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `@recursiv/sdk/assistant`: what an assistant may read about a post, and how it is handed to a model.
3
+ *
4
+ * - `buildSubjectBundle(input)` applies the reading rules (author choices, blocks and mutes both ways, known minors,
5
+ * child-safety holds, moderator suppression, captions instead of pictures, hidden-text stripping, limits) to data the
6
+ * caller already holds, and says what it left out, as counts. The reasons the asker may see never say who blocked
7
+ * them, whose account is a minor's or that a post is held; the detail is in `audit`, for the server's log only.
8
+ * - `renderBundleForModel(bundle, intent, { boundary, pictures? })` spotlights the bundle as model input. With
9
+ * `pictures` (the caller's clock, the pictures it approved and its image cache), pictures on the asked-about post,
10
+ * parent or quote that Minds checked in the last 24 hours also go as image parts; `bundlePictures` lists them first,
11
+ * so a phone can run its own checks and approve only those that pass.
12
+ * - `sealedTurnCaps(input)` decides what one Sealed message may carry: search, the chat so far, memory, About you and
13
+ * pictures from the phone ("never all three", sealed-full-scope-v1 3.2 rule 5, 4.3 and 2.6), with a copy id for
14
+ * whatever it turns off.
15
+ *
16
+ * - `matchFixedCard(text, { country, locale })` finds the fixed election or crisis card a member's message calls for,
17
+ * with no model call: plain phrase lists in English and Spanish over normalised text, and fixed cards whose every
18
+ * number and address has a cited source. `fixedCardAnswer` says whether the model may answer after the card.
19
+ *
20
+ * All are pure (no network, clock, randomness or Node-only API), so the server, a Sealed phone and an app build the
21
+ * same input from the same data, and show the same card. Nothing here sends anything: a clock, where one matters, is
22
+ * an argument.
23
+ */
24
+ export * from './types.js';
25
+ export { buildSubjectBundle, isPictureAddress, resolveAskChoice, SUBJECT_BUNDLE_LIMITS } from './subjectBundle.js';
26
+ export { BUNDLE_PICTURE_LIMITS, bundlePictures, MEMBER_LINE_PREFIX, renderBundleForModel } from './render.js';
27
+ export { sealedTurnCaps } from './turnCaps.js';
28
+ export { findHandles, findUrls, HIDDEN_TEXT_SCAN_LIMIT, stripHiddenText } from './hiddenText.js';
29
+ export { ELECTION_CARD_ONLY_UNTIL, FIXED_CARD_SCAN_LIMIT, fixedCardAnswer, fixedCardFor, matchFixedCard, normaliseForMatching, } from './fixedCards.js';
30
+ export { CRISIS_DIRECTORY, CRISIS_LINES, ELECTION_SITES, FIXED_CARD_DATA_AS_OF, } from './fixedCardData.js';
@@ -0,0 +1,31 @@
1
+ import type { AssistIntent, BundlePicture, BundlePictureOptions, RenderBundleOptions, RenderedBundle, SubjectBundle } from './types.js';
2
+ /** Every line a member wrote starts with this. */
3
+ export declare const MEMBER_LINE_PREFIX = "| ";
4
+ /**
5
+ * When a post picture may go to a model as a picture (sealed-full-scope-v1 2.1, 2.2): at most `max_pictures` in one
6
+ * message, pictures from the phone included (the caller passes the room left as `max`); only while Minds' last
7
+ * child-safety check of it is younger than `fresh_ms`; a check time up to `clock_skew_ms` ahead of the caller's clock
8
+ * still counts (clocks drift), and one further ahead is not a time Minds wrote, so it does not.
9
+ */
10
+ export declare const BUNDLE_PICTURE_LIMITS: Readonly<{
11
+ max_pictures: 3;
12
+ fresh_ms: number;
13
+ clock_skew_ms: number;
14
+ }>;
15
+ /**
16
+ * The post pictures in `bundle` that may go to the model as pictures for `intent`, in the order the renderer attaches
17
+ * them: pictures (not clips or sounds) on the asked-about post, its parent and its quote (never on a reply), with a
18
+ * plain media id, whose `checked_at` is under 24 hours old by `now`; at most `max` (3 when absent, never more), and
19
+ * none for 'translate'. A picture whose check is missing, cannot be read, is 24 hours old or more, or is over 5
20
+ * minutes ahead of `now`, is not listed: it goes as alt text only. Load each listed picture from your own image cache
21
+ * (never fetch it), run your own checks, and pass the ones that passed to renderBundleForModel as `allowed`, with the
22
+ * same `max` and a fresh `now`: it attaches only those that still qualify, so a picture left out, or one that went
23
+ * stale during your checks, goes as alt text only and no other takes its place.
24
+ */
25
+ export declare function bundlePictures(bundle: SubjectBundle, intent: AssistIntent, options: BundlePictureOptions): BundlePicture[];
26
+ /**
27
+ * `bundle` as model input for `intent`. Throws on a missing bundle, an unknown intent, a boundary that is not a fresh
28
+ * random string or that the content contains, a translate without a language tag, and `pictures` without a clock, an
29
+ * approved list or a cache lookup.
30
+ */
31
+ export declare function renderBundleForModel(bundle: SubjectBundle, intent: AssistIntent, options: RenderBundleOptions): RenderedBundle;