@joe-broadhead/opencode-messaging 0.0.0-stage → 0.4.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 ADDED
@@ -0,0 +1,129 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@joe-broadhead/opencode-messaging`. Versions follow
4
+ [Semantic Versioning](https://semver.org). The plugin requires the `messagingd` of its own release
5
+ (the IPC hello); each release names the `@joe-broadhead/messaging` it was built against.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.4.0] - 2026-11-07
10
+
11
+ Published on npm (public). Built against `@joe-broadhead/messaging` **0.4.0** (vendored, release commit `da50b5d`:
12
+ the release `172a6ba` plus a docs commit), and ships with the 0.4.0 daemon: the five platform packages
13
+ `@joe-broadhead/messaging-bin-{darwin-arm64,darwin-x64,linux-arm64-gnu,linux-x64-gnu,win32-x64}` are pinned at exactly
14
+ 0.4.0, each by the registry's integrity in the lockfile and by its executable's sha256 in the launcher, both equal to the
15
+ release's `SHA256SUMS` and provenance. A 0.3.x daemon is refused at the IPC hello (`daemon_mismatch`).
16
+
17
+ - **Sign in with Jelly** in `/messaging`. In a hosted realm, a device that is no one's yet (the managed
18
+ realm right after "Use Messaging by Jelly" too) is offered **Sign in with Jelly** first and **Link this
19
+ device** second. Sign-in shows Jelly ID's link (with a QR code), the user code and this key's
20
+ fingerprint, with the instruction to approve only if the page shows the same; it opens the browser on
21
+ request (never over SSH), waits with `device.login.status`, can be cancelled (closing the waiting screen
22
+ cancels too), and shows the outcome in fixed text. `login_unsupported` (Jelly ID without contract v1.2,
23
+ production today) is one plain sentence pointing to Link this device, never retried. New console text
24
+ for `login_unsupported` and `login_denied`.
25
+ - **Devices** shows how a Jelly-vouched device signed in (`via`: through the browser, or on a Jelly host).
26
+ A device that signed in through the browser removes another device with a fresh sign-in (`device.login`
27
+ with `remove`); one approved by QR says to do it from Jelly.
28
+ - **`/messaging-status`** shows the binding with `via`, and a sign-in still waiting in the browser.
29
+ - **Tasks name their authors** (core): each text of a task in `tasks` and `read` has `author`
30
+ (`titleAuthor`, `statusAuthor`): `ours`, unfenced and cleaned, or `peer`, fenced. The plugin's
31
+ provenance stopgap of 0.3.0 is gone; its test stays as the guard of core's contract. The skill
32
+ explains the field. Goldens follow.
33
+ - Agent tools never sign in.
34
+
35
+ ## [0.3.0] - 2026-11-06
36
+
37
+ The first published release (npm, public). It ships with messaging 0.3.0 and pins the five
38
+ `@joe-broadhead/messaging-bin-*@0.3.0` platform binaries.
39
+
40
+ - **Messaging by Jelly:** the managed deployment's realm is built into the release (relay URL, Jelly ID
41
+ issuer, resource and the directory's epoch key; realm id `I2dIKOtS…`), never fetched. On a fresh home,
42
+ `/messaging` asks first: "Use Messaging by Jelly", then "Create or join a team (self-host)". Using it checks
43
+ `msg.toqanjelly.ai/.well-known/messaging.json` when reachable (a different key, issuer, resource, relay URL
44
+ or realm id is refused and nothing is written), writes the pinned `realm` into `config.json`, starts the
45
+ daemon, and opens Devices to link this computer from a device that has Jelly. Never offered to a home with
46
+ an identity, a realm, a team or a pending join; never writes over a realm. `/messaging-status` names it.
47
+ The hand-written `config.json` block for CLI users is in docs/configuration.md.
48
+ - **Fixes from the 0.3.0 live run:** the `/messaging` TUI takes its home and daemon options from the
49
+ plugin's server part (`config` over the plugin's RPC) and never falls back to `$MESSAGING_HOME` or
50
+ `~/.messaging`; an open room updates delivery states live; "Finish join" on a joined machine says so;
51
+ each wake prompt says which rules hold for that turn; and a task request this side sent is labelled as
52
+ ours, not the peer's (corrected in the plugin until messaging's core is fixed).
53
+
54
+ Built against `@joe-broadhead/messaging` **0.3.0** (vendored, release commit `408e816`), and ships with the 0.3.0
55
+ daemon: the platform packages are pinned at exactly 0.3.0 (each executable's sha256 pinned in the launcher, equal
56
+ to the release's `SHA256SUMS`). A 0.2.x daemon is refused at the IPC hello (`daemon_mismatch`). Messaging 0.3.0's
57
+ new request class is a wire change: a 0.2.x peer refuses message requests and cards.
58
+
59
+ - **Tools:** a person's `can` in `contacts` is what their contact card says (`source: "card"`, `card`: when it
60
+ was signed) or the default; `ask` is `request` in one org by default. `send {to: "me", request: {contact}}`
61
+ suggests a contact to the person (only to `me`; the email is shown in the approval). An agent never sends or
62
+ answers a message request. `request_required`'s next step says so. Goldens follow.
63
+ - **`/messaging` Requests:** message requests (the sender's verified name, email and org, then their text as
64
+ their words; Accept, Decline, Block, with a confirm for Accept and Block; Accept opens the DM) and an agent's
65
+ suggestions (Send request, which composes with the email and passes `suggestion`, or Dismiss), beside the asks.
66
+ - **`/messaging` People:** each person's card (`reach`) in fixed words, with Refresh (`card.get`, once a day);
67
+ in a hosted realm, "Write to someone outside your org": a message request to an exact email after
68
+ `contact.request.preview` finds them, at most 500 characters, behind a confirm, with fixed texts for its
69
+ refusals. A team realm hides it. Your waiting request reads "waiting for them to accept" in the DM.
70
+
71
+ ## [0.2.0] - 2026-11-06 (not published)
72
+
73
+ The people-and-consent release, built against `@joe-broadhead/messaging` **0.2.0** (vendored, release
74
+ commit `344e8fb`; `vendor/messaging.json` names it). It ships with the 0.2.0 daemon: the platform packages
75
+ `@joe-broadhead/messaging-bin-*` are pinned at exactly 0.2.0 (each executable's sha256 pinned in the
76
+ launcher, equal to the release's `SHA256SUMS`), and `dist/messagingd.js` is the 0.2.0 daemon. A 0.1.x daemon
77
+ is refused at the IPC hello (`daemon_mismatch`): stop it with `messaging daemon stop`; the 0.2.0 daemon opens a 0.1 home
78
+ (its store only grows).
79
+
80
+ - **Breaking: the 0.2 IPC, no aliases.** `conv.*`, `person.send`, `search`, `contacts.list`,
81
+ `msg.send`, `item.read`; `agent` and `device` for `session` and `machine`; group addresses are
82
+ `invalid_request` everywhere; `me` is your person's own conversation (`dm:<you>`).
83
+ - **Breaking: trust is a grant.** The `inbound.trusted` option is removed (a configuration that sets
84
+ it is refused). A wake whose every item the daemon marks `consent: "trusted"` (the person's trust
85
+ grant) runs unguarded in its peer session; any other wake guards it again. Tools report the
86
+ daemon's `consent`; a send held for consent says `held` with `expect: "on_consent"`.
87
+ - **Errors in house style:** a tool error's `message` starts with its code and its `remedy` is one
88
+ next step (`Next: …`); the console shows `<code>: <what happened>. Next: <step>.`
89
+ - **Permissions:** a line to `me` does not ask (`messaging.send me allow`, after the send ask).
90
+ - **Peer sessions** of a hosted person (`p_…`) keep their marker, and so their guard.
91
+ - **`/messaging`:** People (search, a person's page, switches and trust through `grant.set` after
92
+ `grant.set.preview`, block, mute, the review list), Requests (allow once, always, decline,
93
+ block), Chats (DMs, Me, channels, search), Devices (link this device by QR and code, approve a
94
+ new device by typing the code it shows, remove this device), Notices; toasts from
95
+ `notice.subscribe` in fixed text; and **`/messaging-status`**.
96
+
97
+ ## [0.1.0] - Unreleased
98
+
99
+ The first release: the OpenCode plugin for Messaging. Built against `@joe-broadhead/messaging`
100
+ 0.1.1 (vendored, from the 0.1 line at `fe1cec8`; `vendor/messaging.json` names the commit).
101
+
102
+ - **Split out of the messaging repository** with its history (`git subtree split` of
103
+ `packages/opencode`). The repository root is the plugin package. It builds against
104
+ `@joe-broadhead/messaging` from a vendored, checksum-pinned tarball, bumped with
105
+ `npm run messaging:bump`; `npm run check:vendor` proves the pin, since npm does not check a
106
+ `file:` tarball's integrity.
107
+ - **Tools**: the eight agent tools of spec §11 in the `messaging` namespace, the same as
108
+ `messaging-mcp`'s, with permissions on every send and every read of a person's conversation.
109
+ - **Inbound**: framed transcript notes, mid-turn notes, wake prompts for idle sessions within a
110
+ per-teammate budget, task notes; peer turns in per-peer sessions under a guard.
111
+ - **Daemon**: connects on first use; starts the `messagingd` it ships when none runs and the machine
112
+ has joined a team; refuses another release's daemon.
113
+ - **`/messaging` TUI**: DMs, channels, people and team admin.
114
+ - **Distribution**: plugin files only (no dependencies but the platform packages, no install
115
+ scripts, no `node_modules`): three bundles, and the daemon and CLI copied byte for byte from
116
+ `@joe-broadhead/messaging`. `npm run smoke:pack` installs it as OpenCode does and unpacks it as Jelly
117
+ does, and runs it end to end. Nothing is published to a registry.
118
+ - **The platform daemon**: the self-contained `messagingd` executables
119
+ `@joe-broadhead/messaging-bin-{darwin-arm64,darwin-x64,linux-arm64-gnu,linux-x64-gnu,win32-x64}@0.1.1` are
120
+ exact optional dependencies (integrity in the lockfile). Autostart runs this platform's executable,
121
+ admitted by version, file type, mode and pinned sha256, with no `BUN_*` variable in its environment;
122
+ else `dist/messagingd.js` on Node 22.15 or later; else a `daemon_unavailable` that names the platform
123
+ and the remedy. `daemon.bin` may now name an executable. Smoke proves the installed and the unpacked
124
+ plugin start the executable, offline; the real-host suite autostarts it for a tool call.
125
+ - **Windows**: the client libraries speak the Windows daemon's named-pipe endpoints (and, from
126
+ messaging 0.1.1, report `daemon_unavailable` rather than `forbidden` while a daemon stops or
127
+ restarts); the plugin skips the POSIX uid and mode checks there, registers `C:\…` directories as
128
+ wake locations, runs `messagingd.exe` from the `win32-x64` package (not code-signed), and the shell
129
+ asks before `messaging.exe` and `messagingd.exe`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joe Broadhead
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,12 @@
1
+ Messaging: the OpenCode plugin (@joe-broadhead/opencode-messaging)
2
+ Copyright (c) 2026 Joe Broadhead. Licensed under the MIT License (see LICENSE).
3
+
4
+ The design was informed by concepts in reason-machines/reason-network (MIT OR Apache-2.0):
5
+ a team session directory, delivery tiers, idempotent sends, staged receipts and framed peer input.
6
+ No code, wire format or names were copied from it.
7
+
8
+ The data model follows the A2A protocol v1 specification (Apache-2.0, Agentic AI Foundation).
9
+
10
+ The LOOKALIKES and WIDE tables in @joe-broadhead/messaging (core, peer-text), bundled into dist/, are derived from Unicode 17.0.0 data
11
+ files (confusables.txt from UTS #39 and EastAsianWidth.txt from the Unicode Character Database),
12
+ Copyright Unicode, Inc., used under the Unicode License v3 (https://www.unicode.org/license.txt).
package/README.md CHANGED
@@ -1,3 +1,282 @@
1
- # Temporary Holding Version
1
+ # @joe-broadhead/opencode-messaging
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The OpenCode plugin for [Messaging](https://github.com/joe-broadhead/messaging) (spec:
4
+ `docs/spec/messaging-v1.md` there, and shipped in `@joe-broadhead/messaging` as `./spec/messaging-v1.md`). With it, an OpenCode session can
5
+ reach teammates and their coding-agent sessions, and delegate A2A tasks to them, through the local
6
+ `messagingd` daemon. It also receives their messages as framed notes, or as a wake prompt when the session is
7
+ idle. Its `/messaging` console is the person's own: people and what they and their agents may do,
8
+ requests waiting for an answer, DMs and channels, devices, and notices.
9
+
10
+ - **Tools** in the `messaging` namespace (Code Mode), the eight agent tools of spec §11, the same
11
+ as `messaging-mcp`'s: `contacts`, `send` (a line to a person, mail or a task to an agent, a post
12
+ to a channel), `inbox`, `read` (an item, or a page of a conversation), `search`, `tasks`,
13
+ `task_update` and `task_cancel`.
14
+ - **Permissions:** the first send to each address (`person/agent`, `person` or `#channel`) asks
15
+ you (a line to `me`, your own conversation, does not), and so does the first read of each
16
+ conversation with a person. "Always" remembers that one
17
+ address or conversation. Configured `allow` and `deny` rules apply; a deny on `bob/*` also blocks
18
+ a line to bob. The agent's
19
+ shell also asks before any `messaging` or `messagingd` command (`.exe` on Windows too) and any command that names the Messaging
20
+ home, and the file tools are denied there. See [docs/security.md](docs/security.md).
21
+ - **Untrusted content:** everything a peer wrote reaches the model inside a
22
+ `<messaging-peer-data-…>` fence, as data, never as instructions.
23
+ - **Inbound:** a framed transcript note for new mail, the same note after the next tool output
24
+ while the session is working, a wake prompt for idle sessions when the sender asks for it (at most 6 per teammate per hour by default), and notes when delegated tasks
25
+ finish or need input.
26
+ - **Daemon:** connects on first use, starts `messagingd` when it is not running and this machine has
27
+ joined a team (optional), and reconnects.
28
+ - **Skill** `opencode-messaging`: tools, safety rules, the task lifecycle, Code Mode recipes and errors.
29
+ - **RPC** `messaging.opencode` (`./rpc`): `wake`, which the daemon calls to load this plugin again
30
+ for wake mail, and `config`, which the plugin's own `/messaging` TUI calls for the home and daemon
31
+ options set in `opencode.json(c)` (OpenCode 2.2 gives the TUI part none).
32
+
33
+ ## Requirements
34
+
35
+ - **OpenCode 2.2.5 or a later 2.2 release.** The package's peer range is `@opencode/plugin
36
+ >=2.2.5 <2.3` (and `@opentui/solid >=0.8.0 <0.9` for the TUI); in any other OpenCode the plugin
37
+ refuses to load with one line naming both versions, and adds nothing.
38
+ - **The daemon, by platform.** npm installs the plugin with its platform package, the
39
+ self-contained `messagingd` executable (no Node, no install script), as an optional dependency:
40
+
41
+ | Platform | Daemon |
42
+ |---|---|
43
+ | macOS arm64, macOS x64 | `@joe-broadhead/messaging-bin-darwin-arm64`, `-darwin-x64` |
44
+ | Linux arm64, Linux x64 (glibc) | `@joe-broadhead/messaging-bin-linux-arm64-gnu`, `-linux-x64-gnu` |
45
+ | Windows x64 | `@joe-broadhead/messaging-bin-win32-x64` (`messagingd.exe`, not code-signed: see [Windows](docs/configuration.md#windows)) |
46
+ | anything else (musl, …) | `dist/messagingd.js` under Node 22.15 or later |
47
+
48
+ The **Node fallback**: without its platform package (another platform, `--omit=optional`, or a
49
+ tarball unpacked alone), the plugin runs the bundled `dist/messagingd.js` on Node 22.15 or later (it
50
+ stores its state with `node:sqlite`). OpenCode itself runs on Bun, so that needs a separate Node on
51
+ `PATH`, or `daemon.node`. See [which daemon runs](docs/configuration.md#which-daemon-runs).
52
+ - **One Messaging daemon per home.** The plugin brings its own (the platform executable, or
53
+ `dist/messagingd.js`, with the `messaging` CLI as `dist/messaging.js`) and starts it only when none
54
+ answers. A daemon of another Messaging version is reported (`daemon_mismatch`), never replaced: stop
55
+ it with `messaging daemon stop`.
56
+
57
+ The package is plugin files only: no dependencies but the platform packages (optional, each at
58
+ exactly the messaging release, one installed per machine), no install scripts, no `node_modules`.
59
+
60
+ Requires a Messaging daemon on the same machine. The machine must
61
+ have created or joined a team (`messaging daemon start`, then `messaging team create` or
62
+ `messaging team join`). Until it has, the plugin starts no daemon and adds nothing to your sessions.
63
+ Messaging tools fail with `no_identity`, and the first one in a session leaves one note with the
64
+ setup commands. A team you join later is picked up by the next tool call, without a reload. See
65
+ [before a team](docs/configuration.md#before-the-machine-has-joined-a-team).
66
+
67
+ ## Install from a tarball
68
+
69
+ `npm run pack:tarball` (or `npm run smoke:pack -- --artifacts <dir>`) builds
70
+ `joe-broadhead-opencode-messaging-<version>.tgz`. Unpack it and point `plugins` at the folder; nothing
71
+ needs to run. Unpack this machine's platform package under its `node_modules` too, so the plugin finds
72
+ the executable (without it, the plugin uses the Node fallback):
73
+
74
+ ```bash
75
+ mkdir -p ~/.local/share/opencode-messaging
76
+ tar -xzf joe-broadhead-opencode-messaging-0.4.0.tgz -C ~/.local/share/opencode-messaging --strip-components=1
77
+ npm pack @joe-broadhead/messaging-bin-darwin-arm64@0.4.0 # your platform's package
78
+ mkdir -p ~/.local/share/opencode-messaging/node_modules/@joe-broadhead/messaging-bin-darwin-arm64
79
+ tar -xzf joe-broadhead-messaging-bin-darwin-arm64-0.4.0.tgz \
80
+ -C ~/.local/share/opencode-messaging/node_modules/@joe-broadhead/messaging-bin-darwin-arm64 --strip-components=1
81
+ node ~/.local/share/opencode-messaging/dist/messaging.js --help # the messaging CLI of this version
82
+ ```
83
+
84
+ ```jsonc
85
+ { "plugins": [{ "package": "/Users/you/.local/share/opencode-messaging" }] }
86
+ ```
87
+
88
+ `npm install ./joe-broadhead-opencode-messaging-0.4.0.tgz` into a folder OpenCode loads works too: it
89
+ adds the package and this machine's platform package, nothing else.
90
+
91
+ ## Install from a local path
92
+
93
+ Compile this repository, then add its directory to `plugins` in `opencode.json(c)`. Use your
94
+ global `~/.config/opencode/opencode.json` to enable it everywhere, or a project config for one
95
+ repository.
96
+
97
+ ```bash
98
+ cd ~/opencode-messaging && npm ci && npm run compile
99
+ ```
100
+
101
+ The `messaging` command is `dist/messaging.js` (the CLI of the vendored `@joe-broadhead/messaging`,
102
+ copied as is); `node dist/messaging.js --help`, or install `@joe-broadhead/messaging`'s tarball (see
103
+ the messaging repository) to put `messaging` on your `PATH`.
104
+
105
+ ```jsonc
106
+ {
107
+ "$schema": "https://opencode.ai/config.json",
108
+ "plugins": [
109
+ {
110
+ "package": "/Users/you/opencode-messaging",
111
+ "options": {
112
+ // all optional; see docs/configuration.md
113
+ "daemon": { "autostart": true },
114
+ "inbound": { "wake": true, "notify": true, "wakeBudget": { "perPeer": 6, "windowMs": 3600000 } },
115
+ },
116
+ },
117
+ ],
118
+ }
119
+ ```
120
+
121
+ OpenCode reloads plugins when the config is saved. The same package's TUI part (`./tui`) loads
122
+ with it. In the terminal, `/messaging` is the human console. On a home with nothing in it yet, it
123
+ first asks how to set up: **Use Messaging by Jelly** (see [below](#messaging-by-jelly)) or **Create
124
+ or join a team (self-host)**; nothing is written until you pick. Then:
125
+
126
+ - **People:** find someone (name or handle), see their org, vouch and devices, and set what they
127
+ and their agents may do: chat (open, muted, blocked), "agents may message me", "may ask my agents"
128
+ (open, ask first, no), trust for 30 days, block and mute. Each switch shows its value, or the
129
+ default. A change shows the daemon's own sentence for it and asks first. Permissions that lapsed
130
+ with a removed device are offered again. Each person's contact card (what they accept) shows with
131
+ Refresh. In a hosted realm, "Write to someone outside your org" sends a message request to an exact
132
+ email (at most 500 characters); it waits until they accept.
133
+ - **Requests:** an agent asking one of yours, waiting for you: Allow once, Always (opens their
134
+ asks), Decline, or Block, for one request or all from that person. A message request from someone
135
+ outside your org (their verified name, email and org, then their text): Accept, Decline or Block.
136
+ A suggestion from one of your agents to contact someone: Send request (you write it) or Dismiss.
137
+ - **Chats:** your DMs with unread counts, **Me** (what your agents tell you), channels and search.
138
+ Open a DM to read it (which marks it read on this device), page back, write a line, or reply in a
139
+ thread. A line of yours that was not delivered can be sent again; a failed send offers Try again
140
+ and is filed once. Channels: create, open, invite or remove people, leave; a channel held under a
141
+ local name shows both, for example `#eng-2 (eng)`. Search: every word must match.
142
+ - **Devices:** your devices with their vouch, and for a Jelly-vouched one how it signed in (through
143
+ the browser, or on a Jelly host). In a hosted realm, a device that is no one's yet is offered
144
+ **Sign in with Jelly** first ([below](#sign-in-with-jelly)) and **Link this device** second (a QR
145
+ code and a six-digit code, approved from a device that already has Jelly). A bound device offers
146
+ **Approve a new device** (paste its link, then type the code the new device shows; the approving
147
+ device never shows it) and Remove this device. A device that signed in through the browser removes
148
+ another of yours with a fresh sign-in.
149
+ - **Notices:** new lines, requests and this device's alarms, with the counts; mark them seen.
150
+ - **Team** (a team realm): prepare to create (shows this machine's id for a relay that lets only
151
+ known machines create teams), create, join, ask to join and finish joining, discard pending keys,
152
+ invite, admit, list and remove members. Pending keys serve one purpose at a time.
153
+ - **This device**, and the command **`/messaging-status`**: who you are, the realm and binding (the
154
+ managed realm by name, "Messaging by Jelly"; how this device signed in; a sign-in still waiting in
155
+ the browser), the relay, the directory, and any alarm with its
156
+ next step.
157
+
158
+ Every error the console shows reads `<code>: <what happened>. Next: <step>.`
159
+
160
+ A chat line never wakes anyone's coding agent. Your own lines show whether they arrived (`sent`,
161
+ `delivered`, or not delivered to whom), updated while the room is open, and the daemon's notices about channel changes show as
162
+ system lines. Once this device has an identity, the TUI keeps one connection to the daemon and
163
+ follows its notices: a new line from someone else or from your own agents, a request waiting for
164
+ you, or an alarm shows as a toast and, when the terminal is in the background, a notification.
165
+ Toasts carry fixed text and a cleaned name, never a line. There is no footer badge. The connection never starts the daemon
166
+ and reconnects by itself when the daemon restarts. The console registers no session. It starts the
167
+ daemon only to create, join or ask to join a team once you have typed everything it needs, to use
168
+ Messaging by Jelly once you have confirmed it, and for
169
+ other actions only on a machine that has joined a team; with `daemon.autostart: false` it never
170
+ starts it. Invite tickets stay in their own dialog. They are copied to the clipboard only when you
171
+ choose that, and they are never written into a session. A device link and its code show only in
172
+ their own dialog on the new device.
173
+
174
+ To check that the plugin is loaded, ask the agent to run `tools.messaging.contacts({})`. Its `me`
175
+ holds this session's address, for example `joe/messaging@laptop`; before the machine has joined a
176
+ team, the call fails with a `no_identity` error.
177
+
178
+ Optional permission rules (see [docs/security.md](docs/security.md)):
179
+
180
+ ```jsonc
181
+ {
182
+ "permissions": [
183
+ { "action": "messaging.send", "resource": "alice/*", "effect": "allow" },
184
+ { "action": "messaging.send", "resource": "mallory/*", "effect": "deny" },
185
+ ],
186
+ }
187
+ ```
188
+
189
+ ## Messaging by Jelly
190
+
191
+ Messaging by Jelly is the managed Messaging deployment (`msg.toqanjelly.ai`), where people sign in
192
+ with Jelly. Its realm is built into this release: the relay URL, the Jelly ID issuer, the resource
193
+ and the directory's epoch key are pinned in the plugin (`src/domain/managed.ts`), never fetched and
194
+ trusted on first use. In `/messaging`, on a fresh home:
195
+
196
+ 1. **Use Messaging by Jelly**, and confirm. The console reads
197
+ `https://msg.toqanjelly.ai/.well-known/messaging.json` when it can; if it describes a different key,
198
+ issuer, resource, relay URL or realm id, it refuses, says so, and writes nothing. If it cannot be
199
+ reached, the pinned values stand on their own.
200
+ 2. It writes the pinned realm into the home's `config.json` and starts the daemon, which makes this
201
+ computer's keys.
202
+ 3. **Devices** opens next: **Sign in with Jelly** approves this computer in your browser; **Link this
203
+ device** shows a QR code and a six-digit code to approve from a device that already has Jelly.
204
+ Once it is approved, this computer is one of your devices. A hosted person's first device is
205
+ always Jelly-rooted: signed in through Jelly ID, or enrolled by Jelly.
206
+
207
+ It is offered only to a home with no identity, no realm, no team and no pending join, and it never
208
+ writes over a realm a `config.json` already has. A home in Messaging by Jelly has no `team.*`: it
209
+ cannot create or join a self-hosted team later. Agent tools never join a realm. Without the TUI, write
210
+ the block by hand; [configuration.md](docs/configuration.md#messaging-by-jelly) has it, and
211
+ [security.md](docs/security.md#messaging-by-jelly-the-pinned-realm) says how to check the key.
212
+
213
+ ## Sign in with Jelly
214
+
215
+ In a hosted realm, a device signs in through the browser at Jelly ID, without another device
216
+ (messaging 0.4.0's `device.login`). In `/messaging`, Devices, **Sign in with Jelly**:
217
+
218
+ 1. Choose to open Jelly ID in your browser, or to show the link (with a QR code, for another device),
219
+ copied if you ask. Over SSH the console opens nothing and says to open the link elsewhere.
220
+ 2. The console shows Jelly ID's link, a user code such as `WDJBM-XQ7KP`, and this device's key
221
+ fingerprint such as `3fa9-1c07-e2b4`. Sign in at Jelly ID; approve only if the page shows the same
222
+ code and the same key, else deny it.
223
+ 3. The console waits for the outcome and closes by itself: signed in, denied, expired or cancelled.
224
+ From the waiting screen you can show the link again or cancel; closing it cancels too.
225
+
226
+ A device that signed in this way is Jelly-rooted (vouched by Jelly, `via: browser`). It can remove
227
+ another of your devices with a fresh sign-in (Devices, pick the device). A device approved by QR
228
+ cannot. **Until Jelly ID offers browser sign-in** (Jelly's contract v1.2), the daemon answers
229
+ `login_unsupported`, and the console says so in one sentence and points to Link this device; it never
230
+ retries. Agent tools never sign in: `device.login` is on the daemon's admin socket only.
231
+
232
+ ## Docs
233
+
234
+ - [Configuration](docs/configuration.md): every option and its default.
235
+ - [Security](docs/security.md): permission actions and resources, framing, and what is never logged.
236
+ - [Architecture](docs/architecture.md): layers, the daemon link, registration and inbound flows.
237
+
238
+ ## Development
239
+
240
+ This repository is the plugin package itself (its root is the package). It builds against
241
+ `@joe-broadhead/messaging` (the client libraries, the daemon, the CLI and the spec), which comes from a
242
+ vendored tarball, `vendor/messaging-<version>.tgz`, pinned in `package-lock.json` until that package is
243
+ published. See [AGENTS.md](AGENTS.md) for the rules.
244
+
245
+ ```bash
246
+ npm ci
247
+ npm run gates # every gate, in order: the merge gate
248
+ npm run compile # tsc -b into build/, src/tui.tsx with Babel, then scripts/bundle.mjs into dist/
249
+ npm test # unit, fake-host and fake-daemon suites
250
+
251
+ # Real-host suite (embedded OpenCode 2.2.5, Node >= 24, separate install)
252
+ npm ci --prefix test/host
253
+ PATH=/path/to/node24/bin:$PATH npm run test:host
254
+
255
+ # A new messaging build: pack a messaging checkout, replace the vendored tarball, update the lockfile
256
+ npm run messaging:bump -- ~/messaging
257
+ ```
258
+
259
+ The real-host suite has its own `package.json` and lockfile under `test/host/`. `npm test`
260
+ excludes it.
261
+
262
+ The package is plugin files only: no dependencies but the platform packages, no install scripts, no
263
+ `node_modules`. `npm ci` installs this machine's platform package, which the unit and real-host suites
264
+ run (`test/daemon-resolve.test.ts`, `test/host/binary.test.ts`). `npm run
265
+ compile` runs `tsc -b` into `build/`, compiles `src/tui.tsx` with Babel (`babel-preset-solid` for
266
+ `@opentui/solid`, because OpenCode compiles TSX only outside `node_modules`) to `build/tui.js`, and
267
+ then `scripts/bundle.mjs` bundles `index.js`, `rpc.js` and `tui.js`, with what they use of
268
+ `@joe-broadhead/messaging` and `uqr`, into one file each in `dist/`, and copies the daemon and CLI
269
+ from `@joe-broadhead/messaging` byte for byte as `messagingd.js` and `messaging.js`. The host
270
+ supplies `@opencode/plugin`, `@opentui/solid` and `solid-js` (optional peers). `npm run
271
+ check:package` checks what `npm pack` would ship; `npm run check:vendor` that the vendored messaging
272
+ package is the pinned one. `npm run smoke:pack` imports the installed `./tui` entry under Bun, the
273
+ TUI's runtime, so it needs Bun 1.3 or later; it installs the platform package offline from the
274
+ npm cache (`MESSAGING_BIN_TARBALLS=<dir>` adds the release's platform tarballs to the cache first)
275
+ and checks that the installed and the unpacked plugin both start its executable. (There is no `build` script on purpose: a plugin
276
+ installed from git must not have one.)
277
+
278
+ After `npm run compile`, or after installing a new tarball, restart OpenCode, both the server and the
279
+ TUI. A reload also starts the wake budget again.
280
+
281
+ `MESSAGING_PLUGIN_DEBUG=1` writes to stderr. When the server runs inside the TUI process, that paints
282
+ over the terminal UI, so set it only for `opencode serve` or the host suite.