@joe-broadhead/messaging 0.7.0 → 0.7.1

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 (43) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/bin/messaging-mcp.js +649 -12
  3. package/dist/bin/messaging-relay.js +774 -20
  4. package/dist/bin/messaging.js +748 -15
  5. package/dist/bin/messagingd.js +785 -18
  6. package/dist/lib/modules/core/agent-tools.js +23 -4
  7. package/dist/lib/modules/core/conversation/context.js +80 -0
  8. package/dist/lib/modules/core/conversation/event.js +100 -0
  9. package/dist/lib/modules/core/conversation/ids.js +38 -0
  10. package/dist/lib/modules/core/conversation/link.js +35 -0
  11. package/dist/lib/modules/core/conversation/log.js +766 -0
  12. package/dist/lib/modules/core/conversation/ratchet.js +243 -0
  13. package/dist/lib/modules/core/conversation/seal.js +198 -0
  14. package/dist/lib/modules/core/index.js +10 -1
  15. package/dist/lib/modules/core/schema/common.js +56 -0
  16. package/dist/lib/modules/core/schema/conversation.js +292 -0
  17. package/dist/lib/modules/core/schema/errors.js +39 -0
  18. package/dist/lib/modules/core/schema/index.js +1 -0
  19. package/dist/lib/modules/core/testing.js +1 -0
  20. package/dist/lib/modules/core/version.js +1 -1
  21. package/dist/spec/messaging-v1.md +324 -6
  22. package/dist/types/core/agent-tools.d.ts +20 -2
  23. package/dist/types/core/conversation/context.d.ts +56 -0
  24. package/dist/types/core/conversation/event.d.ts +36 -0
  25. package/dist/types/core/conversation/ids.d.ts +18 -0
  26. package/dist/types/core/conversation/link.d.ts +16 -0
  27. package/dist/types/core/conversation/log.d.ts +142 -0
  28. package/dist/types/core/conversation/ratchet.d.ts +95 -0
  29. package/dist/types/core/conversation/seal.d.ts +47 -0
  30. package/dist/types/core/index.d.ts +8 -1
  31. package/dist/types/core/schema/common.d.ts +56 -0
  32. package/dist/types/core/schema/conversation.d.ts +1657 -0
  33. package/dist/types/core/schema/envelope.d.ts +6 -0
  34. package/dist/types/core/schema/errors.d.ts +38 -0
  35. package/dist/types/core/schema/index.d.ts +1 -0
  36. package/dist/types/core/schema/ipc/device.d.ts +6 -0
  37. package/dist/types/core/schema/ipc/shapes.d.ts +3 -0
  38. package/dist/types/core/schema/ipc/surfaces.d.ts +6 -0
  39. package/dist/types/core/schema/ipc.d.ts +15 -0
  40. package/dist/types/core/schema/relay.d.ts +12 -0
  41. package/dist/types/core/testing.d.ts +1 -0
  42. package/dist/types/core/version.d.ts +1 -1
  43. package/package.json +1 -1
@@ -0,0 +1,142 @@
1
+ import type { AnchorRelation } from "../realm.js";
2
+ import { type Hash, type MachineId, type PersonId } from "../schema/common.js";
3
+ import { type AgentContext, type BridgePolicy, type ConvEvent, type ConvHead, type ConvId, type ConvKind, type ConvLogEntry, type ConvRole, type HistoryPolicy, type MsgKind, type ParticipantId, type Permit, type RekeyCause, type RespondTo, type Visibility } from "../schema/conversation.js";
4
+ import type { RealmId } from "../schema/realm.js";
5
+ /** A participant at one position. */
6
+ export interface ConvParticipant {
7
+ p: ParticipantId;
8
+ person: PersonId;
9
+ /** The agent's session name, for an agent participant. */
10
+ agent?: string;
11
+ role: ConvRole;
12
+ /** `invited` until the person accepts, or until the agent's person seats it. */
13
+ state: "member" | "invited";
14
+ device?: MachineId;
15
+ respondTo?: RespondTo;
16
+ /** The seq at which it became a participant. */
17
+ since: number;
18
+ }
19
+ /** Everything the log decides at one position. Frozen. */
20
+ export interface ConvSnapshot {
21
+ cid: ConvId;
22
+ kind: ConvKind;
23
+ visibility?: Visibility;
24
+ history: HistoryPolicy;
25
+ retainDays: number;
26
+ org?: string;
27
+ post: Permit;
28
+ invite: Permit;
29
+ pins: Permit;
30
+ archived: boolean;
31
+ epoch: number;
32
+ /** The seq and `ts` of the op that started the epoch. */
33
+ epochSince: number;
34
+ epochTs: number;
35
+ /** Causes that need a `rekey` before the next message (spec §16.7). Sorted. */
36
+ rekeyDue: RekeyCause[];
37
+ /** Sorted by `p`. */
38
+ participants: ConvParticipant[];
39
+ /** The effective policy. In a channel the owners' and admins'; in a dm or a group the strictest vote. */
40
+ agentContext: AgentContext;
41
+ /**
42
+ * Who may mirror the conversation to a linked channel (reserved: no daemon acts on it yet). In a channel the
43
+ * owners' and admins'; in a dm or a group the strictest vote, where a person who has not voted counts as `none`.
44
+ */
45
+ bridges: BridgePolicy;
46
+ /** dm and group: each participant person's votes, sorted by person. */
47
+ votes: ConvVote[];
48
+ /** The seq of the entry that last changed any of the above. */
49
+ changed: number;
50
+ }
51
+ /** One participant person's policy votes in a dm or group. */
52
+ export interface ConvVote {
53
+ person: PersonId;
54
+ agentContext?: AgentContext;
55
+ bridges?: BridgePolicy;
56
+ }
57
+ /** What a member knows of one event, by id. */
58
+ export interface ConvMessage {
59
+ id: string;
60
+ seq: number;
61
+ class: ConvEvent["class"];
62
+ author: ParticipantId;
63
+ /** Known when the body was opened. */
64
+ t?: MsgKind;
65
+ deleted: boolean;
66
+ pinned: boolean;
67
+ reactions: {
68
+ key: string;
69
+ by: ParticipantId[];
70
+ }[];
71
+ /** For a task request: its executor. */
72
+ taskTo?: ParticipantId;
73
+ /** For a task request: true. */
74
+ taskRoot?: boolean;
75
+ }
76
+ /** Checks only a live append can make: they depend on the realm now, not on the log. */
77
+ export interface ConvLiveChecks {
78
+ /** Wall-clock time; the event's `ts` must be within `skewMs` (default `LIMITS.clockSkewMs`) of it (`expired`). */
79
+ now?: number;
80
+ skewMs?: number;
81
+ /** True when `device` is a current device of `person` in the realm (`unknown_device` otherwise). */
82
+ isDevice?: (person: PersonId, device: MachineId) => boolean;
83
+ /** The org of `person`, for `join`, and for an add of a person who is not a guest (`forbidden`). */
84
+ orgOf?: (person: PersonId) => string | undefined;
85
+ }
86
+ export interface ConvApplyOptions {
87
+ /** The opened body of a `msg` entry; the message rules run only with it. */
88
+ body?: unknown;
89
+ live?: ConvLiveChecks;
90
+ }
91
+ /** An entry whose content the relay expired: its seq, its prev and the digest SHA-256(JCS(event)). */
92
+ export interface ConvExpiredEntry {
93
+ seq: number;
94
+ prev: Hash;
95
+ digest: Hash;
96
+ }
97
+ /**
98
+ * A verified conversation log. Every method throws only MessagingError. Entries are kept as frozen copies of the
99
+ * received JSON, so hashes cover exactly what was received.
100
+ */
101
+ export declare class ConvLog {
102
+ #private;
103
+ readonly cid: ConvId;
104
+ readonly realm: RealmId;
105
+ private constructor();
106
+ /** An empty log of `cid` in `realm`: its head is {0, h(0)}. */
107
+ static open(cid: ConvId, realm: RealmId): ConvLog;
108
+ /** Replays `entries` (each with the opened body of a `msg`, when the caller has it). Throws the first failure. */
109
+ static fromEntries(cid: ConvId, realm: RealmId, entries: {
110
+ entry: unknown;
111
+ body?: unknown;
112
+ }[]): ConvLog;
113
+ head(): ConvHead;
114
+ /** The last op's seq and hash, or {0, h(0)}: what an author puts in `opHead`. */
115
+ opHead(): ConvHead;
116
+ /** h(seq), for 0 ≤ seq ≤ head. */
117
+ hashAt(seq: number): Hash | undefined;
118
+ entries(): (ConvLogEntry | ConvExpiredEntry)[];
119
+ /** The state at the head; undefined before `create`. */
120
+ state(): ConvSnapshot | undefined;
121
+ /** The state after the entry at `seq`, as a log holding only entries 1..seq has it. */
122
+ at(seq: number): ConvSnapshot | undefined;
123
+ /** What a member knows of the event `id`. */
124
+ message(id: string): ConvMessage | undefined;
125
+ /**
126
+ * Gossip (spec §16.4): `ahead` past the head (fetch up to it first); `consistent` when h(seq) is ours; `diverged`
127
+ * when this log has another hash at that seq, a fork whose evidence is the signed event that named it.
128
+ */
129
+ relate(head: ConvHead): AnchorRelation;
130
+ /**
131
+ * Checks one entry and appends it, in order: I-JSON and size (`invalid_request`, `too_large`); schema, realm and
132
+ * cid (`invalid_request`); the chain, seq = head + 1 and prev = h(head) (`conv_conflict`); the signature
133
+ * (`bad_signature`); the live checks given (`expired`, `unknown_device`); a reused id (`duplicate`); then the
134
+ * authority rules of spec §16.7. Returns the entry's h(seq).
135
+ */
136
+ apply(raw: unknown, options?: ConvApplyOptions): Hash;
137
+ /**
138
+ * Appends an entry whose content the relay expired (spec §16.4, "Retention"), by its digest: the chain stays
139
+ * verifiable and the state does not change. An op is never expired: a later op head that names this seq fails.
140
+ */
141
+ applyExpired(raw: ConvExpiredEntry): Hash;
142
+ }
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ import { type MachineId } from "../schema/common.js";
3
+ import { type KeyShare } from "../schema/conversation.js";
4
+ /** One ratchet step: the message key for this index and the chain key for the next. */
5
+ export declare function chainStep(ck: Uint8Array): {
6
+ mk: Uint8Array;
7
+ next: Uint8Array;
8
+ };
9
+ /** A sender chain as a device stores it. */
10
+ export declare const SenderChainState: z.ZodObject<{
11
+ epoch: z.ZodNumber;
12
+ device: z.ZodString;
13
+ start: z.ZodNumber;
14
+ origin: z.ZodString;
15
+ idx: z.ZodNumber;
16
+ ck: z.ZodString;
17
+ startedAt: z.ZodNumber;
18
+ }, z.core.$strict>;
19
+ export type SenderChainState = z.infer<typeof SenderChainState>;
20
+ /** The sending side of one chain. `next()` hands out each message key once. */
21
+ export declare class SenderChain {
22
+ #private;
23
+ readonly epoch: number;
24
+ readonly device: MachineId;
25
+ /** The index this chain starts at. */
26
+ readonly start: number;
27
+ readonly startedAt: number;
28
+ private constructor();
29
+ /** A new chain at `idx` (default 0) of `epoch`, from a fresh random chain key. */
30
+ static start(args: {
31
+ epoch: number;
32
+ device: MachineId;
33
+ idx?: number;
34
+ now: number;
35
+ }): SenderChain;
36
+ /** `start` with a caller-chosen chain key: for test vectors, and for a device restoring its own state. */
37
+ static startWith(args: {
38
+ epoch: number;
39
+ device: MachineId;
40
+ idx?: number;
41
+ now: number;
42
+ }, ck: Uint8Array): SenderChain;
43
+ static restore(raw: unknown): SenderChain;
44
+ export(): SenderChainState;
45
+ /** The index the next message gets. */
46
+ get idx(): number;
47
+ /** The share that lets a receiver open every message of this chain: its chain key at its start. */
48
+ share(): KeyShare;
49
+ /** The key for the next message, and its index. The chain moves past it. */
50
+ next(): {
51
+ idx: number;
52
+ mk: Uint8Array;
53
+ };
54
+ /** True once the chain has sent `LIMITS.chainMessages` messages or is `LIMITS.chainMaxMs` old: rotate before the next. */
55
+ due(now: number): boolean;
56
+ /** A fresh chain in the same epoch, starting at this chain's next index. Share it before sending on it. */
57
+ rotate(now: number): SenderChain;
58
+ /** `rotate` with a caller-chosen chain key, for test vectors. */
59
+ rotateWith(now: number, ck: Uint8Array): SenderChain;
60
+ }
61
+ export declare const ReceiverChainsState: z.ZodObject<{
62
+ chains: z.ZodArray<z.ZodObject<{
63
+ epoch: z.ZodNumber;
64
+ device: z.ZodString;
65
+ start: z.ZodNumber;
66
+ origin: z.ZodString;
67
+ pos: z.ZodNumber;
68
+ ck: z.ZodString;
69
+ skipped: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodString], null>>;
70
+ }, z.core.$strict>>;
71
+ }, z.core.$strict>;
72
+ export type ReceiverChainsState = z.infer<typeof ReceiverChainsState>;
73
+ /**
74
+ * The receiving side: every chain this device holds for one conversation, by (epoch, sender device). It derives
75
+ * forward only and hands out each message key once; a message that arrives out of order uses a key kept from an
76
+ * earlier derivation, at most `LIMITS.skippedKeys` per sender and epoch. The chain keys at each share's index are
77
+ * kept, so the device can share them with a late joiner (history `shared`).
78
+ */
79
+ export declare class ReceiverChains {
80
+ #private;
81
+ static restore(raw: unknown): ReceiverChains;
82
+ export(): ReceiverChainsState;
83
+ /**
84
+ * Adds a share. The first share at a given (epoch, device, idx) wins: a later one with another key changes
85
+ * nothing, and `add` returns false. Returns true when the share was new.
86
+ */
87
+ add(raw: KeyShare): boolean;
88
+ /** Every share this device holds, from each chain's start: what it shares with a late joiner. */
89
+ shares(): KeyShare[];
90
+ /**
91
+ * The message key for (`epoch`, `device`, `idx`), used once. `not_found` when no share covers it, or its key was
92
+ * already used or dropped; `too_large` when it is more than `LIMITS.skippedKeys` past the chain's position.
93
+ */
94
+ messageKey(epoch: number, device: MachineId, idx: number): Uint8Array;
95
+ }
@@ -0,0 +1,47 @@
1
+ import type { Identity } from "../identity.js";
2
+ import { type EncKey, type MachineId, type PersonId } from "../schema/common.js";
3
+ import { type ConvKeysEvent, ConvMeta, type ConvMsgEvent, type ConvOpEvent, type KeyShare, MsgBody } from "../schema/conversation.js";
4
+ /** A message event before its body: the header its author signs, with `class: "msg"`. */
5
+ export type UnsealedMsg = Omit<ConvMsgEvent, "body" | "sig">;
6
+ /** A keys event before its body. */
7
+ export type UnsealedKeys = Omit<ConvKeysEvent, "body" | "sig">;
8
+ /** A recipient device of a key share: its person, its machine and its X25519 key, as the realm verified them. */
9
+ export interface ShareRecipient {
10
+ person: PersonId;
11
+ device: MachineId;
12
+ enc: EncKey;
13
+ }
14
+ /** Fixed randomness of one sealed share, for test vectors: the ephemeral X25519 private key and the nonce. */
15
+ export interface ShareRandomness {
16
+ ephemeral: Uint8Array;
17
+ nonce: Uint8Array;
18
+ }
19
+ /** Seals `body` under `mk` as the body of `unsealed` and signs the event. Throws only MessagingError. */
20
+ export declare function sealConvMsg(identity: Identity, unsealed: UnsealedMsg, body: MsgBody, mk: Uint8Array): ConvMsgEvent;
21
+ /** `sealConvMsg` with a caller-chosen nonce: for test vectors only (`@messaging/core/testing`). */
22
+ export declare function sealConvMsgWith(identity: Identity, unsealed: UnsealedMsg, body: MsgBody, mk: Uint8Array, nonce: Uint8Array): ConvMsgEvent;
23
+ /**
24
+ * Opens a `msg` event's body with its message key: `bad_signature` when it fails to open, `too_large` over
25
+ * `LIMITS.convPlaintextBytes`, `invalid_request` when the plaintext is not a `MsgBody` (parsed with `parseWire`).
26
+ * The caller has verified the event's signature.
27
+ */
28
+ export declare function openConvMsg(event: ConvMsgEvent, mk: Uint8Array): MsgBody;
29
+ /** `create.sealedMeta`: JCS(meta) sealed under the creator's epoch-0 key 0, with the create event's header as AAD. */
30
+ export declare function sealConvMeta(header: Omit<ConvOpEvent, "body" | "sig">, meta: ConvMeta, mk0: Uint8Array): string;
31
+ /** `sealConvMeta` with a caller-chosen nonce: for test vectors only. */
32
+ export declare function sealConvMetaWith(header: Omit<ConvOpEvent, "body" | "sig">, meta: ConvMeta, mk0: Uint8Array, nonce: Uint8Array): string;
33
+ /** Opens a `create` event's `sealedMeta`. Errors as for `openConvMsg`; `not_found` when it has none. */
34
+ export declare function openConvMeta(event: ConvOpEvent, mk0: Uint8Array): ConvMeta;
35
+ /**
36
+ * Seals `shares` to each recipient device as the body of `unsealed`, and signs the event. One sealed share per
37
+ * device; at most `LIMITS.keySharesPerEvent` devices and `LIMITS.keyShares` chain keys. Throws only MessagingError.
38
+ */
39
+ export declare function sealKeyShares(identity: Identity, unsealed: UnsealedKeys, recipients: ShareRecipient[], shares: KeyShare[]): ConvKeysEvent;
40
+ /** `sealKeyShares` with fixed randomness per recipient, in order: for test vectors only. */
41
+ export declare function sealKeySharesWith(identity: Identity, unsealed: UnsealedKeys, recipients: ShareRecipient[], shares: KeyShare[], randomness: ShareRandomness[]): ConvKeysEvent;
42
+ /**
43
+ * Opens the share sealed to `identity`'s device in a `keys` event: `not_found` when there is none, `bad_signature`
44
+ * when it fails to open, `invalid_request` when the plaintext is not a share payload. The caller has verified the
45
+ * event's signature and applied it to the log.
46
+ */
47
+ export declare function openKeyShares(event: ConvKeysEvent, identity: Identity): KeyShare[];
@@ -32,7 +32,14 @@ export { createGrant, verifyChain, type GrantArgs, type ChainHop, type ChainCont
32
32
  export { canTransition, isTerminal, isInterrupted } from "./task.js";
33
33
  export { TERMINAL_STATES } from "./schema/a2a.js";
34
34
  export { cleanPeerText, createFence, terminalSafe, displayWidth, HOST_TAGS, PEER_FENCE_TAG, PEER_DATA_NOTICE, provenanceOf, type CleanOptions, type Fence, type FenceAttributes, type FenceOptions, type TerminalOptions } from "./peer-text.js";
35
- export { AgentTool, TOOL_VERBS, TOOL_DESCRIPTIONS, ToolExpect, UNTRUSTED_SENTENCE, TOOL_REMEDIES, DONE_STATES, TaskStateName, ToolErrorBody, ToolFiles, a2aTaskState, conversationOf, decodeToolCursor, encodeToolCursor, formatToolAddress, parseToolAddress, taskStateName, toolErrorBody, toolName, type ToolAddress, type ToolAuthor, type ToolChain, type ToolConsent, type ToolCursor, type ToolInput, type ToolOutput, type ToolPart, type ToolTask, type ToolVerb, } from "./agent-tools.js";
35
+ export { AgentTool, TOOL_VERBS, TOOL_DESCRIPTIONS, ToolExpect, UNTRUSTED_SENTENCE, TOOL_REMEDIES, STORE_REFUSED_REMEDIES, DONE_STATES, TaskStateName, ToolErrorBody, ToolFiles, a2aTaskState, conversationOf, decodeToolCursor, encodeToolCursor, formatToolAddress, parseToolAddress, taskStateName, toolErrorBody, toolName, type ToolAddress, type ToolAuthor, type ToolChain, type ToolConsent, type ToolCursor, type ToolInput, type ToolOutput, type ToolPart, type ToolTask, type ToolVerb, } from "./agent-tools.js";
36
36
  export { Operations, type CallSession, type ChatAccess, type OperationsContext, type OperationsHooks, type SendPlan, type ToolLink, } from "./agent/operations.js";
37
37
  export { agentCan, consentOf, conversationOfPeer, followUpRead, isoOf, presentAgent, presentConvEntry, presentInboxItem, presentLine, presentMail, presentSent, presentTask, presentTaskItem, presentTaskView, } from "./agent/present.js";
38
38
  export { chainPath, compact, delegated, delegationPrompt, EXPECT_TEXT, isSafeId, messageAttributes, validatedAddress, type SessionAddressParts, type ToolFramer, } from "./agent/text.js";
39
+ export { dmConvId, selfConvId, participantOf, participantPerson, participantAgent } from "./conversation/ids.js";
40
+ export { admitConvEvent, convEntryHash, convEventDigest, convEventHeader, convEventSigned, convGenesisHash, convLinkHash, signConvEvent, verifyConvEvent, } from "./conversation/event.js";
41
+ export { chainStep, ReceiverChains, ReceiverChainsState, SenderChain, SenderChainState } from "./conversation/ratchet.js";
42
+ export { openConvMeta, openConvMsg, openKeyShares, sealConvMeta, sealConvMsg, sealKeyShares, type ShareRecipient, type UnsealedKeys, type UnsealedMsg, } from "./conversation/seal.js";
43
+ export { ConvLog, type ConvApplyOptions, type ConvExpiredEntry, type ConvLiveChecks, type ConvMessage, type ConvParticipant, type ConvSnapshot, type ConvVote, } from "./conversation/log.js";
44
+ export { checkVia, currentLinks, linkReader } from "./conversation/link.js";
45
+ export { CONTEXT_DEFAULTS, contextKindOf, matchContextRule, resolveContext, seatReads, type ContextConv, type ContextDecision, } from "./conversation/context.js";
@@ -42,6 +42,18 @@ export declare const DOMAIN: {
42
42
  readonly epoch: "messaging/1/epoch\n";
43
43
  /** Hosted realms (spec §15.6): an epoch-key rotation, signed by the old key and by the new over JCS(rotation without sigs). */
44
44
  readonly dirKey: "messaging/1/dir-key\n";
45
+ /** Conversations (spec §16.4): a conversation event, signed by `by.device` over JCS(event without sig). */
46
+ readonly convEvent: "messaging/1/conv-event\n";
47
+ /** Conversations (spec §16.4): the prefix of a conversation log's genesis hash h(0), over JCS(cid). */
48
+ readonly convGenesis: "messaging/1/conv-genesis\n";
49
+ /** Conversations (spec §16.1): the prefix of the hash that derives a dm's id, over JCS of its two sorted participants. */
50
+ readonly dm: "messaging/1/dm\n";
51
+ /** Conversations (spec §16.1): the prefix of the hash that derives a person's `self` conversation id, over JCS(person). */
52
+ readonly self: "messaging/1/self\n";
53
+ /** Conversations (spec §16.5): HKDF `info` for a sender chain's message key. */
54
+ readonly convMsg: "messaging/1/conv-msg";
55
+ /** Conversations (spec §16.5): HKDF `info` for a sender chain's next chain key. */
56
+ readonly convChain: "messaging/1/conv-chain";
45
57
  };
46
58
  /** A2A extension and binding URIs owned by Messaging. */
47
59
  export declare const URI: {
@@ -242,6 +254,50 @@ export declare const LIMITS: {
242
254
  readonly a2hPerHour: 30;
243
255
  /** Max lines one person's agents send to another person in a day, summed over the agents (spec §6.9, "Pacing"). */
244
256
  readonly a2hPerDay: 100;
257
+ /** Conversations (spec §16): max participants (persons and agents) of a channel. */
258
+ readonly convParticipants: 1000;
259
+ /** Max participants (persons and agents) of a group. A group has at least 3. */
260
+ readonly groupParticipants: 50;
261
+ /** Max participants one `add` or `remove` op names. */
262
+ readonly convOpParticipants: 100;
263
+ /** Max UTF-8 bytes of one conversation event (JCS), signature and body included. */
264
+ readonly convEventBytes: 262144;
265
+ /** Max UTF-8 bytes of one message body's plaintext (JCS) before it is sealed. */
266
+ readonly convPlaintextBytes: 65536;
267
+ /** Max UTF-16 code units of one conversation line's text. */
268
+ readonly lineChars: 40000;
269
+ /** Max mentions in one line or edit. */
270
+ readonly mentions: 50;
271
+ /** Max attachments in one line. */
272
+ readonly files: 10;
273
+ /** Max delegation grants one line carries for the agents it mentions (spec §16.3). */
274
+ readonly agentMentions: 5;
275
+ /** Max bytes of one attached file, encrypted. */
276
+ readonly blobBytes: 26214400;
277
+ /** Max distinct reaction keys on one event. */
278
+ readonly reactionsPerEvent: 50;
279
+ /** Max pinned events in one conversation. */
280
+ readonly pins: 100;
281
+ /** Retention of a conversation that sets none, in days. */
282
+ readonly retainDaysDefault: 90;
283
+ /** Max `retainDays` of a conversation, and max lookback of a context rule. */
284
+ readonly retainDaysMax: 365;
285
+ /** Max message keys a receiver keeps for one sender chain to open messages that arrive out of order. */
286
+ readonly skippedKeys: 1000;
287
+ /** A sender starts a new chain after this many messages on one chain... */
288
+ readonly chainMessages: 500;
289
+ /** ...or this long after it started the chain, whichever comes first. */
290
+ readonly chainMaxMs: number;
291
+ /** An epoch older than this may be ended by a `rekey` with cause `schedule`. */
292
+ readonly rekeyScheduleMs: number;
293
+ /** Max chain keys in one sealed key share, and in one `keys` sync of the `self` conversation. */
294
+ readonly keyShares: 512;
295
+ /** Max recipient devices one `keys` event seals to; a larger share is split over several events. */
296
+ readonly keySharesPerEvent: 500;
297
+ /** Max agents one event's `wake` hint names. */
298
+ readonly wakeHints: 5;
299
+ /** Max context rules a person holds (spec §16.8). */
300
+ readonly contextRules: 500;
245
301
  };
246
302
  /**
247
303
  * True for a relay URL a client may connect to: `wss://`, or `ws://` to a loopback host (`localhost`,