@joe-broadhead/messaging 0.7.1 → 0.9.0

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 (94) hide show
  1. package/CHANGELOG.md +364 -2
  2. package/dist/bin/messaging-mcp.js +5498 -4338
  3. package/dist/bin/messaging-relay.js +8626 -5540
  4. package/dist/bin/messaging.js +4492 -3158
  5. package/dist/bin/messagingd.js +19672 -12412
  6. package/dist/lib/modules/core/agent/operations.js +19 -327
  7. package/dist/lib/modules/core/agent/present.js +20 -55
  8. package/dist/lib/modules/core/agent-tools.js +35 -139
  9. package/dist/lib/modules/core/consent.js +54 -14
  10. package/dist/lib/modules/core/contact-card.js +3 -3
  11. package/dist/lib/modules/core/conversation/event.js +29 -4
  12. package/dist/lib/modules/core/conversation/guest-invitation.js +42 -0
  13. package/dist/lib/modules/core/conversation/log.js +287 -65
  14. package/dist/lib/modules/core/conversation/request-log.js +158 -0
  15. package/dist/lib/modules/core/conversation/request-seal.js +41 -0
  16. package/dist/lib/modules/core/conversation/requests.js +87 -0
  17. package/dist/lib/modules/core/conversation/seal.js +90 -37
  18. package/dist/lib/modules/core/conversation-agent-tools.js +69 -0
  19. package/dist/lib/modules/core/federation/directory-proof.js +83 -0
  20. package/dist/lib/modules/core/federation/proof-state.js +167 -0
  21. package/dist/lib/modules/core/federation/roster-proof.js +74 -0
  22. package/dist/lib/modules/core/index.js +9 -4
  23. package/dist/lib/modules/core/jelly-host.js +8 -5
  24. package/dist/lib/modules/core/schema/common.js +33 -5
  25. package/dist/lib/modules/core/schema/consent.js +23 -5
  26. package/dist/lib/modules/core/schema/contact-request.js +22 -0
  27. package/dist/lib/modules/core/schema/conv-frames.js +101 -0
  28. package/dist/lib/modules/core/schema/conversation.js +67 -33
  29. package/dist/lib/modules/core/schema/directory-frames.js +8 -3
  30. package/dist/lib/modules/core/schema/envelope.js +8 -24
  31. package/dist/lib/modules/core/schema/errors.js +7 -5
  32. package/dist/lib/modules/core/schema/guest-invitation.js +13 -0
  33. package/dist/lib/modules/core/schema/index.js +5 -0
  34. package/dist/lib/modules/core/schema/ipc/consent.js +97 -18
  35. package/dist/lib/modules/core/schema/ipc/conversation-views.js +87 -0
  36. package/dist/lib/modules/core/schema/ipc/conversations.js +132 -0
  37. package/dist/lib/modules/core/schema/ipc/device.js +9 -0
  38. package/dist/lib/modules/core/schema/ipc/jelly.js +17 -45
  39. package/dist/lib/modules/core/schema/ipc/shapes.js +74 -73
  40. package/dist/lib/modules/core/schema/ipc/surfaces.js +12 -195
  41. package/dist/lib/modules/core/schema/ipc/tasks.js +31 -0
  42. package/dist/lib/modules/core/schema/ipc.js +68 -46
  43. package/dist/lib/modules/core/testing.js +1 -1
  44. package/dist/lib/modules/core/version.js +1 -1
  45. package/dist/lib/modules/ipc/client.js +10 -2
  46. package/dist/lib/modules/ipc/server.js +14 -5
  47. package/dist/lib/modules/ipc/testing.js +13 -23
  48. package/dist/spec/messaging-v1.md +854 -445
  49. package/dist/third-party-notices.md +1 -1
  50. package/dist/third-party.json +1 -1
  51. package/dist/types/core/agent/operations.d.ts +5 -52
  52. package/dist/types/core/agent/present.d.ts +6 -21
  53. package/dist/types/core/agent-tools.d.ts +295 -442
  54. package/dist/types/core/consent.d.ts +30 -7
  55. package/dist/types/core/contact-card.d.ts +3 -3
  56. package/dist/types/core/conversation/event.d.ts +6 -6
  57. package/dist/types/core/conversation/guest-invitation.d.ts +8 -0
  58. package/dist/types/core/conversation/log.d.ts +28 -13
  59. package/dist/types/core/conversation/request-log.d.ts +24 -0
  60. package/dist/types/core/conversation/request-seal.d.ts +6 -0
  61. package/dist/types/core/conversation/requests.d.ts +14 -0
  62. package/dist/types/core/conversation/seal.d.ts +32 -9
  63. package/dist/types/core/conversation-agent-tools.d.ts +939 -0
  64. package/dist/types/core/federation/directory-proof.d.ts +8 -0
  65. package/dist/types/core/federation/proof-state.d.ts +114 -0
  66. package/dist/types/core/federation/roster-proof.d.ts +7 -0
  67. package/dist/types/core/index.d.ts +11 -6
  68. package/dist/types/core/jelly-host.d.ts +1 -1
  69. package/dist/types/core/schema/common.d.ts +33 -6
  70. package/dist/types/core/schema/consent.d.ts +29 -8
  71. package/dist/types/core/schema/contact-request.d.ts +264 -0
  72. package/dist/types/core/schema/conv-frames.d.ts +1204 -0
  73. package/dist/types/core/schema/conversation.d.ts +1410 -264
  74. package/dist/types/core/schema/directory-frames.d.ts +8 -3
  75. package/dist/types/core/schema/envelope.d.ts +85 -130
  76. package/dist/types/core/schema/errors.d.ts +5 -1
  77. package/dist/types/core/schema/guest-invitation.d.ts +51 -0
  78. package/dist/types/core/schema/index.d.ts +5 -0
  79. package/dist/types/core/schema/ipc/consent.d.ts +464 -62
  80. package/dist/types/core/schema/ipc/conversation-views.d.ts +1199 -0
  81. package/dist/types/core/schema/ipc/conversations.d.ts +4355 -0
  82. package/dist/types/core/schema/ipc/device.d.ts +10 -0
  83. package/dist/types/core/schema/ipc/jelly.d.ts +16 -111
  84. package/dist/types/core/schema/ipc/shapes.d.ts +211 -95
  85. package/dist/types/core/schema/ipc/surfaces.d.ts +26 -427
  86. package/dist/types/core/schema/ipc/tasks.d.ts +210 -0
  87. package/dist/types/core/schema/ipc.d.ts +3859 -890
  88. package/dist/types/core/schema/relay.d.ts +12 -4
  89. package/dist/types/core/testing.d.ts +1 -1
  90. package/dist/types/core/version.d.ts +1 -1
  91. package/dist/types/ipc/client.d.ts +4 -1
  92. package/dist/types/ipc/server.d.ts +16 -1
  93. package/dist/types/ipc/testing.d.ts +21 -8
  94. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,368 @@ Versions follow [Semantic Versioning](https://semver.org); see
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.0] - 2026-11-11
10
+
11
+ **What Jelly asked for in opencode-messaging #17, #18, #19 and #24.** The person sees their agents' tasks as a view
12
+ (who does the task, for whom, a sign-in it waits on, why it was refused) and stops one with a source-issued action.
13
+ Inbound tasks become notices. A resting Jelly host wakes for a teammate's dm. The person answers two questions about
14
+ who may reach them. The 0.8.0 follow-ups for mixed versions, the `conv.caps` presence oracle and the control-op drain
15
+ are here too.
16
+
17
+ **Upgrade the relay, then the daemons, in any order after it.** 0.9.0 and 0.8.0 daemons interoperate (below). Stores
18
+ only grow: the daemon store goes to schema 5 and the relay database to schema 4, and a rollback keeps the data.
19
+
20
+ ### Compatibility
21
+
22
+ - **0.9.0 and 0.8.0 daemons interoperate** (spec §8.3). Nothing on the wire between daemons was removed or tightened.
23
+ - Conversations, message requests and tasks work in both directions. Both releases advertise the
24
+ `conversations` feature that `conv.caps` checks, so neither answers the other with `peer_outdated`.
25
+ - A 0.9.0 executor's sign-in names its tool in the task status as A2A extension data
26
+ (`metadata["urn:messaging:ext:sign-in:v1"]`, spec §6.2, "Sign-in"). A 0.8.0 requester still reads a valid status
27
+ and its text.
28
+ - A 0.9.0 device advertises the new presence feature `reach` (spec §6.5). A 0.8.0 device does not, and **does not
29
+ enforce the reach choices** (spec §6.9, "Older daemons do not enforce the choices"). A relation grant synced to a
30
+ 0.8.0 device of the same person is skipped there (`grant.sync.skipped`), and it keeps working.
31
+ - **dm wakes need the relay at 0.9.0** (relay schema 4). A 0.8.0 relay parses `wake.arm` without `dms` and still arms
32
+ the host's agents and awaited tasks, so a 0.9.0 host behind it arms no dm and wakes as before. A 0.8.0 daemon never
33
+ sends `dms`, and a 0.9.0 relay arms it as before.
34
+ - **A 0.8.0 daemon behind a 0.9.0 relay** gets the new optional `unknown` in a `conv.caps` answer only over the new
35
+ budget (below). It cannot parse that answer and goes ahead without one, as 0.8.0 already did when `conv.caps` was
36
+ refused.
37
+ - **Rollback keeps the data.** Daemon schema 5 and relay schema 4 are additive, with generation 2 and `min_reader` 1,
38
+ so 0.8.0 opens both. A 0.8.0 daemon ignores the reach tables and does not enforce the choices; a 0.8.0 relay
39
+ ignores `dir_wake_dms`, and the rows it leaves name a ticket that no longer counts.
40
+
41
+ ### Added
42
+
43
+ - **The task view (#17)** (spec §8.2, "The task view"). Every `TaskRecord` the daemon projects carries fields it
44
+ computes and never stores or sends:
45
+ - `agent`, the counterpart session; `person: {id, name}`, verified by the realm, never parsed from an agent's name;
46
+ `org` (`same`, `other` or `none`);
47
+ - `signIn: {tool?, by}` in `AUTH_REQUIRED`, where `by` (`you` or `them`) is computed by the viewer's own daemon and
48
+ never read from the peer. An executor names the tool with `task.update {signIn: {tool}}`;
49
+ - `refusal: {reason}` in `REJECTED`.
50
+ - `tool` and `reason` come only from the executor itself, through `cleanPeerText`, capped at
51
+ `TASK_SIGN_IN_TOOL_CHARS` and `TASK_REFUSAL_CHARS`. Agent tools show them fenced when the peer wrote them.
52
+ - **Task notices (#18)** (spec §8.2, "Notices"). A task another person's agent asked of one of this person's agents
53
+ files a `message` notice with `key: "task:<task id>"` when this device accepts it. It copies no text and is never a
54
+ `request`. An ask held for consent stays a `request` until its person answers. A task between the person's own
55
+ agents files none.
56
+ - **dm wakes (#19)** (spec §7 and §8.11, "Wake tickets").
57
+ - `wake.arm` takes `dms`, at most `WAKE_DMS` (256). The relay presents the ticket for a line in an armed dm by its
58
+ other person while the host is offline, within the existing budget (1 a minute, 6 an hour, 48 a day).
59
+ - The daemon arms the verified dms whose other person shares a channel with this person as a member, and returns
60
+ their count (`dms`). It never arms a person this person blocked or muted, or one their reach choices hide.
61
+ - It reads the candidates' directory views in batches (`LIMITS.dirLookupPersons` to a lookup) before it decides.
62
+ - The list is what held at the arm: a block while the host sleeps applies at the next arm, so at most one wake.
63
+ - Neither side logs a dm's id. The relay can't tell a line from other sealed events in an armed dm, so a reaction or
64
+ edit there may also wake the host, within the budget.
65
+ - **Consent choices (#24)** (spec §6.9, "Reach choices").
66
+ - `reach.get`, `reach.set {revision, changes}` and `reach.set.preview`, on the human tier only. "Who can message
67
+ you" is Open, Ask me first (the default) or Never for people outside the org. "How agents may ask yours" is Open,
68
+ Ask me first or Never for the org's agents.
69
+ - Each choice is the person's own signed grant on a relation subject (`{relation: "outside" | "org"}`). A grant for
70
+ one person or conversation wins over it.
71
+ - A stale `revision` is the new error `conflict`, and nothing changes.
72
+ - Under Never, an outsider whose person grant sets no `chat` is refused, and their lines are hidden in existing
73
+ conversations at read time. Choosing again shows them. The org, the person's own lines and outsiders' agents are
74
+ never hidden. A person the realm no longer knows stays visible.
75
+ - CLI: `messaging reach`.
76
+ - **Action tokens** (spec §8.2, "Action tokens"; design §12.5).
77
+ - On the human tier the daemon puts `{name, token}` on what it renders: `actions` on every `request.list` item, one
78
+ per answer its kind takes, and `cancel` on a task this person's agent asked that has not ended. The executor side
79
+ never gets `cancel`.
80
+ - A token binds the action, the item and the view: the admin connection, and under a Jelly host the grant's `dev`
81
+ and `mbr`. It is spent at its first presentation, matched or not, and lives 10 minutes. At most 256 are held per
82
+ view and 4,096 in all. They are in memory only, so a restart invalidates every one.
83
+ - A token is never logged or printed, and the agent tier never receives one or redeems one.
84
+ - `request.answer` and `request.answer.preview` take `token`, never with `all`. Under a Jelly host `request.answer`
85
+ requires it. `task.cancel` takes exactly one of `key` (the requester session) and `token` (the person, human
86
+ tier). A preview checks a token without spending it. Every failure is `grant_invalid`.
87
+ - **The person's tasks, and Stop** (spec §8.2, "The person's tasks").
88
+ - `tasks.list` and `tasks.get`, on the admin socket with no session key, Jelly class `view`, read every side of a
89
+ task this device's sessions hold, as `PersonTask`. That is the task view as the owner sees it, the agent's
90
+ `session` name and never its key, and `cancel`, which stops the task through `task.cancel {taskId, token}`.
91
+ - A person-level `conv.sync` carries `task` entries, rendered for each reader. Task changes share the replay
92
+ buffer with conversation changes.
93
+ - Only this device's tasks are listed.
94
+ - **`task.question`** (spec §8.2, "The task view").
95
+ - On the agent tier, from the executing session only. It ties a task in `AUTH_REQUIRED` or `INPUT_REQUIRED` to the
96
+ host's own pending approval or question, `{sessionId, approvalId}`.
97
+ - It is held in memory only and untied by any later status. It is shown only on the person's own reads, never on
98
+ the agent tier, in an event, or to the peer.
99
+ - When the session registered a host session, `sessionId` must be that one (`forbidden`).
100
+ - **The `reach` presence feature** (spec §6.5): a device that enforces the reach choices says so.
101
+ - **`device.list` features** (spec §8.2, "Devices"). Each of the person's own devices lists the presence features last
102
+ verified from it. They are absent when none was seen, which means unknown, not none.
103
+ - **`request.waiting` states `resend` and `outdated`** (spec §8.2, §8.3 "Mixed versions").
104
+ - `resend`: a message request sent from 0.7.1 that its recipient held and nobody answered is shown, not re-signed
105
+ or resent. A request still in flight at the upgrade can't be told from a line and is not shown.
106
+ - `outdated`: a request whose recipient device answered with a `delivered` receipt, which only an older client sends
107
+ on accepting. It must be sent again once they update.
108
+
109
+ ### Fixed
110
+
111
+ - **Presence for people who only talk in conversations** (spec §15.10). The presence audience, and the relation
112
+ `conv.caps` uses, counts accepted members of one conversation the relay sequences: an external dm only once
113
+ activated, never an invited participant. A cross-org contact no longer drops out of presence 30 days after their
114
+ last envelope.
115
+ - **A control op no longer fails with a spurious `conv_conflict`** when maintenance queues a share while it drains. It
116
+ drains until nothing is pending, up to 3 drains. A conversation that refills on every drain is `conv_conflict`, "the
117
+ conversation is busy sending; try again", which can be retried as is (spec §13).
118
+
119
+ ### Security
120
+
121
+ - **The engine always sends the strictest cut asked for a removed device** (spec §16.4, "Device cuts"). This was
122
+ an ordering bug in the daemon's conversation engine, present since 0.8.0, that no 0.8.0 IPC call could reach.
123
+ - Asked to rekey a device with an earlier `until` while a weaker rekey of that device was already queued or in
124
+ flight, the engine returned the queued one and dropped the earlier cut.
125
+ - No IPC method passes `until`, the engine isn't part of the package's exports, and the daemon's automatic rekeys
126
+ after a removal never ask for a stricter cut than the one they queue.
127
+ - Fixed and guarded by tests:
128
+ - a queued rekey stands in only when its cut is at least as strict;
129
+ - two cuts of one device combine as a union in the verifier;
130
+ - a cut covers the device's held entries.
131
+ - **`conv.caps` is no longer a free presence oracle** (spec §7). Answers about unrelated persons count against
132
+ `LIMITS.requestsPerDay` (20) distinct persons per asking person per UTC day, shared by that person's devices.
133
+ Related persons, and persons already asked about that day, are always answered. Past the budget the answer names
134
+ the rest in `unknown`, and `peer_outdated` still applies to anyone named `outdated`.
135
+ - **A token answers only what it was offered for.** A forged, spent, expired, dropped or mismatched token, or one
136
+ from another view or an earlier start, does nothing. Under a Jelly host a call that carries a token needs an action
137
+ grant, even for an agent-tier method.
138
+ - **Action tokens and the question association never leave the person's own clients**, and neither is ever logged.
139
+
140
+ ### Stores
141
+
142
+ **Stores only grow** (spec §8.8). Generation stays 2 and `min_reader` stays 1.
143
+
144
+ - **Daemon store, schema 5** (`packages/daemon/store-schemas/5.json`) adds `reach_grants` and `reach_reviews`: the
145
+ reach choices, apart from `grants`. A daemon before schema 5 would delete a `grants` row it cannot parse, so it
146
+ ignores these tables instead.
147
+ - **Relay database, schema 4** (`packages/relay/store-schemas/4.json`) adds `dir_wake_dms` and an index on `exp`. A
148
+ row counts only with its ticket's `exp`, and goes with it.
149
+ - Action tokens and the question association add no table: they are held in memory only.
150
+
151
+ ### Tests
152
+
153
+ - Each new check has a test that goes red when the check is removed: the token bindings, single use, expiry,
154
+ restart, tier and log; the executor never gets `cancel`; Never in existing conversations; the arm's block, mute and
155
+ reach rules; the expiry purge of armed dms; the question on one device holding both sides of a task.
156
+ - The mixed-version e2e with message requests runs in both realms against `@joe-broadhead/messaging@0.7.1` when
157
+ `MESSAGING_E2E_OLD` is set. The gates skip it.
158
+
159
+ ## [0.8.0] - 2026-11-10
160
+
161
+ **Conversations replace chat and mail.** A dm, a group, a channel and a mail between two agents are now all one
162
+ thing: a conversation, a signed, hash-chained log that the relay sequences and stores encrypted, and that each member
163
+ verifies (spec §6.8, §16). Each sender device encrypts to the conversation with a sender key, so each line is one
164
+ ciphertext, stored once (spec §16.5, §16.6). Tasks and delegation keep their envelopes and work as before.
165
+
166
+ **Upgrade the relay first, then every daemon of a realm together** (docs/distribution.md, "Upgrading from 0.7.1 to
167
+ the conversations release"). Stores only grow: nothing resets, and a rollback keeps the data.
168
+
169
+ ### Breaking
170
+
171
+ - **0.7.1 and 0.8.0 daemons cannot exchange chat, mail or message requests** (spec §8.3, "Mixed versions"). Tasks,
172
+ presence and the directory still work in both directions.
173
+ - **0.8.0 → 0.7.1.** Before it creates a dm, group or channel with a person, adds a person, or sends a message
174
+ request, a 0.8.0 daemon asks the relay (the new `conv.caps` frame, spec §7). If every device of that person with
175
+ current presence lacks the `conversations` feature, the call fails with the new error `peer_outdated`, "<handle>
176
+ needs to update Messaging", and nothing is created or sent, so no contact is left half-open. A person with no
177
+ presence within the envelope TTL gives the relay no evidence, and the call goes ahead.
178
+ - **0.7.1 → 0.8.0.** A 0.8.0 daemon refuses a 0.7.1 chat line, plain mail or message request with a `rejected`
179
+ receipt, code `refused`. 0.7.1 shows only `refused` and its hint "do not retry; tell your person": **to a 0.7.1
180
+ user, `refused` from an updated colleague means "update Messaging".**
181
+ - **Rollout.** The relay first: it opens a 0.7.1 database in place and still serves every frame 0.7.1 uses, so 0.7.1
182
+ daemons keep working through it. Then every daemon of a realm, in one window.
183
+ - **Message requests pending at the upgrade must be sent again.** A request you sent from 0.7.1 that was not yet
184
+ answered is not carried over: it no longer shows in `request.waiting`, so send it again once both sides run 0.8.0.
185
+ A request you received and had not answered shows in `request.list` with state `resend`; it cannot be accepted,
186
+ because 0.7.1 requests carry no sender-signed proof, so ask its sender to send it again. Nothing is re-signed from
187
+ copied text.
188
+ - **IPC methods removed, with no aliases** (spec, "Conversation IPC cut-over"; design §14): `msg.send`, `person.send`,
189
+ `channel.create`, `channel.invite`, `channel.leave`, `channel.remove`, `search`, `conv.subscribe`, `agents.list` and
190
+ `agents.read`. `conv.list`, `conv.read` and `people.get` (now a batch of 1–100 ids) keep their names with the
191
+ conversation shapes. The chat `conv.markRead` is gone from the live API; its conversation form is staged (below).
192
+ - Plain mail is a `conv.send` line. A task is `task.create`, and an answer to a task waiting for input is
193
+ `task.continue`, both on the agent tier; `task.list/get/update/cancel` are unchanged.
194
+ - A daemon refuses plain agent-mail envelopes, including mail held before the upgrade and released after it. The
195
+ rows stay in the store but are not served.
196
+ - **New live methods** (`LIVE_CONVERSATION_METHODS`, spec §8.2):
197
+ - agent and human tier: `conv.list`, `conv.read`, `conv.send`, `conv.react`, `conv.edit`, `conv.delete` (an agent
198
+ edits or deletes only its own lines), `conv.search`, `conv.context`, `conv.access`, `conv.sync`;
199
+ - human tier (admin socket only): `conv.get`, `conv.members`, `conv.threads`, `conv.pin`, `conv.create`,
200
+ `conv.add`, `conv.remove`, `conv.role`, `conv.seat`, `conv.settings`, `conv.meta`, `conv.archive`, `conv.leave`,
201
+ `conv.join`, `conv.accept`, `conv.policy`, `conv.backfill`, `people.get`, `sessions.list`, `request.waiting`.
202
+ - **Staged, not live.** These contracts are in `@messaging/core` with no handler and no tier; a call to one is refused
203
+ (design, "Lane 4 live boundary"):
204
+ - read state and preferences: `conv.markRead`, `conv.markUnread`, `conv.prefs`, `conv.follow`, `conv.save`,
205
+ `conv.saved`, `conv.draft`, `conv.localRetention`;
206
+ - history as context: `context.get`, `context.set`, `context.set.preview`;
207
+ - the channel catalogue: `conv.catalogue`, `conv.publish`;
208
+ - files: `file.get`;
209
+ - typing and activity: `conv.typing`, `session.activity`, `turn.cancel`;
210
+ - scheduled lines: `conv.schedule`, `conv.scheduled`, `conv.unschedule`.
211
+
212
+ Until read state lands, views carry no read positions, unread counts, preferences or follow state, and the filters
213
+ that need them (`conv.list` `unread` and `mentions`, `conv.threads` `unread` and `following`) are refused with
214
+ `invalid_request`, never answered with an empty page. `conv.backfill` verifies the relay history it can fetch and
215
+ answers `complete: false`.
216
+ - **CLI.**
217
+ - `messaging message-status` is removed. `messaging status --help` points to `chat read` and `task`.
218
+ - `messaging send <participant|cid> [text…]` sends a conversation line as the person (`conv.send`). It creates the
219
+ dm when there is none, and prints `Pending <id> in <cid>` or `Waiting for consent.`. `--task` and `--continue`
220
+ send to one agent session, as before.
221
+ - `messaging chat list/read/post/search` take conversation ids (`cid`) and opaque `--cursor`s, not room names.
222
+ `chat search --room` is now `--cid`. `chat read` no longer changes read state, and `--keep-unread` is gone.
223
+ - `messaging listen` follows `conv.sync`. It prints conversation metadata and stream gaps, never the text.
224
+ - **MCP** (`messaging-mcp`, spec §11). Ten tools: `contacts`, `conversations`, `send`, `inbox`, `read`, `search`,
225
+ `context`, `tasks`, `task_update` and `task_cancel` (0.7.1 had eight, without `conversations` and `context`).
226
+ `--channel` is removed: the daemon decides which conversations an agent may read. Addresses are participants and
227
+ `cid`s, not `person:`, `agent:` or `channel:` names.
228
+ - **Package exports** (`@joe-broadhead/messaging/core`; a breaking change for the OpenCode plugin):
229
+ - removed: `AgentSummary`, `ChannelConv`, `ChatLine`, `ChatSeq`, `ChatText`, `ConvCursor`, `ConvEntry`,
230
+ `ConvPlace`, `ConvSummary`, `ConversationRef`, `RoomRef`, `conversationOf`, `conversationOfPeer`,
231
+ `decodeToolCursor`, `encodeToolCursor`, `presentConvEntry`, `presentLine`, `presentMail`, `presentSent` and
232
+ `sealKeyShares` (now `sealKeyShare`, one recipient); in `./core/testing`, `sealKeySharesWith` (now
233
+ `sealKeyShareWith`);
234
+ - changed: `AGENT_METHODS`, `HUMAN_METHODS` and `IPC_METHODS` (the method lists above), `TOOL_VERBS` (now only
235
+ `contacts`, `tasks`, `task_update` and `task_cancel`; all ten tools are `CONVERSATION_TOOL_VERBS`, with
236
+ `ConversationTools`), `AgentTool`,
237
+ `TOOL_DESCRIPTIONS`, `TOOL_REMEDIES`, `ErrorCode` (adds `peer_outdated`), `LIMITS`, `DOMAIN`, and the consent,
238
+ device, Jelly and surface method tables;
239
+ - `./ipc`, `./ipc/testing`, `./testing/jelly-id` and `./zod` export the same names as 0.7.1.
240
+
241
+ ### Conversations
242
+
243
+ - **Dms, groups, channels and threads** (spec §6.8, §16). One log per conversation. Membership, roles and policy
244
+ are ops in the log, so every member computes the same participants at every position. A reply names its root.
245
+ Edits, deletes, reactions and pins are events that name their target. A dm starts with no shared history. Channels
246
+ share their history with new members, and groups and dms rekey when someone is added.
247
+ - **Agents are participants.** A participant is a person or one agent of a person (`person/agent`). Mail between two
248
+ agents is a line in their dm.
249
+ - **An agent can start a dm with another agent.** `conv.send {to: "person/agent"}` with no dm makes the person's
250
+ device sign the create, with only the two agents as participants. The daemon relies on the client's first-send
251
+ confirmation, as 0.7.1 mail did. The agent's lines wait on the sending device until the recipient seats its agent,
252
+ keep the id the send returned, and lapse after 7 days (at most 50 waiting per dm). An agent still cannot start a dm
253
+ with a person, or add or seat anyone.
254
+ - **Consent on invitations.** The recipient person's `ask` decides an agent-dm invitation: `open` seats the agent,
255
+ `request` holds it for the person, and `no` declines it silently. Until it is accepted, the relay serves an invitee
256
+ only the signed ops and digests: no ciphertext, no key shares, and only op pushes (spec §7, "Conversation frames").
257
+ - **Message requests on conversations** (spec §16.11). A first message to a person of another org is a signed,
258
+ portable request: the sender's device signs a descriptor that commits to the sealed opening text. The relay refuses
259
+ an external dm without request admission. Accepting signs a decision, activates the dm once, and imports the
260
+ opening as its first line, under its original author and id. `request.waiting` shows `held` only from an
261
+ authenticated hold, and `pending-delivery` while delivery is unconfirmed. Requests pending at the upgrade must be sent
262
+ again (Breaking, above). Crossed requests make one dm.
263
+ - **Guests.** A person of another org joins a conversation as a guest. A guest cannot invite, cannot change role out
264
+ of guest, and an agent of a guest inherits the guest's role. Keys and content reach a guest only after acceptance
265
+ (and, where history is not shared, after a rekey). These rules are enforced by the log and the relay. The daemon
266
+ does not yet create the inviter's signed preview that a guest's acceptance needs, so inviting a guest end to end
267
+ is not available in 0.8.0.
268
+ - **The inbox is a projection.** `inbox.list/get/ack` and `item.read` return task items and verified lines in accepted
269
+ agent dms addressed to that session. A line's id is its event id, with its `cid`. Acknowledgements belong to the
270
+ participant and survive an agent restart with a new session key. Plain mail stored before the upgrade is not
271
+ returned.
272
+ - **Own-person wakes on lines.** A person's line to their own agent (the other end of the dm, or a mention) wakes a
273
+ sleeping Jelly host, as mail did in 0.7.1 (spec §7, "Wake tickets"; §16.2). The relay takes the author's person from
274
+ the authenticated device, never from the event, and uses the existing ticket, gap and budget rules. Another person's
275
+ line never wakes your host.
276
+ - **`contacts.list`** returns accepted conversations only: `cid`, `kind`, optional `name` and `dmWith`. No transcript,
277
+ read position or unread count.
278
+ - **Blocks.** A blocked person's events are hidden from reads, search, previews and replay. The verified log stays
279
+ intact.
280
+ - **Maintenance.** The daemon retries the conversation outbox, schedules due epoch rekeys, answers new members'
281
+ channel history after a random 0–30 s delay, and expires local message text and search rows after retention, keeping
282
+ digests and ops so the chain stays verifiable. Ops never expire (design §4.4, D2).
283
+ - **Key shares go one per recipient device and are not entries of the log**; the relay serves each only to its
284
+ recipient (design §4.4, D1).
285
+ - **Relay frames** (spec §7): `conv.append`, `conv.share`, `conv.fetch`, `conv.head` and `conv.caps`, with `conv.ok`,
286
+ `conv.shared`, `conv.entries`, `conv.heads`, `conv.capabilities` and `conv.push`; and the request mailbox
287
+ `request.settle`, `request.state` and `request.settled`. The relay sequences each log and checks appends against its
288
+ copy (membership, the author's current device, op heads, epochs, orgs), and members verify everything it serves.
289
+ - **Presence** advertises the `conversations` feature.
290
+
291
+ ### Security
292
+
293
+ - **New entries need the author's current device.** A conversation entry from a device that is no longer the author's
294
+ current binding is held: it keeps the chain but adds no state and no text, and is re-checked when the realm view
295
+ changes. Accepted history keeps its historical verification.
296
+ - **Rejected entries leave bounded evidence**: at most 128 rejected wire entries, and at most 512 junk and held
297
+ alarms, deduplicated across retries and restarts.
298
+ - **A message an author was not allowed to send is quarantined**, not shown.
299
+ - **HTTP response bodies are bounded while they stream** (daemon: Jelly environment, device login, directory home;
300
+ relay: Jelly directory). A response over the limit is cancelled, not buffered.
301
+ - **Relay error text stays out of the daemon's logs**: `relay.disconnected` no longer logs the relay's message.
302
+ - **Consent races.** A block made while a request's directory work is in flight is checked again before the request is
303
+ sent. Request pacing is reserved before any asynchronous work, so concurrent requests cannot pass the limit.
304
+ Answering a held request settles the request and the grant (`always` or `block`) in one transaction.
305
+ - **Agents are bound to their session.** The daemon binds every agent call to a session registered on that
306
+ connection, and rechecks membership and session ownership after rendering. Cursors bind to the query and session.
307
+ Peer text, author names and conversation names in MCP output are fenced, and long text carries a truncation marker.
308
+ - **Interim projection bounds**: projection refuses logs above 10,000 entries and responses above 512 KiB, pending
309
+ load tests.
310
+
311
+ ### Fixed
312
+
313
+ - **A sync that overlaps the daemon's own append is accepted.** A page that repeats an entry the flush path just stored
314
+ failed with "entry does not extend head N"; repeated entries are now checked by seq and dropped, and a different
315
+ entry is `conv_conflict`.
316
+ - **`conv.add` and `conv.remove` no longer fail with "a conversation event is still pending"** while maintenance key
317
+ shares are queued: the control path drains them first.
318
+ - **Removing a person while history shares to them are queued no longer stalls the outbox.** A queued share is dropped
319
+ only when the conversation's own verified log removes its device (`conversation.share.dropped`, ids only). An explicit
320
+ device rekey goes ahead of queued events.
321
+ - **Reads no longer re-verify whole logs.** Verified event signatures are remembered (16,384 successes, never
322
+ failures), which removed the main CPU cost of reads and the timeouts it caused under load.
323
+ - **A line that becomes readable when its key arrives late streams as a new event.**
324
+ - **`messaging listen` fixes its stream position before it says it is ready**, so no event is missed at start.
325
+ - **`messaging daemon stop` waits for the home lock to be released**, and checks that an older daemon has exited.
326
+ - **Contact requests whose delivery is unconfirmed stay visible as pending.**
327
+
328
+ ### Federation foundation (not live)
329
+
330
+ Groundwork for messaging between realms (docs/design/federation.md). **Nothing is wired into the daemon or the relay,
331
+ and nothing in 0.8.0 talks to another realm.**
332
+
333
+ - `@messaging/core` verifies another realm's identity proofs: its directory and full roster histories, including
334
+ complete epoch rotation chains.
335
+ - The daemon has a journal of foreign pins (trust on first use), proof results and human key reviews, with bounded
336
+ evidence that never drops an authenticated removal. Its durable storage is not built yet.
337
+ - The relay has a roster discovery handler (`/.well-known/messaging.json`) that is not installed in its HTTP server.
338
+ - An isolated two-realm e2e fixture.
339
+
340
+ ### Stores
341
+
342
+ **Stores only grow** (spec §8.8). Every migration is additive; the generation stays 2 and `min_reader` stays 1, so a
343
+ 0.7.x messagingd or relay still opens what 0.8.0 writes.
344
+
345
+ - **Daemon store, schema 4** (`packages/daemon/store-schemas/2.json`–`4.json`): schema 2 adds `conv_logs`,
346
+ `conv_entries`, `conv_shares`, `conv_cursors`, `conv_outbox`, `conv_keys`, `conv_alarms` and `conv_fts`; schema 3
347
+ `conv_held`; schema 4 `conv_evidence`. Message requests use rows in the existing `kv` table.
348
+ - **Relay database, schema 3** (`packages/relay/store-schemas/2.json`, `3.json`): schema 2 adds `conv_logs`,
349
+ `conv_entries`, `conv_members` and `conv_shares`; schema 3 `conv_request_decisions`.
350
+ - **The old chat and mail tables stay**, unused. No table is dropped.
351
+ - **Rollback keeps the data**, measured for both stores. A 0.7.1 relay database opens in 0.8.0, and 0.7.1 reopens
352
+ it. A 0.7.1 home opens in the 0.8.0 daemon (schema 4); 0.7.1's `messagingd` then starts on it, keeps the team and
353
+ its identity, logs no error and leaves the conversation tables alone, and 0.8.0 reads the same conversations
354
+ again afterwards. Conversations sent on 0.8.0 are not visible on 0.7.1. Spec §8.8 states both schemas.
355
+
356
+ ### Documentation
357
+
358
+ - `packages/daemon/docs/messagingd.md` and `cli.md` describe conversations; docs/distribution.md has "Upgrading from
359
+ 0.7.1 to the conversations release".
360
+ - The spec names the MCP server's ten tools; tests check the spec against the error codes, stream record kinds,
361
+ `request.waiting` states, tool names and staged methods.
362
+ - A test fails when any CLI help names a command that does not exist.
363
+
364
+ ### Tests
365
+
366
+ - An opt-in mixed-version e2e against a real older release (`MESSAGING_E2E_OLD`, `MESSAGING_E2E_OLD_MACHINES`), in a
367
+ roster realm. The directory realm against a real 0.7.1 is covered by unit tests only.
368
+ - Red-when-removed tests for the agent's own-line edit and delete, the agent's dm creation rule, and the relay's
369
+ shares-only-to-readers filter.
370
+
9
371
  ## [0.7.1] - 2026-11-09
10
372
 
11
373
  **No store change.** A 0.7.0 home and a 0.7.0 relay database open unchanged, and a 0.7.0 messagingd or relay still
@@ -67,8 +429,8 @@ opens what 0.7.1 writes. 0.7.0 was the last store reset: from 0.7.0 on, stores o
67
429
  create` / `team join`). Inbox, transcripts, grants, tasks and notices stay in the old home, which messagingd
68
430
  0.6.0 still opens; a 0.6.0 snapshot does not restore. Reset the managed relay and every home that uses it on the
69
431
  same day: the relay's directory restarts empty.
70
- - Until the first release another person uses, a store change edits the baseline, raises the generation and
71
- resets the stores again, and its release notes say so. From that release on, stores only grow.
432
+ - **0.7.0 is the first release another person uses and the last store reset** (owner, 2026-11-08).
433
+ From this release on, stores only grow through additive migrations; the baseline and generation stay fixed.
72
434
  - Stored inbox peers, task peers and provenance now use the API's `device` and `agent`, not the first shape's
73
435
  `machine` and `session`.
74
436