@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 +129 -0
- package/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +281 -2
- package/dist/index.js +13699 -0
- package/dist/messaging.js +14510 -0
- package/dist/messagingd.js +27961 -0
- package/dist/rpc.js +5047 -0
- package/dist/third-party-notices.md +81 -0
- package/dist/tui.js +13805 -0
- package/docs/architecture.md +403 -0
- package/docs/configuration.md +397 -0
- package/docs/security.md +587 -0
- package/package.json +97 -4
- package/skills/opencode-messaging/SKILL.md +206 -0
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
|
-
#
|
|
1
|
+
# @joe-broadhead/opencode-messaging
|
|
2
2
|
|
|
3
|
-
|
|
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.
|