@joe-broadhead/messaging 0.7.1 → 0.8.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +214 -2
  2. package/dist/bin/messaging-mcp.js +5043 -4054
  3. package/dist/bin/messaging-relay.js +8263 -5425
  4. package/dist/bin/messaging.js +5982 -4906
  5. package/dist/bin/messagingd.js +19956 -13716
  6. package/dist/lib/modules/core/agent/operations.js +19 -327
  7. package/dist/lib/modules/core/agent/present.js +15 -55
  8. package/dist/lib/modules/core/agent-tools.js +23 -139
  9. package/dist/lib/modules/core/consent.js +1 -1
  10. package/dist/lib/modules/core/conversation/event.js +29 -4
  11. package/dist/lib/modules/core/conversation/guest-invitation.js +42 -0
  12. package/dist/lib/modules/core/conversation/log.js +287 -65
  13. package/dist/lib/modules/core/conversation/request-log.js +158 -0
  14. package/dist/lib/modules/core/conversation/request-seal.js +41 -0
  15. package/dist/lib/modules/core/conversation/requests.js +87 -0
  16. package/dist/lib/modules/core/conversation/seal.js +90 -37
  17. package/dist/lib/modules/core/conversation-agent-tools.js +69 -0
  18. package/dist/lib/modules/core/federation/directory-proof.js +83 -0
  19. package/dist/lib/modules/core/federation/proof-state.js +167 -0
  20. package/dist/lib/modules/core/federation/roster-proof.js +74 -0
  21. package/dist/lib/modules/core/index.js +8 -3
  22. package/dist/lib/modules/core/jelly-host.js +5 -5
  23. package/dist/lib/modules/core/schema/common.js +31 -5
  24. package/dist/lib/modules/core/schema/consent.js +5 -4
  25. package/dist/lib/modules/core/schema/contact-request.js +22 -0
  26. package/dist/lib/modules/core/schema/conv-frames.js +94 -0
  27. package/dist/lib/modules/core/schema/conversation.js +67 -33
  28. package/dist/lib/modules/core/schema/envelope.js +8 -24
  29. package/dist/lib/modules/core/schema/errors.js +4 -3
  30. package/dist/lib/modules/core/schema/guest-invitation.js +13 -0
  31. package/dist/lib/modules/core/schema/index.js +5 -0
  32. package/dist/lib/modules/core/schema/ipc/consent.js +13 -9
  33. package/dist/lib/modules/core/schema/ipc/conversation-views.js +80 -0
  34. package/dist/lib/modules/core/schema/ipc/conversations.js +128 -0
  35. package/dist/lib/modules/core/schema/ipc/device.js +2 -0
  36. package/dist/lib/modules/core/schema/ipc/jelly.js +3 -38
  37. package/dist/lib/modules/core/schema/ipc/shapes.js +14 -72
  38. package/dist/lib/modules/core/schema/ipc/surfaces.js +12 -195
  39. package/dist/lib/modules/core/schema/ipc/tasks.js +31 -0
  40. package/dist/lib/modules/core/schema/ipc.js +18 -44
  41. package/dist/lib/modules/core/testing.js +1 -1
  42. package/dist/lib/modules/core/version.js +1 -1
  43. package/dist/lib/modules/ipc/client.js +10 -2
  44. package/dist/lib/modules/ipc/server.js +14 -5
  45. package/dist/lib/modules/ipc/testing.js +13 -23
  46. package/dist/spec/messaging-v1.md +546 -409
  47. package/dist/third-party-notices.md +1 -1
  48. package/dist/third-party.json +1 -1
  49. package/dist/types/core/agent/operations.d.ts +5 -52
  50. package/dist/types/core/agent/present.d.ts +6 -21
  51. package/dist/types/core/agent-tools.d.ts +263 -442
  52. package/dist/types/core/conversation/event.d.ts +6 -6
  53. package/dist/types/core/conversation/guest-invitation.d.ts +8 -0
  54. package/dist/types/core/conversation/log.d.ts +28 -13
  55. package/dist/types/core/conversation/request-log.d.ts +24 -0
  56. package/dist/types/core/conversation/request-seal.d.ts +6 -0
  57. package/dist/types/core/conversation/requests.d.ts +14 -0
  58. package/dist/types/core/conversation/seal.d.ts +32 -9
  59. package/dist/types/core/conversation-agent-tools.d.ts +918 -0
  60. package/dist/types/core/federation/directory-proof.d.ts +8 -0
  61. package/dist/types/core/federation/proof-state.d.ts +114 -0
  62. package/dist/types/core/federation/roster-proof.d.ts +7 -0
  63. package/dist/types/core/index.d.ts +10 -5
  64. package/dist/types/core/jelly-host.d.ts +1 -1
  65. package/dist/types/core/schema/common.d.ts +31 -6
  66. package/dist/types/core/schema/consent.d.ts +7 -7
  67. package/dist/types/core/schema/contact-request.d.ts +264 -0
  68. package/dist/types/core/schema/conv-frames.d.ts +1200 -0
  69. package/dist/types/core/schema/conversation.d.ts +1410 -264
  70. package/dist/types/core/schema/envelope.d.ts +83 -130
  71. package/dist/types/core/schema/errors.d.ts +3 -1
  72. package/dist/types/core/schema/guest-invitation.d.ts +51 -0
  73. package/dist/types/core/schema/index.d.ts +5 -0
  74. package/dist/types/core/schema/ipc/consent.d.ts +89 -49
  75. package/dist/types/core/schema/ipc/conversation-views.d.ts +1023 -0
  76. package/dist/types/core/schema/ipc/conversations.d.ts +3987 -0
  77. package/dist/types/core/schema/ipc/device.d.ts +4 -0
  78. package/dist/types/core/schema/ipc/jelly.d.ts +2 -105
  79. package/dist/types/core/schema/ipc/shapes.d.ts +13 -95
  80. package/dist/types/core/schema/ipc/surfaces.d.ts +25 -427
  81. package/dist/types/core/schema/ipc/tasks.d.ts +208 -0
  82. package/dist/types/core/schema/ipc.d.ts +2671 -525
  83. package/dist/types/core/schema/relay.d.ts +8 -4
  84. package/dist/types/core/testing.d.ts +1 -1
  85. package/dist/types/core/version.d.ts +1 -1
  86. package/dist/types/ipc/client.d.ts +4 -1
  87. package/dist/types/ipc/server.d.ts +10 -1
  88. package/dist/types/ipc/testing.d.ts +21 -8
  89. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import net from "node:net";
2
2
  import { dirname } from "node:path";
3
- import { ErrorCode, IPC_METHODS, IPC_PROTOCOL, IpcEvent, IpcHello, LIMITS, MESSAGING_ERROR_RPC_CODE, MessagingError, VERSION, parseWire, } from "../core/index.js";
3
+ import { ErrorCode, CONVERSATION_METHODS, IPC_METHODS, IPC_PROTOCOL, IpcEvent, IpcHello, LIMITS, MESSAGING_ERROR_RPC_CODE, MessagingError, VERSION, parseWire, } from "../core/index.js";
4
4
  import { encodeLine, LineDecoder } from "./framing.js";
5
5
  import { configuredDaemonUid, IS_WINDOWS, messagingHome, socketPath as defaultSocketPath } from "./paths.js";
6
6
  import { readPointer } from "./pipes.js";
@@ -153,6 +153,13 @@ export class MessagingClient {
153
153
  return this.isClosed;
154
154
  }
155
155
  call(method, params, options = {}) {
156
+ return this.callWith(method, params, IPC_METHODS[method].result, options);
157
+ }
158
+ /** The staged conversation contract. Use only with a server that opted into it, until cut-over. */
159
+ callConversation(method, params, options = {}) {
160
+ return this.callWith(method, params, CONVERSATION_METHODS[method].result, options);
161
+ }
162
+ callWith(method, params, result, options) {
156
163
  if (this.isClosed)
157
164
  return Promise.reject(new MessagingError("daemon_unavailable", "connection to the Messaging daemon is closed"));
158
165
  if (options.signal?.aborted)
@@ -168,6 +175,7 @@ export class MessagingClient {
168
175
  options.signal?.addEventListener("abort", onAbort, { once: true });
169
176
  this.pending.set(id, {
170
177
  method,
178
+ result,
171
179
  resolve: resolve,
172
180
  reject,
173
181
  cleanup: () => {
@@ -236,7 +244,7 @@ export class MessagingClient {
236
244
  this.settle(message.id, undefined, toError(message.error));
237
245
  return;
238
246
  }
239
- const checked = IPC_METHODS[pending.method].result.safeParse(message.result);
247
+ const checked = pending.result.safeParse(message.result);
240
248
  if (!checked.success) {
241
249
  this.settle(message.id, undefined, new MessagingError("internal", `daemon returned an invalid result for ${pending.method}`));
242
250
  return;
@@ -2,7 +2,7 @@ import { mkdirSync } from "node:fs";
2
2
  import { chmod, chown, rm, stat } from "node:fs/promises";
3
3
  import net from "node:net";
4
4
  import { dirname } from "node:path";
5
- import { AGENT_METHODS, IPC_METHODS, IPC_PROTOCOL, IpcEvent, VERSION, LIMITS, MESSAGING_ERROR_RPC_CODE, MessagingError, parseWire, } from "../core/index.js";
5
+ import { AGENT_METHODS, CONVERSATION_METHODS, CONVERSATION_TIERS, IPC_METHODS, IPC_PROTOCOL, IpcEvent, VERSION, LIMITS, MESSAGING_ERROR_RPC_CODE, MessagingError, parseWire, } from "../core/index.js";
6
6
  import { encodeLine, LineDecoder, LineTooLongError } from "./framing.js";
7
7
  import { IS_WINDOWS, MAX_SOCKET_PATH_BYTES } from "./paths.js";
8
8
  import { newPipeName, readPointer, writePointer } from "./pipes.js";
@@ -29,6 +29,13 @@ function agentTierRefusal(method) {
29
29
  * `group` is refused there: Node cannot give a pipe to a group.
30
30
  */
31
31
  export async function createIpcServer(options) {
32
+ return createServer(options, false);
33
+ }
34
+ /** Used only by the test entry: never changes the real daemon's method table. */
35
+ export async function createConversationIpcServerForTesting(options) {
36
+ return createServer({ ...options, handlers: options.handlers }, true);
37
+ }
38
+ async function createServer(options, conversations) {
32
39
  const path = options.socketPath;
33
40
  if (options.tier !== "agent" && options.tier !== "admin")
34
41
  throw new MessagingError("invalid_request", "an IPC server needs a tier: agent or admin");
@@ -100,14 +107,16 @@ export async function createIpcServer(options) {
100
107
  const { id, method } = request;
101
108
  if (id === undefined)
102
109
  return; // client notifications are not part of the API
103
- const spec = Object.hasOwn(IPC_METHODS, method) ? IPC_METHODS[method] : undefined;
110
+ const methods = conversations ? CONVERSATION_METHODS : IPC_METHODS;
111
+ const spec = Object.hasOwn(methods, method) ? methods[method] : undefined;
104
112
  const handler = options.handlers[method];
105
- if (!spec || (!handler && method !== "events.subscribe")) {
113
+ if (!spec || (!handler && (conversations || method !== "events.subscribe"))) {
106
114
  ctx.send({ jsonrpc: "2.0", id, error: { code: RPC.methodNotFound, message: `method not found: ${method}` } });
107
115
  return;
108
116
  }
109
117
  // The tier is checked before the params are read, so the agent socket tells nothing about the rest.
110
- if (options.tier === "agent" && !AGENT_TIER.has(method)) {
118
+ const agentAllowed = conversations ? CONVERSATION_TIERS[method] === "agent" : AGENT_TIER.has(method);
119
+ if (options.tier === "agent" && !agentAllowed) {
111
120
  ctx.send({ jsonrpc: "2.0", id, error: toRpcError(new MessagingError("forbidden", agentTierRefusal(method))) });
112
121
  return;
113
122
  }
@@ -130,7 +139,7 @@ export async function createIpcServer(options) {
130
139
  try {
131
140
  const result = handler ? await handler(parsed.data, ctx, authority) : { ok: true };
132
141
  // Subscriptions start only once the handler has accepted the keys.
133
- if (method === "events.subscribe")
142
+ if (!conversations && method === "events.subscribe")
134
143
  for (const key of parsed.data.keys)
135
144
  ctx.subscriptions.add(key);
136
145
  const checked = spec.result.safeParse(result);
@@ -1,10 +1,10 @@
1
1
  import { mkdtempSync, rmSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { IPC_METHODS, MessagingError } from "../core/index.js";
4
+ import { CONVERSATION_METHODS, IPC_METHODS, MessagingError } from "../core/index.js";
5
5
  import { MessagingClient } from "./client.js";
6
6
  import { IS_WINDOWS, MAX_SOCKET_PATH_BYTES, socketPath as socketOf } from "./paths.js";
7
- import { createIpcServer } from "./server.js";
7
+ import { createConversationIpcServerForTesting, createIpcServer } from "./server.js";
8
8
  import { newPipeName, readPointer, writePointer } from "./pipes.js";
9
9
  import { securePrivate } from "./windows-acl.js";
10
10
  /**
@@ -55,36 +55,30 @@ export function rawEndpoint(socketPath) {
55
55
  export function ulid(n) {
56
56
  return `01JC${String(n).padStart(22, "0")}`;
57
57
  }
58
- /** Polls `predicate` every 10 ms until it holds; throws after `timeoutMs`. */
59
- export async function until(predicate, timeoutMs = 3_000, what = "condition") {
60
- const deadline = Date.now() + timeoutMs;
58
+ /** Polls state with no notification; the enclosing test's timeout is the single bound. Legacy timeout and label are ignored. */
59
+ export async function until(predicate, _timeoutMs, _what = "condition") {
61
60
  while (!predicate()) {
62
- if (Date.now() > deadline)
63
- throw new Error(`timed out waiting for ${what}`);
64
- await new Promise((resolve) => setTimeout(resolve, 10));
61
+ await new Promise((resolve) => setTimeout(resolve, 10).unref());
65
62
  }
66
63
  }
67
64
  export async function recordingDaemon(options = {}) {
65
+ const conversationMode = "methods" in options && options.methods !== undefined;
66
+ if (conversationMode && options.methods !== CONVERSATION_METHODS)
67
+ throw new TypeError("recordingDaemon: unsupported method table");
68
68
  const owned = options.at === undefined;
69
69
  const dir = options.at?.dir ?? tempSocketPath(options.prefix).dir;
70
70
  const socketPath = socketOf(dir, "agent");
71
71
  const adminSocketPath = socketOf(dir, "admin");
72
72
  const handlers = { ...options.handlers };
73
73
  const calls = [];
74
- const convSubscribers = new Set();
75
74
  const dispatch = {};
76
- for (const method of Object.keys(IPC_METHODS)) {
75
+ for (const method of Object.keys(conversationMode ? CONVERSATION_METHODS : IPC_METHODS)) {
77
76
  dispatch[method] = async (params, ctx) => {
78
77
  calls.push({ method, params: structuredClone(params) });
79
78
  const handler = handlers[method];
80
79
  if (!handler)
81
80
  throw new MessagingError("forbidden", `fake daemon: no handler for ${method}`);
82
- const result = await handler(params, ctx);
83
- if (method === "conv.subscribe") {
84
- convSubscribers.add(ctx);
85
- ctx.signal.addEventListener("abort", () => convSubscribers.delete(ctx));
86
- }
87
- return result;
81
+ return handler(params, ctx);
88
82
  };
89
83
  }
90
84
  let servers;
@@ -92,8 +86,9 @@ export async function recordingDaemon(options = {}) {
92
86
  if (servers)
93
87
  return;
94
88
  const version = options.version === undefined ? {} : { version: options.version };
95
- const agent = await createIpcServer({ socketPath, tier: "agent", handlers: dispatch, ...version });
96
- const admin = await createIpcServer({ socketPath: adminSocketPath, tier: "admin", handlers: dispatch, ...version }).catch(async (error) => {
89
+ const serve = conversationMode ? createConversationIpcServerForTesting : createIpcServer;
90
+ const agent = await serve({ socketPath, tier: "agent", handlers: dispatch, ...version });
91
+ const admin = await serve({ socketPath: adminSocketPath, tier: "admin", handlers: dispatch, ...version }).catch(async (error) => {
97
92
  await agent.close();
98
93
  throw error;
99
94
  });
@@ -116,17 +111,12 @@ export async function recordingDaemon(options = {}) {
116
111
  callsTo,
117
112
  last: (method) => callsTo(method).at(-1),
118
113
  handle: (method, handler) => {
119
- ;
120
114
  handlers[method] = handler;
121
115
  },
122
116
  publish: (event) => {
123
117
  for (const server of servers ?? [])
124
118
  server.publish(event);
125
119
  },
126
- conv: (event) => {
127
- for (const ctx of convSubscribers)
128
- ctx.notify(event);
129
- },
130
120
  connect: (tier = "agent") => MessagingClient.connect({ socketPath: tier === "agent" ? socketPath : adminSocketPath, connectTimeoutMs: 500 }),
131
121
  connections: () => (servers ?? []).reduce((sum, server) => sum + server.connections(), 0),
132
122
  start,