@joe-broadhead/opencode-messaging 0.3.0 → 0.5.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 +69 -0
- package/README.md +96 -11
- package/assets/icon.svg +1 -0
- package/dist/index.js +722 -252
- package/dist/messaging.js +640 -119
- package/dist/messagingd.js +1844 -379
- package/dist/tui.js +578 -126
- package/docs/architecture.md +32 -1
- package/docs/configuration.md +11 -4
- package/docs/security.md +60 -10
- package/package.json +1251 -8
- package/skills/opencode-messaging/SKILL.md +3 -0
- package/views/section.html +299 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,75 @@ All notable changes to `@joe-broadhead/opencode-messaging`. Versions follow
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-11-07
|
|
10
|
+
|
|
11
|
+
Published on npm (public). The release that carries the `jelly` declaration (hey-jelly#36, A13) and runs
|
|
12
|
+
inside Jelly (A3, A4). Built against `@joe-broadhead/messaging` **0.5.0** (vendored, release commit `0462258`:
|
|
13
|
+
`37723bf`, which includes the Jelly mode lane `10438fe`, plus three fixes), and ships with the 0.5.0 daemon: the five
|
|
14
|
+
platform packages `@joe-broadhead/messaging-bin-{darwin-arm64,darwin-x64,linux-arm64-gnu,linux-x64-gnu,win32-x64}`
|
|
15
|
+
are pinned at exactly 0.5.0, each by the registry's integrity in the lockfile and by its executable's sha256 in
|
|
16
|
+
the launcher, both equal to the release's `SHA256SUMS` and provenance. A 0.4.x daemon is refused at the IPC
|
|
17
|
+
hello (`daemon_mismatch`). Messaging 0.5.0 brings the environment start contract, `cls` required with `view`
|
|
18
|
+
on reads, `notice.ack` as `chat`, previews taking their action's whole input, `request.answer.preview`,
|
|
19
|
+
`daemon.woke`, `person.attached`, `daemon.snapshot`, wake tickets, `ACTION_CLASSES`, and renewal of a
|
|
20
|
+
session ticket by key. Nothing is removed: outside Jelly the plugin behaves as in 0.4.0.
|
|
21
|
+
|
|
22
|
+
- **The `jelly` declaration** in `package.json`, as Jelly's master reads it: title, summary and icon
|
|
23
|
+
(`assets/icon.svg`); the eight tools (`messaging.read` or `messaging.send`, `allow`, read-only where
|
|
24
|
+
they only read); `asks: ["messaging.send"]`; `resources: ["messaging"]`; the `messaging` service
|
|
25
|
+
(`agent.sock`, `admin.sock`, `daemon.health` on the agent socket, wakes, `identity.json` and
|
|
26
|
+
`pairing.json` left out of backups); the section (`views/section.html`: People, Chats, Requests,
|
|
27
|
+
Devices, Your agents) with its eleven read-only methods; twelve service actions with Jelly's
|
|
28
|
+
classes and confirmations (`person.send` chat/session; `conv.markRead`, `notice.ack` chat/never;
|
|
29
|
+
`request.answer`, `grant.set`, `contact.request`, `channel.*`, `device.approve`, `device.remove`
|
|
30
|
+
local/always, with the daemon's previews); notices (Message, Request, Messaging alert); `turns`
|
|
31
|
+
labelled "Messages"; and `requires` `service`, `section`, `actions`, `turns`, `notices` and
|
|
32
|
+
`resources`, never `act`. It is built from the vendored daemon's IPC schemas
|
|
33
|
+
(`npm run jelly:update`), `check:package` validates it with Jelly's rules, and a test holds every
|
|
34
|
+
action's class, every section read (`view`, or agent tier with no grant) and every preview's class
|
|
35
|
+
equal to the daemon's `ACTION_CLASSES`.
|
|
36
|
+
- **Inside Jelly** (Jelly's services accessor is present): the plugin connects to the agent socket
|
|
37
|
+
Jelly names, never starts a daemon, never resolves a home or reads `~/.messaging`, and registers
|
|
38
|
+
exactly the session name Jelly's ticket names, with the ticket. Every OpenCode session of the agent
|
|
39
|
+
shares that one Messaging session. Renewal is messaging 0.5.0's: when the agent's ticket ends, the
|
|
40
|
+
daemon keeps the session and its mail and answers `ticket_invalid`; the plugin re-reads Jelly's
|
|
41
|
+
accessor, registers the same key with the fresh ticket on the same connection, and makes the call
|
|
42
|
+
again. If the accessor still holds the ended ticket, the tool says to restart the agent in Jelly.
|
|
43
|
+
- **Turns through Jelly:** inside Jelly a wake files a `turn` request with Jelly's core plugin (the
|
|
44
|
+
sender person as key, `own`, `trusted` or `guarded` from the daemon's record, a fixed prompt)
|
|
45
|
+
instead of prompting a session; every refusal falls back to notes. A guarded turn keeps the plugin's
|
|
46
|
+
Messaging send and read limit; Jelly's `jelly.peer` mark fences the rest.
|
|
47
|
+
- Inside Jelly there is no console (the `config` RPC refuses) and no setup note.
|
|
48
|
+
- Without the accessor nothing changes.
|
|
49
|
+
- **Not yet:** the plugin does not raise the declared notices itself (Jelly reads the service's
|
|
50
|
+
counts for its badge), and the section page has no search and no card refresh.
|
|
51
|
+
|
|
52
|
+
## [0.4.0] - 2026-11-07
|
|
53
|
+
|
|
54
|
+
Published on npm (public). Built against `@joe-broadhead/messaging` **0.4.0** (vendored, release commit `da50b5d`:
|
|
55
|
+
the release `172a6ba` plus a docs commit), and ships with the 0.4.0 daemon: the five platform packages
|
|
56
|
+
`@joe-broadhead/messaging-bin-{darwin-arm64,darwin-x64,linux-arm64-gnu,linux-x64-gnu,win32-x64}` are pinned at exactly
|
|
57
|
+
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
|
|
58
|
+
release's `SHA256SUMS` and provenance. A 0.3.x daemon is refused at the IPC hello (`daemon_mismatch`).
|
|
59
|
+
|
|
60
|
+
- **Sign in with Jelly** in `/messaging`. In a hosted realm, a device that is no one's yet (the managed
|
|
61
|
+
realm right after "Use Messaging by Jelly" too) is offered **Sign in with Jelly** first and **Link this
|
|
62
|
+
device** second. Sign-in shows Jelly ID's link (with a QR code), the user code and this key's
|
|
63
|
+
fingerprint, with the instruction to approve only if the page shows the same; it opens the browser on
|
|
64
|
+
request (never over SSH), waits with `device.login.status`, can be cancelled (closing the waiting screen
|
|
65
|
+
cancels too), and shows the outcome in fixed text. `login_unsupported` (Jelly ID without contract v1.2,
|
|
66
|
+
production today) is one plain sentence pointing to Link this device, never retried. New console text
|
|
67
|
+
for `login_unsupported` and `login_denied`.
|
|
68
|
+
- **Devices** shows how a Jelly-vouched device signed in (`via`: through the browser, or on a Jelly host).
|
|
69
|
+
A device that signed in through the browser removes another device with a fresh sign-in (`device.login`
|
|
70
|
+
with `remove`); one approved by QR says to do it from Jelly.
|
|
71
|
+
- **`/messaging-status`** shows the binding with `via`, and a sign-in still waiting in the browser.
|
|
72
|
+
- **Tasks name their authors** (core): each text of a task in `tasks` and `read` has `author`
|
|
73
|
+
(`titleAuthor`, `statusAuthor`): `ours`, unfenced and cleaned, or `peer`, fenced. The plugin's
|
|
74
|
+
provenance stopgap of 0.3.0 is gone; its test stays as the guard of core's contract. The skill
|
|
75
|
+
explains the field. Goldens follow.
|
|
76
|
+
- Agent tools never sign in.
|
|
77
|
+
|
|
9
78
|
## [0.3.0] - 2026-11-06
|
|
10
79
|
|
|
11
80
|
The first published release (npm, public). It ships with messaging 0.3.0 and pins the five
|
package/README.md
CHANGED
|
@@ -73,10 +73,10 @@ the executable (without it, the plugin uses the Node fallback):
|
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
75
|
mkdir -p ~/.local/share/opencode-messaging
|
|
76
|
-
tar -xzf joe-broadhead-opencode-messaging-0.
|
|
77
|
-
npm pack @joe-broadhead/messaging-bin-darwin-arm64@0.
|
|
76
|
+
tar -xzf joe-broadhead-opencode-messaging-0.5.0.tgz -C ~/.local/share/opencode-messaging --strip-components=1
|
|
77
|
+
npm pack @joe-broadhead/messaging-bin-darwin-arm64@0.5.0 # your platform's package
|
|
78
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.
|
|
79
|
+
tar -xzf joe-broadhead-messaging-bin-darwin-arm64-0.5.0.tgz \
|
|
80
80
|
-C ~/.local/share/opencode-messaging/node_modules/@joe-broadhead/messaging-bin-darwin-arm64 --strip-components=1
|
|
81
81
|
node ~/.local/share/opencode-messaging/dist/messaging.js --help # the messaging CLI of this version
|
|
82
82
|
```
|
|
@@ -85,7 +85,7 @@ node ~/.local/share/opencode-messaging/dist/messaging.js --help # the messagi
|
|
|
85
85
|
{ "plugins": [{ "package": "/Users/you/.local/share/opencode-messaging" }] }
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
`npm install ./joe-broadhead-opencode-messaging-0.
|
|
88
|
+
`npm install ./joe-broadhead-opencode-messaging-0.5.0.tgz` into a folder OpenCode loads works too: it
|
|
89
89
|
adds the package and this machine's platform package, nothing else.
|
|
90
90
|
|
|
91
91
|
## Install from a local path
|
|
@@ -139,15 +139,20 @@ or join a team (self-host)**; nothing is written until you pick. Then:
|
|
|
139
139
|
thread. A line of yours that was not delivered can be sent again; a failed send offers Try again
|
|
140
140
|
and is filed once. Channels: create, open, invite or remove people, leave; a channel held under a
|
|
141
141
|
local name shows both, for example `#eng-2 (eng)`. Search: every word must match.
|
|
142
|
-
- **Devices:** your devices with their vouch
|
|
143
|
-
|
|
144
|
-
|
|
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.
|
|
145
149
|
- **Notices:** new lines, requests and this device's alarms, with the counts; mark them seen.
|
|
146
150
|
- **Team** (a team realm): prepare to create (shows this machine's id for a relay that lets only
|
|
147
151
|
known machines create teams), create, join, ask to join and finish joining, discard pending keys,
|
|
148
152
|
invite, admit, list and remove members. Pending keys serve one purpose at a time.
|
|
149
153
|
- **This device**, and the command **`/messaging-status`**: who you are, the realm and binding (the
|
|
150
|
-
managed realm by name, "Messaging by Jelly"
|
|
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
|
|
151
156
|
next step.
|
|
152
157
|
|
|
153
158
|
Every error the console shows reads `<code>: <what happened>. Next: <step>.`
|
|
@@ -194,9 +199,10 @@ trusted on first use. In `/messaging`, on a fresh home:
|
|
|
194
199
|
reached, the pinned values stand on their own.
|
|
195
200
|
2. It writes the pinned realm into the home's `config.json` and starts the daemon, which makes this
|
|
196
201
|
computer's keys.
|
|
197
|
-
3. **Devices
|
|
198
|
-
|
|
199
|
-
|
|
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.
|
|
200
206
|
|
|
201
207
|
It is offered only to a home with no identity, no realm, no team and no pending join, and it never
|
|
202
208
|
writes over a realm a `config.json` already has. A home in Messaging by Jelly has no `team.*`: it
|
|
@@ -204,6 +210,85 @@ cannot create or join a self-hosted team later. Agent tools never join a realm.
|
|
|
204
210
|
the block by hand; [configuration.md](docs/configuration.md#messaging-by-jelly) has it, and
|
|
205
211
|
[security.md](docs/security.md#messaging-by-jelly-the-pinned-realm) says how to check the key.
|
|
206
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
|
+
## Running inside Jelly
|
|
233
|
+
|
|
234
|
+
Jelly installs this package as a curated plugin with a service: Jelly's host runs the pinned
|
|
235
|
+
`messagingd` itself, as the host owner's, and hands each agent's engine its place in it. The
|
|
236
|
+
package's `jelly` declaration (in `package.json`) says what Jelly runs and draws. It is built from the
|
|
237
|
+
vendored daemon's own method schemas by `npm run jelly:update`, and `npm run check:package` checks it
|
|
238
|
+
with Jelly's rules.
|
|
239
|
+
|
|
240
|
+
**How the plugin knows.** In a Jelly engine,
|
|
241
|
+
`globalThis[Symbol.for("jelly.services")]?.get("@joe-broadhead/opencode-messaging")` answers
|
|
242
|
+
`{ socket, name, ticket, expiresAt }`: the service's agent socket, the one session name this agent
|
|
243
|
+
may use, and a session ticket that proves it. When that accessor is present, the plugin:
|
|
244
|
+
|
|
245
|
+
- connects to that socket and **never starts a daemon** (`daemon.autostart`, `daemon.bin` and
|
|
246
|
+
`daemon.node` do nothing there), and never resolves a Messaging home, reads `~/.messaging` or looks
|
|
247
|
+
for `identity.json`; the host enrolled the service's device;
|
|
248
|
+
- registers **exactly the ticket's name, with the ticket**, and no wake host. Every OpenCode session
|
|
249
|
+
of the agent (its conversation, and each turn Jelly starts) speaks as that one session, so
|
|
250
|
+
teammates see one address per agent;
|
|
251
|
+
- renews with Jelly's fresh ticket. Jelly keeps the accessor's ticket current (a new one for the
|
|
252
|
+
same agent and name before `expiresAt`). Once the ticket the agent registered with ends, the service
|
|
253
|
+
keeps the session and its mail and answers its calls `ticket_invalid`; the plugin then reads the
|
|
254
|
+
accessor again, registers the same key with the fresh ticket on the same connection (which takes
|
|
255
|
+
the registration back for every connection of the agent), and makes the call again. A refused
|
|
256
|
+
`session.register` is retried the same way. If the accessor still holds the ticket that ended, the
|
|
257
|
+
tool says to restart the agent in Jelly, and the next call reads the accessor again;
|
|
258
|
+
- starts **turns through Jelly**, never by prompting a session: a wake becomes a `turn` request to
|
|
259
|
+
Jelly's core plugin, one per sender person, with that person as the conversation key, the profile
|
|
260
|
+
from the daemon's record (`own` for your own devices and agents, `trusted` only when the daemon
|
|
261
|
+
calls every item trusted, else `guarded`) and a fixed prompt that carries nothing a peer wrote. Jelly
|
|
262
|
+
decides: only while the agent's owner has turned "Messages can start work" on, within Jelly's
|
|
263
|
+
budget. Any refusal (`invalid`, `agent`, `not_declared`, `off`, `budget`, `engine`) gives the items
|
|
264
|
+
notes in the agent's conversation instead. The plugin's per-person wake budget still applies;
|
|
265
|
+
- in a guarded turn keeps its own Messaging limit (reads and sends reach the person the turn is for,
|
|
266
|
+
and nothing waits on a person), and leaves the rest of the fence (no shell, edits only in the
|
|
267
|
+
conversation's folder, no outward tools) to Jelly's own `jelly.peer` mark. It sets no session
|
|
268
|
+
permissions and removes no tools there. A turn Jelly marked that the plugin has no record of may
|
|
269
|
+
not read or send at all.
|
|
270
|
+
|
|
271
|
+
Without the accessor, nothing above applies, and the plugin behaves exactly as in stock OpenCode.
|
|
272
|
+
|
|
273
|
+
**What Jelly draws instead of `/messaging`.** Jelly runs no terminal UI: the person's console is the
|
|
274
|
+
plugin's **section**, a sealed page in Jelly's navigation (`views/section.html`) with People, Chats,
|
|
275
|
+
Requests, Devices and Your agents. It reads through the service's admin socket (each read is a
|
|
276
|
+
declared read-only method; Jelly signs it with class `view`) and changes things only through the
|
|
277
|
+
declared actions, each under the confirmation Jelly draws in its own words:
|
|
278
|
+
|
|
279
|
+
| Action | Class | Confirmation |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| `person.send` | `chat` | once per opening |
|
|
282
|
+
| `conv.markRead`, `notice.ack` | `chat` | none |
|
|
283
|
+
| `request.answer`, `grant.set`, `contact.request`, `device.approve`, `device.remove` | `local` (owner devices) | every time, with the daemon's preview |
|
|
284
|
+
| `channel.create`, `channel.invite`, `channel.remove`, `channel.leave` | `local` | every time |
|
|
285
|
+
|
|
286
|
+
Under Jelly the plugin's `config` RPC refuses, so a TUI part loaded there anyway connects to nothing
|
|
287
|
+
and starts nothing. The section's badge is Jelly's own count from the service (`notice.list`, unread and
|
|
288
|
+
waiting). The declaration names the notices a banner may show (Message, Request, Messaging alert);
|
|
289
|
+
this release does not raise them yet. The service's key files (`identity.json`, `pairing.json`) are
|
|
290
|
+
left out of Jelly's backups.
|
|
291
|
+
|
|
207
292
|
## Docs
|
|
208
293
|
|
|
209
294
|
- [Configuration](docs/configuration.md): every option and its default.
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><title>Messaging</title><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v7a2.5 2.5 0 0 1-2.5 2.5H11l-4.5 4v-4h0A2.5 2.5 0 0 1 4 12.5z"/><path d="M8 8h8"/><path d="M8 11h5"/></svg>
|