opencode-collaboration 0.2.3

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 (47) hide show
  1. package/LICENSE +194 -0
  2. package/README.md +224 -0
  3. package/README.zh-CN.md +224 -0
  4. package/commands/list-agents.md +8 -0
  5. package/commands/peers-inbox.md +8 -0
  6. package/commands/peers-name.md +8 -0
  7. package/commands/peers-outbox.md +8 -0
  8. package/commands/peers.md +8 -0
  9. package/dist/commands.d.ts +29 -0
  10. package/dist/commands.js +95 -0
  11. package/dist/config.d.ts +31 -0
  12. package/dist/config.js +50 -0
  13. package/dist/delivery.d.ts +42 -0
  14. package/dist/delivery.js +177 -0
  15. package/dist/feedback.d.ts +8 -0
  16. package/dist/feedback.js +40 -0
  17. package/dist/format.d.ts +32 -0
  18. package/dist/format.js +107 -0
  19. package/dist/gating.d.ts +4 -0
  20. package/dist/gating.js +16 -0
  21. package/dist/index.d.ts +43 -0
  22. package/dist/index.js +410 -0
  23. package/dist/listener.d.ts +37 -0
  24. package/dist/listener.js +335 -0
  25. package/dist/outbox.d.ts +12 -0
  26. package/dist/outbox.js +110 -0
  27. package/dist/permissions.d.ts +47 -0
  28. package/dist/permissions.js +194 -0
  29. package/dist/queue.d.ts +89 -0
  30. package/dist/queue.js +824 -0
  31. package/dist/registry.d.ts +70 -0
  32. package/dist/registry.js +308 -0
  33. package/dist/sender.d.ts +27 -0
  34. package/dist/sender.js +139 -0
  35. package/dist/session-runtime.d.ts +40 -0
  36. package/dist/session-runtime.js +355 -0
  37. package/dist/session-tracker.d.ts +16 -0
  38. package/dist/session-tracker.js +39 -0
  39. package/dist/tools/peers-tools.d.ts +26 -0
  40. package/dist/tools/peers-tools.js +173 -0
  41. package/dist/transport.d.ts +20 -0
  42. package/dist/transport.js +46 -0
  43. package/dist/tui.d.ts +3 -0
  44. package/dist/tui.js +228 -0
  45. package/dist/types.d.ts +162 -0
  46. package/dist/types.js +1 -0
  47. package/package.json +93 -0
package/dist/gating.js ADDED
@@ -0,0 +1,16 @@
1
+ import { resolve } from "node:path";
2
+ export function gateMessage(policy, msg, receiverDirectory) {
3
+ if (policy === "refuse")
4
+ return "refuse";
5
+ if (policy === "hold")
6
+ return "hold";
7
+ if (policy === "auto") {
8
+ if (!receiverDirectory)
9
+ return "hold";
10
+ return resolve(msg.from.directory) === resolve(receiverDirectory) ? "queue" : "hold";
11
+ }
12
+ return "queue";
13
+ }
14
+ export function isLoopMessage(msg, maxHops = 4) {
15
+ return msg.via.length > maxHops;
16
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * opencode-collaboration — cross-session messaging for opencode.
3
+ *
4
+ * Lets independent opencode instances on the same machine discover each
5
+ * other and exchange plain-text messages, modeled after Claude Code's
6
+ * cross-session messaging:
7
+ *
8
+ * - list_agents / send_message tools for the agent
9
+ * - /peers, /peers-name, /peers-inbox commands for the user
10
+ * - accept/hold/refuse inbound gating
11
+ * - each session is independently addressable and receives peer messages immediately
12
+ * - messages are ordinary (synthetic) user messages: no shared history,
13
+ * no file transfer; permissions in peer-triggered turns are governed
14
+ * by the peerPermissions option (default: auto-allow)
15
+ *
16
+ * Architecture: each instance writes a registry file in
17
+ * $XDG_DATA_HOME/opencode-collaboration/peers.d/ and runs a 127.0.0.1-only
18
+ * inbox HTTP listener (random port, bearer token). Peers POST to the
19
+ * listener; the receiving instance injects into its own session via the
20
+ * opencode SDK immediately, including while the target session is busy.
21
+ */
22
+ import type { Plugin, PluginModule } from "@opencode-ai/plugin";
23
+ import type { QueueInstance } from "./queue.js";
24
+ import type { DeliveryInstance } from "./delivery.js";
25
+ export declare function runReliabilitySweep(queue: QueueInstance, delivery: Pick<DeliveryInstance, "flush">): Promise<void>;
26
+ export declare const PeersPlugin: Plugin;
27
+ export declare const plugin: PluginModule;
28
+ export default plugin;
29
+ export { Registry, uniqueName } from "./registry.js";
30
+ export { MessageQueue, RateLimiter, createProcessMessageQueue, createSessionMessageQueue, hasSpoolRecords, migrateWorkspaceSpool, stableSessionEndpointId, stableSpoolEndpointId, } from "./queue.js";
31
+ export { SessionTracker } from "./session-tracker.js";
32
+ export { SessionRuntime } from "./session-runtime.js";
33
+ export { Delivery, deterministicPeerMessageId, formatMessages } from "./delivery.js";
34
+ export { Sender, buildMessage, buildMessageV2 } from "./sender.js";
35
+ export { InboxListener } from "./listener.js";
36
+ export { LocalTransport } from "./transport.js";
37
+ export type { LocalTransportOptions, Transport, TransportResponse, TransportTarget, } from "./transport.js";
38
+ export { gateMessage } from "./gating.js";
39
+ export { PeerPermissions, isProtectedPermission } from "./permissions.js";
40
+ export { Outbox } from "./outbox.js";
41
+ export { collapseToProcesses, formatSessionList, relativeAge, sortPeers } from "./format.js";
42
+ export { resolveConfig, validateName } from "./config.js";
43
+ export * from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,410 @@
1
+ /**
2
+ * opencode-collaboration — cross-session messaging for opencode.
3
+ *
4
+ * Lets independent opencode instances on the same machine discover each
5
+ * other and exchange plain-text messages, modeled after Claude Code's
6
+ * cross-session messaging:
7
+ *
8
+ * - list_agents / send_message tools for the agent
9
+ * - /peers, /peers-name, /peers-inbox commands for the user
10
+ * - accept/hold/refuse inbound gating
11
+ * - each session is independently addressable and receives peer messages immediately
12
+ * - messages are ordinary (synthetic) user messages: no shared history,
13
+ * no file transfer; permissions in peer-triggered turns are governed
14
+ * by the peerPermissions option (default: auto-allow)
15
+ *
16
+ * Architecture: each instance writes a registry file in
17
+ * $XDG_DATA_HOME/opencode-collaboration/peers.d/ and runs a 127.0.0.1-only
18
+ * inbox HTTP listener (random port, bearer token). Peers POST to the
19
+ * listener; the receiving instance injects into its own session via the
20
+ * opencode SDK immediately, including while the target session is busy.
21
+ */
22
+ import { resolveConfig, defaultPeerName } from "./config.js";
23
+ import { Registry, newInboxToken, newInstanceId } from "./registry.js";
24
+ import { InboxListener } from "./listener.js";
25
+ import { RateLimiter } from "./queue.js";
26
+ import { SessionRuntime } from "./session-runtime.js";
27
+ import { Sender } from "./sender.js";
28
+ import { LocalTransport } from "./transport.js";
29
+ import { Outbox } from "./outbox.js";
30
+ import { PeerPermissions } from "./permissions.js";
31
+ import { buildPeerTools } from "./tools/peers-tools.js";
32
+ import { handlePeersCommand } from "./commands.js";
33
+ import { consumeCommand, createLogger, errorMessage } from "./feedback.js";
34
+ const PLUGIN_VERSION = "0.2.2";
35
+ const COMMAND_NAMES = new Set(["peers", "list-agents", "peers-name", "peers-inbox", "peers-outbox"]);
36
+ /**
37
+ * Command definitions injected through the `config` hook. opencode 1.18 does
38
+ * NOT scan plugin packages for commands/*.md, so without this a fresh install
39
+ * has no /peers* commands at all (the shipped commands/ directory only serves
40
+ * users who copy it into their config dir). User-defined commands with the
41
+ * same name always win.
42
+ */
43
+ const INJECTED_COMMANDS = {
44
+ peers: {
45
+ description: "List same-machine opencode peers you can exchange messages with (cross-session messaging)",
46
+ template: "$ARGUMENTS\n\nIf this command was not intercepted by the opencode-collaboration plugin, call the list_agents tool and show the result to the user verbatim.",
47
+ },
48
+ "list-agents": {
49
+ description: "List same-machine opencode peers you can exchange messages with (alias of /peers, compatible with Claude Code's /list-agents)",
50
+ template: "$ARGUMENTS\n\nIf this command was not intercepted by the opencode-collaboration plugin, call the list_agents tool and show the result to the user verbatim.",
51
+ },
52
+ "peers-name": {
53
+ description: "Show or set this instance's peer name (used by other sessions to address you)",
54
+ template: "$ARGUMENTS\n\nIf this command was not intercepted by the opencode-collaboration plugin, tell the user the plugin is not loaded and no action was taken.",
55
+ },
56
+ "peers-inbox": {
57
+ description: "Review held peer messages. Usage: /peers-inbox [accept <n|all> | drop <n|all>]",
58
+ template: "$ARGUMENTS\n\nIf this command was not intercepted by the opencode-collaboration plugin, tell the user the plugin is not loaded and no action was taken.",
59
+ },
60
+ "peers-outbox": {
61
+ description: "Show transport receipts and final ACK outcomes for messages sent by this session",
62
+ template: "$ARGUMENTS\n\nIf this command was not intercepted by the opencode-collaboration plugin, tell the user the plugin is not loaded and no action was taken.",
63
+ },
64
+ };
65
+ export async function runReliabilitySweep(queue, delivery) {
66
+ await queue.expireHeld();
67
+ await delivery.flush();
68
+ }
69
+ export const PeersPlugin = async (ctx, pluginOptions) => {
70
+ const logger = createLogger(ctx.client);
71
+ // Options arrive either as the tuple's second element (pluginOptions) or,
72
+ // on some versions, attached to the context.
73
+ const opts = pluginOptions ??
74
+ ctx.options;
75
+ const config = resolveConfig(opts);
76
+ const instanceId = newInstanceId();
77
+ const inboxToken = newInboxToken();
78
+ // Default name: <dir-name>-<hex4> (matches Claude Code's "my-app-3f" pattern).
79
+ // Only auto-generated names get the suffix; an explicit config.name or
80
+ // /peers-name override replaces it entirely, keeping the user's choice.
81
+ let currentName = config.name || defaultPeerName(ctx.directory, instanceId);
82
+ let policy = config.inboundPolicy;
83
+ const runtime = SessionRuntime({
84
+ client: ctx.client,
85
+ config,
86
+ directory: ctx.directory,
87
+ name: () => currentName,
88
+ logger,
89
+ });
90
+ const recvLimit = RateLimiter(config.recvRatePerMin);
91
+ const sendLimit = RateLimiter(config.sendRatePerMin);
92
+ const outbox = Outbox({ storageDir: config.storageDir });
93
+ let dispatchAcknowledgements = async () => { };
94
+ const listener = InboxListener({
95
+ token: inboxToken,
96
+ maxBodyBytes: config.maxMessageBytes * 2 + 4096,
97
+ maxMessageBytes: config.maxMessageBytes,
98
+ maxMessageAgeMs: config.maxMessageAgeMs,
99
+ processId: instanceId,
100
+ resolveEndpoint: async ({ version, toEndpointId }) => {
101
+ const resolve = () => version === 1
102
+ ? runtime.compatibilityEndpointId()
103
+ : toEndpointId && runtime.hasEndpoint(toEndpointId)
104
+ ? toEndpointId
105
+ : null;
106
+ const immediate = resolve();
107
+ if (immediate)
108
+ return immediate;
109
+ // Startup window: the listener is up before deferred session discovery
110
+ // finishes. Wait (bounded) for it instead of returning a terminal 404
111
+ // for an endpoint that exists moments later.
112
+ let timeout = null;
113
+ try {
114
+ await Promise.race([
115
+ runtime.whenReady(),
116
+ new Promise((res) => {
117
+ timeout = setTimeout(res, 10_000);
118
+ timeout.unref?.();
119
+ }),
120
+ ]);
121
+ }
122
+ finally {
123
+ if (timeout)
124
+ clearTimeout(timeout);
125
+ }
126
+ return resolve();
127
+ },
128
+ logger,
129
+ onMessage: async (msg, endpointId) => {
130
+ if (!recvLimit(msg.from.instanceId))
131
+ return "full";
132
+ const status = await runtime.receive(msg, endpointId, policy);
133
+ // Fire-and-forget, but never let a rejection escape: an unhandled
134
+ // rejection would crash the host opencode process.
135
+ void dispatchAcknowledgements().catch((err) => logger("warn", "acknowledgement dispatch failed; will retry", { error: String(err) }));
136
+ return status;
137
+ },
138
+ onAcknowledgement: async (acknowledgement) => {
139
+ if (!(await outbox.applyAcknowledgement(acknowledgement))) {
140
+ await logger("warn", "ignored unmatched peer acknowledgement", {
141
+ messageId: acknowledgement.messageId,
142
+ fromEndpointId: acknowledgement.fromEndpointId,
143
+ toEndpointId: acknowledgement.toEndpointId,
144
+ });
145
+ }
146
+ },
147
+ });
148
+ const { url: transportUrl, compatibilityUrl: inboxUrl, address: transport } = await listener.start();
149
+ const latestEndpoint = () => {
150
+ const compatibilityId = runtime.compatibilityEndpointId();
151
+ return runtime.registryEndpoints().find((endpoint) => endpoint.endpointId === compatibilityId);
152
+ };
153
+ const registry = Registry({
154
+ peersDir: config.peersDir,
155
+ instanceId,
156
+ pid: process.pid,
157
+ directory: ctx.directory,
158
+ serverUrl: ctx.serverUrl?.toString() ?? "",
159
+ inboxUrl,
160
+ inboxToken,
161
+ pluginVersion: PLUGIN_VERSION,
162
+ heartbeatMs: config.heartbeatMs,
163
+ staleMs: config.staleMs,
164
+ getDynamic: () => {
165
+ const latest = latestEndpoint();
166
+ return {
167
+ name: currentName,
168
+ inboundPolicy: policy,
169
+ activeSessionId: latest?.sessionId ?? null,
170
+ activeSessionTitle: latest?.title ?? null,
171
+ busy: latest ? latest.status !== "idle" : false,
172
+ queuedCount: latest?.queuedCount ?? 0,
173
+ };
174
+ },
175
+ getEndpoints: () => {
176
+ const base = runtime.publishableEndpoints();
177
+ const seen = new Set(base.map((endpoint) => endpoint.endpointId));
178
+ // Also keep advertising sessions that still owe an outbound message
179
+ // (no final ACK yet). Otherwise a sender that goes idle on a
180
+ // non-representative session would stop being advertised, and the
181
+ // receiver could never route the final ACK back to it — the sender's
182
+ // outbox would stay stuck on "awaiting final ACK" forever.
183
+ const owing = runtime
184
+ .registryEndpoints()
185
+ .filter((endpoint) => !seen.has(endpoint.endpointId) &&
186
+ (outbox.list(endpoint.endpointId) ?? []).some((record) => !record.finalStatus));
187
+ return [...base, ...owing];
188
+ },
189
+ getCompatibilityEndpointId: runtime.compatibilityEndpointId,
190
+ transport,
191
+ peerPermissions: config.peerPermissions,
192
+ logger,
193
+ });
194
+ await registry.start();
195
+ const acknowledgementTransport = LocalTransport();
196
+ dispatchAcknowledgements = async () => {
197
+ const pending = runtime.pendingAcknowledgements();
198
+ if (pending.length === 0)
199
+ return;
200
+ const peers = await registry.list();
201
+ for (const { queue, acknowledgement } of pending) {
202
+ const target = peers.find((peer) => peer.alive && peer.entry.version === 2 &&
203
+ peer.entry.endpointId === acknowledgement.fromEndpointId)?.entry;
204
+ if (!target || target.version !== 2)
205
+ continue;
206
+ try {
207
+ await acknowledgementTransport.ack(target, acknowledgement);
208
+ await queue.markAcknowledgementSent(acknowledgement);
209
+ }
210
+ catch (err) {
211
+ await logger("warn", "failed to return peer acknowledgement; will retry", {
212
+ error: String(err),
213
+ messageId: acknowledgement.messageId,
214
+ senderEndpointId: acknowledgement.fromEndpointId,
215
+ });
216
+ }
217
+ }
218
+ };
219
+ const sender = Sender({
220
+ self: {
221
+ get instanceId() {
222
+ return instanceId;
223
+ },
224
+ get name() {
225
+ return currentName;
226
+ },
227
+ directory: ctx.directory,
228
+ },
229
+ outbox,
230
+ });
231
+ const sweepReliability = async () => {
232
+ if (disposing)
233
+ return;
234
+ try {
235
+ await runtime.sweep();
236
+ await dispatchAcknowledgements();
237
+ }
238
+ catch (err) {
239
+ await logger("error", "reliability sweep failed", { error: errorMessage(err) });
240
+ }
241
+ };
242
+ const permissions = PeerPermissions({
243
+ client: ctx.client,
244
+ mode: () => config.peerPermissions,
245
+ directory: ctx.directory,
246
+ logger,
247
+ });
248
+ let disposing = false;
249
+ let disposePromise = null;
250
+ let discoveryTimer = null;
251
+ // Fallback sweep: session.idle is not guaranteed on every version/scenario,
252
+ // so poll idle state periodically as a safety net.
253
+ const sweeper = setInterval(() => {
254
+ void sweepReliability();
255
+ }, config.sweepMs);
256
+ sweeper.unref?.();
257
+ const hooks = {
258
+ config: async (input) => {
259
+ // Register our slash commands server-side; without this a fresh install
260
+ // has no /peers* commands (opencode does not scan plugin packages for
261
+ // commands/*.md). Never override a user-defined command.
262
+ input.command = input.command ?? {};
263
+ for (const [name, definition] of Object.entries(INJECTED_COMMANDS)) {
264
+ input.command[name] ??= definition;
265
+ }
266
+ },
267
+ event: async ({ event }) => {
268
+ if (disposing)
269
+ return;
270
+ const e = event;
271
+ const changed = await runtime.handleEvent(e);
272
+ if (changed && !disposing)
273
+ await registry.heartbeat();
274
+ if (disposing)
275
+ return;
276
+ await permissions.handleEvent(e);
277
+ },
278
+ "chat.message": async (input) => {
279
+ if (disposing)
280
+ return;
281
+ await runtime.noteActivity(input.sessionID);
282
+ if (!disposing)
283
+ await registry.heartbeat();
284
+ },
285
+ "command.execute.before": async (input, output) => {
286
+ if (disposing)
287
+ return;
288
+ if (!COMMAND_NAMES.has(input.command))
289
+ return;
290
+ await runtime.noteActivity(input.sessionID);
291
+ const queue = runtime.queueForSession(input.sessionID);
292
+ const delivery = runtime.deliveryForSession(input.sessionID);
293
+ if (!queue || !delivery)
294
+ return;
295
+ let message;
296
+ try {
297
+ const result = await handlePeersCommand({
298
+ registry,
299
+ queue,
300
+ delivery,
301
+ getName: () => currentName,
302
+ setName: async (name) => {
303
+ if (name === currentName) {
304
+ await registry.heartbeat();
305
+ return { name, taken: false };
306
+ }
307
+ const peers = await registry.list();
308
+ const taken = peers.some((p) => p.alive && p.entry.name === name);
309
+ if (taken) {
310
+ return { name, taken: true };
311
+ }
312
+ currentName = name;
313
+ await registry.heartbeat();
314
+ return { name, taken: false };
315
+ },
316
+ selfInstanceId: instanceId,
317
+ selfEndpointId: runtime.endpointIdForSession(input.sessionID) ?? instanceId,
318
+ outbox,
319
+ }, input.command, input.arguments || "");
320
+ message = result.message ?? "✅ Done.";
321
+ await dispatchAcknowledgements();
322
+ }
323
+ catch (err) {
324
+ message = `❌ /${input.command} failed: ${errorMessage(err)}`;
325
+ }
326
+ consumeCommand(output.parts, message);
327
+ await logger(message.startsWith("❌") ? "error" : "info", message, {
328
+ command: input.command,
329
+ });
330
+ },
331
+ };
332
+ hooks.tool = buildPeerTools({
333
+ registry,
334
+ sender,
335
+ sendLimit,
336
+ maxMessageBytes: config.maxMessageBytes,
337
+ selfName: () => currentName,
338
+ selfInstanceId: instanceId,
339
+ endpointForSession: (sessionId) => {
340
+ const endpointId = runtime.endpointIdForSession(sessionId);
341
+ const endpoint = runtime.registryEndpoints().find((entry) => entry.sessionId === sessionId);
342
+ return endpointId && endpoint
343
+ ? { endpointId, name: endpoint.name, directory: endpoint.directory }
344
+ : null;
345
+ },
346
+ outbox,
347
+ });
348
+ hooks.dispose = () => {
349
+ if (disposePromise)
350
+ return disposePromise;
351
+ disposing = true;
352
+ if (discoveryTimer)
353
+ clearTimeout(discoveryTimer);
354
+ discoveryTimer = null;
355
+ clearInterval(sweeper);
356
+ const registryStopping = registry.stop();
357
+ const runtimeStopping = runtime.stop();
358
+ disposePromise = Promise.all([
359
+ registryStopping,
360
+ runtimeStopping,
361
+ listener.stop(),
362
+ acknowledgementTransport.close(),
363
+ ]).then(() => undefined);
364
+ return disposePromise;
365
+ };
366
+ await logger("info", "opencode-collaboration started", {
367
+ instanceId,
368
+ name: currentName,
369
+ inboxUrl,
370
+ transportUrl,
371
+ policy,
372
+ });
373
+ // OpenCode constructs plugins while servicing the first session request.
374
+ // Calling this server's session API before returning hooks deadlocks that
375
+ // request, so discover pre-existing sessions only after bootstrap unwinds.
376
+ discoveryTimer = setTimeout(() => {
377
+ discoveryTimer = null;
378
+ if (disposing)
379
+ return;
380
+ void runtime.initialize()
381
+ .then(async () => {
382
+ if (!disposing)
383
+ await registry.heartbeat();
384
+ })
385
+ .catch((err) => logger("warn", "deferred session discovery failed", { error: String(err) }));
386
+ }, 0);
387
+ discoveryTimer.unref?.();
388
+ return hooks;
389
+ };
390
+ // OpenCode v1 detects the default {id, server} object before its legacy
391
+ // loader scans named exports.
392
+ export const plugin = {
393
+ id: "opencode-collaboration",
394
+ server: PeersPlugin,
395
+ };
396
+ export default plugin;
397
+ export { Registry, uniqueName } from "./registry.js";
398
+ export { MessageQueue, RateLimiter, createProcessMessageQueue, createSessionMessageQueue, hasSpoolRecords, migrateWorkspaceSpool, stableSessionEndpointId, stableSpoolEndpointId, } from "./queue.js";
399
+ export { SessionTracker } from "./session-tracker.js";
400
+ export { SessionRuntime } from "./session-runtime.js";
401
+ export { Delivery, deterministicPeerMessageId, formatMessages } from "./delivery.js";
402
+ export { Sender, buildMessage, buildMessageV2 } from "./sender.js";
403
+ export { InboxListener } from "./listener.js";
404
+ export { LocalTransport } from "./transport.js";
405
+ export { gateMessage } from "./gating.js";
406
+ export { PeerPermissions, isProtectedPermission } from "./permissions.js";
407
+ export { Outbox } from "./outbox.js";
408
+ export { collapseToProcesses, formatSessionList, relativeAge, sortPeers } from "./format.js";
409
+ export { resolveConfig, validateName } from "./config.js";
410
+ export * from "./types.js";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Per-instance inbox HTTP listener (127.0.0.1 only, bearer-token auth).
3
+ * Peers POST messages here instead of touching the opencode server directly,
4
+ * so inbound gating (accept/hold/refuse) and queueing stay under our control.
5
+ */
6
+ import type { InboundMessage, LocalTransportAddress, Logger, PeerAcknowledgementV2, ReceiveStatus } from "./types.js";
7
+ export interface MessageRoute {
8
+ version: 1 | 2;
9
+ toEndpointId?: string;
10
+ }
11
+ export interface ListenerOptions {
12
+ token: string;
13
+ maxBodyBytes: number;
14
+ /** Receiver-side text limit, measured as UTF-8 bytes. */
15
+ maxMessageBytes?: number;
16
+ /** Maximum permitted clock age/skew for a sender timestamp. */
17
+ maxMessageAgeMs?: number;
18
+ runtimeDir?: string;
19
+ processId?: string;
20
+ platform?: NodeJS.Platform;
21
+ resolveEndpoint?: (route: MessageRoute) => string | null | Promise<string | null>;
22
+ onMessage: (msg: InboundMessage, endpointId?: string) => Promise<ReceiveStatus>;
23
+ onAcknowledgement?: (ack: PeerAcknowledgementV2) => Promise<void>;
24
+ logger: Logger;
25
+ }
26
+ export interface ListenerInstance {
27
+ start: () => Promise<{
28
+ port: number;
29
+ url: string;
30
+ address: LocalTransportAddress;
31
+ /** Ordinary loopback HTTP URL published to protocol-v1 peers. */
32
+ compatibilityUrl: string;
33
+ }>;
34
+ stop: () => Promise<void>;
35
+ }
36
+ export declare function defaultRuntimeDirectory(env?: NodeJS.ProcessEnv, uid?: number): string;
37
+ export declare function InboxListener(opts: ListenerOptions): ListenerInstance;