@joe-broadhead/messaging 0.0.0-stage → 0.7.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 +175 -0
- package/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +36 -2
- package/dist/bin/messaging-mcp.js +25887 -0
- package/dist/bin/messaging-relay.js +19014 -0
- package/dist/bin/messaging.js +14732 -0
- package/dist/bin/messagingd.js +28682 -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 +458 -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/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 +44 -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 +254 -0
- package/dist/lib/modules/core/schema/consent.js +120 -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 +65 -0
- package/dist/lib/modules/core/schema/index.js +15 -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 +20 -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 +3426 -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 +948 -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/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 +38 -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 +272 -0
- package/dist/types/core/schema/consent.d.ts +140 -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 +750 -0
- package/dist/types/core/schema/errors.d.ts +104 -0
- package/dist/types/core/schema/index.d.ts +15 -0
- package/dist/types/core/schema/ipc/consent.d.ts +555 -0
- package/dist/types/core/schema/ipc/device.d.ts +729 -0
- package/dist/types/core/schema/ipc/jelly.d.ts +501 -0
- package/dist/types/core/schema/ipc/shapes.d.ts +411 -0
- package/dist/types/core/schema/ipc/surfaces.d.ts +674 -0
- package/dist/types/core/schema/ipc.d.ts +3131 -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 +1122 -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 +18 -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,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IPC methods a Jelly host needs (spec §8.2, §8.11; design §13.6, §13.10-§13.12), served in every deployment:
|
|
3
|
+
*
|
|
4
|
+
* - Agent tier: `daemon.health`, a cheap status with no secrets and no counts.
|
|
5
|
+
* - Human tier: `agents.list` and `agents.read` (the person's view of their own agents' traffic on this device),
|
|
6
|
+
* `notice.subscribe`, `notice.list` and `notice.ack` (notices in fixed categories), and the read-only preview
|
|
7
|
+
* `device.remove.preview`.
|
|
8
|
+
*
|
|
9
|
+
* Spread into `IPC_METHODS`; `HOST_SCOPE_METHODS` (in `ipc.ts`) is what a host-scope action grant allows.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "../../../vendor/zod/index.js";
|
|
12
|
+
import { MachineId, MachineLabel, Millis, SessionName } from "../common.js";
|
|
13
|
+
import { WAKE_AGENTS, WAKE_TICKET_BYTES, WakeUrl } from "../directory-frames.js";
|
|
14
|
+
import { Jws } from "../jelly.js";
|
|
15
|
+
import { DeviceKind } from "../directory.js";
|
|
16
|
+
import { DeviceChangeView, DeviceRemoveParams } from "./device.js";
|
|
17
|
+
import { ConvCursor, ConvEntry } from "./shapes.js";
|
|
18
|
+
import { ConversationRef } from "./surfaces.js";
|
|
19
|
+
/**
|
|
20
|
+
* A notice's id, stable per source: `m_<line id>` (a line), `r_<request id>` (a held request), `a_<id>` (an alert).
|
|
21
|
+
* A replay repeats the same ids, so a host rings once.
|
|
22
|
+
*/
|
|
23
|
+
export const NoticeId = z.string().regex(/^[mra]_[0-9A-Za-z]{1,64}$/, "notice id");
|
|
24
|
+
/** Most characters of a notice's `line`. */
|
|
25
|
+
export const NOTICE_LINE_CHARS = 120;
|
|
26
|
+
/**
|
|
27
|
+
* An alert about another device of this person (design §2.8): the change, as `DeviceChangeView` gives it, with
|
|
28
|
+
* `device` the device the person may want to remove (for `replaced`, the successor; `replaces` is the device it
|
|
29
|
+
* replaced). `current`: whether that device is current now. `remove`: how this device removes it: `token` with a
|
|
30
|
+
* fresh Jelly token from its host (`device.remove {device, token}`), `sign-in` with a fresh sign-in through the
|
|
31
|
+
* browser (`device.login {remove: {device}}`), `team` as the roster's admin (`team.remove`); absent when it cannot
|
|
32
|
+
* (it is not current, or this device was approved by link and removes no device).
|
|
33
|
+
*/
|
|
34
|
+
export const NoticeDevice = DeviceChangeView.extend({
|
|
35
|
+
replaces: z.object({ device: MachineId, label: MachineLabel }).optional(),
|
|
36
|
+
current: z.boolean(),
|
|
37
|
+
remove: z.enum(["token", "sign-in", "team"]).optional(),
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* One notice (design §13.11). `message`: a line in a DM or a channel from another person or their agent, or an
|
|
41
|
+
* agent's line to its own person; `request`: a request held for the person; `alert`: an alarm of this device, a
|
|
42
|
+
* change of the person's other devices (with `device`), or trust the person gave that ends within 7 days (with `from`).
|
|
43
|
+
* `line` is the first line of a message, through `cleanPeerText`, only when the caller asked for lines; alerts carry
|
|
44
|
+
* fixed text. `from.name` and `from.email` are untrusted text in a hosted realm.
|
|
45
|
+
*/
|
|
46
|
+
export const Notice = z.object({
|
|
47
|
+
id: NoticeId,
|
|
48
|
+
category: z.enum(["message", "request", "alert"]),
|
|
49
|
+
conversation: ConversationRef.optional(),
|
|
50
|
+
from: z.object({ name: z.string().max(200), email: z.string().max(320).optional(), org: z.enum(["same", "other", "none"]) }).optional(),
|
|
51
|
+
line: z.string().max(NOTICE_LINE_CHARS).optional(),
|
|
52
|
+
device: NoticeDevice.optional(),
|
|
53
|
+
at: Millis,
|
|
54
|
+
});
|
|
55
|
+
/** What the person's badges count: unread lines in their conversations, and requests held for them. */
|
|
56
|
+
export const NoticeCounts = z.object({ unread: z.number().int().nonnegative(), requests: z.number().int().nonnegative() });
|
|
57
|
+
/** `daemon.health` (spec §8.2): the daemon's state, with no secrets and no counts. */
|
|
58
|
+
export const DaemonHealth = z.object({
|
|
59
|
+
state: z.enum(["starting", "running", "stopping"]),
|
|
60
|
+
relay: z.enum(["connected", "connecting", "offline"]),
|
|
61
|
+
/** `ok` in a roster realm, and in a directory realm whose directory answers; `alarm` when it contradicted itself. */
|
|
62
|
+
directory: z.enum(["ok", "unavailable", "alarm"]),
|
|
63
|
+
alarm: z.enum(["restored", "keys_missing", "directory"]).nullable(),
|
|
64
|
+
/** True while items wait that no registered session's client has been handed yet. */
|
|
65
|
+
busy: z.boolean(),
|
|
66
|
+
version: z.string().max(64),
|
|
67
|
+
storeSchema: z.number().int().positive(),
|
|
68
|
+
});
|
|
69
|
+
/** One local agent (a registered or kept session) and its traffic on this device. */
|
|
70
|
+
export const AgentSummary = z.object({
|
|
71
|
+
agent: SessionName,
|
|
72
|
+
/** Other agents it has mail or tasks with. */
|
|
73
|
+
conversations: z.number().int().nonnegative(),
|
|
74
|
+
/** Mail it received and has not read. */
|
|
75
|
+
unread: z.number().int().nonnegative(),
|
|
76
|
+
lastAt: Millis.optional(),
|
|
77
|
+
state: z.enum(["idle", "busy", "kept"]),
|
|
78
|
+
});
|
|
79
|
+
const AgentConversation = z.object({ conversation: ConversationRef, unread: z.number().int().nonnegative(), lastAt: Millis });
|
|
80
|
+
export const JELLY_METHODS = {
|
|
81
|
+
/**
|
|
82
|
+
* Agent tier. A cheap status for a host that polls: the daemon's state, the relay, the directory, an alarm, and
|
|
83
|
+
* whether it is busy. No counts: they are the person's (`notice.list`).
|
|
84
|
+
*/
|
|
85
|
+
"daemon.health": { params: z.strictObject({}), result: DaemonHealth },
|
|
86
|
+
/** Human tier. This person's local agents on this device, most recent first. Read-only. */
|
|
87
|
+
"agents.list": { params: z.strictObject({}), result: z.object({ agents: z.array(AgentSummary) }) },
|
|
88
|
+
/**
|
|
89
|
+
* Human tier. One local agent's traffic: without `conversation`, the other agents it has mail or tasks with, most
|
|
90
|
+
* recent first; with `conversation` (`agent:<person>/<agent>`), a page of its entries, as `conv.read` gives them to
|
|
91
|
+
* the agent, latest first unless `before` names an entry. Changes no stage and sends no receipt. Peer text is
|
|
92
|
+
* untrusted: a client cleans it for display.
|
|
93
|
+
*/
|
|
94
|
+
"agents.read": {
|
|
95
|
+
params: z.strictObject({
|
|
96
|
+
agent: SessionName,
|
|
97
|
+
conversation: ConversationRef.refine((c) => c.startsWith("agent:"), { message: "a conversation with an agent: agent:<person>/<agent>" }).optional(),
|
|
98
|
+
before: ConvCursor.optional(),
|
|
99
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
100
|
+
}),
|
|
101
|
+
result: z.discriminatedUnion("kind", [
|
|
102
|
+
z.object({ kind: z.literal("conversations"), conversations: z.array(AgentConversation) }),
|
|
103
|
+
z.object({ kind: z.literal("entries"), conversation: ConversationRef, entries: z.array(ConvEntry), more: z.boolean() }),
|
|
104
|
+
]),
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* Human tier; host scope in `deployment: "jelly"`. Replays the unacknowledged notices after `after` (all of them
|
|
108
|
+
* without it), oldest first, as `notice` events on this connection, then sends each new one. With `lines`, a
|
|
109
|
+
* message carries its first line. A subscription under a host-scope grant ends when the grant expires.
|
|
110
|
+
*/
|
|
111
|
+
"notice.subscribe": {
|
|
112
|
+
params: z.strictObject({ after: NoticeId.optional(), lines: z.boolean().optional() }),
|
|
113
|
+
result: z.object({ replayed: z.number().int().nonnegative() }),
|
|
114
|
+
},
|
|
115
|
+
/** Human tier; host scope in `deployment: "jelly"`. The unacknowledged notices, newest first, and the badge counts. */
|
|
116
|
+
"notice.list": {
|
|
117
|
+
params: z.strictObject({ lines: z.boolean().optional(), limit: z.number().int().min(1).max(200).optional() }),
|
|
118
|
+
result: z.object({ notices: z.array(Notice), counts: NoticeCounts }),
|
|
119
|
+
},
|
|
120
|
+
/** Human tier. Marks notices seen; unknown ids are ignored. */
|
|
121
|
+
"notice.ack": { params: z.strictObject({ ids: z.array(NoticeId).min(1).max(200) }), result: z.object({ ok: z.literal(true) }) },
|
|
122
|
+
/**
|
|
123
|
+
* Admin tier; host scope in `deployment: "jelly"` (Jelly's M-3). The host sends it after the machine wakes from sleep:
|
|
124
|
+
* the daemon retries its relay link at once instead of at its backoff, runs its sweep, sends presence, and disarms a
|
|
125
|
+
* wake ticket it armed (`wake.disarm`), which this wake spent or no longer needs.
|
|
126
|
+
*/
|
|
127
|
+
"daemon.woke": { params: z.strictObject({}), result: z.object({ ok: z.literal(true) }) },
|
|
128
|
+
/**
|
|
129
|
+
* Admin tier; host scope in `deployment: "jelly"` (Jelly's M-4). Whether a window of the person is visible on this
|
|
130
|
+
* host. While it is not, under a Jelly host, this device's presence says `away` (spec §6.5), and the person's
|
|
131
|
+
* contacts see that device as away. Before the first call a Jelly host's device is away; elsewhere never.
|
|
132
|
+
*/
|
|
133
|
+
"person.attached": { params: z.strictObject({ attached: z.boolean() }), result: z.object({ ok: z.literal(true) }) },
|
|
134
|
+
/**
|
|
135
|
+
* Admin tier; host scope in `deployment: "jelly"` (Jelly's A5). Before the host sleeps: the daemon hands its relay the
|
|
136
|
+
* host's wake ticket (`jelly-wake+jwt`, for `url`, Jelly ID's wake endpoint of this host), the sessions in `agents`
|
|
137
|
+
* whose own person's mail may wake the host, and the tasks those sessions await (their unfinished requests). The relay
|
|
138
|
+
* presents the ticket once, for such traffic only, while this device is offline (spec §7, "Wake tickets"). `relay`:
|
|
139
|
+
* `armed` once the relay holds it, `pending` while it is not connected (it is sent at the next connection);
|
|
140
|
+
* `awaiting`: how many task requests the relay may wake for. A directory realm only.
|
|
141
|
+
*/
|
|
142
|
+
"wake.arm": {
|
|
143
|
+
params: z.strictObject({
|
|
144
|
+
url: WakeUrl,
|
|
145
|
+
ticket: Jws.refine((ticket) => ticket.length <= WAKE_TICKET_BYTES, { message: `at most ${WAKE_TICKET_BYTES} bytes` }),
|
|
146
|
+
agents: z.array(SessionName).max(WAKE_AGENTS),
|
|
147
|
+
}),
|
|
148
|
+
result: z.object({ relay: z.enum(["armed", "pending"]), awaiting: z.number().int().nonnegative() }),
|
|
149
|
+
},
|
|
150
|
+
/** Admin tier; host scope in `deployment: "jelly"`. The relay forgets this device's wake ticket (`pending` until connected). */
|
|
151
|
+
"wake.disarm": { params: z.strictObject({}), result: z.object({ relay: z.enum(["disarmed", "pending"]) }) },
|
|
152
|
+
/**
|
|
153
|
+
* Admin tier; host scope in `deployment: "jelly"` (Jelly's backup proposal, design §13.9). `{}` writes a consistent
|
|
154
|
+
* copy of the home while the daemon runs into `folder`, `<run>/snapshot` (the socket folder), and holds it there:
|
|
155
|
+
* the store (`messaging.db`, by SQLite `VACUUM INTO`), `config.json` and `manifest.json`. Never a private key:
|
|
156
|
+
* `identity.json` is not copied. `{done: true}` releases it (deletes the folder); a held snapshot is also released
|
|
157
|
+
* after an hour and when the daemon starts. `invalid_state` while one is held.
|
|
158
|
+
*/
|
|
159
|
+
"daemon.snapshot": {
|
|
160
|
+
params: z.strictObject({ done: z.literal(true).optional() }),
|
|
161
|
+
result: z.union([
|
|
162
|
+
z.object({
|
|
163
|
+
folder: z.string().max(4096),
|
|
164
|
+
files: z.array(z.object({ name: z.string().max(64), bytes: z.number().int().nonnegative(), sha256: z.string().regex(/^[0-9a-f]{64}$/) })).max(8),
|
|
165
|
+
storeSchema: z.number().int().positive(),
|
|
166
|
+
at: Millis,
|
|
167
|
+
/** When the daemon releases it unasked. */
|
|
168
|
+
until: Millis,
|
|
169
|
+
}),
|
|
170
|
+
z.object({ released: z.boolean() }),
|
|
171
|
+
]),
|
|
172
|
+
},
|
|
173
|
+
/**
|
|
174
|
+
* Human tier, read-only. What removing `device` would remove, for a confirmation: its label, kind and vouch, and
|
|
175
|
+
* whether it is current. `not_found` for a device that is not this person's.
|
|
176
|
+
*/
|
|
177
|
+
/** `device.remove`'s input (spec §8.2, "Previews"); `reason` and `token` are not used, and `token` is never logged. */
|
|
178
|
+
"device.remove.preview": {
|
|
179
|
+
params: DeviceRemoveParams,
|
|
180
|
+
result: z.object({
|
|
181
|
+
label: MachineLabel,
|
|
182
|
+
kind: DeviceKind.optional(),
|
|
183
|
+
vouch: z.enum(["jelly", "device", "admin"]),
|
|
184
|
+
current: z.boolean(),
|
|
185
|
+
self: z.boolean(),
|
|
186
|
+
since: Millis.optional(),
|
|
187
|
+
}),
|
|
188
|
+
},
|
|
189
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shapes the local API's methods share (spec §8.2): session keys, inbox items, task records, send
|
|
3
|
+
* results, directory entries, conversation entries and chat lines. Each lane's method file under
|
|
4
|
+
* `schema/ipc/` imports them from here, and `schema/ipc.ts` re-exports them, so no method file
|
|
5
|
+
* imports `ipc.ts` (which spreads their tables).
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "../../../vendor/zod/index.js";
|
|
8
|
+
import { Task, TaskState } from "../a2a.js";
|
|
9
|
+
import { AgentKind, DeliveryStage, DeliveryTier, MachineId, MachineLabel, Millis, PersonId, SessionAddress, SessionName, SessionState, Ulid, LIMITS, } from "../common.js";
|
|
10
|
+
import { DelegationSummary } from "../delegation.js";
|
|
11
|
+
import { ErrorCode } from "../errors.js";
|
|
12
|
+
/** Opaque, client-chosen session key, stable for the life of a client session, e.g. "opencode:<sessionID>". */
|
|
13
|
+
export const SessionKey = z.string().min(1).max(200);
|
|
14
|
+
/**
|
|
15
|
+
* When the recipient will likely see a message (spec §8.2): a hint from its machine's last presence
|
|
16
|
+
* at send time, never a promise.
|
|
17
|
+
* - `next_turn`: a live push session that is busy; its client shows the message after its next tool call or at its next turn.
|
|
18
|
+
* - `wake`: an idle push session, or a kept one its host can load again, whose machine acts on wake hints; the
|
|
19
|
+
* client may start a turn for it, within its own budget.
|
|
20
|
+
* - `next_prompt`: a live push session that is idle and will not be woken; it sees the message when next prompted.
|
|
21
|
+
* - `on_read`: a pull session; its agent sees the message when it checks its inbox.
|
|
22
|
+
* - `kept`: no client holds the session; the message waits for one to register it again.
|
|
23
|
+
* - `offline`: the recipient machine is not connected; the relay holds the envelope until it connects or it expires.
|
|
24
|
+
*/
|
|
25
|
+
export const SendExpect = z.enum(["next_turn", "wake", "next_prompt", "on_read", "kept", "offline"]);
|
|
26
|
+
/** One agent session of a person, as this device knows it: `agent` is its name, `client` its client kind. */
|
|
27
|
+
export const DirectoryEntry = z.object({
|
|
28
|
+
address: SessionAddress,
|
|
29
|
+
person: PersonId,
|
|
30
|
+
agent: SessionName,
|
|
31
|
+
device: MachineId,
|
|
32
|
+
label: MachineLabel,
|
|
33
|
+
client: AgentKind,
|
|
34
|
+
repo: z.string().optional(),
|
|
35
|
+
branch: z.string().optional(),
|
|
36
|
+
state: SessionState,
|
|
37
|
+
doing: z.string().optional(),
|
|
38
|
+
tier: DeliveryTier,
|
|
39
|
+
online: z.boolean(),
|
|
40
|
+
lastSeen: Millis,
|
|
41
|
+
self: z.boolean().describe("true for sessions registered on this machine"),
|
|
42
|
+
/**
|
|
43
|
+
* When a message sent to this session now, with the wake hint set, would likely be seen: the
|
|
44
|
+
* same `SendExpect` a `msg.send` result carries (spec §8.2). Absent when no presence lists it.
|
|
45
|
+
*/
|
|
46
|
+
expect: SendExpect.optional(),
|
|
47
|
+
});
|
|
48
|
+
/** Chat text, at most `LIMITS.chatChars` UTF-16 code units (`max` would count code points). */
|
|
49
|
+
export const ChatText = z.string().refine((text) => text.length <= LIMITS.chatChars, { message: `at most ${LIMITS.chatChars} UTF-16 code units` });
|
|
50
|
+
/**
|
|
51
|
+
* A position in this machine's chat order (spec §6.8): every filed line and every chat change takes
|
|
52
|
+
* the next one. Local: another machine numbers the same line differently. 0 is before the first.
|
|
53
|
+
*/
|
|
54
|
+
export const ChatSeq = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
55
|
+
/** One transcript line (spec §6.8). `id` is the line's `messageId`, the same on every machine. */
|
|
56
|
+
export const ChatLine = z.object({
|
|
57
|
+
id: Ulid,
|
|
58
|
+
/** This machine's filing order of the line: the cursor for `conv.read` paging and `conv.markRead`. */
|
|
59
|
+
seq: ChatSeq,
|
|
60
|
+
at: Millis,
|
|
61
|
+
person: PersonId,
|
|
62
|
+
/** The sending agent, when an agent posted it; absent on the person's own line. */
|
|
63
|
+
agent: SessionName.optional(),
|
|
64
|
+
direction: z.enum(["in", "out"]),
|
|
65
|
+
body: ChatText,
|
|
66
|
+
/** The `id` of the thread's first line; absent on a first line. */
|
|
67
|
+
thread: Ulid.optional(),
|
|
68
|
+
/**
|
|
69
|
+
* On a line this machine sent to other persons (spec §6.8): `sent` until every person has a
|
|
70
|
+
* delivered copy, `delivered` then, `refused` once some person's every copy was rejected or expired.
|
|
71
|
+
* `held` while every person still to deliver it holds it for their person to answer: a message request
|
|
72
|
+
* waiting to be accepted (spec §6.9).
|
|
73
|
+
*/
|
|
74
|
+
state: z.enum(["sent", "held", "delivered", "refused"]).optional(),
|
|
75
|
+
/** With `refused`: the persons whose every copy was rejected or expired. */
|
|
76
|
+
refused: z.array(PersonId).max(LIMITS.channelPersons).optional(),
|
|
77
|
+
/** A line this daemon wrote about a channel change or a refusal of one; its text is the daemon's, never a peer's. */
|
|
78
|
+
notice: z.literal(true).optional(),
|
|
79
|
+
});
|
|
80
|
+
/** The other end of an item or a task: a person's device and, when an agent wrote it, the agent. */
|
|
81
|
+
export const PeerRef = z.object({
|
|
82
|
+
address: SessionAddress,
|
|
83
|
+
person: PersonId,
|
|
84
|
+
agent: SessionName.optional(),
|
|
85
|
+
device: MachineId,
|
|
86
|
+
label: MachineLabel,
|
|
87
|
+
});
|
|
88
|
+
export const InboxItem = z.object({
|
|
89
|
+
id: Ulid.describe("envelope id"),
|
|
90
|
+
key: SessionKey,
|
|
91
|
+
from: PeerRef,
|
|
92
|
+
kind: z.enum(["message", "task"]),
|
|
93
|
+
messageId: z.string().min(1).max(128),
|
|
94
|
+
taskId: Ulid.optional(),
|
|
95
|
+
contextId: z.string().max(128).optional(),
|
|
96
|
+
replyTo: Ulid.optional(),
|
|
97
|
+
receivedAt: Millis,
|
|
98
|
+
stage: z.enum(["delivered", "handed_to_agent", "read"]),
|
|
99
|
+
bytes: z.number().int().nonnegative(),
|
|
100
|
+
/** Untrusted peer text, truncated. Clients must frame it as data, never as instructions. */
|
|
101
|
+
preview: z.string().max(LIMITS.previewChars),
|
|
102
|
+
wake: z.boolean().describe("sender hint, already filtered by local policy"),
|
|
103
|
+
/** kind "task" only: the delegation chain this daemon verified (spec §6.7). */
|
|
104
|
+
chain: DelegationSummary.optional(),
|
|
105
|
+
/**
|
|
106
|
+
* How a request from the sender may run here, as this daemon decided it (spec §6.9): `self` (this person's own),
|
|
107
|
+
* `trusted` (a person this person trusts, under the chain rule) or `guarded` (a peer turn). Never from peer content.
|
|
108
|
+
* Absent only on items stored before consent.
|
|
109
|
+
*/
|
|
110
|
+
consent: z.enum(["self", "guarded", "trusted"]).optional(),
|
|
111
|
+
});
|
|
112
|
+
/**
|
|
113
|
+
* A message this side sent to continue a task it requested (`msg.send` with `kind: "message"`
|
|
114
|
+
* and the `taskId`), and how far it got, from this daemon's own delivery records (spec §6.3).
|
|
115
|
+
*/
|
|
116
|
+
export const TaskFollowUp = z.object({
|
|
117
|
+
envelopeId: Ulid,
|
|
118
|
+
messageId: z.string().min(1).max(128),
|
|
119
|
+
sentAt: Millis,
|
|
120
|
+
stage: DeliveryStage,
|
|
121
|
+
/** When it reached `stage`. */
|
|
122
|
+
at: Millis,
|
|
123
|
+
});
|
|
124
|
+
export const TaskRecord = z.object({
|
|
125
|
+
task: Task.extend({ id: Ulid }),
|
|
126
|
+
role: z.enum(["requester", "executor"]),
|
|
127
|
+
key: SessionKey.describe("local session that owns this side of the task"),
|
|
128
|
+
peer: PeerRef,
|
|
129
|
+
createdAt: Millis,
|
|
130
|
+
updatedAt: Millis,
|
|
131
|
+
/** The delegation chain of this task (spec §6.7): as verified on arrival (executor), or as sent (requester). */
|
|
132
|
+
chain: DelegationSummary.optional(),
|
|
133
|
+
/**
|
|
134
|
+
* Requester side, in `task.get` results and `task` events only (not `task.list`): the newest
|
|
135
|
+
* `LIMITS.taskFollowUps` follow-ups this daemon still has delivery records for, oldest first.
|
|
136
|
+
* Absent when there are none.
|
|
137
|
+
*/
|
|
138
|
+
followUps: z.array(TaskFollowUp).min(1).max(LIMITS.taskFollowUps).optional(),
|
|
139
|
+
});
|
|
140
|
+
/**
|
|
141
|
+
* Where a send went (`msg.send`, spec §8.2): an agent session `person/agent@label`, a person (a line
|
|
142
|
+
* from an agent to them) or `#<channel>` (a post).
|
|
143
|
+
*/
|
|
144
|
+
export const SendTarget = z.union([SessionAddress, PersonId, z.string().regex(/^#[a-z][a-z0-9-]{0,31}$/, "#<channel>")]);
|
|
145
|
+
export const SendResult = z.object({
|
|
146
|
+
to: SendTarget,
|
|
147
|
+
envelopeId: Ulid,
|
|
148
|
+
messageId: z.string().min(1).max(128),
|
|
149
|
+
taskId: Ulid.optional(),
|
|
150
|
+
contextId: z.string().max(128),
|
|
151
|
+
stage: DeliveryStage,
|
|
152
|
+
code: ErrorCode.optional(),
|
|
153
|
+
/** Absent when the stage is `rejected` or `expired`, or when no presence lists the session. */
|
|
154
|
+
expect: SendExpect.optional(),
|
|
155
|
+
});
|
|
156
|
+
/** A place in a conversation page (`conv.read`): an entry's time and id. */
|
|
157
|
+
export const ConvCursor = z.strictObject({ at: Millis, id: Ulid });
|
|
158
|
+
/**
|
|
159
|
+
* One entry of a conversation between a local session and one other session (spec §8.2
|
|
160
|
+
* `conv.read`): mail either way, or a task either way. `text` is untrusted peer text for incoming
|
|
161
|
+
* mail and for a task the other side asked for; clients frame it as data.
|
|
162
|
+
*/
|
|
163
|
+
export const ConvEntry = z.object({
|
|
164
|
+
/** The envelope id (mail) or the task id (task). */
|
|
165
|
+
id: Ulid,
|
|
166
|
+
kind: z.enum(["mail", "task"]),
|
|
167
|
+
/** `in`: from the other session (mail it sent, a task it asked this session to run); `out`: the reverse. */
|
|
168
|
+
direction: z.enum(["in", "out"]),
|
|
169
|
+
/** The other session, as this machine knows it. */
|
|
170
|
+
peer: SessionAddress,
|
|
171
|
+
/** Mail: received (in) or sent (out). Task: created. */
|
|
172
|
+
at: Millis,
|
|
173
|
+
/**
|
|
174
|
+
* Incoming mail: its preview. Task: the start of its request. Absent on outgoing mail: the
|
|
175
|
+
* daemon keeps no copy of what it sent.
|
|
176
|
+
*/
|
|
177
|
+
text: z.string().max(LIMITS.previewChars).optional(),
|
|
178
|
+
/** Mail that continues a task. */
|
|
179
|
+
taskId: Ulid.optional(),
|
|
180
|
+
/** Mail: its stage (incoming: this side's inbox stage; outgoing: how far it got). */
|
|
181
|
+
stage: DeliveryStage.optional(),
|
|
182
|
+
/** Task: this session's role and the task's state now. */
|
|
183
|
+
role: z.enum(["requester", "executor"]).optional(),
|
|
184
|
+
state: TaskState.optional(),
|
|
185
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The local API's surface methods (spec §8.2; design §7.5): what agents reach through their tools
|
|
3
|
+
* and what people reach through the CLI and the TUI, by conversation.
|
|
4
|
+
*
|
|
5
|
+
* - Agent tier (`run/agent.sock`): `contacts.list`, `msg.send`, `item.read`, `conv.read`, `search`.
|
|
6
|
+
* - Human tier (`run/admin.sock`): `conv.list`, `conv.read`, `conv.markRead`, `conv.subscribe`,
|
|
7
|
+
* `person.send`, `people.search`, `people.get`.
|
|
8
|
+
*
|
|
9
|
+
* A conversation is named by a `ConversationRef`: `dm:<person>` (this person's DM with another),
|
|
10
|
+
* `channel:<name>` (a channel this person is in, by its local name) or `agent:<person>/<agent>`
|
|
11
|
+
* (a local session's mail and tasks with that agent, on any of the person's devices).
|
|
12
|
+
*/
|
|
13
|
+
import { z } from "../../../vendor/zod/index.js";
|
|
14
|
+
import { Message, Part } from "../a2a.js";
|
|
15
|
+
import { Address, DeliveryStage, MachineId, MachineLabel, Millis, PersonId, RoomRef, Ulid, LIMITS } from "../common.js";
|
|
16
|
+
import { DelegationScope } from "../delegation.js";
|
|
17
|
+
import { Email } from "../directory-frames.js";
|
|
18
|
+
import { ErrorCode } from "../errors.js";
|
|
19
|
+
import { PersonReach } from "./consent.js";
|
|
20
|
+
import { DeviceChangeView } from "./device.js";
|
|
21
|
+
import { ChatLine, ChatSeq, ChatText, ConvCursor, ConvEntry, DirectoryEntry, InboxItem, SendResult, SendTarget, SessionKey } from "./shapes.js";
|
|
22
|
+
/**
|
|
23
|
+
* Max UTF-8 bytes of `msg.send` metadata as JSON. For wire JSON that is its JCS length: JCS only
|
|
24
|
+
* reorders members.
|
|
25
|
+
*/
|
|
26
|
+
const METADATA_BYTES = 16_384;
|
|
27
|
+
/** A conversation: `dm:<person>`, `channel:<name>` or `agent:<person>/<agent>`. */
|
|
28
|
+
export const ConversationRef = z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(/^(?:dm:(?:[a-z][a-z0-9-]{0,31}|p_[a-z2-7]{26})|channel:[a-z][a-z0-9-]{0,31}|agent:(?:[a-z][a-z0-9-]{0,31}|p_[a-z2-7]{26})\/[a-z0-9][a-z0-9._-]{0,63})$/, "dm:<person>, channel:<name> or agent:<person>/<agent>");
|
|
31
|
+
/** A place in a conversation: a line's `seq` in a DM or a channel, an entry's `{at, id}` with an agent. */
|
|
32
|
+
export const ConvPlace = z.union([ChatSeq, ConvCursor]);
|
|
33
|
+
/** One conversation this person has (DMs with lines, channels it is in) and this device's read state. */
|
|
34
|
+
export const ConvSummary = z.object({
|
|
35
|
+
conversation: RoomRef,
|
|
36
|
+
kind: z.enum(["dm", "channel"]),
|
|
37
|
+
lastAt: Millis.optional(),
|
|
38
|
+
/** A channel held under a local name (another channel here already had its name) carries its own name. */
|
|
39
|
+
name: z.string().max(32).optional(),
|
|
40
|
+
/** The newest line's seq; absent in a channel with no lines. */
|
|
41
|
+
lastSeq: ChatSeq.optional(),
|
|
42
|
+
/** 0 when nothing is marked read. */
|
|
43
|
+
readUpTo: ChatSeq,
|
|
44
|
+
/** Lines from others after `readUpTo`. */
|
|
45
|
+
unread: z.number().int().nonnegative(),
|
|
46
|
+
});
|
|
47
|
+
/** A person of the realm, as verified (a roster realm: a team person). */
|
|
48
|
+
export const PersonSummary = z.object({
|
|
49
|
+
person: PersonId,
|
|
50
|
+
/** The display name; a roster realm has only the person name. Untrusted text in a hosted realm. */
|
|
51
|
+
name: z.string().max(200),
|
|
52
|
+
/** What a person types to address them. */
|
|
53
|
+
handle: z.string().max(64),
|
|
54
|
+
/** `same`: in this device's person's org (a roster realm: the team). */
|
|
55
|
+
org: z.enum(["same", "other", "none"]),
|
|
56
|
+
/** The weakest vouch among their current devices. */
|
|
57
|
+
vouched: z.enum(["jelly", "device", "service", "admin"]),
|
|
58
|
+
});
|
|
59
|
+
export const SURFACE_METHODS = {
|
|
60
|
+
/**
|
|
61
|
+
* Agent tier. Who this session can reach: every known agent session (`entries`, as `directory.list`
|
|
62
|
+
* did in 0.1), and this person's conversations with their read state (`conversations`, as
|
|
63
|
+
* `conv.list` returns them).
|
|
64
|
+
*/
|
|
65
|
+
"contacts.list": {
|
|
66
|
+
params: z.strictObject({ person: PersonId.optional(), includeOffline: z.boolean().optional() }),
|
|
67
|
+
/**
|
|
68
|
+
* `reach`: for each other person among `entries` and the DMs, and each person whose card this device holds, what they may
|
|
69
|
+
* be sent, from their card or the defaults of the relation (spec §6.9, "Contact cards").
|
|
70
|
+
*/
|
|
71
|
+
result: z.object({ entries: z.array(DirectoryEntry), conversations: z.array(ConvSummary), reach: z.array(PersonReach) }),
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* Agent tier. One send from the session `key` (spec §8.2): mail or a task to an agent
|
|
75
|
+
* (`person/agent[@label]`), a line to a person (`<person>`), or a post to a channel (`#<channel>`).
|
|
76
|
+
* A line or a post is attributed to the session; it takes one text part of at most
|
|
77
|
+
* `LIMITS.chatChars`, `kind: "message"`, and `replyTo` as the thread (the thread's first line),
|
|
78
|
+
* and none of the A2A fields. `idempotencyKey` makes any send idempotent.
|
|
79
|
+
*/
|
|
80
|
+
"msg.send": {
|
|
81
|
+
params: z.strictObject({
|
|
82
|
+
key: SessionKey,
|
|
83
|
+
to: z.union([Address, z.string().regex(/^#[a-z][a-z0-9-]{0,31}$/, "#<channel>")]),
|
|
84
|
+
kind: z.enum(["message", "task"]),
|
|
85
|
+
parts: z.array(Part).min(1).max(LIMITS.partsPerMessage),
|
|
86
|
+
contextId: z.string().max(128).optional(),
|
|
87
|
+
taskId: Ulid.optional().describe("for kind=message: the task this message continues (INPUT_REQUIRED replies)"),
|
|
88
|
+
replyTo: Ulid.optional(),
|
|
89
|
+
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
90
|
+
/** A2A passthrough. The daemon generates messageId when omitted and always overwrites metadata[URI.provenance]. */
|
|
91
|
+
messageId: z.string().min(1).max(128).optional(),
|
|
92
|
+
metadata: z
|
|
93
|
+
.record(z.string(), z.unknown())
|
|
94
|
+
.refine((m) => new TextEncoder().encode(JSON.stringify(m)).length <= METADATA_BYTES, { message: `metadata is limited to ${METADATA_BYTES} bytes of JSON` })
|
|
95
|
+
.optional(),
|
|
96
|
+
extensions: z.array(z.string().min(1).max(256)).max(16).optional(),
|
|
97
|
+
referenceTaskIds: z.array(Ulid).max(16).optional(),
|
|
98
|
+
wake: z.boolean().optional().describe("hint that the recipient may wake an idle session; default true"),
|
|
99
|
+
/**
|
|
100
|
+
* Only on a line to the session's own person (`<own person>`, spec §6.9 "Message requests"): the agent asks its
|
|
101
|
+
* person to send a message request to this exact email. The daemon files it for the person to answer; only the
|
|
102
|
+
* person sends the request.
|
|
103
|
+
*/
|
|
104
|
+
request: z.strictObject({ contact: Email }).optional(),
|
|
105
|
+
/**
|
|
106
|
+
* kind=task only (spec §6.7): delegate on behalf of a task this session executes. The daemon extends that
|
|
107
|
+
* task's chain to each recipient, narrowed to `scope` and `maxDepth` when given. Without it, a task starts a new chain.
|
|
108
|
+
*/
|
|
109
|
+
onBehalfOf: z
|
|
110
|
+
.strictObject({
|
|
111
|
+
taskId: Ulid.describe("a task this session executes"),
|
|
112
|
+
scope: z.array(DelegationScope).max(LIMITS.delegationScopes).optional().describe("narrow the scope; never wider than the task's"),
|
|
113
|
+
maxDepth: z.number().int().min(1).max(LIMITS.delegationDepth).optional().describe("narrow the depth; never above the task's"),
|
|
114
|
+
})
|
|
115
|
+
.optional(),
|
|
116
|
+
}),
|
|
117
|
+
result: z.object({ results: z.array(SendResult) }),
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* Agent tier. One item of the session `key` by id: something it received (`received`: returns the
|
|
121
|
+
* full message, marks it read and sends a read receipt, as `inbox.read` did in 0.1), or something
|
|
122
|
+
* it sent to an agent (`sent`: how far it got, as `message.status` did). `not_found` otherwise.
|
|
123
|
+
*/
|
|
124
|
+
"item.read": {
|
|
125
|
+
params: z.strictObject({ key: SessionKey, id: Ulid }),
|
|
126
|
+
result: z.discriminatedUnion("kind", [
|
|
127
|
+
z.object({ kind: z.literal("received"), item: InboxItem, message: Message }),
|
|
128
|
+
z.object({
|
|
129
|
+
kind: z.literal("sent"),
|
|
130
|
+
envelopeId: Ulid,
|
|
131
|
+
to: SendTarget,
|
|
132
|
+
stage: DeliveryStage,
|
|
133
|
+
history: z.array(z.object({ stage: DeliveryStage, at: Millis, code: ErrorCode.optional(), note: z.string().optional() })),
|
|
134
|
+
}),
|
|
135
|
+
]),
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Agent and human tiers. A page of one conversation, oldest first. With no cursor, the latest
|
|
139
|
+
* `limit` (default 30 with an agent, 50 in a DM or a channel); with `before`, the latest `limit`
|
|
140
|
+
* before that place; with `after`, the earliest `limit` after it. `more`: entries remain past the
|
|
141
|
+
* page in the direction read. Changes no stage and sends no receipt.
|
|
142
|
+
* - `dm:` and `channel:` give `lines`; `thread` narrows to a thread's first line and its replies;
|
|
143
|
+
* the cursor is a line's `seq`.
|
|
144
|
+
* - `agent:<person>/<agent>` needs the session `key` and gives `entries`: mail either way and tasks
|
|
145
|
+
* either way between that session and the agent; the cursor is an entry's `{at, id}`.
|
|
146
|
+
*/
|
|
147
|
+
"conv.read": {
|
|
148
|
+
params: z
|
|
149
|
+
.strictObject({
|
|
150
|
+
key: SessionKey.optional(),
|
|
151
|
+
conversation: ConversationRef,
|
|
152
|
+
thread: Ulid.optional(),
|
|
153
|
+
before: ConvPlace.optional(),
|
|
154
|
+
after: ConvPlace.optional(),
|
|
155
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
156
|
+
})
|
|
157
|
+
.refine((p) => p.before === undefined || p.after === undefined, { message: "before and after do not combine" }),
|
|
158
|
+
result: z.discriminatedUnion("kind", [
|
|
159
|
+
z.object({ kind: z.literal("lines"), lines: z.array(ChatLine), more: z.boolean() }),
|
|
160
|
+
z.object({ kind: z.literal("agent"), entries: z.array(ConvEntry), more: z.boolean() }),
|
|
161
|
+
]),
|
|
162
|
+
},
|
|
163
|
+
/** Agent tier. Full-text search over the DM and channel lines `conv.read` can read, newest first. */
|
|
164
|
+
search: {
|
|
165
|
+
params: z.strictObject({ query: z.string().min(1).max(200), conversation: RoomRef.optional(), limit: z.number().int().min(1).max(50).optional() }),
|
|
166
|
+
result: z.object({ hits: z.array(ChatLine.extend({ conversation: RoomRef })) }),
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Human tier. The channels this person is in and the DMs with lines, newest first, at most `limit`
|
|
170
|
+
* (default 100), each with this device's read state.
|
|
171
|
+
*/
|
|
172
|
+
"conv.list": {
|
|
173
|
+
params: z.strictObject({ limit: z.number().int().min(1).max(200).optional() }),
|
|
174
|
+
result: z.object({ conversations: z.array(ConvSummary) }),
|
|
175
|
+
},
|
|
176
|
+
/**
|
|
177
|
+
* Human tier. Marks a DM or a channel read on this device up to `upTo` (a line's `seq`): the cursor
|
|
178
|
+
* only moves forward, and never past the newest seq here. Returns the cursor and what is still unread.
|
|
179
|
+
*/
|
|
180
|
+
"conv.markRead": {
|
|
181
|
+
params: z.strictObject({ conversation: RoomRef, upTo: ChatSeq }),
|
|
182
|
+
result: z.object({ readUpTo: ChatSeq, unread: z.number().int().nonnegative() }),
|
|
183
|
+
},
|
|
184
|
+
/**
|
|
185
|
+
* Human tier. `conv` events go only to connections that call this (spec §6.8). With `since` (the
|
|
186
|
+
* `seq` of the last event the client handled), the daemon first replays one event per line changed
|
|
187
|
+
* after it, oldest first, or sends none and answers `gap: true` when it cannot replay them all; the
|
|
188
|
+
* client then reloads with `conv.list` and `conv.read`. `seq` is the newest seq at the call.
|
|
189
|
+
*/
|
|
190
|
+
"conv.subscribe": { params: z.strictObject({ since: ChatSeq.optional() }), result: z.object({ seq: ChatSeq, gap: z.boolean() }) },
|
|
191
|
+
/**
|
|
192
|
+
* Human tier. A line from the person (no agent) to a DM or a channel this person is in. `thread` is
|
|
193
|
+
* the `id` of the thread's first line. `clientId` makes the call idempotent: a repeat with the same
|
|
194
|
+
* `clientId` and the same conversation, thread and text returns the first line's result and sends nothing.
|
|
195
|
+
*/
|
|
196
|
+
"person.send": {
|
|
197
|
+
params: z.strictObject({
|
|
198
|
+
conversation: RoomRef,
|
|
199
|
+
text: ChatText.min(1),
|
|
200
|
+
thread: Ulid.optional(),
|
|
201
|
+
clientId: z.string().min(1).max(200).optional(),
|
|
202
|
+
}),
|
|
203
|
+
result: z.object({ id: Ulid, conversation: RoomRef }),
|
|
204
|
+
},
|
|
205
|
+
/** Human tier. The persons of the realm whose handle starts with `query` (all with none), at most `limit` (default 50). */
|
|
206
|
+
"people.search": {
|
|
207
|
+
params: z.strictObject({ query: z.string().max(100).optional(), limit: z.number().int().min(1).max(200).optional() }),
|
|
208
|
+
result: z.object({ people: z.array(PersonSummary) }),
|
|
209
|
+
},
|
|
210
|
+
/** Human tier. One person and their current devices, as verified. `not_found` for a person this realm does not know. */
|
|
211
|
+
"people.get": {
|
|
212
|
+
params: z.strictObject({ person: PersonId }),
|
|
213
|
+
/** A device's `away`: its latest presence, still fresh, says no window of the person is visible on it (spec §6.5). */
|
|
214
|
+
result: PersonSummary.extend({
|
|
215
|
+
devices: z.array(z.object({ device: MachineId, label: MachineLabel, self: z.boolean(), away: z.literal(true).optional() })),
|
|
216
|
+
/**
|
|
217
|
+
* Their devices added, removed or replaced in the last 7 days, newest first, at most 10 (design §2.8: "Bob's
|
|
218
|
+
* devices changed: added 'bob-vps' (vouched by Jelly ID)"). Absent for this device's own person and when none.
|
|
219
|
+
*/
|
|
220
|
+
deviceChanges: z.array(DeviceChangeView).max(10).optional(),
|
|
221
|
+
}),
|
|
222
|
+
},
|
|
223
|
+
};
|