@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.
- package/CHANGELOG.md +364 -2
- package/dist/bin/messaging-mcp.js +5498 -4338
- package/dist/bin/messaging-relay.js +8626 -5540
- package/dist/bin/messaging.js +4492 -3158
- package/dist/bin/messagingd.js +19672 -12412
- package/dist/lib/modules/core/agent/operations.js +19 -327
- package/dist/lib/modules/core/agent/present.js +20 -55
- package/dist/lib/modules/core/agent-tools.js +35 -139
- package/dist/lib/modules/core/consent.js +54 -14
- package/dist/lib/modules/core/contact-card.js +3 -3
- package/dist/lib/modules/core/conversation/event.js +29 -4
- package/dist/lib/modules/core/conversation/guest-invitation.js +42 -0
- package/dist/lib/modules/core/conversation/log.js +287 -65
- package/dist/lib/modules/core/conversation/request-log.js +158 -0
- package/dist/lib/modules/core/conversation/request-seal.js +41 -0
- package/dist/lib/modules/core/conversation/requests.js +87 -0
- package/dist/lib/modules/core/conversation/seal.js +90 -37
- package/dist/lib/modules/core/conversation-agent-tools.js +69 -0
- package/dist/lib/modules/core/federation/directory-proof.js +83 -0
- package/dist/lib/modules/core/federation/proof-state.js +167 -0
- package/dist/lib/modules/core/federation/roster-proof.js +74 -0
- package/dist/lib/modules/core/index.js +9 -4
- package/dist/lib/modules/core/jelly-host.js +8 -5
- package/dist/lib/modules/core/schema/common.js +33 -5
- package/dist/lib/modules/core/schema/consent.js +23 -5
- package/dist/lib/modules/core/schema/contact-request.js +22 -0
- package/dist/lib/modules/core/schema/conv-frames.js +101 -0
- package/dist/lib/modules/core/schema/conversation.js +67 -33
- package/dist/lib/modules/core/schema/directory-frames.js +8 -3
- package/dist/lib/modules/core/schema/envelope.js +8 -24
- package/dist/lib/modules/core/schema/errors.js +7 -5
- package/dist/lib/modules/core/schema/guest-invitation.js +13 -0
- package/dist/lib/modules/core/schema/index.js +5 -0
- package/dist/lib/modules/core/schema/ipc/consent.js +97 -18
- package/dist/lib/modules/core/schema/ipc/conversation-views.js +87 -0
- package/dist/lib/modules/core/schema/ipc/conversations.js +132 -0
- package/dist/lib/modules/core/schema/ipc/device.js +9 -0
- package/dist/lib/modules/core/schema/ipc/jelly.js +17 -45
- package/dist/lib/modules/core/schema/ipc/shapes.js +74 -73
- package/dist/lib/modules/core/schema/ipc/surfaces.js +12 -195
- package/dist/lib/modules/core/schema/ipc/tasks.js +31 -0
- package/dist/lib/modules/core/schema/ipc.js +68 -46
- package/dist/lib/modules/core/testing.js +1 -1
- package/dist/lib/modules/core/version.js +1 -1
- package/dist/lib/modules/ipc/client.js +10 -2
- package/dist/lib/modules/ipc/server.js +14 -5
- package/dist/lib/modules/ipc/testing.js +13 -23
- package/dist/spec/messaging-v1.md +854 -445
- package/dist/third-party-notices.md +1 -1
- package/dist/third-party.json +1 -1
- package/dist/types/core/agent/operations.d.ts +5 -52
- package/dist/types/core/agent/present.d.ts +6 -21
- package/dist/types/core/agent-tools.d.ts +295 -442
- package/dist/types/core/consent.d.ts +30 -7
- package/dist/types/core/contact-card.d.ts +3 -3
- package/dist/types/core/conversation/event.d.ts +6 -6
- package/dist/types/core/conversation/guest-invitation.d.ts +8 -0
- package/dist/types/core/conversation/log.d.ts +28 -13
- package/dist/types/core/conversation/request-log.d.ts +24 -0
- package/dist/types/core/conversation/request-seal.d.ts +6 -0
- package/dist/types/core/conversation/requests.d.ts +14 -0
- package/dist/types/core/conversation/seal.d.ts +32 -9
- package/dist/types/core/conversation-agent-tools.d.ts +939 -0
- package/dist/types/core/federation/directory-proof.d.ts +8 -0
- package/dist/types/core/federation/proof-state.d.ts +114 -0
- package/dist/types/core/federation/roster-proof.d.ts +7 -0
- package/dist/types/core/index.d.ts +11 -6
- package/dist/types/core/jelly-host.d.ts +1 -1
- package/dist/types/core/schema/common.d.ts +33 -6
- package/dist/types/core/schema/consent.d.ts +29 -8
- package/dist/types/core/schema/contact-request.d.ts +264 -0
- package/dist/types/core/schema/conv-frames.d.ts +1204 -0
- package/dist/types/core/schema/conversation.d.ts +1410 -264
- package/dist/types/core/schema/directory-frames.d.ts +8 -3
- package/dist/types/core/schema/envelope.d.ts +85 -130
- package/dist/types/core/schema/errors.d.ts +5 -1
- package/dist/types/core/schema/guest-invitation.d.ts +51 -0
- package/dist/types/core/schema/index.d.ts +5 -0
- package/dist/types/core/schema/ipc/consent.d.ts +464 -62
- package/dist/types/core/schema/ipc/conversation-views.d.ts +1199 -0
- package/dist/types/core/schema/ipc/conversations.d.ts +4355 -0
- package/dist/types/core/schema/ipc/device.d.ts +10 -0
- package/dist/types/core/schema/ipc/jelly.d.ts +16 -111
- package/dist/types/core/schema/ipc/shapes.d.ts +211 -95
- package/dist/types/core/schema/ipc/surfaces.d.ts +26 -427
- package/dist/types/core/schema/ipc/tasks.d.ts +210 -0
- package/dist/types/core/schema/ipc.d.ts +3859 -890
- package/dist/types/core/schema/relay.d.ts +12 -4
- package/dist/types/core/testing.d.ts +1 -1
- package/dist/types/core/version.d.ts +1 -1
- package/dist/types/ipc/client.d.ts +4 -1
- package/dist/types/ipc/server.d.ts +16 -1
- package/dist/types/ipc/testing.d.ts +21 -8
- 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
|
-
-
|
|
71
|
-
|
|
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
|
|