@joe-broadhead/messaging 0.0.0-stage → 0.7.1
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 +220 -0
- package/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +36 -2
- package/dist/bin/messaging-mcp.js +26524 -0
- package/dist/bin/messaging-relay.js +19768 -0
- package/dist/bin/messaging.js +15465 -0
- package/dist/bin/messagingd.js +29449 -0
- package/dist/lib/core-testing.js +1 -0
- package/dist/lib/core.js +1 -0
- package/dist/lib/ipc-testing.js +1 -0
- package/dist/lib/ipc.js +1 -0
- package/dist/lib/modules/core/address.js +42 -0
- package/dist/lib/modules/core/agent/operations.js +595 -0
- package/dist/lib/modules/core/agent/present.js +244 -0
- package/dist/lib/modules/core/agent/text.js +79 -0
- package/dist/lib/modules/core/agent-tools.js +477 -0
- package/dist/lib/modules/core/b64u.js +20 -0
- package/dist/lib/modules/core/cards.js +55 -0
- package/dist/lib/modules/core/check.js +20 -0
- package/dist/lib/modules/core/consent.js +199 -0
- package/dist/lib/modules/core/contact-card.js +74 -0
- package/dist/lib/modules/core/conversation/context.js +80 -0
- package/dist/lib/modules/core/conversation/event.js +100 -0
- package/dist/lib/modules/core/conversation/ids.js +38 -0
- package/dist/lib/modules/core/conversation/link.js +35 -0
- package/dist/lib/modules/core/conversation/log.js +766 -0
- package/dist/lib/modules/core/conversation/ratchet.js +243 -0
- package/dist/lib/modules/core/conversation/seal.js +198 -0
- package/dist/lib/modules/core/delegation.js +174 -0
- package/dist/lib/modules/core/directory/device-log.js +258 -0
- package/dist/lib/modules/core/directory/epoch.js +103 -0
- package/dist/lib/modules/core/directory/handle.js +29 -0
- package/dist/lib/modules/core/directory/jelly-device.js +204 -0
- package/dist/lib/modules/core/directory/jelly-issuer.js +12 -0
- package/dist/lib/modules/core/directory/jelly-token.js +305 -0
- package/dist/lib/modules/core/directory/keys-statement.js +43 -0
- package/dist/lib/modules/core/directory/link.js +81 -0
- package/dist/lib/modules/core/directory/merkle-log.js +250 -0
- package/dist/lib/modules/core/directory/person-view.js +197 -0
- package/dist/lib/modules/core/directory/person.js +40 -0
- package/dist/lib/modules/core/directory/realm.js +59 -0
- package/dist/lib/modules/core/directory/smt.js +266 -0
- package/dist/lib/modules/core/envelope.js +320 -0
- package/dist/lib/modules/core/hash.js +5 -0
- package/dist/lib/modules/core/head.js +27 -0
- package/dist/lib/modules/core/identity.js +71 -0
- package/dist/lib/modules/core/index.js +53 -0
- package/dist/lib/modules/core/jcs.js +96 -0
- package/dist/lib/modules/core/jelly-host-double.js +35 -0
- package/dist/lib/modules/core/jelly-host.js +211 -0
- package/dist/lib/modules/core/keys.js +79 -0
- package/dist/lib/modules/core/peer-text.js +563 -0
- package/dist/lib/modules/core/realm.js +1 -0
- package/dist/lib/modules/core/roster.js +454 -0
- package/dist/lib/modules/core/schema/a2a.js +85 -0
- package/dist/lib/modules/core/schema/common.js +310 -0
- package/dist/lib/modules/core/schema/consent.js +120 -0
- package/dist/lib/modules/core/schema/conversation.js +292 -0
- package/dist/lib/modules/core/schema/delegation.js +58 -0
- package/dist/lib/modules/core/schema/directory-frames.js +270 -0
- package/dist/lib/modules/core/schema/directory.js +169 -0
- package/dist/lib/modules/core/schema/envelope.js +211 -0
- package/dist/lib/modules/core/schema/errors.js +104 -0
- package/dist/lib/modules/core/schema/index.js +16 -0
- package/dist/lib/modules/core/schema/ipc/consent.js +181 -0
- package/dist/lib/modules/core/schema/ipc/device.js +276 -0
- package/dist/lib/modules/core/schema/ipc/jelly.js +189 -0
- package/dist/lib/modules/core/schema/ipc/shapes.js +185 -0
- package/dist/lib/modules/core/schema/ipc/surfaces.js +223 -0
- package/dist/lib/modules/core/schema/ipc.js +451 -0
- package/dist/lib/modules/core/schema/jelly.js +122 -0
- package/dist/lib/modules/core/schema/realm.js +27 -0
- package/dist/lib/modules/core/schema/relay.js +112 -0
- package/dist/lib/modules/core/schema/roster.js +126 -0
- package/dist/lib/modules/core/sign.js +43 -0
- package/dist/lib/modules/core/task.js +37 -0
- package/dist/lib/modules/core/testing.js +21 -0
- package/dist/lib/modules/core/ulid.js +36 -0
- package/dist/lib/modules/core/version.js +6 -0
- package/dist/lib/modules/core/wire.js +255 -0
- package/dist/lib/modules/ipc/client.js +289 -0
- package/dist/lib/modules/ipc/framing.js +33 -0
- package/dist/lib/modules/ipc/index.js +6 -0
- package/dist/lib/modules/ipc/paths.js +102 -0
- package/dist/lib/modules/ipc/pipes.js +65 -0
- package/dist/lib/modules/ipc/rpc.js +8 -0
- package/dist/lib/modules/ipc/server.js +317 -0
- package/dist/lib/modules/ipc/testing.js +145 -0
- package/dist/lib/modules/ipc/trust.js +166 -0
- package/dist/lib/modules/ipc/windows-acl.js +275 -0
- package/dist/lib/modules/vendor/zod/index.js +7 -0
- package/dist/lib/modules/vendor/zod/v4/classic/checks.js +1 -0
- package/dist/lib/modules/vendor/zod/v4/classic/coerce.js +17 -0
- package/dist/lib/modules/vendor/zod/v4/classic/compat.js +31 -0
- package/dist/lib/modules/vendor/zod/v4/classic/deep-partial.js +13 -0
- package/dist/lib/modules/vendor/zod/v4/classic/errors.js +57 -0
- package/dist/lib/modules/vendor/zod/v4/classic/external.js +17 -0
- package/dist/lib/modules/vendor/zod/v4/classic/from-json-schema.js +881 -0
- package/dist/lib/modules/vendor/zod/v4/classic/in-out.js +36 -0
- package/dist/lib/modules/vendor/zod/v4/classic/iso.js +15 -0
- package/dist/lib/modules/vendor/zod/v4/classic/parse.js +16 -0
- package/dist/lib/modules/vendor/zod/v4/classic/schemas.js +1535 -0
- package/dist/lib/modules/vendor/zod/v4/core/api.js +1107 -0
- package/dist/lib/modules/vendor/zod/v4/core/checks.js +516 -0
- package/dist/lib/modules/vendor/zod/v4/core/compile.js +1866 -0
- package/dist/lib/modules/vendor/zod/v4/core/core.js +141 -0
- package/dist/lib/modules/vendor/zod/v4/core/doc.js +38 -0
- package/dist/lib/modules/vendor/zod/v4/core/errors.js +267 -0
- package/dist/lib/modules/vendor/zod/v4/core/index.js +19 -0
- package/dist/lib/modules/vendor/zod/v4/core/json-schema-generator.js +99 -0
- package/dist/lib/modules/vendor/zod/v4/core/json-schema-processors.js +843 -0
- package/dist/lib/modules/vendor/zod/v4/core/json-schema.js +1 -0
- package/dist/lib/modules/vendor/zod/v4/core/memoizer.js +311 -0
- package/dist/lib/modules/vendor/zod/v4/core/parse.js +175 -0
- package/dist/lib/modules/vendor/zod/v4/core/regexes.js +156 -0
- package/dist/lib/modules/vendor/zod/v4/core/registries.js +51 -0
- package/dist/lib/modules/vendor/zod/v4/core/schemas.js +2636 -0
- package/dist/lib/modules/vendor/zod/v4/core/to-json-schema.js +651 -0
- package/dist/lib/modules/vendor/zod/v4/core/util.js +873 -0
- package/dist/lib/modules/vendor/zod/v4/core/versions.js +5 -0
- package/dist/lib/modules/vendor/zod/v4/core/visit.js +169 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ar.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/az.js +110 -0
- package/dist/lib/modules/vendor/zod/v4/locales/be.js +168 -0
- package/dist/lib/modules/vendor/zod/v4/locales/bg.js +125 -0
- package/dist/lib/modules/vendor/zod/v4/locales/bn.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ca.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ckb.js +133 -0
- package/dist/lib/modules/vendor/zod/v4/locales/cs.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/da.js +120 -0
- package/dist/lib/modules/vendor/zod/v4/locales/de.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/el.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/en.js +125 -0
- package/dist/lib/modules/vendor/zod/v4/locales/eo.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/es.js +136 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fa.js +119 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fi.js +117 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fr-CA.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fr.js +129 -0
- package/dist/lib/modules/vendor/zod/v4/locales/gu.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/he.js +221 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hi.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hr.js +126 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hu.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hy.js +158 -0
- package/dist/lib/modules/vendor/zod/v4/locales/id.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/index.js +63 -0
- package/dist/lib/modules/vendor/zod/v4/locales/is.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/it.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ja.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ka.js +117 -0
- package/dist/lib/modules/vendor/zod/v4/locales/kh.js +5 -0
- package/dist/lib/modules/vendor/zod/v4/locales/km.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/kn.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ko.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/lt.js +207 -0
- package/dist/lib/modules/vendor/zod/v4/locales/mk.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ms.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ne.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/nl.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/nn.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/no.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ota.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/pl.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ps.js +119 -0
- package/dist/lib/modules/vendor/zod/v4/locales/pt-BR.js +143 -0
- package/dist/lib/modules/vendor/zod/v4/locales/pt.js +142 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ro.js +122 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ru.js +168 -0
- package/dist/lib/modules/vendor/zod/v4/locales/sk.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/sl.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/sv.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ta.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/tg.js +117 -0
- package/dist/lib/modules/vendor/zod/v4/locales/th.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/tk.js +107 -0
- package/dist/lib/modules/vendor/zod/v4/locales/tr.js +110 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ua.js +5 -0
- package/dist/lib/modules/vendor/zod/v4/locales/uk.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ur.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/uz.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/vi.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/yo.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/zh-CN.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/zh-TW.js +112 -0
- package/dist/lib/testing-jelly-id.js +1219 -0
- package/dist/lib/zod.js +1 -0
- package/dist/spec/messaging-v1.md +3744 -0
- package/dist/third-party-notices.md +239 -0
- package/dist/third-party.json +88 -0
- package/dist/types/core/address.d.ts +16 -0
- package/dist/types/core/agent/operations.d.ts +129 -0
- package/dist/types/core/agent/present.d.ts +57 -0
- package/dist/types/core/agent/text.d.ts +62 -0
- package/dist/types/core/agent-tools.d.ts +966 -0
- package/dist/types/core/b64u.d.ts +6 -0
- package/dist/types/core/cards.d.ts +21 -0
- package/dist/types/core/check.d.ts +10 -0
- package/dist/types/core/consent.d.ts +132 -0
- package/dist/types/core/contact-card.d.ts +34 -0
- package/dist/types/core/conversation/context.d.ts +56 -0
- package/dist/types/core/conversation/event.d.ts +36 -0
- package/dist/types/core/conversation/ids.d.ts +18 -0
- package/dist/types/core/conversation/link.d.ts +16 -0
- package/dist/types/core/conversation/log.d.ts +142 -0
- package/dist/types/core/conversation/ratchet.d.ts +95 -0
- package/dist/types/core/conversation/seal.d.ts +47 -0
- package/dist/types/core/delegation.d.ts +71 -0
- package/dist/types/core/directory/device-log.d.ts +119 -0
- package/dist/types/core/directory/epoch.d.ts +53 -0
- package/dist/types/core/directory/handle.d.ts +11 -0
- package/dist/types/core/directory/jelly-device.d.ts +88 -0
- package/dist/types/core/directory/jelly-issuer.d.ts +8 -0
- package/dist/types/core/directory/jelly-token.d.ts +116 -0
- package/dist/types/core/directory/keys-statement.d.ts +22 -0
- package/dist/types/core/directory/link.d.ts +36 -0
- package/dist/types/core/directory/merkle-log.d.ts +40 -0
- package/dist/types/core/directory/person-view.d.ts +90 -0
- package/dist/types/core/directory/person.d.ts +8 -0
- package/dist/types/core/directory/realm.d.ts +23 -0
- package/dist/types/core/directory/smt.d.ts +52 -0
- package/dist/types/core/envelope.d.ts +68 -0
- package/dist/types/core/hash.d.ts +2 -0
- package/dist/types/core/head.d.ts +34 -0
- package/dist/types/core/identity.d.ts +35 -0
- package/dist/types/core/index.d.ts +45 -0
- package/dist/types/core/jcs.d.ts +12 -0
- package/dist/types/core/jelly-host-double.d.ts +21 -0
- package/dist/types/core/jelly-host.d.ts +86 -0
- package/dist/types/core/keys.d.ts +16 -0
- package/dist/types/core/peer-text.d.ts +87 -0
- package/dist/types/core/realm.d.ts +36 -0
- package/dist/types/core/roster.d.ts +146 -0
- package/dist/types/core/schema/a2a.d.ts +251 -0
- package/dist/types/core/schema/common.d.ts +328 -0
- package/dist/types/core/schema/consent.d.ts +140 -0
- package/dist/types/core/schema/conversation.d.ts +1657 -0
- package/dist/types/core/schema/delegation.d.ts +58 -0
- package/dist/types/core/schema/directory-frames.d.ts +1301 -0
- package/dist/types/core/schema/directory.d.ts +375 -0
- package/dist/types/core/schema/envelope.d.ts +756 -0
- package/dist/types/core/schema/errors.d.ts +142 -0
- package/dist/types/core/schema/index.d.ts +16 -0
- package/dist/types/core/schema/ipc/consent.d.ts +555 -0
- package/dist/types/core/schema/ipc/device.d.ts +735 -0
- package/dist/types/core/schema/ipc/jelly.d.ts +501 -0
- package/dist/types/core/schema/ipc/shapes.d.ts +414 -0
- package/dist/types/core/schema/ipc/surfaces.d.ts +680 -0
- package/dist/types/core/schema/ipc.d.ts +3146 -0
- package/dist/types/core/schema/jelly.d.ts +160 -0
- package/dist/types/core/schema/realm.d.ts +38 -0
- package/dist/types/core/schema/relay.d.ts +1134 -0
- package/dist/types/core/schema/roster.d.ts +347 -0
- package/dist/types/core/sign.d.ts +13 -0
- package/dist/types/core/task.d.ts +6 -0
- package/dist/types/core/testing.d.ts +19 -0
- package/dist/types/core/ulid.d.ts +5 -0
- package/dist/types/core/version.d.ts +6 -0
- package/dist/types/core/wire.d.ts +14 -0
- package/dist/types/ipc/client.d.ts +85 -0
- package/dist/types/ipc/framing.d.ts +12 -0
- package/dist/types/ipc/index.d.ts +6 -0
- package/dist/types/ipc/paths.d.ts +39 -0
- package/dist/types/ipc/pipes.d.ts +16 -0
- package/dist/types/ipc/rpc.d.ts +13 -0
- package/dist/types/ipc/server.d.ts +94 -0
- package/dist/types/ipc/testing.d.ts +89 -0
- package/dist/types/ipc/trust.d.ts +49 -0
- package/dist/types/ipc/windows-acl.d.ts +56 -0
- package/dist/types/testing/jelly-id.d.ts +518 -0
- package/dist/types/zod.d.ts +1 -0
- package/package.json +77 -4
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local daemon API. JSON-RPC 2.0, one JSON object per line (NDJSON), over two Unix sockets in
|
|
3
|
+
* `$MESSAGING_HOME/run/` (spec §8.2, "Two sockets"), each 0600 (the agent socket 0660 to
|
|
4
|
+
* `ipc.agentGroup` when set):
|
|
5
|
+
*
|
|
6
|
+
* - `run/agent.sock` serves only `AGENT_METHODS` (the agent tier): the session, contacts, send,
|
|
7
|
+
* inbox, item, conversation, search, task and events methods. The daemon refuses anything else with
|
|
8
|
+
* `forbidden`. Agent-facing clients (OpenCode plugin tools, MCP server) connect here.
|
|
9
|
+
* - `run/admin.sock` serves every method: the agent tier, `HUMAN_METHODS` (acting as the person:
|
|
10
|
+
* conversations, the person's own lines, people, channels) and `ADMIN_METHODS` (this device and the
|
|
11
|
+
* team). Human clients (the `messaging` CLI and the `/messaging` TUI) connect here; agent-facing
|
|
12
|
+
* clients never expose these methods.
|
|
13
|
+
*
|
|
14
|
+
* Params are strict: a key a method does not define is refused (-32602), never dropped.
|
|
15
|
+
*
|
|
16
|
+
* Errors: standard JSON-RPC codes for transport/schema problems (-32700, -32600, -32601, -32602,
|
|
17
|
+
* -32603); every Messaging error uses code -32000 with `data: { code: ErrorCode }`.
|
|
18
|
+
* The daemon pushes notifications `{"jsonrpc":"2.0","method":"event","params":Event}`: `conv` events
|
|
19
|
+
* to connections that called `conv.subscribe`, every other event to connections that called
|
|
20
|
+
* `events.subscribe` for its key (or to all of them, for events without a key).
|
|
21
|
+
*/
|
|
22
|
+
import { z } from "../../vendor/zod/index.js";
|
|
23
|
+
import { DEVICE_METHODS, DeviceStatus, LoginPurpose } from "./ipc/device.js";
|
|
24
|
+
import { Artifact, Message, Part, TaskState, TaskStatus } from "./a2a.js";
|
|
25
|
+
import { AgentKind, ChannelName, DeliveryStage, DeliveryTier, EncKey, Hash, MachineId, MachineLabel, MemberRole, Millis, PersonId, PersonName, RelayUrl, RoomRef, SessionAddress, SessionName, SessionState, TeamId, TeamName, Ulid, LIMITS, } from "./common.js";
|
|
26
|
+
import { ErrorCode } from "./errors.js";
|
|
27
|
+
import { ChatSeq, InboxItem, SessionKey, TaskRecord } from "./ipc/shapes.js";
|
|
28
|
+
import { SURFACE_METHODS } from "./ipc/surfaces.js";
|
|
29
|
+
import { Member } from "./roster.js";
|
|
30
|
+
import { CONSENT_METHODS } from "./ipc/consent.js";
|
|
31
|
+
import { JELLY_METHODS, Notice } from "./ipc/jelly.js";
|
|
32
|
+
import { Jws } from "./jelly.js";
|
|
33
|
+
export * from "./ipc/shapes.js";
|
|
34
|
+
export * from "./ipc/surfaces.js";
|
|
35
|
+
export * from "./ipc/jelly.js";
|
|
36
|
+
export const MESSAGING_ERROR_RPC_CODE = -32000;
|
|
37
|
+
/**
|
|
38
|
+
* Where the daemon can reach a session's host to wake it while no connection holds the session's
|
|
39
|
+
* key (spec §8.6). Local only: the daemon never publishes it.
|
|
40
|
+
*
|
|
41
|
+
* `opencode`: the OpenCode session `sessionID` lives in the location (directory) `location` of the
|
|
42
|
+
* OpenCode server process `pid`. `location` is absolute: a POSIX path (`/…`), or a Windows drive path
|
|
43
|
+
* (`C:\…` or `C:/…`) below the drive's root. The daemon may call that server's `messaging.opencode/wake` RPC at
|
|
44
|
+
* `location`, and nothing else, while OpenCode's service registration names the same `pid`.
|
|
45
|
+
*/
|
|
46
|
+
export const SessionHost = z.strictObject({
|
|
47
|
+
kind: z.literal("opencode"),
|
|
48
|
+
location: z
|
|
49
|
+
.string()
|
|
50
|
+
.min(2)
|
|
51
|
+
.max(1024)
|
|
52
|
+
.refine((path) => (path.startsWith("/") || /^[A-Za-z]:[\\/]./s.test(path)) && !/[\u0000-\u001f\u007f]/.test(path), {
|
|
53
|
+
message: "an absolute path without control characters",
|
|
54
|
+
}),
|
|
55
|
+
sessionID: z.string().min(1).max(200),
|
|
56
|
+
pid: z.number().int().min(1).max(2_147_483_647),
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* The local API's protocol number. A client and a daemon must speak the same one, and be the same
|
|
60
|
+
* release (`VERSION`): there is no compatibility across versions (spec §8.2).
|
|
61
|
+
*/
|
|
62
|
+
export const IPC_PROTOCOL = 1;
|
|
63
|
+
/**
|
|
64
|
+
* The daemon's first line on every connection, before any request: a JSON-RPC notification
|
|
65
|
+
* `{"jsonrpc":"2.0","method":"hello","params":IpcHello}` (spec §8.2). A client reads it before it
|
|
66
|
+
* sends anything, and refuses a daemon of another `version` or `protocol` with `daemon_mismatch`.
|
|
67
|
+
* Every release keeps this shape, so any client can say which daemon it found and `messaging daemon
|
|
68
|
+
* stop` can stop it by `pid`. Unknown fields are ignored.
|
|
69
|
+
*/
|
|
70
|
+
export const IpcHello = z.object({
|
|
71
|
+
daemon: z.literal("messagingd"),
|
|
72
|
+
version: z.string().min(1).max(64),
|
|
73
|
+
protocol: z.number().int().min(1).max(1_000_000),
|
|
74
|
+
pid: z.number().int().min(1).max(2_147_483_647),
|
|
75
|
+
/** `jelly`: the daemon runs under a Jelly host (spec §8.11); absent otherwise. */
|
|
76
|
+
deployment: z.literal("jelly").optional(),
|
|
77
|
+
});
|
|
78
|
+
const Ok = z.object({ ok: z.literal(true) });
|
|
79
|
+
const Empty = z.strictObject({});
|
|
80
|
+
export const DaemonStatus = z.object({
|
|
81
|
+
version: z.string(),
|
|
82
|
+
pid: z.number().int(),
|
|
83
|
+
home: z.string(),
|
|
84
|
+
identity: z.object({ device: MachineId, person: PersonId, label: MachineLabel }).optional(),
|
|
85
|
+
team: z
|
|
86
|
+
.object({ id: TeamId, name: TeamName, role: MemberRole, head: z.object({ seq: z.number().int(), hash: Hash }) })
|
|
87
|
+
.optional(),
|
|
88
|
+
relay: z.object({ url: z.string().optional(), connected: z.boolean(), lastError: z.string().max(500).optional() }),
|
|
89
|
+
sessions: z.number().int().nonnegative(),
|
|
90
|
+
/**
|
|
91
|
+
* A signed envelope committed to a roster hash this chain does not have (spec §5, §12). Persists
|
|
92
|
+
* across restarts while the evidence still proves it. Absent when there is no such alarm.
|
|
93
|
+
*/
|
|
94
|
+
divergence: z
|
|
95
|
+
.object({
|
|
96
|
+
device: MachineId,
|
|
97
|
+
person: PersonId,
|
|
98
|
+
seq: z.number().int().nonnegative(),
|
|
99
|
+
ours: Hash,
|
|
100
|
+
theirs: Hash,
|
|
101
|
+
at: Millis,
|
|
102
|
+
id: Ulid,
|
|
103
|
+
})
|
|
104
|
+
.optional(),
|
|
105
|
+
/**
|
|
106
|
+
* This machine cannot act as itself (spec §8.9). `restored`: its keys came from an older copy, and the roster
|
|
107
|
+
* names a newer key this machine signed and does not hold. `keys_missing`: the store names this machine, but its
|
|
108
|
+
* key files are gone. Either way the daemon publishes no key. Absent when there is no such alarm.
|
|
109
|
+
*/
|
|
110
|
+
alarm: z.enum(["restored", "keys_missing"]).optional(),
|
|
111
|
+
/** A directory realm (spec §15.9): this device's binding, the adopted tree head and the realm's alarm. */
|
|
112
|
+
directory: DeviceStatus.optional(),
|
|
113
|
+
});
|
|
114
|
+
export const InviteSummary = z.object({
|
|
115
|
+
inviteId: Ulid,
|
|
116
|
+
person: PersonName,
|
|
117
|
+
expires: Millis,
|
|
118
|
+
state: z.enum(["open", "redeemed", "revoked", "expired"]),
|
|
119
|
+
});
|
|
120
|
+
/** Method table: name → params/result schemas. The daemon validates params; clients validate results. */
|
|
121
|
+
export const IPC_METHODS = {
|
|
122
|
+
...DEVICE_METHODS,
|
|
123
|
+
"daemon.status": { params: Empty, result: DaemonStatus },
|
|
124
|
+
/** Human CLI only: replies, then shuts the daemon down cleanly. */
|
|
125
|
+
"daemon.stop": { params: Empty, result: Ok },
|
|
126
|
+
"session.register": {
|
|
127
|
+
params: z.strictObject({
|
|
128
|
+
key: SessionKey,
|
|
129
|
+
agent: AgentKind.describe('client kind, e.g. "opencode", "claude-code"'),
|
|
130
|
+
name: SessionName.optional().describe("requested name; the daemon may suffix it to keep it unique"),
|
|
131
|
+
cwd: z.string().max(1024).optional(),
|
|
132
|
+
repo: z.string().max(200).optional(),
|
|
133
|
+
branch: z.string().max(200).optional(),
|
|
134
|
+
tier: DeliveryTier,
|
|
135
|
+
state: SessionState,
|
|
136
|
+
doing: z.string().max(200).optional(),
|
|
137
|
+
host: SessionHost.optional().describe("where the daemon can wake the session while no connection holds its key; kind must equal agent"),
|
|
138
|
+
/** `deployment: "jelly"` only, and required there: the host's session ticket naming this session (spec §8.11). */
|
|
139
|
+
ticket: Jws.optional(),
|
|
140
|
+
}),
|
|
141
|
+
result: z.object({ session: SessionName, address: SessionAddress }),
|
|
142
|
+
},
|
|
143
|
+
"session.update": {
|
|
144
|
+
params: z.strictObject({
|
|
145
|
+
key: SessionKey,
|
|
146
|
+
state: SessionState.optional(),
|
|
147
|
+
doing: z.string().max(200).optional(),
|
|
148
|
+
branch: z.string().max(200).optional(),
|
|
149
|
+
tier: DeliveryTier.optional(),
|
|
150
|
+
}),
|
|
151
|
+
result: Ok,
|
|
152
|
+
},
|
|
153
|
+
"session.unregister": { params: z.strictObject({ key: SessionKey }), result: Ok },
|
|
154
|
+
/** Newest first; default limit 50. */
|
|
155
|
+
"inbox.list": {
|
|
156
|
+
params: z.strictObject({
|
|
157
|
+
key: SessionKey,
|
|
158
|
+
unreadOnly: z.boolean().optional().describe('only items not yet "read" (stage delivered or handed_to_agent)'),
|
|
159
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
160
|
+
}),
|
|
161
|
+
result: z.object({ items: z.array(InboxItem) }),
|
|
162
|
+
},
|
|
163
|
+
/**
|
|
164
|
+
* Looks up one item without changing its stage or sending a receipt. With `message: true` it
|
|
165
|
+
* also returns the full message, still with no stage change: a client can show the message first
|
|
166
|
+
* and ack after (`inbox.ack`), so a crash in between leaves the item unread.
|
|
167
|
+
*/
|
|
168
|
+
"inbox.get": {
|
|
169
|
+
params: z.strictObject({ key: SessionKey, id: Ulid, message: z.boolean().optional() }),
|
|
170
|
+
result: z.object({ item: InboxItem, message: Message.optional() }),
|
|
171
|
+
},
|
|
172
|
+
"inbox.ack": {
|
|
173
|
+
params: z.strictObject({ key: SessionKey, id: Ulid, stage: z.enum(["handed_to_agent", "read"]) }),
|
|
174
|
+
result: Ok,
|
|
175
|
+
},
|
|
176
|
+
"task.list": {
|
|
177
|
+
params: z.strictObject({
|
|
178
|
+
key: SessionKey,
|
|
179
|
+
role: z.enum(["requester", "executor"]).optional(),
|
|
180
|
+
state: TaskState.optional(),
|
|
181
|
+
contextId: z.string().max(128).optional(),
|
|
182
|
+
peer: SessionAddress.optional().describe("only tasks whose peer is this session"),
|
|
183
|
+
updatedAfter: Millis.optional().describe("only tasks updated at or after this time"),
|
|
184
|
+
cursor: z.string().min(1).max(200).optional().describe("opaque; from a previous nextCursor"),
|
|
185
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
186
|
+
}),
|
|
187
|
+
/** Most recently updated first. */
|
|
188
|
+
result: z.object({
|
|
189
|
+
tasks: z.array(TaskRecord),
|
|
190
|
+
nextCursor: z.string().max(200).optional().describe("absent when there are no more pages"),
|
|
191
|
+
total: z.number().int().nonnegative().describe("matching tasks across all pages"),
|
|
192
|
+
}),
|
|
193
|
+
},
|
|
194
|
+
"task.get": { params: z.strictObject({ key: SessionKey, taskId: Ulid }), result: TaskRecord },
|
|
195
|
+
"task.update": {
|
|
196
|
+
params: z.strictObject({
|
|
197
|
+
key: SessionKey,
|
|
198
|
+
taskId: Ulid,
|
|
199
|
+
state: TaskState,
|
|
200
|
+
message: z.strictObject({ parts: z.array(Part).min(1).max(LIMITS.partsPerMessage) }).optional(),
|
|
201
|
+
artifact: Artifact.optional(),
|
|
202
|
+
append: z.boolean().optional(),
|
|
203
|
+
lastChunk: z.boolean().optional(),
|
|
204
|
+
}),
|
|
205
|
+
result: TaskRecord,
|
|
206
|
+
},
|
|
207
|
+
"task.cancel": {
|
|
208
|
+
params: z.strictObject({ key: SessionKey, taskId: Ulid, reason: z.string().max(1000).optional() }),
|
|
209
|
+
result: TaskRecord,
|
|
210
|
+
},
|
|
211
|
+
"events.subscribe": { params: z.strictObject({ keys: z.array(SessionKey).min(1).max(256) }), result: Ok },
|
|
212
|
+
// ---- human clients only (the CLI and the TUI) ----
|
|
213
|
+
"team.create": {
|
|
214
|
+
params: z.strictObject({ name: TeamName, person: PersonName, label: MachineLabel, relay: RelayUrl }),
|
|
215
|
+
result: z.object({ team: TeamId, name: TeamName, machine: MachineId }),
|
|
216
|
+
},
|
|
217
|
+
"team.invite": {
|
|
218
|
+
params: z.strictObject({ person: PersonName, ttlMs: z.number().int().min(60_000).max(LIMITS.ttlMs).optional() }),
|
|
219
|
+
result: z.object({ ticket: z.string(), inviteId: Ulid, expires: Millis }),
|
|
220
|
+
},
|
|
221
|
+
"team.join": {
|
|
222
|
+
params: z.strictObject({ ticket: z.string().min(1).max(4096), label: MachineLabel }),
|
|
223
|
+
result: z.object({ team: TeamId, name: TeamName, machine: MachineId, person: PersonName }),
|
|
224
|
+
},
|
|
225
|
+
"team.members": {
|
|
226
|
+
params: Empty,
|
|
227
|
+
result: z.object({
|
|
228
|
+
team: z.object({ id: TeamId, name: TeamName }),
|
|
229
|
+
members: z.array(Member.extend({ self: z.boolean(), online: z.boolean() })),
|
|
230
|
+
invites: z.array(InviteSummary),
|
|
231
|
+
}),
|
|
232
|
+
},
|
|
233
|
+
"team.remove": { params: z.strictObject({ machine: MachineId, reason: z.string().max(200).optional() }), result: Ok },
|
|
234
|
+
"team.role": { params: z.strictObject({ machine: MachineId, role: MemberRole }), result: Ok },
|
|
235
|
+
"team.revokeInvite": { params: z.strictObject({ inviteId: Ulid }), result: Ok },
|
|
236
|
+
/**
|
|
237
|
+
* Rotates this machine's encryption key (spec §4 `member.rekey`, §8.2). Returns the new public key
|
|
238
|
+
* and when the previous one is dropped. Never a private key.
|
|
239
|
+
*/
|
|
240
|
+
"identity.rotateEnc": { params: Empty, result: z.object({ enc: EncKey, until: Millis }) },
|
|
241
|
+
/**
|
|
242
|
+
* Admin admits a machine by the public keys it showed. No invite seed. The joiner connects only after
|
|
243
|
+
* this entry is on the roster (spec §4 `member.add`, §8.2).
|
|
244
|
+
*/
|
|
245
|
+
"team.add": {
|
|
246
|
+
params: z.strictObject({
|
|
247
|
+
machine: MachineId,
|
|
248
|
+
enc: EncKey,
|
|
249
|
+
person: PersonName,
|
|
250
|
+
label: MachineLabel,
|
|
251
|
+
}),
|
|
252
|
+
result: Ok,
|
|
253
|
+
},
|
|
254
|
+
/**
|
|
255
|
+
* Joiner: generate keys and store them aside from `identity.json`, so the plugin does not treat the
|
|
256
|
+
* machine as joined. Returns the public admit card. Private keys stay in the daemon.
|
|
257
|
+
*/
|
|
258
|
+
"team.pair": {
|
|
259
|
+
params: z.strictObject({ relay: RelayUrl, team: TeamId, person: PersonName, label: MachineLabel }),
|
|
260
|
+
result: z.object({
|
|
261
|
+
machine: MachineId,
|
|
262
|
+
enc: EncKey,
|
|
263
|
+
person: PersonName,
|
|
264
|
+
label: MachineLabel,
|
|
265
|
+
admit: z.string().min(1).max(2048),
|
|
266
|
+
}),
|
|
267
|
+
},
|
|
268
|
+
/**
|
|
269
|
+
* Joiner: try hello with the paired keys. `waiting` means the admin has not admitted this machine yet.
|
|
270
|
+
* `joined` means the roster names these exact keys, person and label, and the daemon has adopted them.
|
|
271
|
+
*/
|
|
272
|
+
"team.claim": {
|
|
273
|
+
params: z.strictObject({ waitMs: z.number().int().min(0).max(120_000).optional() }),
|
|
274
|
+
result: z.discriminatedUnion("state", [
|
|
275
|
+
z.object({ state: z.literal("waiting") }),
|
|
276
|
+
z.object({
|
|
277
|
+
state: z.literal("joined"),
|
|
278
|
+
team: TeamId,
|
|
279
|
+
name: TeamName,
|
|
280
|
+
machine: MachineId,
|
|
281
|
+
person: PersonName,
|
|
282
|
+
label: MachineLabel,
|
|
283
|
+
}),
|
|
284
|
+
]),
|
|
285
|
+
},
|
|
286
|
+
/** Discard pending keys (a pairing or founding keys), including their private keys. */
|
|
287
|
+
"team.pairCancel": { params: Empty, result: Ok },
|
|
288
|
+
/**
|
|
289
|
+
* Founder: generate the keys `team.create` will use and store them aside from `identity.json`, so a
|
|
290
|
+
* relay operator can list the machine id in `--allow-team-create` first. Returns the same public keys
|
|
291
|
+
* while they are pending. Private keys stay in the daemon.
|
|
292
|
+
*/
|
|
293
|
+
"team.prepare": { params: Empty, result: z.object({ machine: MachineId, enc: EncKey }) },
|
|
294
|
+
/** Creates a channel with this person and `persons` (at most `LIMITS.channelPersons` together). */
|
|
295
|
+
"channel.create": {
|
|
296
|
+
params: z.strictObject({ name: ChannelName, persons: z.array(PersonId).max(LIMITS.channelPersons) }),
|
|
297
|
+
result: z.object({ id: Ulid, name: ChannelName }),
|
|
298
|
+
},
|
|
299
|
+
"channel.invite": {
|
|
300
|
+
params: z.strictObject({ name: ChannelName, persons: z.array(PersonId).min(1).max(LIMITS.channelPersons) }),
|
|
301
|
+
result: Ok,
|
|
302
|
+
},
|
|
303
|
+
"channel.leave": { params: z.strictObject({ name: ChannelName }), result: Ok },
|
|
304
|
+
/** Removes other persons from a channel; only its creator or a team admin may (spec §6.8). */
|
|
305
|
+
"channel.remove": {
|
|
306
|
+
params: z.strictObject({ name: ChannelName, persons: z.array(PersonId).min(1).max(LIMITS.channelPersons) }),
|
|
307
|
+
result: Ok,
|
|
308
|
+
},
|
|
309
|
+
...CONSENT_METHODS,
|
|
310
|
+
...JELLY_METHODS,
|
|
311
|
+
...SURFACE_METHODS,
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* The agent tier (design §7.5): the methods an agent-facing client may call, and exactly what the
|
|
315
|
+
* daemon serves on `run/agent.sock` (spec §8.2). Anything else is for human clients, on
|
|
316
|
+
* `run/admin.sock`: the `messaging` CLI and the `/messaging` TUI.
|
|
317
|
+
*/
|
|
318
|
+
export const AGENT_METHODS = [
|
|
319
|
+
"daemon.status",
|
|
320
|
+
"daemon.health",
|
|
321
|
+
"device.status",
|
|
322
|
+
"device.bind",
|
|
323
|
+
"session.register",
|
|
324
|
+
"session.update",
|
|
325
|
+
"session.unregister",
|
|
326
|
+
"events.subscribe",
|
|
327
|
+
"contacts.list",
|
|
328
|
+
"msg.send",
|
|
329
|
+
"inbox.list",
|
|
330
|
+
"inbox.get",
|
|
331
|
+
"inbox.ack",
|
|
332
|
+
"item.read",
|
|
333
|
+
"conv.read",
|
|
334
|
+
"search",
|
|
335
|
+
"task.list",
|
|
336
|
+
"task.get",
|
|
337
|
+
"task.update",
|
|
338
|
+
"task.cancel",
|
|
339
|
+
];
|
|
340
|
+
/**
|
|
341
|
+
* The human tier (design §7.5): methods that act as the person, on `run/admin.sock` only: their
|
|
342
|
+
* conversations and own lines, the people of the realm, their grants and held requests (spec §6.9), and channels.
|
|
343
|
+
* `conv.read` is in both tiers.
|
|
344
|
+
*/
|
|
345
|
+
export const HUMAN_METHODS = [
|
|
346
|
+
"conv.list",
|
|
347
|
+
"conv.read",
|
|
348
|
+
"conv.markRead",
|
|
349
|
+
"conv.subscribe",
|
|
350
|
+
"person.send",
|
|
351
|
+
"people.search",
|
|
352
|
+
"people.get",
|
|
353
|
+
"grant.list",
|
|
354
|
+
"grant.set",
|
|
355
|
+
"grant.set.preview",
|
|
356
|
+
"request.list",
|
|
357
|
+
"request.answer",
|
|
358
|
+
"request.answer.preview",
|
|
359
|
+
"contact.request",
|
|
360
|
+
"contact.request.preview",
|
|
361
|
+
"card.get",
|
|
362
|
+
"agents.list",
|
|
363
|
+
"agents.read",
|
|
364
|
+
"notice.subscribe",
|
|
365
|
+
"notice.list",
|
|
366
|
+
"notice.ack",
|
|
367
|
+
"device.approve.preview",
|
|
368
|
+
"device.remove.preview",
|
|
369
|
+
"identity.replace.preview",
|
|
370
|
+
"directory.erase.preview",
|
|
371
|
+
"channel.create",
|
|
372
|
+
"channel.invite",
|
|
373
|
+
"channel.leave",
|
|
374
|
+
"channel.remove",
|
|
375
|
+
];
|
|
376
|
+
/**
|
|
377
|
+
* The admin tier (design §7.5): this device and the daemon, and in a roster realm the team, on
|
|
378
|
+
* `run/admin.sock` only.
|
|
379
|
+
*/
|
|
380
|
+
export const ADMIN_METHODS = [
|
|
381
|
+
"daemon.stop",
|
|
382
|
+
"daemon.woke",
|
|
383
|
+
"person.attached",
|
|
384
|
+
"daemon.snapshot",
|
|
385
|
+
"wake.arm",
|
|
386
|
+
"wake.disarm",
|
|
387
|
+
"device.list",
|
|
388
|
+
"device.remove",
|
|
389
|
+
"device.link",
|
|
390
|
+
"device.login",
|
|
391
|
+
"device.login.status",
|
|
392
|
+
"device.login.cancel",
|
|
393
|
+
"device.approve",
|
|
394
|
+
"identity.rotateEnc",
|
|
395
|
+
"identity.replace",
|
|
396
|
+
"directory.erase",
|
|
397
|
+
"team.create",
|
|
398
|
+
"team.invite",
|
|
399
|
+
"team.join",
|
|
400
|
+
"team.members",
|
|
401
|
+
"team.remove",
|
|
402
|
+
"team.role",
|
|
403
|
+
"team.revokeInvite",
|
|
404
|
+
"team.add",
|
|
405
|
+
"team.pair",
|
|
406
|
+
"team.claim",
|
|
407
|
+
"team.pairCancel",
|
|
408
|
+
"team.prepare",
|
|
409
|
+
];
|
|
410
|
+
/**
|
|
411
|
+
* What a host-scope action grant (`act: "host"`) allows in `deployment: "jelly"` (spec §8.11), and nothing else: the
|
|
412
|
+
* notice stream and its counts, `conv.list` with counts only, stopping the daemon, and telling it the host woke
|
|
413
|
+
* (`daemon.woke`) and whether the person is at it (`person.attached`), a backup of the home (`daemon.snapshot`), and its
|
|
414
|
+
* wake ticket (`wake.arm`, `wake.disarm`).
|
|
415
|
+
*/
|
|
416
|
+
export const HOST_SCOPE_METHODS = ["notice.subscribe", "notice.list", "conv.list", "daemon.stop", "daemon.woke", "person.attached", "daemon.snapshot", "wake.arm", "wake.disarm"];
|
|
417
|
+
export const IpcEvent = z.discriminatedUnion("type", [
|
|
418
|
+
z.object({ type: z.literal("inbox"), key: SessionKey, item: InboxItem }),
|
|
419
|
+
z.object({ type: z.literal("receipt"), key: SessionKey, envelopeId: Ulid, to: SessionAddress, stage: DeliveryStage, code: ErrorCode.optional() }),
|
|
420
|
+
/** origin "local": caused by this key's own call; "peer": caused by an envelope from the other side or a daemon timeout. */
|
|
421
|
+
z.object({
|
|
422
|
+
type: z.literal("task"),
|
|
423
|
+
key: SessionKey,
|
|
424
|
+
record: TaskRecord,
|
|
425
|
+
origin: z.enum(["local", "peer"]),
|
|
426
|
+
/** The delta that caused this snapshot, when it came from a status or artifact update. */
|
|
427
|
+
update: z
|
|
428
|
+
.union([
|
|
429
|
+
z.object({ status: TaskStatus }),
|
|
430
|
+
z.object({ artifact: Artifact, append: z.boolean().optional(), lastChunk: z.boolean().optional() }),
|
|
431
|
+
])
|
|
432
|
+
.optional(),
|
|
433
|
+
}),
|
|
434
|
+
/** What `contacts.list` returns changed: a presence, a session, the roster. */
|
|
435
|
+
z.object({ type: z.literal("contacts") }),
|
|
436
|
+
z.object({ type: z.literal("relay"), connected: z.boolean() }),
|
|
437
|
+
/**
|
|
438
|
+
* A line was stored in a DM or a channel, a channel this person is in changed, or the delivery state
|
|
439
|
+
* of an outgoing line changed. Only for connections that called `conv.subscribe`. The text stays in
|
|
440
|
+
* the transcript. `seq` rises with every event; a client resumes from the last one it handled
|
|
441
|
+
* (`conv.subscribe {since}`).
|
|
442
|
+
*/
|
|
443
|
+
z.object({ type: z.literal("conv"), conversation: RoomRef, id: Ulid, person: PersonId, seq: ChatSeq }),
|
|
444
|
+
/**
|
|
445
|
+
* A device's sign-in through the browser changed state (spec §15.9): its state and, when it failed, the code. Never
|
|
446
|
+
* the code to type, the link or a token; `device.login.status` gives the rest on the admin socket.
|
|
447
|
+
*/
|
|
448
|
+
z.object({ type: z.literal("login"), state: z.enum(["pending", "done", "failed", "cancelled"]), purpose: LoginPurpose, code: ErrorCode.optional() }),
|
|
449
|
+
/** A notice (spec §8.2, "Notices"), only for connections that called `notice.subscribe`. */
|
|
450
|
+
z.object({ type: z.literal("notice"), notice: Notice }),
|
|
451
|
+
]);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shapes of Jelly ID that a hosted realm reads (Jelly's resource-token contract v1.1; spec §15.2). Jelly
|
|
3
|
+
* owns them; these schemas are what Messaging accepts of them. Jelly's versions are additive, so headers,
|
|
4
|
+
* claims and key-set entries may carry members a later version adds; the members Messaging reads are strict.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "../../vendor/zod/index.js";
|
|
7
|
+
import { Key32 } from "./common.js";
|
|
8
|
+
/** A Jelly account id (contract v1.1 §5 step 6). */
|
|
9
|
+
export const JellySubject = z.string().regex(/^acc_[A-Za-z0-9._:-]{1,123}$/, "Jelly account id");
|
|
10
|
+
/** A Jelly host id (contract v1.1 §5 step 6). */
|
|
11
|
+
export const JellyHost = z.string().regex(/^hst_[A-Za-z0-9._:-]{1,123}$/, "Jelly host id");
|
|
12
|
+
/** A key id: printable ASCII, 1 to 200 characters. */
|
|
13
|
+
export const JellyKid = z.string().regex(/^[\x21-\x7e]{1,200}$/, "key id");
|
|
14
|
+
/** A compact JWS: three base64url segments. The bound is far above a Jelly resource token's size. */
|
|
15
|
+
export const Jws = z
|
|
16
|
+
.string()
|
|
17
|
+
.max(8192)
|
|
18
|
+
.regex(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, "compact JWS");
|
|
19
|
+
/** The key a token is bound to (RFC 7800 `cnf.jwk`, contract v1.1 §3): exactly these three members. */
|
|
20
|
+
export const CnfJwk = z.strictObject({ kty: z.literal("OKP"), crv: z.literal("Ed25519"), x: Key32 });
|
|
21
|
+
/** The JWS header of a resource token (contract v1.1 §4). */
|
|
22
|
+
export const JellyTokenHeader = z.looseObject({ alg: z.literal("EdDSA"), typ: z.literal("jelly-resource+jwt"), kid: JellyKid });
|
|
23
|
+
const Seconds = z.number().int().nonnegative();
|
|
24
|
+
/**
|
|
25
|
+
* How a token was minted when it was not by a host (contract v1.2 §12.4): `"device"`, a service's device
|
|
26
|
+
* that signed in through the browser. Read as a string, so a later value is refused by the verifier, not the schema.
|
|
27
|
+
*/
|
|
28
|
+
export const JELLY_DEVICE_SOURCE = "device";
|
|
29
|
+
/**
|
|
30
|
+
* The claims of a resource token (contract v1.1 §4; v1.2 §12.4). `aud` is one string; `cnf` is exactly
|
|
31
|
+
* `{jwk}`. A host's token carries `hst`; a device's carries `src: "device"` and no `hst` (`verifyJellyToken`).
|
|
32
|
+
*/
|
|
33
|
+
export const JellyClaims = z
|
|
34
|
+
.looseObject({
|
|
35
|
+
iss: z.string().min(1).max(2048),
|
|
36
|
+
aud: z.string().min(1).max(2048),
|
|
37
|
+
sub: JellySubject,
|
|
38
|
+
email: z.string().min(3).max(320).optional(),
|
|
39
|
+
email_verified: z.literal(true).optional(),
|
|
40
|
+
name: z.string().max(256).optional(),
|
|
41
|
+
hst: JellyHost.optional(),
|
|
42
|
+
src: z.string().min(1).max(32).optional(),
|
|
43
|
+
cnf: z.strictObject({ jwk: CnfJwk }).optional(),
|
|
44
|
+
iat: Seconds,
|
|
45
|
+
exp: Seconds,
|
|
46
|
+
jti: z.string().min(1).max(128),
|
|
47
|
+
})
|
|
48
|
+
.refine((claims) => (claims.email === undefined) === (claims.email_verified === undefined), {
|
|
49
|
+
message: "email and email_verified come together",
|
|
50
|
+
});
|
|
51
|
+
/** One public Ed25519 key of Jelly ID's key set (contract v1.1 §5). Entries of other key types are skipped. */
|
|
52
|
+
export const JellyJwk = z.looseObject({ kty: z.literal("OKP"), crv: z.literal("Ed25519"), x: Key32, kid: JellyKid });
|
|
53
|
+
/** `<issuer>/.well-known/jwks.json`. Each entry is read with `JellyJwk`; one that does not parse is skipped. */
|
|
54
|
+
export const JellyJwks = z.looseObject({ keys: z.array(z.unknown()).max(64) });
|
|
55
|
+
/** One key of `<issuer>/.well-known/jwks-history.json` (contract v1.1 §11): times are seconds since the epoch. */
|
|
56
|
+
export const JellyHistoryKey = JellyJwk.extend({
|
|
57
|
+
nbf: Seconds,
|
|
58
|
+
retired_at: Seconds.nullable(),
|
|
59
|
+
revoked_at: Seconds.nullable().optional(),
|
|
60
|
+
});
|
|
61
|
+
/** `<issuer>/.well-known/jwks-history.json`, oldest first; it never shrinks. */
|
|
62
|
+
export const JellyKeyHistory = z.looseObject({ keys: z.array(z.unknown()).max(4096) });
|
|
63
|
+
/**
|
|
64
|
+
* A Jelly key a client has verified, as it persists it (its Jelly key archive, spec §15.2). `nbf`,
|
|
65
|
+
* `retiredAt` and `revokedAt` are the key history's (seconds) as last seen; `historyAt` is when that was (ms).
|
|
66
|
+
*/
|
|
67
|
+
export const ArchivedJellyKey = z.strictObject({
|
|
68
|
+
kid: JellyKid,
|
|
69
|
+
x: Key32,
|
|
70
|
+
nbf: Seconds.optional(),
|
|
71
|
+
retiredAt: Seconds.nullable().optional(),
|
|
72
|
+
revokedAt: Seconds.nullable().optional(),
|
|
73
|
+
historyAt: z.number().int().nonnegative().optional(),
|
|
74
|
+
});
|
|
75
|
+
// ---- the Jelly host (deployment "jelly", spec §8.11) ----
|
|
76
|
+
/** The audience of every action grant and session ticket a Jelly host mints for Messaging (spec §8.11). */
|
|
77
|
+
export const JELLY_SERVICE_AUDIENCE = "@joe-broadhead/opencode-messaging#service";
|
|
78
|
+
/** A Jelly agent id (`agt`), the engine a session ticket names. */
|
|
79
|
+
export const JellyAgentId = z.string().regex(/^agt_[A-Za-z0-9._:-]{1,123}$/, "Jelly agent id");
|
|
80
|
+
/**
|
|
81
|
+
* The host's public action key (`--act-key`): exactly a public Ed25519 JWK. `kid`, `alg` and `use` may be present;
|
|
82
|
+
* any other member, a private `d` above all, is refused, and so is the daemon's start.
|
|
83
|
+
*/
|
|
84
|
+
export const ActKeyJwk = z.strictObject({
|
|
85
|
+
kty: z.literal("OKP"),
|
|
86
|
+
crv: z.literal("Ed25519"),
|
|
87
|
+
x: Key32,
|
|
88
|
+
kid: JellyKid.optional(),
|
|
89
|
+
alg: z.enum(["EdDSA", "Ed25519"]).optional(),
|
|
90
|
+
use: z.literal("sig").optional(),
|
|
91
|
+
});
|
|
92
|
+
/** The JWS header of an action grant (`jelly-act+jwt`). */
|
|
93
|
+
export const JellyActHeader = z.looseObject({ alg: z.literal("EdDSA"), typ: z.literal("jelly-act+jwt"), kid: JellyKid });
|
|
94
|
+
/** The JWS header of a session ticket (`jelly-session+jwt`). */
|
|
95
|
+
export const JellySessionHeader = z.looseObject({ alg: z.literal("EdDSA"), typ: z.literal("jelly-session+jwt"), kid: JellyKid });
|
|
96
|
+
/** base64url of a SHA-256 digest: 43 characters. */
|
|
97
|
+
const Digest = z.string().regex(/^[A-Za-z0-9_-]{43}$/, "base64url SHA-256");
|
|
98
|
+
/**
|
|
99
|
+
* The claims of an action grant (spec §8.11). `act` is the IPC method it allows, or `host` for a host-scope grant.
|
|
100
|
+
* `in` (the digest of the call's params) and `cls` (the action class) are required for a method grant. `iss`, `dev`
|
|
101
|
+
* and `mbr` (the host's member who acted, Jelly D29) are Jelly's; the daemon takes no authority from them.
|
|
102
|
+
*/
|
|
103
|
+
export const JellyActClaims = z.looseObject({
|
|
104
|
+
aud: z.string().min(1).max(2048),
|
|
105
|
+
act: z.string().regex(/^[a-z][A-Za-z.]{0,63}$/, "an IPC method or host"),
|
|
106
|
+
in: Digest.optional(),
|
|
107
|
+
iat: Seconds,
|
|
108
|
+
exp: Seconds,
|
|
109
|
+
jti: z.string().regex(/^[\x21-\x7e]{1,128}$/, "printable jti"),
|
|
110
|
+
iss: z.string().max(2048).optional(),
|
|
111
|
+
dev: z.string().max(200).optional(),
|
|
112
|
+
cls: z.string().max(32).optional(),
|
|
113
|
+
mbr: z.string().max(200).optional(),
|
|
114
|
+
});
|
|
115
|
+
/** The claims of a session ticket (spec §8.11): the agent and the session name it may register. */
|
|
116
|
+
export const JellySessionClaims = z.looseObject({
|
|
117
|
+
aud: z.string().min(1).max(2048),
|
|
118
|
+
agt: JellyAgentId,
|
|
119
|
+
name: z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/, "lowercase session name"),
|
|
120
|
+
iat: Seconds,
|
|
121
|
+
exp: Seconds,
|
|
122
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realms (spec §5, "Realm and anchor"). A realm is the trust domain one daemon home belongs to: who the
|
|
3
|
+
* persons and their machines are, and which keys to seal to. A roster realm's id is the team id (§4) and its
|
|
4
|
+
* authority is the team's roster; a directory realm's id derives from its directory (§15.8) and its authority is
|
|
5
|
+
* the key directory's epoch log (§15).
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "../../vendor/zod/index.js";
|
|
8
|
+
import { Hash, Key32 } from "./common.js";
|
|
9
|
+
/** A realm's id. For a roster realm, the team id: the hash of the genesis entry; for a directory realm, `directoryRealmId`. */
|
|
10
|
+
export const RealmId = Key32.describe("Realm id; for a roster realm, the team id");
|
|
11
|
+
/** A roster realm's anchor: the sender's roster head (spec §4, §5). */
|
|
12
|
+
export const RosterAnchor = z.object({
|
|
13
|
+
kind: z.literal("roster"),
|
|
14
|
+
seq: z.number().int().nonnegative(),
|
|
15
|
+
hash: Hash,
|
|
16
|
+
});
|
|
17
|
+
/** A directory realm's anchor (spec §5, §15.6): the sender's adopted signed tree head, by its size and root. */
|
|
18
|
+
export const EpochAnchor = z.object({
|
|
19
|
+
kind: z.literal("epoch"),
|
|
20
|
+
size: z.number().int().positive(),
|
|
21
|
+
root: Hash,
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* What an envelope commits to of its sender's view of the realm, so a receiver can tell whether the
|
|
25
|
+
* sender saw more than it has (catch up first) or something else (a divergence).
|
|
26
|
+
*/
|
|
27
|
+
export const Anchor = z.discriminatedUnion("kind", [RosterAnchor, EpochAnchor]);
|