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
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Instance registry: each opencode instance writes one file into peers.d/.
3
+ * One file per instance eliminates multi-writer races; no locking needed.
4
+ */
5
+ import type { InboundPolicy, LocalTransportAddress, Logger, PeerPermissionMode, PeerRegistryEntry, SessionEndpointStatus } from "./types.js";
6
+ export interface RegistryEndpoint {
7
+ endpointId: string;
8
+ sessionId: string;
9
+ parentSessionId?: string;
10
+ title: string;
11
+ name: string;
12
+ directory: string;
13
+ status: SessionEndpointStatus;
14
+ startedAt: number;
15
+ updatedAt: number;
16
+ queuedCount: number;
17
+ }
18
+ export interface RegistryDynamic {
19
+ name: string;
20
+ inboundPolicy: InboundPolicy;
21
+ activeSessionId: string | null;
22
+ activeSessionTitle: string | null;
23
+ /** True while a turn is running in the active session. */
24
+ busy: boolean;
25
+ /** Messages queued locally awaiting delivery. */
26
+ queuedCount: number;
27
+ }
28
+ export interface RegistryOptions {
29
+ peersDir: string;
30
+ instanceId: string;
31
+ pid: number;
32
+ directory: string;
33
+ serverUrl: string;
34
+ inboxUrl: string;
35
+ inboxToken: string;
36
+ pluginVersion: string;
37
+ heartbeatMs: number;
38
+ staleMs: number;
39
+ getDynamic: () => RegistryDynamic;
40
+ /** Enables protocol-v2 publication while retaining one v1 compatibility file. */
41
+ getEndpoints?: () => RegistryEndpoint[];
42
+ /** Exact endpoint used by protocol-v1 routing and publication. */
43
+ getCompatibilityEndpointId?: () => string | null;
44
+ transport?: LocalTransportAddress;
45
+ peerPermissions?: PeerPermissionMode;
46
+ logger: Logger;
47
+ }
48
+ export interface ListedPeer {
49
+ entry: PeerRegistryEntry;
50
+ alive: boolean;
51
+ staleReason: string | null;
52
+ }
53
+ export declare function newInstanceId(): string;
54
+ export declare function newInboxToken(): string;
55
+ export declare function pidAlive(pid: number): boolean;
56
+ export interface RegistryInstance {
57
+ start: () => Promise<void>;
58
+ stop: () => Promise<void>;
59
+ heartbeat: () => Promise<void>;
60
+ list: () => Promise<ListedPeer[]>;
61
+ isAlive: (entry: PeerRegistryEntry) => boolean;
62
+ cleanupStale: () => Promise<number>;
63
+ selfFile: string;
64
+ }
65
+ export declare function Registry(opts: RegistryOptions): RegistryInstance;
66
+ /** Pick a unique name among alive peers, appending -2, -3, ... on conflict. */
67
+ export declare function uniqueName(desired: string, peers: ListedPeer[]): {
68
+ name: string;
69
+ changed: boolean;
70
+ };
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Instance registry: each opencode instance writes one file into peers.d/.
3
+ * One file per instance eliminates multi-writer races; no locking needed.
4
+ */
5
+ import { randomBytes } from "node:crypto";
6
+ import { chmod, mkdir, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
7
+ import { hostname } from "node:os";
8
+ import { join } from "node:path";
9
+ export function newInstanceId() {
10
+ return randomBytes(4).toString("hex");
11
+ }
12
+ export function newInboxToken() {
13
+ return randomBytes(24).toString("hex");
14
+ }
15
+ export function pidAlive(pid) {
16
+ if (pid === process.pid)
17
+ return true;
18
+ try {
19
+ process.kill(pid, 0);
20
+ return true;
21
+ }
22
+ catch (err) {
23
+ const code = err.code;
24
+ return code === "EPERM";
25
+ }
26
+ }
27
+ export function Registry(opts) {
28
+ const selfFile = join(opts.peersDir, `${opts.instanceId}.json`);
29
+ const selfV2Files = new Set();
30
+ let timer = null;
31
+ let writeTail = Promise.resolve();
32
+ let stopPromise = null;
33
+ let lifecycle = "new";
34
+ const startedAt = Date.now();
35
+ function compatibilityDynamic() {
36
+ const dyn = opts.getDynamic();
37
+ const endpoints = opts.getEndpoints?.() ?? [];
38
+ const compatibilityId = opts.getCompatibilityEndpointId?.();
39
+ const latest = endpoints.find((endpoint) => endpoint.endpointId === compatibilityId)
40
+ ?? endpoints.slice().sort((a, b) => b.updatedAt - a.updatedAt)[0];
41
+ if (!latest)
42
+ return dyn;
43
+ return {
44
+ name: latest.name,
45
+ inboundPolicy: dyn.inboundPolicy,
46
+ activeSessionId: latest.sessionId,
47
+ activeSessionTitle: latest.title,
48
+ busy: latest.status !== "idle",
49
+ queuedCount: latest.queuedCount,
50
+ };
51
+ }
52
+ function buildEntry() {
53
+ const dyn = compatibilityDynamic();
54
+ return {
55
+ version: 1,
56
+ instanceId: opts.instanceId,
57
+ name: dyn.name,
58
+ pid: opts.pid,
59
+ hostname: hostname(),
60
+ directory: opts.directory,
61
+ serverUrl: opts.serverUrl,
62
+ inboxUrl: opts.inboxUrl,
63
+ inboxToken: opts.inboxToken,
64
+ activeSessionId: dyn.activeSessionId,
65
+ activeSessionTitle: dyn.activeSessionTitle,
66
+ busy: dyn.busy,
67
+ queuedCount: dyn.queuedCount,
68
+ inboundPolicy: dyn.inboundPolicy,
69
+ startedAt,
70
+ heartbeatAt: Date.now(),
71
+ pluginVersion: opts.pluginVersion,
72
+ };
73
+ }
74
+ function buildV2Entry(endpoint, heartbeatAt) {
75
+ const inboundPolicy = opts.getDynamic().inboundPolicy;
76
+ return {
77
+ version: 2,
78
+ endpointId: endpoint.endpointId,
79
+ processId: opts.instanceId,
80
+ pid: opts.pid,
81
+ sessionId: endpoint.sessionId,
82
+ ...(endpoint.parentSessionId ? { parentSessionId: endpoint.parentSessionId } : {}),
83
+ title: endpoint.title,
84
+ name: endpoint.name,
85
+ hostname: hostname(),
86
+ directory: endpoint.directory,
87
+ status: endpoint.status,
88
+ transport: opts.transport,
89
+ serverUrl: opts.serverUrl,
90
+ inboxUrl: opts.inboxUrl,
91
+ inboxToken: opts.inboxToken,
92
+ capabilities: ["local", "protocol-v2", "prompt-async", "ack"],
93
+ timestamps: {
94
+ startedAt: endpoint.startedAt,
95
+ updatedAt: endpoint.updatedAt,
96
+ heartbeatAt,
97
+ },
98
+ policy: {
99
+ inboundPolicy,
100
+ peerPermissions: opts.peerPermissions ?? "allow",
101
+ },
102
+ pluginVersion: opts.pluginVersion,
103
+ activeSessionId: endpoint.sessionId,
104
+ activeSessionTitle: endpoint.title,
105
+ busy: endpoint.status !== "idle",
106
+ queuedCount: endpoint.queuedCount,
107
+ inboundPolicy,
108
+ startedAt: endpoint.startedAt,
109
+ heartbeatAt,
110
+ };
111
+ }
112
+ async function writeEntry(path, entry) {
113
+ const tmp = `${path}.${process.pid}.tmp`;
114
+ await writeFile(tmp, JSON.stringify(entry, null, 2), { mode: 0o600 });
115
+ await chmod(tmp, 0o600).catch(() => { });
116
+ await rename(tmp, path);
117
+ }
118
+ async function writeSelf() {
119
+ await writeEntry(selfFile, buildEntry());
120
+ if (!opts.getEndpoints || !opts.transport)
121
+ return;
122
+ const heartbeatAt = Date.now();
123
+ const nextFiles = new Set();
124
+ for (const endpoint of opts.getEndpoints()) {
125
+ const safeId = endpoint.endpointId.replace(/[^a-zA-Z0-9_-]/g, "_");
126
+ const path = join(opts.peersDir, `${opts.instanceId}.${safeId}.v2.json`);
127
+ await writeEntry(path, buildV2Entry(endpoint, heartbeatAt));
128
+ nextFiles.add(path);
129
+ }
130
+ for (const path of selfV2Files) {
131
+ if (!nextFiles.has(path))
132
+ await rm(path, { force: true });
133
+ }
134
+ selfV2Files.clear();
135
+ for (const path of nextFiles)
136
+ selfV2Files.add(path);
137
+ }
138
+ function scheduleWrite() {
139
+ if (lifecycle !== "running")
140
+ return Promise.resolve();
141
+ const writeIfRunning = () => lifecycle === "running" ? writeSelf() : Promise.resolve();
142
+ const pending = writeTail.then(writeIfRunning, writeIfRunning);
143
+ writeTail = pending.catch(() => { });
144
+ return pending;
145
+ }
146
+ async function readEntry(file) {
147
+ try {
148
+ const raw = await readFile(join(opts.peersDir, file), "utf8");
149
+ const entry = JSON.parse(raw);
150
+ if (entry.version === 1 && entry.instanceId && entry.inboxUrl)
151
+ return entry;
152
+ if (entry.version === 2 && entry.endpointId && entry.processId && entry.transport && entry.sessionId)
153
+ return entry;
154
+ return null;
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ }
160
+ function staleReason(entry, now) {
161
+ const ageMs = now - entry.heartbeatAt;
162
+ if (ageMs > opts.staleMs)
163
+ return `last heartbeat ${Math.round(ageMs / 1000)}s ago`;
164
+ if (!pidAlive(entry.pid))
165
+ return `pid ${entry.pid} is not running`;
166
+ return null;
167
+ }
168
+ const inst = {
169
+ selfFile,
170
+ async start() {
171
+ if (lifecycle !== "new")
172
+ throw new Error(`registry cannot start while ${lifecycle}`);
173
+ await mkdir(opts.peersDir, { recursive: true, mode: 0o700 });
174
+ await chmod(opts.peersDir, 0o700).catch(() => { });
175
+ lifecycle = "running";
176
+ try {
177
+ await scheduleWrite();
178
+ }
179
+ catch (err) {
180
+ lifecycle = "stopped";
181
+ throw err;
182
+ }
183
+ timer = setInterval(() => {
184
+ inst.heartbeat().catch((err) => {
185
+ opts.logger("warn", "heartbeat failed", { error: String(err) });
186
+ });
187
+ }, opts.heartbeatMs);
188
+ timer.unref?.();
189
+ },
190
+ async stop() {
191
+ if (stopPromise)
192
+ return stopPromise;
193
+ if (lifecycle === "stopped")
194
+ return;
195
+ lifecycle = "stopping";
196
+ if (timer)
197
+ clearInterval(timer);
198
+ timer = null;
199
+ stopPromise = (async () => {
200
+ await writeTail;
201
+ await rm(selfFile, { force: true });
202
+ await Promise.all([...selfV2Files].map((path) => rm(path, { force: true })));
203
+ selfV2Files.clear();
204
+ lifecycle = "stopped";
205
+ })();
206
+ return stopPromise;
207
+ },
208
+ async heartbeat() {
209
+ if (lifecycle !== "running")
210
+ return;
211
+ await scheduleWrite();
212
+ if (lifecycle !== "running")
213
+ return;
214
+ await inst.cleanupStale();
215
+ },
216
+ async list() {
217
+ let files = [];
218
+ try {
219
+ files = await readdir(opts.peersDir);
220
+ }
221
+ catch {
222
+ return [];
223
+ }
224
+ const now = Date.now();
225
+ const entries = [];
226
+ for (const file of files) {
227
+ if (!file.endsWith(".json"))
228
+ continue;
229
+ const entry = await readEntry(file);
230
+ if (!entry)
231
+ continue;
232
+ if (entry.version === 1 && entry.instanceId === opts.instanceId)
233
+ continue;
234
+ entries.push(entry);
235
+ }
236
+ const v2Processes = new Set(entries.flatMap((entry) => entry.version === 2 ? [entry.processId] : []));
237
+ const out = [];
238
+ const v2ByEndpoint = new Map();
239
+ for (const entry of entries) {
240
+ if (entry.version === 1 && v2Processes.has(entry.instanceId))
241
+ continue;
242
+ const reason = staleReason(entry, now);
243
+ const listed = { entry, alive: reason === null, staleReason: reason };
244
+ if (entry.version !== 2) {
245
+ out.push(listed);
246
+ continue;
247
+ }
248
+ const current = v2ByEndpoint.get(entry.endpointId);
249
+ const currentHeartbeat = current?.entry.heartbeatAt ?? -Infinity;
250
+ if (!current || (listed.alive && !current.alive) ||
251
+ (listed.alive === current.alive && entry.heartbeatAt > currentHeartbeat)) {
252
+ v2ByEndpoint.set(entry.endpointId, listed);
253
+ }
254
+ }
255
+ return [...out, ...v2ByEndpoint.values()];
256
+ },
257
+ isAlive(entry) {
258
+ return staleReason(entry, Date.now()) === null;
259
+ },
260
+ async cleanupStale() {
261
+ if (lifecycle !== "running")
262
+ return 0;
263
+ let files = [];
264
+ try {
265
+ files = await readdir(opts.peersDir);
266
+ }
267
+ catch {
268
+ return 0;
269
+ }
270
+ const now = Date.now();
271
+ let removed = 0;
272
+ for (const file of files) {
273
+ if (!file.endsWith(".json"))
274
+ continue;
275
+ const path = join(opts.peersDir, file);
276
+ if (path === selfFile || selfV2Files.has(path))
277
+ continue;
278
+ try {
279
+ const st = await stat(path);
280
+ if (now - st.mtimeMs < 5 * 60_000)
281
+ continue;
282
+ const entry = await readEntry(file);
283
+ if (entry && pidAlive(entry.pid))
284
+ continue;
285
+ await rm(path, { force: true });
286
+ removed++;
287
+ }
288
+ catch {
289
+ // best effort
290
+ }
291
+ }
292
+ return removed;
293
+ },
294
+ };
295
+ return inst;
296
+ }
297
+ /** Pick a unique name among alive peers, appending -2, -3, ... on conflict. */
298
+ export function uniqueName(desired, peers) {
299
+ const taken = new Set(peers.filter((p) => p.alive).map((p) => p.entry.name));
300
+ if (!taken.has(desired))
301
+ return { name: desired, changed: false };
302
+ for (let i = 2; i < 100; i++) {
303
+ const candidate = `${desired}-${i}`;
304
+ if (!taken.has(candidate))
305
+ return { name: candidate, changed: true };
306
+ }
307
+ return { name: `${desired}-${randomBytes(2).toString("hex")}`, changed: true };
308
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Outbound delivery over the local v1/v2 transport boundary.
3
+ */
4
+ import type { InboundMessage, PeerFrom, PeerMessageV2, PeerRegistryEntry, ReceiveStatus } from "./types.js";
5
+ import { type Transport } from "./transport.js";
6
+ import type { OutboxInstance } from "./outbox.js";
7
+ export type SendOutcome = {
8
+ ok: true;
9
+ status: ReceiveStatus;
10
+ messageId?: string;
11
+ } | {
12
+ ok: false;
13
+ error: string;
14
+ messageId?: string;
15
+ };
16
+ export interface SenderOptions {
17
+ self: PeerFrom;
18
+ timeoutMs?: number;
19
+ transport?: Transport;
20
+ outbox?: OutboxInstance;
21
+ }
22
+ export declare function buildMessage(self: PeerFrom, text: string, via?: string[]): InboundMessage;
23
+ export declare function buildMessageV2(self: PeerFrom, toEndpointId: string, text: string, via?: string[]): PeerMessageV2;
24
+ export declare function Sender(opts: SenderOptions): {
25
+ buildMessage: (text: string) => import("./types.js").InboundMessageV1;
26
+ send(entry: PeerRegistryEntry, text: string, sender?: PeerFrom): Promise<SendOutcome>;
27
+ };
package/dist/sender.js ADDED
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Outbound delivery over the local v1/v2 transport boundary.
3
+ */
4
+ import { randomBytes } from "node:crypto";
5
+ import { LocalTransport } from "./transport.js";
6
+ export function buildMessage(self, text, via = []) {
7
+ return {
8
+ id: randomBytes(8).toString("hex"),
9
+ from: self,
10
+ text,
11
+ via: [...via, self.instanceId],
12
+ sentAt: Date.now(),
13
+ };
14
+ }
15
+ export function buildMessageV2(self, toEndpointId, text, via = []) {
16
+ return {
17
+ version: 2,
18
+ messageId: randomBytes(8).toString("hex"),
19
+ fromEndpointId: self.instanceId,
20
+ toEndpointId,
21
+ from: self,
22
+ text,
23
+ via: [...via, self.instanceId],
24
+ sentAt: Date.now(),
25
+ };
26
+ }
27
+ async function postMessage(entry, msg, timeoutMs) {
28
+ const controller = new AbortController();
29
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
30
+ try {
31
+ const res = await fetch(`${entry.inboxUrl}/message`, {
32
+ method: "POST",
33
+ headers: {
34
+ "content-type": "application/json",
35
+ authorization: `Bearer ${entry.inboxToken}`,
36
+ },
37
+ body: JSON.stringify(msg),
38
+ signal: controller.signal,
39
+ });
40
+ let status;
41
+ try {
42
+ const body = (await res.json());
43
+ status = body.status;
44
+ }
45
+ catch {
46
+ // non-JSON body; fall through with just the HTTP code
47
+ }
48
+ return { http: res.status, status };
49
+ }
50
+ finally {
51
+ clearTimeout(timer);
52
+ }
53
+ }
54
+ async function healthCheck(entry, timeoutMs) {
55
+ const controller = new AbortController();
56
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
57
+ try {
58
+ const res = await fetch(`${entry.inboxUrl}/health`, {
59
+ headers: { authorization: `Bearer ${entry.inboxToken}` },
60
+ signal: controller.signal,
61
+ });
62
+ return res.ok;
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ finally {
68
+ clearTimeout(timer);
69
+ }
70
+ }
71
+ export function Sender(opts) {
72
+ const timeoutMs = opts.timeoutMs ?? 3_000;
73
+ const transport = opts.transport ?? LocalTransport({ timeoutMs });
74
+ return {
75
+ buildMessage: (text) => buildMessage(opts.self, text),
76
+ async send(entry, text, sender = opts.self) {
77
+ if (entry.version === 2) {
78
+ const msg = buildMessageV2(sender, entry.endpointId, text);
79
+ await opts.outbox?.recordPending(msg, entry.name);
80
+ try {
81
+ const { http, status } = await transport.send(entry, msg);
82
+ if (status)
83
+ await opts.outbox?.recordReceipt(msg.messageId, msg.fromEndpointId, status);
84
+ if (http === 202 && status) {
85
+ return { ok: true, status, messageId: msg.messageId };
86
+ }
87
+ if (http === 403)
88
+ return { ok: false, error: `"${entry.name}" refuses inbound messages.`, messageId: msg.messageId };
89
+ if (http === 429)
90
+ return { ok: false, error: `"${entry.name}" is rate limiting or its queue is full; try again later.`, messageId: msg.messageId };
91
+ if (http === 401)
92
+ return { ok: false, error: `Authentication failed for "${entry.name}" (stale registry entry?).`, messageId: msg.messageId };
93
+ if (http === 404)
94
+ return { ok: false, error: `Endpoint "${entry.endpointId}" is no longer registered.`, messageId: msg.messageId };
95
+ const error = `Unexpected response ${http} from "${entry.name}".`;
96
+ await opts.outbox?.recordFailure(msg.messageId, msg.fromEndpointId, error);
97
+ return { ok: false, error, messageId: msg.messageId };
98
+ }
99
+ catch (err) {
100
+ const error = `Failed to send to "${entry.name}": ${String(err)}`;
101
+ await opts.outbox?.recordFailure(msg.messageId, msg.fromEndpointId, error);
102
+ return { ok: false, error, messageId: msg.messageId };
103
+ }
104
+ }
105
+ const msg = buildMessage(sender, text);
106
+ let lastErr = null;
107
+ for (let attempt = 0; attempt < 2; attempt++) {
108
+ try {
109
+ const { http, status } = await postMessage(entry, msg, timeoutMs);
110
+ if (http === 202 && status)
111
+ return { ok: true, status };
112
+ if (http === 403)
113
+ return { ok: false, error: `"${entry.name}" refuses inbound messages.` };
114
+ if (http === 429) {
115
+ return { ok: false, error: `"${entry.name}" is rate limiting or its queue is full; try again later.` };
116
+ }
117
+ if (http === 401) {
118
+ return { ok: false, error: `Authentication failed for "${entry.name}" (stale registry entry?).` };
119
+ }
120
+ return { ok: false, error: `Unexpected response ${http} from "${entry.name}".` };
121
+ }
122
+ catch (err) {
123
+ lastErr = err;
124
+ // Retry once to cover a stale registry entry racing a peer restart.
125
+ if (attempt === 0)
126
+ await new Promise((r) => setTimeout(r, 300));
127
+ }
128
+ }
129
+ const alive = await healthCheck(entry, timeoutMs);
130
+ const hint = alive
131
+ ? "inbox reachable but POST failed"
132
+ : "peer appears offline (inbox unreachable)";
133
+ return {
134
+ ok: false,
135
+ error: `Failed to send to "${entry.name}": ${hint}. ${String(lastErr)}`,
136
+ };
137
+ },
138
+ };
139
+ }
@@ -0,0 +1,40 @@
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ import type { ResolvedConfig } from "./config.js";
3
+ import { type DeliveryInstance } from "./delivery.js";
4
+ import { type QueueInstance } from "./queue.js";
5
+ import type { RegistryEndpoint } from "./registry.js";
6
+ import type { InboundMessage, InboundPolicy, Logger, PeerAcknowledgementV2, ReceiveStatus } from "./types.js";
7
+ type Client = PluginInput["client"];
8
+ export interface SessionRuntimeOptions {
9
+ client: Client;
10
+ config: ResolvedConfig;
11
+ directory: string;
12
+ name: () => string;
13
+ logger: Logger;
14
+ }
15
+ export interface SessionRuntimeInstance {
16
+ initialize: () => Promise<void>;
17
+ /** Resolves once the first initialize() attempt has settled (never rejects). */
18
+ whenReady: () => Promise<void>;
19
+ stop: () => Promise<void>;
20
+ registryEndpoints: () => RegistryEndpoint[];
21
+ publishableEndpoints: () => RegistryEndpoint[];
22
+ compatibilityEndpointId: () => string | null;
23
+ hasEndpoint: (endpointId: string) => boolean;
24
+ endpointIdForSession: (sessionId: string) => string | null;
25
+ receive: (message: InboundMessage, endpointId: string, policy: InboundPolicy) => Promise<ReceiveStatus>;
26
+ handleEvent: (event: {
27
+ type?: string;
28
+ properties?: Record<string, unknown>;
29
+ }) => Promise<boolean>;
30
+ noteActivity: (sessionId: string) => Promise<void>;
31
+ queueForSession: (sessionId: string) => QueueInstance | null;
32
+ deliveryForSession: (sessionId: string) => DeliveryInstance | null;
33
+ sweep: () => Promise<void>;
34
+ pendingAcknowledgements: () => Array<{
35
+ queue: QueueInstance;
36
+ acknowledgement: PeerAcknowledgementV2;
37
+ }>;
38
+ }
39
+ export declare function SessionRuntime(opts: SessionRuntimeOptions): SessionRuntimeInstance;
40
+ export {};