@unagents/messenger-protocol 0.9.0 → 0.10.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/dist/index.d.mts CHANGED
@@ -56,6 +56,31 @@ declare const ENV: {
56
56
  readonly DELIVERY: "AGENT_MESSENGER_DELIVERY";
57
57
  };
58
58
  type EnvName = (typeof ENV)[keyof typeof ENV];
59
+ /**
60
+ * Extra POST routes of the protocol registry. Like `RELAY_ROUTE`, not part of `BROKER_POST_ROUTES`
61
+ * (that table mirrors the legacy broker); request / response shapes are `ProtocolRouteMap` in `http.ts`.
62
+ */
63
+ declare const PROTOCOL_ROUTES: readonly ["/set-protocol", "/unset-protocol", "/get-protocol"];
64
+ type ProtocolRoute = (typeof PROTOCOL_ROUTES)[number];
65
+ /** Max size of `protocol.json` (UTF-8 bytes). */
66
+ declare const PROTOCOL_MANIFEST_MAX_BYTES: number;
67
+ /** Max size of `PROTOCOL.md` (UTF-8 bytes). */
68
+ declare const PROTOCOL_DOC_MAX_BYTES: number;
69
+ /** Max size of manifest + doc together (UTF-8 bytes). */
70
+ declare const PROTOCOL_TOTAL_MAX_BYTES: number;
71
+ /** Max number of `(name, version)` definitions one broker holds; more is `LIMIT`. */
72
+ declare const PROTOCOL_MAX_STORED = 256;
73
+ /** Max length (characters) of each of `meta.protocol` and `meta.version`. */
74
+ declare const PROTOCOL_META_MAX_LENGTH = 128;
75
+ /** Valid protocol `name`. */
76
+ declare const PROTOCOL_NAME_PATTERN: RegExp;
77
+ /**
78
+ * Error `code`s of the protocol feature (design 7.2). `NO_TAG` is not listed: a repo without tags is
79
+ * not an error (`0.0.0+<sha7>`). Raised by the broker: HASH_MISMATCH, PROTOCOL_CONFLICT, LIMIT,
80
+ * PROTOCOL_NOT_FOUND, and TOO_LARGE / INVALID_DEFINITION on re-check. The rest are raised by the client.
81
+ */
82
+ declare const PROTOCOL_ERROR_CODES: readonly ["REPO_UNREACHABLE", "UNSUPPORTED_REPO", "VERSION_NOT_FOUND", "INVALID_DEFINITION", "VERSION_MISMATCH", "TOO_LARGE", "HASH_MISMATCH", "PROTOCOL_CONFLICT", "LIMIT", "PROTOCOL_NOT_FOUND", "BROKER_UNSUPPORTED", "INVALID_META"];
83
+ type ProtocolErrorCode = (typeof PROTOCOL_ERROR_CODES)[number];
59
84
 
60
85
  /**
61
86
  * Broker HTTP API — request / response JSON shapes.
@@ -72,6 +97,7 @@ type EnvName = (typeof ENV)[keyof typeof ENV];
72
97
  * - Unknown POST path: `404 {"error":"not found"}`. Handler exception: `500 {"error":<message>}`.
73
98
  * - Any other method: `200 text/plain` `agent-messenger broker <version>\n`.
74
99
  */
100
+
75
101
  /** A registered session as stored and returned by the broker. */
76
102
  interface Peer {
77
103
  /** 8-char broker-generated id (`[a-z0-9]`). */
@@ -95,6 +121,42 @@ interface Peer {
95
121
  * registered directly. Never returned by `/list-peers`.
96
122
  */
97
123
  origin?: string;
124
+ /**
125
+ * Protocols this peer serves (`/set-protocol`, re-sent on `/register` and `/heartbeat`). Hash only; the
126
+ * definition body lives in the broker's protocol store. Absent when none (and from 0.9.x brokers).
127
+ */
128
+ protocols?: ProtocolDecl[];
129
+ }
130
+ /** A protocol a peer declares: identity (`name` + `version`) and the hash of its definition body. */
131
+ interface ProtocolDecl {
132
+ /** `^[a-z0-9][a-z0-9._-]{0,63}$` (`PROTOCOL_NAME_PATTERN`). */
133
+ name: string;
134
+ /** semver, or `0.0.0+<sha7>` for a repo without tags. */
135
+ version: string;
136
+ /** `sha256(canonicalJSON({manifest, doc}))` as lower-case hex. */
137
+ hash: string;
138
+ }
139
+ /** The definition body a server fetched from its protocol repo. */
140
+ interface ProtocolDefinition {
141
+ /** Parsed `protocol.json`. */
142
+ manifest: object;
143
+ /** `PROTOCOL.md` text; `""` when the repo has none. */
144
+ doc: string;
145
+ /** Normalized repo notation (`github:o/r`, `gitea:<base>/o/r`, `file:<abs path>`). Not part of the hash. */
146
+ source: string;
147
+ /** tag or sha the body was read at (`local` for a local path). Not part of the hash. */
148
+ ref: string;
149
+ }
150
+ /** What the broker keeps per `(name, version)`. */
151
+ interface StoredProtocol extends ProtocolDecl {
152
+ definition: ProtocolDefinition;
153
+ /** ISO 8601 timestamp of the first successful `/set-protocol` for this `(name, version)`. */
154
+ registered_at: string;
155
+ }
156
+ /** Protocol tag of a message: which protocol (and version) the sender spoke. Both fields are required when present. */
157
+ interface MessageMeta {
158
+ protocol: string;
159
+ version: string;
98
160
  }
99
161
  type MessageMode = "pull" | "push" | "ping";
100
162
  /** A message as stored and returned by the broker. */
@@ -120,6 +182,8 @@ interface Message {
120
182
  acknowledged: 0 | 1;
121
183
  /** Mesh only: `<sender broker id>:<id there>` of a message forwarded by another broker (duplicate guard). */
122
184
  mesh_key?: string;
185
+ /** Protocol tag given to `/send-message`; returned by `/poll-messages` and kept across relay / mesh forwarding. */
186
+ meta?: MessageMeta;
123
187
  }
124
188
  /** `/list-peers` scope filter. Unknown values behave as `"all"`. */
125
189
  type PeerScope = "all" | "host" | "directory" | "repo";
@@ -155,15 +219,29 @@ interface RegisterRequest {
155
219
  tty?: string | null;
156
220
  summary?: string;
157
221
  name?: string;
222
+ /** Protocols to declare right away (hash only; bodies follow via `/set-protocol`). */
223
+ protocols?: ProtocolDecl[];
158
224
  }
159
225
  interface RegisterResponse {
160
226
  id: string;
161
227
  }
162
228
  interface HeartbeatRequest {
163
229
  id: string;
230
+ /** Every protocol the peer currently serves (hash only), sent on each heartbeat. */
231
+ protocols?: ProtocolDecl[];
164
232
  }
165
- /** `ok:false` with `"unknown peer (re-register)"` when the broker does not know the id. */
166
- type HeartbeatResponse = OkResult;
233
+ /**
234
+ * `ok:false` with `"unknown peer (re-register)"` when the broker does not know the id.
235
+ * `resend` lists the `"name@version"` of declared protocols whose body the broker does not hold; the client answers
236
+ * with `/set-protocol`. Absent when nothing is missing (and from 0.9.x brokers).
237
+ */
238
+ type HeartbeatResponse = {
239
+ ok: true;
240
+ resend?: string[];
241
+ } | {
242
+ ok: false;
243
+ error: string;
244
+ };
167
245
  interface SetSummaryRequest {
168
246
  id: string;
169
247
  summary?: string;
@@ -184,6 +262,10 @@ interface ListPeersRequest {
184
262
  git_root?: string | null;
185
263
  /** Peer id to omit from the result (the caller itself). */
186
264
  exclude_id?: string;
265
+ /** Only peers that declare this protocol (AND with `scope`). */
266
+ protocol?: string;
267
+ /** Only with `protocol`: only peers that declare exactly this version. */
268
+ version?: string;
187
269
  }
188
270
  type ListPeersResponse = Peer[];
189
271
  interface SendMessageRequest {
@@ -195,6 +277,8 @@ interface SendMessageRequest {
195
277
  text?: string;
196
278
  /** Defaults to `"pull"`. Unknown values are rejected. */
197
279
  mode?: MessageMode;
280
+ /** Protocol tag. Both fields must be strings of at most `PROTOCOL_META_MAX_LENGTH` characters. */
281
+ meta?: MessageMeta;
198
282
  }
199
283
  type SendMessageResponse = {
200
284
  ok: true;
@@ -243,6 +327,7 @@ interface RelayedMessage {
243
327
  text: string;
244
328
  mode?: MessageMode;
245
329
  sent_at: string;
330
+ meta?: MessageMeta;
246
331
  }
247
332
  interface RelayMessagesRequest {
248
333
  messages: RelayedMessage[];
@@ -256,6 +341,72 @@ interface RelayMessagesResponse {
256
341
  }
257
342
  /** Extra POST route served by the TypeScript broker. Not in `BROKER_POST_ROUTES` (that table mirrors the legacy broker). */
258
343
  declare const RELAY_ROUTE: "/relay-messages";
344
+ /** `{ok:false}` answer of the protocol routes; `code` is one of `PROTOCOL_ERROR_CODES`. */
345
+ interface ProtocolErrorResponse {
346
+ ok: false;
347
+ error: string;
348
+ code: ProtocolErrorCode;
349
+ /** `PROTOCOL_CONFLICT`: hash the broker already holds for this `(name, version)`. */
350
+ existing_hash?: string;
351
+ /** `PROTOCOL_CONFLICT`: servers (`name@host`) that hold it. */
352
+ servers?: string[];
353
+ /** `PROTOCOL_NOT_FOUND`: versions the broker does hold for the name (may be empty). */
354
+ versions?: string[];
355
+ }
356
+ interface SetProtocolRequest {
357
+ /** Peer id of the server. */
358
+ id: string;
359
+ name: string;
360
+ version: string;
361
+ /** Re-computed by the broker; a different value is `HASH_MISMATCH`. */
362
+ hash: string;
363
+ definition: ProtocolDefinition;
364
+ }
365
+ /** `{ok:false, error}` without `code` only for an unknown peer id. */
366
+ type SetProtocolResponse = {
367
+ ok: true;
368
+ } | ProtocolErrorResponse | {
369
+ ok: false;
370
+ error: string;
371
+ };
372
+ interface UnsetProtocolRequest {
373
+ id: string;
374
+ /** Protocol to drop; all declarations of the peer when omitted. */
375
+ name?: string;
376
+ }
377
+ type UnsetProtocolResponse = {
378
+ ok: true;
379
+ removed: number;
380
+ } | {
381
+ ok: false;
382
+ error: string;
383
+ };
384
+ interface GetProtocolRequest {
385
+ name: string;
386
+ /** Highest version the broker holds when omitted (`0.0.0+sha` counts as lowest). */
387
+ version?: string;
388
+ }
389
+ /** `servers` = number of alive peers (replicas included) that declare the returned protocol. */
390
+ type GetProtocolResponse = {
391
+ ok: true;
392
+ protocol: StoredProtocol;
393
+ servers: number;
394
+ } | ProtocolErrorResponse;
395
+ /** Request / response pair per protocol route. */
396
+ interface ProtocolRouteMap {
397
+ "/set-protocol": {
398
+ request: SetProtocolRequest;
399
+ response: SetProtocolResponse;
400
+ };
401
+ "/unset-protocol": {
402
+ request: UnsetProtocolRequest;
403
+ response: UnsetProtocolResponse;
404
+ };
405
+ "/get-protocol": {
406
+ request: GetProtocolRequest;
407
+ response: GetProtocolResponse;
408
+ };
409
+ }
259
410
  /** The only GET route. */
260
411
  declare const HEALTH_ROUTE: "/health";
261
412
  /** POST routes, in the order of `ROUTES` in `legacy/src/broker.js`. */
@@ -319,6 +470,8 @@ interface MeshSyncRequest {
319
470
  peers: Peer[];
320
471
  /** Undelivered messages the sender holds for peers that live on the receiver. Ids are the sender's. */
321
472
  messages: Message[];
473
+ /** Definitions declared by the sender's direct peers. Absent from 0.9.x brokers. */
474
+ protocols?: StoredProtocol[];
322
475
  }
323
476
  interface MeshSyncResponse {
324
477
  /** Id of the receiving broker. */
@@ -327,6 +480,8 @@ interface MeshSyncResponse {
327
480
  peers: Peer[];
328
481
  /** Sender-side ids of the messages the receiver took over; the sender drops them. */
329
482
  accepted: number[];
483
+ /** Definitions declared by the receiver's direct peers. Absent from 0.9.x brokers. */
484
+ protocols?: StoredProtocol[];
330
485
  }
331
486
 
332
487
  /**
@@ -390,5 +545,17 @@ interface SessionUserLine {
390
545
  }
391
546
  type SessionSocketLine = SessionAuthLine | SessionUserLine;
392
547
 
393
- export { BROKER_POST_ROUTES, DEFAULT_BIND, DEFAULT_DISCOVERY_PORT, DEFAULT_DISCOVERY_TIMEOUT_MS, DEFAULT_HTTP_PORT, DISCOVERY_BROADCAST_ADDR, DISCOVERY_KEY, DISCOVERY_PROBE, DISCOVERY_PROBE_VALUE, DISCOVERY_REPLY_VALUE, ENV, HEALTH_ROUTE, HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS, LOCAL_HOSTS, MESH_SYNC_ROUTE, POLL_INTERVAL_MS, RELAY_ROUTE, SESSION_SOCKET_ENV, SESSION_TOKEN_ENV };
394
- export type { AcknowledgeMessagesRequest, AcknowledgeMessagesResponse, BrokerErrorResponse, BrokerPostRoute, BrokerRequest, BrokerResponse, BrokerRouteMap, DiscoveryProbe, DiscoveryReply, EnvName, GetBufferedMessagesRequest, GetBufferedMessagesResponse, HealthResponse, HeartbeatRequest, HeartbeatResponse, ListPeersRequest, ListPeersResponse, MeshSyncRequest, MeshSyncResponse, Message, MessageMode, OkResult, Peer, PeerScope, PollMessagesRequest, PollMessagesResponse, RegisterRequest, RegisterResponse, RelayMessagesRequest, RelayMessagesResponse, RelayedMessage, SendMessageRequest, SendMessageResponse, SessionAuthLine, SessionSocketLine, SessionUserLine, SetNameRequest, SetNameResponse, SetSummaryRequest, SetSummaryResponse, UnregisterRequest, UnregisterResponse };
548
+ /**
549
+ * Canonical JSON for protocol hashes: object keys in ascending (UTF-16 code unit) order at every depth,
550
+ * no whitespace, otherwise exactly what `JSON.stringify` writes for each value. Pure TypeScript with no
551
+ * Node dependency, so any runtime can import it. The hash itself,
552
+ * `sha256(canonicalJSON({manifest, doc}))` in lower-case hex, is computed by each side with its own
553
+ * sha256 (broker: `node:crypto`).
554
+ *
555
+ * Input is expected to be JSON data (what `JSON.parse` returns). As in `JSON.stringify`, an object member whose
556
+ * value is `undefined` / a function / a symbol is skipped, and such an array element becomes `null`.
557
+ */
558
+ declare function canonicalJSON(value: unknown): string;
559
+
560
+ export { BROKER_POST_ROUTES, DEFAULT_BIND, DEFAULT_DISCOVERY_PORT, DEFAULT_DISCOVERY_TIMEOUT_MS, DEFAULT_HTTP_PORT, DISCOVERY_BROADCAST_ADDR, DISCOVERY_KEY, DISCOVERY_PROBE, DISCOVERY_PROBE_VALUE, DISCOVERY_REPLY_VALUE, ENV, HEALTH_ROUTE, HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS, LOCAL_HOSTS, MESH_SYNC_ROUTE, POLL_INTERVAL_MS, PROTOCOL_DOC_MAX_BYTES, PROTOCOL_ERROR_CODES, PROTOCOL_MANIFEST_MAX_BYTES, PROTOCOL_MAX_STORED, PROTOCOL_META_MAX_LENGTH, PROTOCOL_NAME_PATTERN, PROTOCOL_ROUTES, PROTOCOL_TOTAL_MAX_BYTES, RELAY_ROUTE, SESSION_SOCKET_ENV, SESSION_TOKEN_ENV, canonicalJSON };
561
+ export type { AcknowledgeMessagesRequest, AcknowledgeMessagesResponse, BrokerErrorResponse, BrokerPostRoute, BrokerRequest, BrokerResponse, BrokerRouteMap, DiscoveryProbe, DiscoveryReply, EnvName, GetBufferedMessagesRequest, GetBufferedMessagesResponse, GetProtocolRequest, GetProtocolResponse, HealthResponse, HeartbeatRequest, HeartbeatResponse, ListPeersRequest, ListPeersResponse, MeshSyncRequest, MeshSyncResponse, Message, MessageMeta, MessageMode, OkResult, Peer, PeerScope, PollMessagesRequest, PollMessagesResponse, ProtocolDecl, ProtocolDefinition, ProtocolErrorCode, ProtocolErrorResponse, ProtocolRoute, ProtocolRouteMap, RegisterRequest, RegisterResponse, RelayMessagesRequest, RelayMessagesResponse, RelayedMessage, SendMessageRequest, SendMessageResponse, SessionAuthLine, SessionSocketLine, SessionUserLine, SetNameRequest, SetNameResponse, SetProtocolRequest, SetProtocolResponse, SetSummaryRequest, SetSummaryResponse, StoredProtocol, UnregisterRequest, UnregisterResponse, UnsetProtocolRequest, UnsetProtocolResponse };
package/dist/index.d.ts CHANGED
@@ -56,6 +56,31 @@ declare const ENV: {
56
56
  readonly DELIVERY: "AGENT_MESSENGER_DELIVERY";
57
57
  };
58
58
  type EnvName = (typeof ENV)[keyof typeof ENV];
59
+ /**
60
+ * Extra POST routes of the protocol registry. Like `RELAY_ROUTE`, not part of `BROKER_POST_ROUTES`
61
+ * (that table mirrors the legacy broker); request / response shapes are `ProtocolRouteMap` in `http.ts`.
62
+ */
63
+ declare const PROTOCOL_ROUTES: readonly ["/set-protocol", "/unset-protocol", "/get-protocol"];
64
+ type ProtocolRoute = (typeof PROTOCOL_ROUTES)[number];
65
+ /** Max size of `protocol.json` (UTF-8 bytes). */
66
+ declare const PROTOCOL_MANIFEST_MAX_BYTES: number;
67
+ /** Max size of `PROTOCOL.md` (UTF-8 bytes). */
68
+ declare const PROTOCOL_DOC_MAX_BYTES: number;
69
+ /** Max size of manifest + doc together (UTF-8 bytes). */
70
+ declare const PROTOCOL_TOTAL_MAX_BYTES: number;
71
+ /** Max number of `(name, version)` definitions one broker holds; more is `LIMIT`. */
72
+ declare const PROTOCOL_MAX_STORED = 256;
73
+ /** Max length (characters) of each of `meta.protocol` and `meta.version`. */
74
+ declare const PROTOCOL_META_MAX_LENGTH = 128;
75
+ /** Valid protocol `name`. */
76
+ declare const PROTOCOL_NAME_PATTERN: RegExp;
77
+ /**
78
+ * Error `code`s of the protocol feature (design 7.2). `NO_TAG` is not listed: a repo without tags is
79
+ * not an error (`0.0.0+<sha7>`). Raised by the broker: HASH_MISMATCH, PROTOCOL_CONFLICT, LIMIT,
80
+ * PROTOCOL_NOT_FOUND, and TOO_LARGE / INVALID_DEFINITION on re-check. The rest are raised by the client.
81
+ */
82
+ declare const PROTOCOL_ERROR_CODES: readonly ["REPO_UNREACHABLE", "UNSUPPORTED_REPO", "VERSION_NOT_FOUND", "INVALID_DEFINITION", "VERSION_MISMATCH", "TOO_LARGE", "HASH_MISMATCH", "PROTOCOL_CONFLICT", "LIMIT", "PROTOCOL_NOT_FOUND", "BROKER_UNSUPPORTED", "INVALID_META"];
83
+ type ProtocolErrorCode = (typeof PROTOCOL_ERROR_CODES)[number];
59
84
 
60
85
  /**
61
86
  * Broker HTTP API — request / response JSON shapes.
@@ -72,6 +97,7 @@ type EnvName = (typeof ENV)[keyof typeof ENV];
72
97
  * - Unknown POST path: `404 {"error":"not found"}`. Handler exception: `500 {"error":<message>}`.
73
98
  * - Any other method: `200 text/plain` `agent-messenger broker <version>\n`.
74
99
  */
100
+
75
101
  /** A registered session as stored and returned by the broker. */
76
102
  interface Peer {
77
103
  /** 8-char broker-generated id (`[a-z0-9]`). */
@@ -95,6 +121,42 @@ interface Peer {
95
121
  * registered directly. Never returned by `/list-peers`.
96
122
  */
97
123
  origin?: string;
124
+ /**
125
+ * Protocols this peer serves (`/set-protocol`, re-sent on `/register` and `/heartbeat`). Hash only; the
126
+ * definition body lives in the broker's protocol store. Absent when none (and from 0.9.x brokers).
127
+ */
128
+ protocols?: ProtocolDecl[];
129
+ }
130
+ /** A protocol a peer declares: identity (`name` + `version`) and the hash of its definition body. */
131
+ interface ProtocolDecl {
132
+ /** `^[a-z0-9][a-z0-9._-]{0,63}$` (`PROTOCOL_NAME_PATTERN`). */
133
+ name: string;
134
+ /** semver, or `0.0.0+<sha7>` for a repo without tags. */
135
+ version: string;
136
+ /** `sha256(canonicalJSON({manifest, doc}))` as lower-case hex. */
137
+ hash: string;
138
+ }
139
+ /** The definition body a server fetched from its protocol repo. */
140
+ interface ProtocolDefinition {
141
+ /** Parsed `protocol.json`. */
142
+ manifest: object;
143
+ /** `PROTOCOL.md` text; `""` when the repo has none. */
144
+ doc: string;
145
+ /** Normalized repo notation (`github:o/r`, `gitea:<base>/o/r`, `file:<abs path>`). Not part of the hash. */
146
+ source: string;
147
+ /** tag or sha the body was read at (`local` for a local path). Not part of the hash. */
148
+ ref: string;
149
+ }
150
+ /** What the broker keeps per `(name, version)`. */
151
+ interface StoredProtocol extends ProtocolDecl {
152
+ definition: ProtocolDefinition;
153
+ /** ISO 8601 timestamp of the first successful `/set-protocol` for this `(name, version)`. */
154
+ registered_at: string;
155
+ }
156
+ /** Protocol tag of a message: which protocol (and version) the sender spoke. Both fields are required when present. */
157
+ interface MessageMeta {
158
+ protocol: string;
159
+ version: string;
98
160
  }
99
161
  type MessageMode = "pull" | "push" | "ping";
100
162
  /** A message as stored and returned by the broker. */
@@ -120,6 +182,8 @@ interface Message {
120
182
  acknowledged: 0 | 1;
121
183
  /** Mesh only: `<sender broker id>:<id there>` of a message forwarded by another broker (duplicate guard). */
122
184
  mesh_key?: string;
185
+ /** Protocol tag given to `/send-message`; returned by `/poll-messages` and kept across relay / mesh forwarding. */
186
+ meta?: MessageMeta;
123
187
  }
124
188
  /** `/list-peers` scope filter. Unknown values behave as `"all"`. */
125
189
  type PeerScope = "all" | "host" | "directory" | "repo";
@@ -155,15 +219,29 @@ interface RegisterRequest {
155
219
  tty?: string | null;
156
220
  summary?: string;
157
221
  name?: string;
222
+ /** Protocols to declare right away (hash only; bodies follow via `/set-protocol`). */
223
+ protocols?: ProtocolDecl[];
158
224
  }
159
225
  interface RegisterResponse {
160
226
  id: string;
161
227
  }
162
228
  interface HeartbeatRequest {
163
229
  id: string;
230
+ /** Every protocol the peer currently serves (hash only), sent on each heartbeat. */
231
+ protocols?: ProtocolDecl[];
164
232
  }
165
- /** `ok:false` with `"unknown peer (re-register)"` when the broker does not know the id. */
166
- type HeartbeatResponse = OkResult;
233
+ /**
234
+ * `ok:false` with `"unknown peer (re-register)"` when the broker does not know the id.
235
+ * `resend` lists the `"name@version"` of declared protocols whose body the broker does not hold; the client answers
236
+ * with `/set-protocol`. Absent when nothing is missing (and from 0.9.x brokers).
237
+ */
238
+ type HeartbeatResponse = {
239
+ ok: true;
240
+ resend?: string[];
241
+ } | {
242
+ ok: false;
243
+ error: string;
244
+ };
167
245
  interface SetSummaryRequest {
168
246
  id: string;
169
247
  summary?: string;
@@ -184,6 +262,10 @@ interface ListPeersRequest {
184
262
  git_root?: string | null;
185
263
  /** Peer id to omit from the result (the caller itself). */
186
264
  exclude_id?: string;
265
+ /** Only peers that declare this protocol (AND with `scope`). */
266
+ protocol?: string;
267
+ /** Only with `protocol`: only peers that declare exactly this version. */
268
+ version?: string;
187
269
  }
188
270
  type ListPeersResponse = Peer[];
189
271
  interface SendMessageRequest {
@@ -195,6 +277,8 @@ interface SendMessageRequest {
195
277
  text?: string;
196
278
  /** Defaults to `"pull"`. Unknown values are rejected. */
197
279
  mode?: MessageMode;
280
+ /** Protocol tag. Both fields must be strings of at most `PROTOCOL_META_MAX_LENGTH` characters. */
281
+ meta?: MessageMeta;
198
282
  }
199
283
  type SendMessageResponse = {
200
284
  ok: true;
@@ -243,6 +327,7 @@ interface RelayedMessage {
243
327
  text: string;
244
328
  mode?: MessageMode;
245
329
  sent_at: string;
330
+ meta?: MessageMeta;
246
331
  }
247
332
  interface RelayMessagesRequest {
248
333
  messages: RelayedMessage[];
@@ -256,6 +341,72 @@ interface RelayMessagesResponse {
256
341
  }
257
342
  /** Extra POST route served by the TypeScript broker. Not in `BROKER_POST_ROUTES` (that table mirrors the legacy broker). */
258
343
  declare const RELAY_ROUTE: "/relay-messages";
344
+ /** `{ok:false}` answer of the protocol routes; `code` is one of `PROTOCOL_ERROR_CODES`. */
345
+ interface ProtocolErrorResponse {
346
+ ok: false;
347
+ error: string;
348
+ code: ProtocolErrorCode;
349
+ /** `PROTOCOL_CONFLICT`: hash the broker already holds for this `(name, version)`. */
350
+ existing_hash?: string;
351
+ /** `PROTOCOL_CONFLICT`: servers (`name@host`) that hold it. */
352
+ servers?: string[];
353
+ /** `PROTOCOL_NOT_FOUND`: versions the broker does hold for the name (may be empty). */
354
+ versions?: string[];
355
+ }
356
+ interface SetProtocolRequest {
357
+ /** Peer id of the server. */
358
+ id: string;
359
+ name: string;
360
+ version: string;
361
+ /** Re-computed by the broker; a different value is `HASH_MISMATCH`. */
362
+ hash: string;
363
+ definition: ProtocolDefinition;
364
+ }
365
+ /** `{ok:false, error}` without `code` only for an unknown peer id. */
366
+ type SetProtocolResponse = {
367
+ ok: true;
368
+ } | ProtocolErrorResponse | {
369
+ ok: false;
370
+ error: string;
371
+ };
372
+ interface UnsetProtocolRequest {
373
+ id: string;
374
+ /** Protocol to drop; all declarations of the peer when omitted. */
375
+ name?: string;
376
+ }
377
+ type UnsetProtocolResponse = {
378
+ ok: true;
379
+ removed: number;
380
+ } | {
381
+ ok: false;
382
+ error: string;
383
+ };
384
+ interface GetProtocolRequest {
385
+ name: string;
386
+ /** Highest version the broker holds when omitted (`0.0.0+sha` counts as lowest). */
387
+ version?: string;
388
+ }
389
+ /** `servers` = number of alive peers (replicas included) that declare the returned protocol. */
390
+ type GetProtocolResponse = {
391
+ ok: true;
392
+ protocol: StoredProtocol;
393
+ servers: number;
394
+ } | ProtocolErrorResponse;
395
+ /** Request / response pair per protocol route. */
396
+ interface ProtocolRouteMap {
397
+ "/set-protocol": {
398
+ request: SetProtocolRequest;
399
+ response: SetProtocolResponse;
400
+ };
401
+ "/unset-protocol": {
402
+ request: UnsetProtocolRequest;
403
+ response: UnsetProtocolResponse;
404
+ };
405
+ "/get-protocol": {
406
+ request: GetProtocolRequest;
407
+ response: GetProtocolResponse;
408
+ };
409
+ }
259
410
  /** The only GET route. */
260
411
  declare const HEALTH_ROUTE: "/health";
261
412
  /** POST routes, in the order of `ROUTES` in `legacy/src/broker.js`. */
@@ -319,6 +470,8 @@ interface MeshSyncRequest {
319
470
  peers: Peer[];
320
471
  /** Undelivered messages the sender holds for peers that live on the receiver. Ids are the sender's. */
321
472
  messages: Message[];
473
+ /** Definitions declared by the sender's direct peers. Absent from 0.9.x brokers. */
474
+ protocols?: StoredProtocol[];
322
475
  }
323
476
  interface MeshSyncResponse {
324
477
  /** Id of the receiving broker. */
@@ -327,6 +480,8 @@ interface MeshSyncResponse {
327
480
  peers: Peer[];
328
481
  /** Sender-side ids of the messages the receiver took over; the sender drops them. */
329
482
  accepted: number[];
483
+ /** Definitions declared by the receiver's direct peers. Absent from 0.9.x brokers. */
484
+ protocols?: StoredProtocol[];
330
485
  }
331
486
 
332
487
  /**
@@ -390,5 +545,17 @@ interface SessionUserLine {
390
545
  }
391
546
  type SessionSocketLine = SessionAuthLine | SessionUserLine;
392
547
 
393
- export { BROKER_POST_ROUTES, DEFAULT_BIND, DEFAULT_DISCOVERY_PORT, DEFAULT_DISCOVERY_TIMEOUT_MS, DEFAULT_HTTP_PORT, DISCOVERY_BROADCAST_ADDR, DISCOVERY_KEY, DISCOVERY_PROBE, DISCOVERY_PROBE_VALUE, DISCOVERY_REPLY_VALUE, ENV, HEALTH_ROUTE, HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS, LOCAL_HOSTS, MESH_SYNC_ROUTE, POLL_INTERVAL_MS, RELAY_ROUTE, SESSION_SOCKET_ENV, SESSION_TOKEN_ENV };
394
- export type { AcknowledgeMessagesRequest, AcknowledgeMessagesResponse, BrokerErrorResponse, BrokerPostRoute, BrokerRequest, BrokerResponse, BrokerRouteMap, DiscoveryProbe, DiscoveryReply, EnvName, GetBufferedMessagesRequest, GetBufferedMessagesResponse, HealthResponse, HeartbeatRequest, HeartbeatResponse, ListPeersRequest, ListPeersResponse, MeshSyncRequest, MeshSyncResponse, Message, MessageMode, OkResult, Peer, PeerScope, PollMessagesRequest, PollMessagesResponse, RegisterRequest, RegisterResponse, RelayMessagesRequest, RelayMessagesResponse, RelayedMessage, SendMessageRequest, SendMessageResponse, SessionAuthLine, SessionSocketLine, SessionUserLine, SetNameRequest, SetNameResponse, SetSummaryRequest, SetSummaryResponse, UnregisterRequest, UnregisterResponse };
548
+ /**
549
+ * Canonical JSON for protocol hashes: object keys in ascending (UTF-16 code unit) order at every depth,
550
+ * no whitespace, otherwise exactly what `JSON.stringify` writes for each value. Pure TypeScript with no
551
+ * Node dependency, so any runtime can import it. The hash itself,
552
+ * `sha256(canonicalJSON({manifest, doc}))` in lower-case hex, is computed by each side with its own
553
+ * sha256 (broker: `node:crypto`).
554
+ *
555
+ * Input is expected to be JSON data (what `JSON.parse` returns). As in `JSON.stringify`, an object member whose
556
+ * value is `undefined` / a function / a symbol is skipped, and such an array element becomes `null`.
557
+ */
558
+ declare function canonicalJSON(value: unknown): string;
559
+
560
+ export { BROKER_POST_ROUTES, DEFAULT_BIND, DEFAULT_DISCOVERY_PORT, DEFAULT_DISCOVERY_TIMEOUT_MS, DEFAULT_HTTP_PORT, DISCOVERY_BROADCAST_ADDR, DISCOVERY_KEY, DISCOVERY_PROBE, DISCOVERY_PROBE_VALUE, DISCOVERY_REPLY_VALUE, ENV, HEALTH_ROUTE, HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS, LOCAL_HOSTS, MESH_SYNC_ROUTE, POLL_INTERVAL_MS, PROTOCOL_DOC_MAX_BYTES, PROTOCOL_ERROR_CODES, PROTOCOL_MANIFEST_MAX_BYTES, PROTOCOL_MAX_STORED, PROTOCOL_META_MAX_LENGTH, PROTOCOL_NAME_PATTERN, PROTOCOL_ROUTES, PROTOCOL_TOTAL_MAX_BYTES, RELAY_ROUTE, SESSION_SOCKET_ENV, SESSION_TOKEN_ENV, canonicalJSON };
561
+ export type { AcknowledgeMessagesRequest, AcknowledgeMessagesResponse, BrokerErrorResponse, BrokerPostRoute, BrokerRequest, BrokerResponse, BrokerRouteMap, DiscoveryProbe, DiscoveryReply, EnvName, GetBufferedMessagesRequest, GetBufferedMessagesResponse, GetProtocolRequest, GetProtocolResponse, HealthResponse, HeartbeatRequest, HeartbeatResponse, ListPeersRequest, ListPeersResponse, MeshSyncRequest, MeshSyncResponse, Message, MessageMeta, MessageMode, OkResult, Peer, PeerScope, PollMessagesRequest, PollMessagesResponse, ProtocolDecl, ProtocolDefinition, ProtocolErrorCode, ProtocolErrorResponse, ProtocolRoute, ProtocolRouteMap, RegisterRequest, RegisterResponse, RelayMessagesRequest, RelayMessagesResponse, RelayedMessage, SendMessageRequest, SendMessageResponse, SessionAuthLine, SessionSocketLine, SessionUserLine, SetNameRequest, SetNameResponse, SetProtocolRequest, SetProtocolResponse, SetSummaryRequest, SetSummaryResponse, StoredProtocol, UnregisterRequest, UnregisterResponse, UnsetProtocolRequest, UnsetProtocolResponse };
package/dist/index.mjs CHANGED
@@ -36,6 +36,27 @@ const ENV = {
36
36
  /** Inbound delivery mode: `"auto"` (default) / `"socket"` / `"channel"`. */
37
37
  DELIVERY: "AGENT_MESSENGER_DELIVERY"
38
38
  };
39
+ const PROTOCOL_ROUTES = ["/set-protocol", "/unset-protocol", "/get-protocol"];
40
+ const PROTOCOL_MANIFEST_MAX_BYTES = 64 * 1024;
41
+ const PROTOCOL_DOC_MAX_BYTES = 64 * 1024;
42
+ const PROTOCOL_TOTAL_MAX_BYTES = 128 * 1024;
43
+ const PROTOCOL_MAX_STORED = 256;
44
+ const PROTOCOL_META_MAX_LENGTH = 128;
45
+ const PROTOCOL_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
46
+ const PROTOCOL_ERROR_CODES = [
47
+ "REPO_UNREACHABLE",
48
+ "UNSUPPORTED_REPO",
49
+ "VERSION_NOT_FOUND",
50
+ "INVALID_DEFINITION",
51
+ "VERSION_MISMATCH",
52
+ "TOO_LARGE",
53
+ "HASH_MISMATCH",
54
+ "PROTOCOL_CONFLICT",
55
+ "LIMIT",
56
+ "PROTOCOL_NOT_FOUND",
57
+ "BROKER_UNSUPPORTED",
58
+ "INVALID_META"
59
+ ];
39
60
 
40
61
  const RELAY_ROUTE = "/relay-messages";
41
62
  const HEALTH_ROUTE = "/health";
@@ -62,4 +83,21 @@ const DISCOVERY_BROADCAST_ADDR = "255.255.255.255";
62
83
  const SESSION_SOCKET_ENV = "CLAUDE_CODE_MESSAGING_SOCKET";
63
84
  const SESSION_TOKEN_ENV = "CLAUDE_CODE_MESSAGING_TOKEN";
64
85
 
65
- export { BROKER_POST_ROUTES, DEFAULT_BIND, DEFAULT_DISCOVERY_PORT, DEFAULT_DISCOVERY_TIMEOUT_MS, DEFAULT_HTTP_PORT, DISCOVERY_BROADCAST_ADDR, DISCOVERY_KEY, DISCOVERY_PROBE, DISCOVERY_PROBE_VALUE, DISCOVERY_REPLY_VALUE, ENV, HEALTH_ROUTE, HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS, LOCAL_HOSTS, MESH_SYNC_ROUTE, POLL_INTERVAL_MS, RELAY_ROUTE, SESSION_SOCKET_ENV, SESSION_TOKEN_ENV };
86
+ function canonicalJSON(value) {
87
+ if (value === null || typeof value !== "object") {
88
+ return JSON.stringify(value) ?? "null";
89
+ }
90
+ if (Array.isArray(value)) {
91
+ return `[${value.map((v) => canonicalJSON(v)).join(",")}]`;
92
+ }
93
+ const obj = value;
94
+ const parts = [];
95
+ for (const k of Object.keys(obj).sort()) {
96
+ const v = obj[k];
97
+ if (v === void 0 || typeof v === "function" || typeof v === "symbol") continue;
98
+ parts.push(`${JSON.stringify(k)}:${canonicalJSON(v)}`);
99
+ }
100
+ return `{${parts.join(",")}}`;
101
+ }
102
+
103
+ export { BROKER_POST_ROUTES, DEFAULT_BIND, DEFAULT_DISCOVERY_PORT, DEFAULT_DISCOVERY_TIMEOUT_MS, DEFAULT_HTTP_PORT, DISCOVERY_BROADCAST_ADDR, DISCOVERY_KEY, DISCOVERY_PROBE, DISCOVERY_PROBE_VALUE, DISCOVERY_REPLY_VALUE, ENV, HEALTH_ROUTE, HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS, LOCAL_HOSTS, MESH_SYNC_ROUTE, POLL_INTERVAL_MS, PROTOCOL_DOC_MAX_BYTES, PROTOCOL_ERROR_CODES, PROTOCOL_MANIFEST_MAX_BYTES, PROTOCOL_MAX_STORED, PROTOCOL_META_MAX_LENGTH, PROTOCOL_NAME_PATTERN, PROTOCOL_ROUTES, PROTOCOL_TOTAL_MAX_BYTES, RELAY_ROUTE, SESSION_SOCKET_ENV, SESSION_TOKEN_ENV, canonicalJSON };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unagents/messenger-protocol",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "agent-messenger protocol: types and constants shared by broker, client and CLI. No runtime logic.",
5
5
  "type": "module",
6
6
  "license": "MIT",