@joe-broadhead/messaging 0.8.0 → 0.9.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 +150 -0
- package/dist/bin/messaging-mcp.js +202 -31
- package/dist/bin/messaging-relay.js +291 -43
- package/dist/bin/messaging.js +290 -32
- package/dist/bin/messagingd.js +1158 -138
- package/dist/lib/modules/core/agent/present.js +5 -0
- package/dist/lib/modules/core/agent-tools.js +12 -0
- package/dist/lib/modules/core/consent.js +54 -14
- package/dist/lib/modules/core/contact-card.js +3 -3
- package/dist/lib/modules/core/index.js +1 -1
- package/dist/lib/modules/core/jelly-host.js +3 -0
- package/dist/lib/modules/core/schema/common.js +2 -0
- package/dist/lib/modules/core/schema/consent.js +20 -3
- package/dist/lib/modules/core/schema/conv-frames.js +10 -3
- package/dist/lib/modules/core/schema/directory-frames.js +8 -3
- package/dist/lib/modules/core/schema/errors.js +3 -2
- package/dist/lib/modules/core/schema/ipc/consent.js +85 -10
- package/dist/lib/modules/core/schema/ipc/conversation-views.js +8 -1
- package/dist/lib/modules/core/schema/ipc/conversations.js +5 -1
- package/dist/lib/modules/core/schema/ipc/device.js +7 -0
- package/dist/lib/modules/core/schema/ipc/jelly.js +14 -7
- package/dist/lib/modules/core/schema/ipc/shapes.js +60 -1
- package/dist/lib/modules/core/schema/ipc.js +51 -3
- package/dist/lib/modules/core/version.js +1 -1
- package/dist/spec/messaging-v1.md +321 -49
- package/dist/types/core/agent/present.d.ts +1 -1
- package/dist/types/core/agent-tools.d.ts +32 -0
- package/dist/types/core/consent.d.ts +30 -7
- package/dist/types/core/contact-card.d.ts +3 -3
- package/dist/types/core/conversation-agent-tools.d.ts +21 -0
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/core/schema/common.d.ts +2 -0
- package/dist/types/core/schema/consent.d.ts +23 -2
- package/dist/types/core/schema/conv-frames.d.ts +6 -2
- package/dist/types/core/schema/directory-frames.d.ts +8 -3
- package/dist/types/core/schema/envelope.d.ts +2 -0
- package/dist/types/core/schema/errors.d.ts +2 -0
- package/dist/types/core/schema/ipc/consent.d.ts +377 -15
- package/dist/types/core/schema/ipc/conversation-views.d.ts +176 -0
- package/dist/types/core/schema/ipc/conversations.d.ts +368 -0
- package/dist/types/core/schema/ipc/device.d.ts +6 -0
- package/dist/types/core/schema/ipc/jelly.d.ts +14 -6
- package/dist/types/core/schema/ipc/shapes.d.ts +198 -0
- package/dist/types/core/schema/ipc/surfaces.d.ts +1 -0
- package/dist/types/core/schema/ipc/tasks.d.ts +2 -0
- package/dist/types/core/schema/ipc.d.ts +826 -3
- package/dist/types/core/schema/relay.d.ts +4 -0
- package/dist/types/core/version.d.ts +1 -1
- package/dist/types/ipc/server.d.ts +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,156 @@ Versions follow [Semantic Versioning](https://semver.org); see
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.0] - 2026-11-11
|
|
10
|
+
|
|
11
|
+
**What Jelly asked for in opencode-messaging #17, #18, #19 and #24.** The person sees their agents' tasks as a view
|
|
12
|
+
(who does the task, for whom, a sign-in it waits on, why it was refused) and stops one with a source-issued action.
|
|
13
|
+
Inbound tasks become notices. A resting Jelly host wakes for a teammate's dm. The person answers two questions about
|
|
14
|
+
who may reach them. The 0.8.0 follow-ups for mixed versions, the `conv.caps` presence oracle and the control-op drain
|
|
15
|
+
are here too.
|
|
16
|
+
|
|
17
|
+
**Upgrade the relay, then the daemons, in any order after it.** 0.9.0 and 0.8.0 daemons interoperate (below). Stores
|
|
18
|
+
only grow: the daemon store goes to schema 5 and the relay database to schema 4, and a rollback keeps the data.
|
|
19
|
+
|
|
20
|
+
### Compatibility
|
|
21
|
+
|
|
22
|
+
- **0.9.0 and 0.8.0 daemons interoperate** (spec §8.3). Nothing on the wire between daemons was removed or tightened.
|
|
23
|
+
- Conversations, message requests and tasks work in both directions. Both releases advertise the
|
|
24
|
+
`conversations` feature that `conv.caps` checks, so neither answers the other with `peer_outdated`.
|
|
25
|
+
- A 0.9.0 executor's sign-in names its tool in the task status as A2A extension data
|
|
26
|
+
(`metadata["urn:messaging:ext:sign-in:v1"]`, spec §6.2, "Sign-in"). A 0.8.0 requester still reads a valid status
|
|
27
|
+
and its text.
|
|
28
|
+
- A 0.9.0 device advertises the new presence feature `reach` (spec §6.5). A 0.8.0 device does not, and **does not
|
|
29
|
+
enforce the reach choices** (spec §6.9, "Older daemons do not enforce the choices"). A relation grant synced to a
|
|
30
|
+
0.8.0 device of the same person is skipped there (`grant.sync.skipped`), and it keeps working.
|
|
31
|
+
- **dm wakes need the relay at 0.9.0** (relay schema 4). A 0.8.0 relay parses `wake.arm` without `dms` and still arms
|
|
32
|
+
the host's agents and awaited tasks, so a 0.9.0 host behind it arms no dm and wakes as before. A 0.8.0 daemon never
|
|
33
|
+
sends `dms`, and a 0.9.0 relay arms it as before.
|
|
34
|
+
- **A 0.8.0 daemon behind a 0.9.0 relay** gets the new optional `unknown` in a `conv.caps` answer only over the new
|
|
35
|
+
budget (below). It cannot parse that answer and goes ahead without one, as 0.8.0 already did when `conv.caps` was
|
|
36
|
+
refused.
|
|
37
|
+
- **Rollback keeps the data.** Daemon schema 5 and relay schema 4 are additive, with generation 2 and `min_reader` 1,
|
|
38
|
+
so 0.8.0 opens both. A 0.8.0 daemon ignores the reach tables and does not enforce the choices; a 0.8.0 relay
|
|
39
|
+
ignores `dir_wake_dms`, and the rows it leaves name a ticket that no longer counts.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **The task view (#17)** (spec §8.2, "The task view"). Every `TaskRecord` the daemon projects carries fields it
|
|
44
|
+
computes and never stores or sends:
|
|
45
|
+
- `agent`, the counterpart session; `person: {id, name}`, verified by the realm, never parsed from an agent's name;
|
|
46
|
+
`org` (`same`, `other` or `none`);
|
|
47
|
+
- `signIn: {tool?, by}` in `AUTH_REQUIRED`, where `by` (`you` or `them`) is computed by the viewer's own daemon and
|
|
48
|
+
never read from the peer. An executor names the tool with `task.update {signIn: {tool}}`;
|
|
49
|
+
- `refusal: {reason}` in `REJECTED`.
|
|
50
|
+
- `tool` and `reason` come only from the executor itself, through `cleanPeerText`, capped at
|
|
51
|
+
`TASK_SIGN_IN_TOOL_CHARS` and `TASK_REFUSAL_CHARS`. Agent tools show them fenced when the peer wrote them.
|
|
52
|
+
- **Task notices (#18)** (spec §8.2, "Notices"). A task another person's agent asked of one of this person's agents
|
|
53
|
+
files a `message` notice with `key: "task:<task id>"` when this device accepts it. It copies no text and is never a
|
|
54
|
+
`request`. An ask held for consent stays a `request` until its person answers. A task between the person's own
|
|
55
|
+
agents files none.
|
|
56
|
+
- **dm wakes (#19)** (spec §7 and §8.11, "Wake tickets").
|
|
57
|
+
- `wake.arm` takes `dms`, at most `WAKE_DMS` (256). The relay presents the ticket for a line in an armed dm by its
|
|
58
|
+
other person while the host is offline, within the existing budget (1 a minute, 6 an hour, 48 a day).
|
|
59
|
+
- The daemon arms the verified dms whose other person shares a channel with this person as a member, and returns
|
|
60
|
+
their count (`dms`). It never arms a person this person blocked or muted, or one their reach choices hide.
|
|
61
|
+
- It reads the candidates' directory views in batches (`LIMITS.dirLookupPersons` to a lookup) before it decides.
|
|
62
|
+
- The list is what held at the arm: a block while the host sleeps applies at the next arm, so at most one wake.
|
|
63
|
+
- Neither side logs a dm's id. The relay can't tell a line from other sealed events in an armed dm, so a reaction or
|
|
64
|
+
edit there may also wake the host, within the budget.
|
|
65
|
+
- **Consent choices (#24)** (spec §6.9, "Reach choices").
|
|
66
|
+
- `reach.get`, `reach.set {revision, changes}` and `reach.set.preview`, on the human tier only. "Who can message
|
|
67
|
+
you" is Open, Ask me first (the default) or Never for people outside the org. "How agents may ask yours" is Open,
|
|
68
|
+
Ask me first or Never for the org's agents.
|
|
69
|
+
- Each choice is the person's own signed grant on a relation subject (`{relation: "outside" | "org"}`). A grant for
|
|
70
|
+
one person or conversation wins over it.
|
|
71
|
+
- A stale `revision` is the new error `conflict`, and nothing changes.
|
|
72
|
+
- Under Never, an outsider whose person grant sets no `chat` is refused, and their lines are hidden in existing
|
|
73
|
+
conversations at read time. Choosing again shows them. The org, the person's own lines and outsiders' agents are
|
|
74
|
+
never hidden. A person the realm no longer knows stays visible.
|
|
75
|
+
- CLI: `messaging reach`.
|
|
76
|
+
- **Action tokens** (spec §8.2, "Action tokens"; design §12.5).
|
|
77
|
+
- On the human tier the daemon puts `{name, token}` on what it renders: `actions` on every `request.list` item, one
|
|
78
|
+
per answer its kind takes, and `cancel` on a task this person's agent asked that has not ended. The executor side
|
|
79
|
+
never gets `cancel`.
|
|
80
|
+
- A token binds the action, the item and the view: the admin connection, and under a Jelly host the grant's `dev`
|
|
81
|
+
and `mbr`. It is spent at its first presentation, matched or not, and lives 10 minutes. At most 256 are held per
|
|
82
|
+
view and 4,096 in all. They are in memory only, so a restart invalidates every one.
|
|
83
|
+
- A token is never logged or printed, and the agent tier never receives one or redeems one.
|
|
84
|
+
- `request.answer` and `request.answer.preview` take `token`, never with `all`. Under a Jelly host `request.answer`
|
|
85
|
+
requires it. `task.cancel` takes exactly one of `key` (the requester session) and `token` (the person, human
|
|
86
|
+
tier). A preview checks a token without spending it. Every failure is `grant_invalid`.
|
|
87
|
+
- **The person's tasks, and Stop** (spec §8.2, "The person's tasks").
|
|
88
|
+
- `tasks.list` and `tasks.get`, on the admin socket with no session key, Jelly class `view`, read every side of a
|
|
89
|
+
task this device's sessions hold, as `PersonTask`. That is the task view as the owner sees it, the agent's
|
|
90
|
+
`session` name and never its key, and `cancel`, which stops the task through `task.cancel {taskId, token}`.
|
|
91
|
+
- A person-level `conv.sync` carries `task` entries, rendered for each reader. Task changes share the replay
|
|
92
|
+
buffer with conversation changes.
|
|
93
|
+
- Only this device's tasks are listed.
|
|
94
|
+
- **`task.question`** (spec §8.2, "The task view").
|
|
95
|
+
- On the agent tier, from the executing session only. It ties a task in `AUTH_REQUIRED` or `INPUT_REQUIRED` to the
|
|
96
|
+
host's own pending approval or question, `{sessionId, approvalId}`.
|
|
97
|
+
- It is held in memory only and untied by any later status. It is shown only on the person's own reads, never on
|
|
98
|
+
the agent tier, in an event, or to the peer.
|
|
99
|
+
- When the session registered a host session, `sessionId` must be that one (`forbidden`).
|
|
100
|
+
- **The `reach` presence feature** (spec §6.5): a device that enforces the reach choices says so.
|
|
101
|
+
- **`device.list` features** (spec §8.2, "Devices"). Each of the person's own devices lists the presence features last
|
|
102
|
+
verified from it. They are absent when none was seen, which means unknown, not none.
|
|
103
|
+
- **`request.waiting` states `resend` and `outdated`** (spec §8.2, §8.3 "Mixed versions").
|
|
104
|
+
- `resend`: a message request sent from 0.7.1 that its recipient held and nobody answered is shown, not re-signed
|
|
105
|
+
or resent. A request still in flight at the upgrade can't be told from a line and is not shown.
|
|
106
|
+
- `outdated`: a request whose recipient device answered with a `delivered` receipt, which only an older client sends
|
|
107
|
+
on accepting. It must be sent again once they update.
|
|
108
|
+
|
|
109
|
+
### Fixed
|
|
110
|
+
|
|
111
|
+
- **Presence for people who only talk in conversations** (spec §15.10). The presence audience, and the relation
|
|
112
|
+
`conv.caps` uses, counts accepted members of one conversation the relay sequences: an external dm only once
|
|
113
|
+
activated, never an invited participant. A cross-org contact no longer drops out of presence 30 days after their
|
|
114
|
+
last envelope.
|
|
115
|
+
- **A control op no longer fails with a spurious `conv_conflict`** when maintenance queues a share while it drains. It
|
|
116
|
+
drains until nothing is pending, up to 3 drains. A conversation that refills on every drain is `conv_conflict`, "the
|
|
117
|
+
conversation is busy sending; try again", which can be retried as is (spec §13).
|
|
118
|
+
|
|
119
|
+
### Security
|
|
120
|
+
|
|
121
|
+
- **The engine always sends the strictest cut asked for a removed device** (spec §16.4, "Device cuts"). This was
|
|
122
|
+
an ordering bug in the daemon's conversation engine, present since 0.8.0, that no 0.8.0 IPC call could reach.
|
|
123
|
+
- Asked to rekey a device with an earlier `until` while a weaker rekey of that device was already queued or in
|
|
124
|
+
flight, the engine returned the queued one and dropped the earlier cut.
|
|
125
|
+
- No IPC method passes `until`, the engine isn't part of the package's exports, and the daemon's automatic rekeys
|
|
126
|
+
after a removal never ask for a stricter cut than the one they queue.
|
|
127
|
+
- Fixed and guarded by tests:
|
|
128
|
+
- a queued rekey stands in only when its cut is at least as strict;
|
|
129
|
+
- two cuts of one device combine as a union in the verifier;
|
|
130
|
+
- a cut covers the device's held entries.
|
|
131
|
+
- **`conv.caps` is no longer a free presence oracle** (spec §7). Answers about unrelated persons count against
|
|
132
|
+
`LIMITS.requestsPerDay` (20) distinct persons per asking person per UTC day, shared by that person's devices.
|
|
133
|
+
Related persons, and persons already asked about that day, are always answered. Past the budget the answer names
|
|
134
|
+
the rest in `unknown`, and `peer_outdated` still applies to anyone named `outdated`.
|
|
135
|
+
- **A token answers only what it was offered for.** A forged, spent, expired, dropped or mismatched token, or one
|
|
136
|
+
from another view or an earlier start, does nothing. Under a Jelly host a call that carries a token needs an action
|
|
137
|
+
grant, even for an agent-tier method.
|
|
138
|
+
- **Action tokens and the question association never leave the person's own clients**, and neither is ever logged.
|
|
139
|
+
|
|
140
|
+
### Stores
|
|
141
|
+
|
|
142
|
+
**Stores only grow** (spec §8.8). Generation stays 2 and `min_reader` stays 1.
|
|
143
|
+
|
|
144
|
+
- **Daemon store, schema 5** (`packages/daemon/store-schemas/5.json`) adds `reach_grants` and `reach_reviews`: the
|
|
145
|
+
reach choices, apart from `grants`. A daemon before schema 5 would delete a `grants` row it cannot parse, so it
|
|
146
|
+
ignores these tables instead.
|
|
147
|
+
- **Relay database, schema 4** (`packages/relay/store-schemas/4.json`) adds `dir_wake_dms` and an index on `exp`. A
|
|
148
|
+
row counts only with its ticket's `exp`, and goes with it.
|
|
149
|
+
- Action tokens and the question association add no table: they are held in memory only.
|
|
150
|
+
|
|
151
|
+
### Tests
|
|
152
|
+
|
|
153
|
+
- Each new check has a test that goes red when the check is removed: the token bindings, single use, expiry,
|
|
154
|
+
restart, tier and log; the executor never gets `cancel`; Never in existing conversations; the arm's block, mute and
|
|
155
|
+
reach rules; the expiry purge of armed dms; the question on one device holding both sides of a task.
|
|
156
|
+
- The mixed-version e2e with message requests runs in both realms against `@joe-broadhead/messaging@0.7.1` when
|
|
157
|
+
`MESSAGING_E2E_OLD` is set. The gates skip it.
|
|
158
|
+
|
|
9
159
|
## [0.8.0] - 2026-11-10
|
|
10
160
|
|
|
11
161
|
**Conversations replace chat and mail.** A dm, a group, a channel and a mail between two agents are now all one
|
|
@@ -6746,7 +6746,8 @@ const DOMAIN = {
|
|
|
6746
6746
|
const URI = {
|
|
6747
6747
|
binding: "urn:messaging:binding:relay:v1",
|
|
6748
6748
|
provenance: "urn:messaging:ext:provenance:v1",
|
|
6749
|
-
presence: "urn:messaging:ext:presence:v1"
|
|
6749
|
+
presence: "urn:messaging:ext:presence:v1",
|
|
6750
|
+
signIn: "urn:messaging:ext:sign-in:v1"
|
|
6750
6751
|
};
|
|
6751
6752
|
const Base64Url = string().regex(/^[A-Za-z0-9_-]*$/, "base64url without padding");
|
|
6752
6753
|
const Key32 = string().regex(/^[A-Za-z0-9_-]{43}$/, "32 bytes, base64url without padding");
|
|
@@ -6917,6 +6918,7 @@ const ErrorCode = _enum([
|
|
|
6917
6918
|
"jelly_unavailable",
|
|
6918
6919
|
"log_conflict",
|
|
6919
6920
|
"conv_conflict",
|
|
6921
|
+
"conflict",
|
|
6920
6922
|
"stale_epoch",
|
|
6921
6923
|
"device_limit",
|
|
6922
6924
|
"admission_required",
|
|
@@ -7866,7 +7868,12 @@ const AskLevel = _enum([
|
|
|
7866
7868
|
"request",
|
|
7867
7869
|
"no"
|
|
7868
7870
|
]);
|
|
7869
|
-
const
|
|
7871
|
+
const GrantRelation = _enum(["outside", "org"]);
|
|
7872
|
+
const GrantSubject = union$1([
|
|
7873
|
+
strictObject({ person: PersonId }),
|
|
7874
|
+
strictObject({ conv: ConvId }),
|
|
7875
|
+
strictObject({ relation: GrantRelation })
|
|
7876
|
+
]);
|
|
7870
7877
|
object$1({
|
|
7871
7878
|
v: literal(PROTOCOL),
|
|
7872
7879
|
realm: RealmId,
|
|
@@ -7893,6 +7900,12 @@ const GrantSync = strictObject({ grants: array(record(string(), unknown())).min(
|
|
|
7893
7900
|
function grantProblem(grant) {
|
|
7894
7901
|
const own = "person" in grant.subject && grant.subject.person === grant.person;
|
|
7895
7902
|
if ("conv" in grant.subject && (grant.chat !== void 0 || grant.trustedUntil !== void 0 || grant.discoverable !== void 0)) return "a conversation grant sets only ask and agentMail";
|
|
7903
|
+
if ("relation" in grant.subject) {
|
|
7904
|
+
if (grant.agentMail !== void 0 || grant.trustedUntil !== void 0 || grant.discoverable !== void 0) return "a relation grant sets only chat or ask";
|
|
7905
|
+
if (grant.subject.relation === "outside" && grant.ask !== void 0) return "the outside relation grant sets only chat";
|
|
7906
|
+
if (grant.subject.relation === "outside" && grant.chat === "muted") return "the outside relation grant sets chat open or blocked";
|
|
7907
|
+
if (grant.subject.relation === "org" && grant.chat !== void 0) return "the org relation grant sets only ask";
|
|
7908
|
+
}
|
|
7896
7909
|
if (own && (grant.chat !== void 0 || grant.agentMail !== void 0 || grant.ask !== void 0 || grant.trustedUntil !== void 0)) return "a grant about its own person sets only discoverable";
|
|
7897
7910
|
if (!own && grant.discoverable !== void 0) return "discoverable is set only on a grant about its own person";
|
|
7898
7911
|
if (grant.trustedUntil !== void 0) {
|
|
@@ -8356,7 +8369,8 @@ discriminatedUnion("t", [
|
|
|
8356
8369
|
url: WakeUrl,
|
|
8357
8370
|
ticket: Jws.refine((ticket) => ticket.length <= WAKE_TICKET_BYTES, { message: `at most ${WAKE_TICKET_BYTES} bytes` }),
|
|
8358
8371
|
agents: array(SessionName).max(64),
|
|
8359
|
-
awaiting: array(Ulid).max(500)
|
|
8372
|
+
awaiting: array(Ulid).max(500),
|
|
8373
|
+
dms: array(ConvId).max(256).optional()
|
|
8360
8374
|
}),
|
|
8361
8375
|
object$1({
|
|
8362
8376
|
t: literal("wake.disarm"),
|
|
@@ -8471,6 +8485,7 @@ const DeviceChangeView = object$1({
|
|
|
8471
8485
|
approver: MachineLabel.optional(),
|
|
8472
8486
|
at: Millis
|
|
8473
8487
|
});
|
|
8488
|
+
const PresenceFeature = string().regex(/^[a-z][a-z0-9-]{0,31}$/, "a feature name");
|
|
8474
8489
|
const DeviceListEntry = object$1({
|
|
8475
8490
|
device: MachineId,
|
|
8476
8491
|
label: MachineLabel,
|
|
@@ -8489,7 +8504,8 @@ const DeviceListEntry = object$1({
|
|
|
8489
8504
|
at: Millis,
|
|
8490
8505
|
reason: UnbindReason,
|
|
8491
8506
|
by: MachineId
|
|
8492
|
-
}).optional()
|
|
8507
|
+
}).optional(),
|
|
8508
|
+
features: array(PresenceFeature).max(32).optional()
|
|
8493
8509
|
});
|
|
8494
8510
|
const LinkText = string().min(1).max(2048);
|
|
8495
8511
|
const Sas = string().regex(/^[0-9]{6}$/, "six digits");
|
|
@@ -8696,6 +8712,7 @@ const DEVICE_METHODS = {
|
|
|
8696
8712
|
};
|
|
8697
8713
|
// packages/core/dist/schema/ipc/shapes.js
|
|
8698
8714
|
const SessionKey = string().min(1).max(200);
|
|
8715
|
+
const ActionToken = string().regex(/^[A-Za-z0-9_-]{43}$/, "action token");
|
|
8699
8716
|
const SendExpect = _enum([
|
|
8700
8717
|
"next_turn",
|
|
8701
8718
|
"wake",
|
|
@@ -8766,7 +8783,14 @@ const TaskFollowUp = object$1({
|
|
|
8766
8783
|
stage: DeliveryStage,
|
|
8767
8784
|
at: Millis
|
|
8768
8785
|
});
|
|
8769
|
-
const
|
|
8786
|
+
const TASK_SIGN_IN_TOOL_CHARS = LIMITS.previewChars;
|
|
8787
|
+
const TASK_REFUSAL_CHARS = LIMITS.requestChars;
|
|
8788
|
+
const HostLocalId = string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/, "a plain identifier of at most 200 characters");
|
|
8789
|
+
const TaskQuestion = strictObject({
|
|
8790
|
+
sessionId: HostLocalId,
|
|
8791
|
+
approvalId: HostLocalId
|
|
8792
|
+
});
|
|
8793
|
+
const TaskRecordFields = object$1({
|
|
8770
8794
|
task: Task$1.extend({ id: Ulid }),
|
|
8771
8795
|
role: _enum(["requester", "executor"]),
|
|
8772
8796
|
key: SessionKey.describe("local session that owns this side of the task"),
|
|
@@ -8774,8 +8798,36 @@ const TaskRecord = object$1({
|
|
|
8774
8798
|
createdAt: Millis,
|
|
8775
8799
|
updatedAt: Millis,
|
|
8776
8800
|
chain: DelegationSummary.optional(),
|
|
8777
|
-
followUps: array(TaskFollowUp).min(1).max(LIMITS.taskFollowUps).optional()
|
|
8778
|
-
|
|
8801
|
+
followUps: array(TaskFollowUp).min(1).max(LIMITS.taskFollowUps).optional(),
|
|
8802
|
+
agent: SessionName.optional(),
|
|
8803
|
+
person: object$1({
|
|
8804
|
+
id: PersonId,
|
|
8805
|
+
name: string().max(200)
|
|
8806
|
+
}).optional(),
|
|
8807
|
+
org: _enum([
|
|
8808
|
+
"same",
|
|
8809
|
+
"other",
|
|
8810
|
+
"none"
|
|
8811
|
+
]).optional(),
|
|
8812
|
+
signIn: object$1({
|
|
8813
|
+
tool: string().min(1).max(TASK_SIGN_IN_TOOL_CHARS).optional(),
|
|
8814
|
+
by: _enum(["you", "them"])
|
|
8815
|
+
}).optional(),
|
|
8816
|
+
refusal: object$1({ reason: string().min(1).max(TASK_REFUSAL_CHARS) }).optional(),
|
|
8817
|
+
question: TaskQuestion.optional()
|
|
8818
|
+
});
|
|
8819
|
+
function taskViewRules(schema) {
|
|
8820
|
+
return schema.refine((r) => r.signIn === void 0 || r.task.status.state === "TASK_STATE_AUTH_REQUIRED", "signIn is for an AUTH_REQUIRED task").refine((r) => r.refusal === void 0 || r.task.status.state === "TASK_STATE_REJECTED", "refusal is for a REJECTED task").refine((r) => r.question === void 0 || r.role === "executor" && (r.task.status.state === "TASK_STATE_AUTH_REQUIRED" || r.task.status.state === "TASK_STATE_INPUT_REQUIRED"), "question is for the executor's side of an AUTH_REQUIRED or INPUT_REQUIRED task");
|
|
8821
|
+
}
|
|
8822
|
+
const TaskRecord = taskViewRules(TaskRecordFields);
|
|
8823
|
+
const PersonTask = taskViewRules(TaskRecordFields.omit({ key: true }).extend({
|
|
8824
|
+
key: never().optional(),
|
|
8825
|
+
session: SessionName.describe("this person's agent on this device that holds this side of the task"),
|
|
8826
|
+
cancel: strictObject({
|
|
8827
|
+
name: literal("cancel"),
|
|
8828
|
+
token: ActionToken
|
|
8829
|
+
}).optional()
|
|
8830
|
+
})).refine((r) => r.cancel === void 0 || r.role === "requester", "cancel is for the requester side");
|
|
8779
8831
|
const SendTarget = SessionAddress;
|
|
8780
8832
|
const SendResult = object$1({
|
|
8781
8833
|
to: SendTarget,
|
|
@@ -8818,6 +8870,17 @@ const GrantSetParams = strictObject({
|
|
|
8818
8870
|
...Levels,
|
|
8819
8871
|
trust: literal(true).optional()
|
|
8820
8872
|
});
|
|
8873
|
+
const RequestAnswer = _enum([
|
|
8874
|
+
"once",
|
|
8875
|
+
"always",
|
|
8876
|
+
"accept",
|
|
8877
|
+
"decline",
|
|
8878
|
+
"block"
|
|
8879
|
+
]);
|
|
8880
|
+
const Actions = { actions: array(strictObject({
|
|
8881
|
+
name: RequestAnswer,
|
|
8882
|
+
token: ActionToken
|
|
8883
|
+
})).max(4).optional() };
|
|
8821
8884
|
const AskRequest = object$1({
|
|
8822
8885
|
id: Ulid,
|
|
8823
8886
|
kind: _enum(["message", "task"]),
|
|
@@ -8831,7 +8894,8 @@ const AskRequest = object$1({
|
|
|
8831
8894
|
to: SessionName,
|
|
8832
8895
|
preview: string().max(LIMITS.previewChars),
|
|
8833
8896
|
at: Millis,
|
|
8834
|
-
exp: Millis
|
|
8897
|
+
exp: Millis,
|
|
8898
|
+
...Actions
|
|
8835
8899
|
});
|
|
8836
8900
|
const ContactRequestView = object$1({
|
|
8837
8901
|
id: Ulid,
|
|
@@ -8857,7 +8921,8 @@ const ContactRequestView = object$1({
|
|
|
8857
8921
|
}),
|
|
8858
8922
|
text: string().max(LIMITS.requestChars),
|
|
8859
8923
|
at: Millis,
|
|
8860
|
-
exp: Millis
|
|
8924
|
+
exp: Millis,
|
|
8925
|
+
...Actions
|
|
8861
8926
|
});
|
|
8862
8927
|
const AgentInvitationRequest = strictObject({
|
|
8863
8928
|
kind: literal("agent-invitation"),
|
|
@@ -8865,7 +8930,8 @@ const AgentInvitationRequest = strictObject({
|
|
|
8865
8930
|
cid: ConvId,
|
|
8866
8931
|
from: ParticipantId,
|
|
8867
8932
|
to: ParticipantId,
|
|
8868
|
-
at: Millis
|
|
8933
|
+
at: Millis,
|
|
8934
|
+
...Actions
|
|
8869
8935
|
});
|
|
8870
8936
|
const HeldRequest = union$1([
|
|
8871
8937
|
AskRequest,
|
|
@@ -8875,17 +8941,11 @@ const HeldRequest = union$1([
|
|
|
8875
8941
|
kind: literal("suggestion"),
|
|
8876
8942
|
agent: SessionName,
|
|
8877
8943
|
email: Email,
|
|
8878
|
-
at: Millis
|
|
8944
|
+
at: Millis,
|
|
8945
|
+
...Actions
|
|
8879
8946
|
}),
|
|
8880
8947
|
AgentInvitationRequest
|
|
8881
8948
|
]);
|
|
8882
|
-
const RequestAnswer = _enum([
|
|
8883
|
-
"once",
|
|
8884
|
-
"always",
|
|
8885
|
-
"accept",
|
|
8886
|
-
"decline",
|
|
8887
|
-
"block"
|
|
8888
|
-
]);
|
|
8889
8949
|
const RequestText = string().min(1).refine((text) => text.length <= LIMITS.requestChars, { message: `at most ${LIMITS.requestChars} UTF-16 code units` });
|
|
8890
8950
|
const PersonReach = object$1({
|
|
8891
8951
|
person: PersonId,
|
|
@@ -8897,14 +8957,57 @@ const PersonReach = object$1({
|
|
|
8897
8957
|
const RequestAnswerParams = strictObject({
|
|
8898
8958
|
id: Ulid,
|
|
8899
8959
|
answer: RequestAnswer,
|
|
8900
|
-
all: boolean().optional()
|
|
8901
|
-
|
|
8960
|
+
all: boolean().optional(),
|
|
8961
|
+
token: ActionToken.optional()
|
|
8962
|
+
}).refine((p) => p.token === void 0 || p.all !== true, "an action token answers one request: drop all");
|
|
8902
8963
|
const ContactRequestParams = strictObject({
|
|
8903
8964
|
email: Email,
|
|
8904
8965
|
text: RequestText,
|
|
8905
8966
|
suggestion: Ulid.optional()
|
|
8906
8967
|
});
|
|
8968
|
+
const ReachGroupId = _enum(["message", "ask"]);
|
|
8969
|
+
const ReachValue = _enum([
|
|
8970
|
+
"open",
|
|
8971
|
+
"ask",
|
|
8972
|
+
"never"
|
|
8973
|
+
]);
|
|
8974
|
+
const ReachOption = object$1({
|
|
8975
|
+
value: ReachValue,
|
|
8976
|
+
label: string().max(40),
|
|
8977
|
+
description: string().max(300)
|
|
8978
|
+
});
|
|
8979
|
+
const ReachGroup = object$1({
|
|
8980
|
+
id: ReachGroupId,
|
|
8981
|
+
label: string().max(80),
|
|
8982
|
+
options: array(ReachOption).length(3),
|
|
8983
|
+
value: ReachValue.nullable(),
|
|
8984
|
+
lapsed: ReachValue.optional()
|
|
8985
|
+
});
|
|
8986
|
+
const ReachSnapshot = object$1({
|
|
8987
|
+
revision: string().min(1).max(64),
|
|
8988
|
+
available: boolean(),
|
|
8989
|
+
groups: array(ReachGroup).length(2)
|
|
8990
|
+
});
|
|
8991
|
+
const ReachSetParams = strictObject({
|
|
8992
|
+
revision: string().min(1).max(64),
|
|
8993
|
+
changes: strictObject({
|
|
8994
|
+
message: ReachValue.optional(),
|
|
8995
|
+
ask: ReachValue.optional()
|
|
8996
|
+
})
|
|
8997
|
+
});
|
|
8907
8998
|
const CONSENT_METHODS = {
|
|
8999
|
+
"reach.get": {
|
|
9000
|
+
params: strictObject({}),
|
|
9001
|
+
result: ReachSnapshot
|
|
9002
|
+
},
|
|
9003
|
+
"reach.set": {
|
|
9004
|
+
params: ReachSetParams,
|
|
9005
|
+
result: ReachSnapshot
|
|
9006
|
+
},
|
|
9007
|
+
"reach.set.preview": {
|
|
9008
|
+
params: ReachSetParams,
|
|
9009
|
+
result: object$1({ sentence: string().max(500) })
|
|
9010
|
+
},
|
|
8908
9011
|
"grant.list": {
|
|
8909
9012
|
params: strictObject({ subject: GrantTarget.optional() }),
|
|
8910
9013
|
result: object$1({
|
|
@@ -9068,6 +9171,7 @@ const Notice = object$1({
|
|
|
9068
9171
|
}).optional(),
|
|
9069
9172
|
line: string().max(120).optional(),
|
|
9070
9173
|
device: NoticeDevice.optional(),
|
|
9174
|
+
key: string().regex(/^task:[0-9A-HJKMNP-TV-Z]{26}$/, "task:<task id>").optional(),
|
|
9071
9175
|
at: Millis
|
|
9072
9176
|
});
|
|
9073
9177
|
const NoticeCounts = object$1({ requests: number().int().nonnegative() });
|
|
@@ -9138,7 +9242,8 @@ const JELLY_METHODS = {
|
|
|
9138
9242
|
}),
|
|
9139
9243
|
result: object$1({
|
|
9140
9244
|
relay: _enum(["armed", "pending"]),
|
|
9141
|
-
awaiting: number().int().nonnegative()
|
|
9245
|
+
awaiting: number().int().nonnegative(),
|
|
9246
|
+
dms: number().int().nonnegative()
|
|
9142
9247
|
})
|
|
9143
9248
|
},
|
|
9144
9249
|
"wake.disarm": {
|
|
@@ -9251,7 +9356,11 @@ const FileView = object$1({
|
|
|
9251
9356
|
});
|
|
9252
9357
|
const TaskView = object$1({
|
|
9253
9358
|
id: Ulid,
|
|
9254
|
-
state: TaskState
|
|
9359
|
+
state: TaskState,
|
|
9360
|
+
cancel: strictObject({
|
|
9361
|
+
name: literal("cancel"),
|
|
9362
|
+
token: ActionToken
|
|
9363
|
+
}).optional()
|
|
9255
9364
|
});
|
|
9256
9365
|
const EventView = object$1({
|
|
9257
9366
|
cid: ConvId,
|
|
@@ -9410,6 +9519,11 @@ const ConvStreamEvent = discriminatedUnion("type", [
|
|
|
9410
9519
|
object$1({
|
|
9411
9520
|
type: literal("gap"),
|
|
9412
9521
|
pos: Seq$1
|
|
9522
|
+
}),
|
|
9523
|
+
object$1({
|
|
9524
|
+
type: literal("task"),
|
|
9525
|
+
pos: Seq$1,
|
|
9526
|
+
task: PersonTask
|
|
9413
9527
|
})
|
|
9414
9528
|
]).refine((entry) => entry.type !== "event.update" || entry.cid === entry.event.cid && entry.id === entry.event.id, { message: "event.update names the same conversation and event as its projection" });
|
|
9415
9529
|
// packages/core/dist/schema/ipc/conversations.js
|
|
@@ -9516,7 +9630,12 @@ const WaitingOnThemView = object$1({
|
|
|
9516
9630
|
at: Millis,
|
|
9517
9631
|
cid: ConvId.optional(),
|
|
9518
9632
|
agent: SessionName.optional(),
|
|
9519
|
-
state: _enum([
|
|
9633
|
+
state: _enum([
|
|
9634
|
+
"held",
|
|
9635
|
+
"pending-delivery",
|
|
9636
|
+
"resend",
|
|
9637
|
+
"outdated"
|
|
9638
|
+
])
|
|
9520
9639
|
});
|
|
9521
9640
|
const ConvListParams = strictObject({
|
|
9522
9641
|
key: Key,
|
|
@@ -10131,16 +10250,47 @@ const IPC_METHODS = {
|
|
|
10131
10250
|
message: strictObject({ parts: array(Part$1).min(1).max(LIMITS.partsPerMessage) }).optional(),
|
|
10132
10251
|
artifact: Artifact.optional(),
|
|
10133
10252
|
append: boolean().optional(),
|
|
10134
|
-
lastChunk: boolean().optional()
|
|
10135
|
-
|
|
10253
|
+
lastChunk: boolean().optional(),
|
|
10254
|
+
signIn: strictObject({ tool: string().min(1).max(TASK_SIGN_IN_TOOL_CHARS) }).optional()
|
|
10255
|
+
}).refine((p) => p.signIn === void 0 || p.state === "TASK_STATE_AUTH_REQUIRED", "signIn goes with TASK_STATE_AUTH_REQUIRED"),
|
|
10136
10256
|
result: TaskRecord
|
|
10137
10257
|
},
|
|
10138
|
-
"task.
|
|
10258
|
+
"task.question": {
|
|
10139
10259
|
params: strictObject({
|
|
10140
10260
|
key: SessionKey,
|
|
10141
10261
|
taskId: Ulid,
|
|
10142
|
-
|
|
10262
|
+
question: TaskQuestion.nullable()
|
|
10143
10263
|
}),
|
|
10264
|
+
result: object$1({ ok: literal(true) })
|
|
10265
|
+
},
|
|
10266
|
+
"tasks.list": {
|
|
10267
|
+
params: strictObject({
|
|
10268
|
+
role: _enum(["requester", "executor"]).optional(),
|
|
10269
|
+
state: TaskState.optional(),
|
|
10270
|
+
session: SessionName.optional().describe("only this agent's tasks"),
|
|
10271
|
+
cursor: string().min(1).max(200).optional().describe("opaque; from a previous nextCursor"),
|
|
10272
|
+
limit: number().int().min(1).max(100).optional()
|
|
10273
|
+
}),
|
|
10274
|
+
result: object$1({
|
|
10275
|
+
tasks: array(PersonTask),
|
|
10276
|
+
nextCursor: string().max(200).optional().describe("absent when there are no more pages"),
|
|
10277
|
+
total: number().int().nonnegative()
|
|
10278
|
+
})
|
|
10279
|
+
},
|
|
10280
|
+
"tasks.get": {
|
|
10281
|
+
params: strictObject({
|
|
10282
|
+
taskId: Ulid,
|
|
10283
|
+
role: _enum(["requester", "executor"]).optional()
|
|
10284
|
+
}),
|
|
10285
|
+
result: PersonTask
|
|
10286
|
+
},
|
|
10287
|
+
"task.cancel": {
|
|
10288
|
+
params: strictObject({
|
|
10289
|
+
key: SessionKey.optional(),
|
|
10290
|
+
taskId: Ulid,
|
|
10291
|
+
reason: string().max(1e3).optional(),
|
|
10292
|
+
token: ActionToken.optional()
|
|
10293
|
+
}).refine((p) => p.key === void 0 !== (p.token === void 0), "give exactly one of key or token"),
|
|
10144
10294
|
result: TaskRecord
|
|
10145
10295
|
},
|
|
10146
10296
|
"events.subscribe": {
|
|
@@ -10293,7 +10443,8 @@ const AGENT_METHODS = [
|
|
|
10293
10443
|
"task.list",
|
|
10294
10444
|
"task.get",
|
|
10295
10445
|
"task.update",
|
|
10296
|
-
"task.cancel"
|
|
10446
|
+
"task.cancel",
|
|
10447
|
+
"task.question"
|
|
10297
10448
|
];
|
|
10298
10449
|
const HUMAN_METHODS = [
|
|
10299
10450
|
...conversationTier("human"),
|
|
@@ -10302,6 +10453,9 @@ const HUMAN_METHODS = [
|
|
|
10302
10453
|
"grant.list",
|
|
10303
10454
|
"grant.set",
|
|
10304
10455
|
"grant.set.preview",
|
|
10456
|
+
"reach.get",
|
|
10457
|
+
"reach.set",
|
|
10458
|
+
"reach.set.preview",
|
|
10305
10459
|
"request.list",
|
|
10306
10460
|
"request.answer",
|
|
10307
10461
|
"request.answer.preview",
|
|
@@ -10311,6 +10465,8 @@ const HUMAN_METHODS = [
|
|
|
10311
10465
|
"notice.subscribe",
|
|
10312
10466
|
"notice.list",
|
|
10313
10467
|
"notice.ack",
|
|
10468
|
+
"tasks.list",
|
|
10469
|
+
"tasks.get",
|
|
10314
10470
|
"device.approve.preview",
|
|
10315
10471
|
"device.remove.preview",
|
|
10316
10472
|
"identity.replace.preview",
|
|
@@ -10461,7 +10617,8 @@ strictObject({
|
|
|
10461
10617
|
strictObject({
|
|
10462
10618
|
t: literal("conv.capabilities"),
|
|
10463
10619
|
req: Req,
|
|
10464
|
-
outdated: array(PersonId).max(LIMITS.convOpParticipants)
|
|
10620
|
+
outdated: array(PersonId).max(LIMITS.convOpParticipants),
|
|
10621
|
+
unknown: array(PersonId).min(1).max(LIMITS.convOpParticipants).optional()
|
|
10465
10622
|
});
|
|
10466
10623
|
union$1([strictObject({
|
|
10467
10624
|
t: literal("conv.push"),
|
|
@@ -10575,6 +10732,7 @@ const TOOL_REMEDIES = {
|
|
|
10575
10732
|
login_denied: "tell your person: the sign-in at Jelly ID was denied.",
|
|
10576
10733
|
log_conflict: "try again: their devices changed at the same time.",
|
|
10577
10734
|
conv_conflict: "try again: the conversation changed at the same time.",
|
|
10735
|
+
conflict: "tell your person: their settings changed at the same time; they choose again.",
|
|
10578
10736
|
stale_epoch: "try again: the conversation's keys changed at the same time.",
|
|
10579
10737
|
device_limit: "ask your person to remove one of their devices first.",
|
|
10580
10738
|
admission_required: "ask your person to open Messaging in Jelly once: this device is not bound to their Jelly account yet.",
|
|
@@ -10752,6 +10910,11 @@ const Task = object$1({
|
|
|
10752
10910
|
titleAuthor: Author$1.optional().describe("With title: who wrote the request; ours when you asked (mine)"),
|
|
10753
10911
|
status: authored("The latest status message").optional(),
|
|
10754
10912
|
statusAuthor: Author$1.optional().describe("With status: who wrote it; ours when you run the task (asked)"),
|
|
10913
|
+
signIn: object$1({
|
|
10914
|
+
tool: authored("The tool the executor named").optional(),
|
|
10915
|
+
by: _enum(["you", "them"]).describe("Whose sign-in it is: you (your person, for an agent of theirs) or them (the other person); your Messaging daemon decides, never the peer")
|
|
10916
|
+
}).optional().describe("auth_required only: the task waits on a sign-in; the tool was written by the executor, as statusAuthor says"),
|
|
10917
|
+
refusal: object$1({ reason: authored("The executor's reason") }).optional().describe("rejected only: why the executor declined, in its words, as statusAuthor says"),
|
|
10755
10918
|
createdAt: iso,
|
|
10756
10919
|
updatedAt: iso,
|
|
10757
10920
|
consent: ToolConsentSchema.optional(),
|
|
@@ -11312,8 +11475,11 @@ const VIEW_METHODS = [
|
|
|
11312
11475
|
"people.search",
|
|
11313
11476
|
"people.get",
|
|
11314
11477
|
"grant.list",
|
|
11478
|
+
"reach.get",
|
|
11315
11479
|
"request.list",
|
|
11316
11480
|
"sessions.list",
|
|
11481
|
+
"tasks.list",
|
|
11482
|
+
"tasks.get",
|
|
11317
11483
|
"notice.subscribe",
|
|
11318
11484
|
"notice.list",
|
|
11319
11485
|
"device.list"
|
|
@@ -12623,6 +12789,11 @@ function presentTask(framer, record, consent) {
|
|
|
12623
12789
|
status: shown$1(framer, statusAuthor, peerAttributes(record))(status, PART_CHARS),
|
|
12624
12790
|
statusAuthor
|
|
12625
12791
|
} : {},
|
|
12792
|
+
...record.signIn !== void 0 ? { signIn: {
|
|
12793
|
+
...record.signIn.tool !== void 0 ? { tool: shown$1(framer, statusAuthor, peerAttributes(record))(record.signIn.tool, TASK_SIGN_IN_TOOL_CHARS) } : {},
|
|
12794
|
+
by: record.signIn.by
|
|
12795
|
+
} } : {},
|
|
12796
|
+
...record.refusal !== void 0 ? { refusal: { reason: shown$1(framer, statusAuthor, peerAttributes(record))(record.refusal.reason, TASK_REFUSAL_CHARS) } } : {},
|
|
12626
12797
|
updatedAt: isoOf(record.updatedAt),
|
|
12627
12798
|
...consent !== void 0 && record.role === "executor" ? { consent } : {},
|
|
12628
12799
|
...record.chain !== void 0 ? { chain: presentChain(framer, record.chain) } : {}
|
|
@@ -13445,7 +13616,7 @@ var MessagingClient = class MessagingClient {
|
|
|
13445
13616
|
timer = setTimeout(() => reject(new DaemonMismatchError(`the process on the Messaging daemon socket did not say which version it is within ${wait} ms: it is a messagingd from before this version, or it is stuck. ${REMEDY}`)), wait);
|
|
13446
13617
|
})]);
|
|
13447
13618
|
if (pointer !== void 0 && hello.pid !== pointer.pid) throw new MessagingError("forbidden", "the process on the Messaging daemon's pipe is not the daemon that published it");
|
|
13448
|
-
const expected = options.version ?? "0.
|
|
13619
|
+
const expected = options.version ?? "0.9.0";
|
|
13449
13620
|
if (hello.protocol !== 1 || hello.version !== expected) throw new DaemonMismatchError(`the Messaging daemon for this home is version ${shown(hello.version)} (IPC protocol ${hello.protocol}), and this client is version ${shown(expected)} (IPC protocol 1). ${REMEDY}`, hello);
|
|
13450
13621
|
client.daemonHello = hello;
|
|
13451
13622
|
return client;
|
|
@@ -27335,7 +27506,7 @@ function registerTools(server, deps) {
|
|
|
27335
27506
|
}
|
|
27336
27507
|
}
|
|
27337
27508
|
// packages/mcp/dist/version.js
|
|
27338
|
-
const VERSION = "0.
|
|
27509
|
+
const VERSION = "0.9.0";
|
|
27339
27510
|
// packages/mcp/dist/server.js
|
|
27340
27511
|
function createMessagingMcpServer(options) {
|
|
27341
27512
|
if (!options.client && !options.connect) throw new TypeError("createMessagingMcpServer needs `client` or `connect`");
|