@h-rig/transport-plugin 0.0.6-alpha.158
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/README.md +1 -0
- package/dist/relay-registry/src/client.d.ts +43 -0
- package/dist/relay-registry/src/schema.d.ts +317 -0
- package/dist/src/discovery.d.ts +33 -0
- package/dist/src/discovery.js +308 -0
- package/dist/src/dispatch.d.ts +47 -0
- package/dist/src/dispatch.js +915 -0
- package/dist/src/identity-env.d.ts +13 -0
- package/dist/src/identity-env.js +45 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +943 -0
- package/dist/src/plugin.d.ts +12 -0
- package/dist/src/plugin.js +923 -0
- package/dist/src/relay-registry.d.ts +3 -0
- package/dist/src/relay-registry.js +284 -0
- package/dist/src/run-discovery-bus.d.ts +32 -0
- package/dist/src/run-discovery-bus.js +24 -0
- package/dist/src/transport-provider.d.ts +9 -0
- package/dist/src/transport-provider.js +915 -0
- package/package.json +57 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { connectWorkerProjection, createRegistryClient, subscribeDiscovery, type CollabRegistryFilter, type DiscoverySubscriptionOptions, type RegistryClient, type RegistryClientOptions, type WorkerProjectionConnection, type WorkerProjectionOptions, } from "../relay-registry/src/client";
|
|
2
|
+
export { coerceRegistryStatus } from "../relay-registry/src/schema";
|
|
3
|
+
export type { HeartbeatRequest, ListedRegistryEntry, ProjectionIngestRequest, RegisterRequest, RegistryDiscoveryFrame, RegistryEntry, RegistryRunProjection, RegistrySnapshotFrame, RemoveRequest, WorkerProjectionFrame, } from "../relay-registry/src/schema";
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/transport-plugin/relay-registry/src/client.ts
|
|
3
|
+
import { Effect, Queue, Stream } from "effect";
|
|
4
|
+
function registryUrl(baseUrl, pathname) {
|
|
5
|
+
const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
6
|
+
return new URL(pathname.replace(/^\/+/, ""), base);
|
|
7
|
+
}
|
|
8
|
+
function registryWsUrl(baseUrl, pathname) {
|
|
9
|
+
const url = registryUrl(baseUrl, pathname);
|
|
10
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
11
|
+
return url;
|
|
12
|
+
}
|
|
13
|
+
async function parseResponseJson(response) {
|
|
14
|
+
const text = await response.text();
|
|
15
|
+
const data = text ? JSON.parse(text) : null;
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
const message = data && typeof data === "object" && "error" in data ? String(data.error) : response.statusText;
|
|
18
|
+
throw new Error(`registry request failed (${response.status}): ${message}`);
|
|
19
|
+
}
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
function workerProjectionFrame(frame) {
|
|
23
|
+
return typeof frame === "object" && frame !== null && "type" in frame && frame.type === "projection" ? frame : { type: "projection", projection: frame };
|
|
24
|
+
}
|
|
25
|
+
function connectWorkerProjection(first, runId, options) {
|
|
26
|
+
const cfg = typeof first === "string" ? { baseUrl: options?.baseUrl ?? "", namespaceKey: first, runId: runId ?? "", ...options?.WebSocket ? { WebSocket: options.WebSocket } : {} } : first;
|
|
27
|
+
if (!cfg.baseUrl)
|
|
28
|
+
throw new Error("baseUrl is required for worker projection websocket");
|
|
29
|
+
if (!cfg.runId)
|
|
30
|
+
throw new Error("runId is required for worker projection websocket");
|
|
31
|
+
const WebSocketCtor = cfg.WebSocket ?? WebSocket;
|
|
32
|
+
const url = registryWsUrl(cfg.baseUrl, "worker");
|
|
33
|
+
url.searchParams.set("namespaceKey", cfg.namespaceKey);
|
|
34
|
+
url.searchParams.set("runId", cfg.runId);
|
|
35
|
+
const ws = new WebSocketCtor(url);
|
|
36
|
+
const pending = [];
|
|
37
|
+
let closed = false;
|
|
38
|
+
const ready = new Promise((resolve, reject) => {
|
|
39
|
+
ws.addEventListener("open", () => {
|
|
40
|
+
resolve();
|
|
41
|
+
while (pending.length && ws.readyState === WebSocketCtor.OPEN)
|
|
42
|
+
ws.send(JSON.stringify(pending.shift()));
|
|
43
|
+
}, { once: true });
|
|
44
|
+
ws.addEventListener("error", () => {
|
|
45
|
+
closed = true;
|
|
46
|
+
pending.length = 0;
|
|
47
|
+
reject(new Error("worker projection websocket error"));
|
|
48
|
+
}, { once: true });
|
|
49
|
+
});
|
|
50
|
+
ready.catch(() => {
|
|
51
|
+
return;
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
ready,
|
|
55
|
+
push(frame) {
|
|
56
|
+
if (closed)
|
|
57
|
+
return;
|
|
58
|
+
const encoded = workerProjectionFrame(frame);
|
|
59
|
+
if (ws.readyState === WebSocketCtor.OPEN) {
|
|
60
|
+
ws.send(JSON.stringify(encoded));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
pending.push(encoded);
|
|
64
|
+
},
|
|
65
|
+
close() {
|
|
66
|
+
closed = true;
|
|
67
|
+
pending.length = 0;
|
|
68
|
+
try {
|
|
69
|
+
ws.close();
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function subscribeDiscovery(namespaceKey, repo, options) {
|
|
75
|
+
return Stream.callback((queue) => Effect.sync(() => {
|
|
76
|
+
const WebSocketCtor = options.WebSocket ?? WebSocket;
|
|
77
|
+
const url = registryWsUrl(options.baseUrl, "ws");
|
|
78
|
+
url.searchParams.set("namespaceKey", namespaceKey);
|
|
79
|
+
if (repo)
|
|
80
|
+
url.searchParams.set("repo", repo);
|
|
81
|
+
const ws = new WebSocketCtor(url);
|
|
82
|
+
const entries = new Map;
|
|
83
|
+
const snapshot = (sentAt) => ({
|
|
84
|
+
type: "registry.snapshot",
|
|
85
|
+
namespaceKey,
|
|
86
|
+
sentAt,
|
|
87
|
+
entries: Array.from(entries.values())
|
|
88
|
+
});
|
|
89
|
+
ws.addEventListener("message", (event) => {
|
|
90
|
+
try {
|
|
91
|
+
const text = typeof event.data === "string" ? event.data : String(event.data);
|
|
92
|
+
const frame = JSON.parse(text);
|
|
93
|
+
if (frame?.type === "registry.snapshot") {
|
|
94
|
+
entries.clear();
|
|
95
|
+
for (const entry of frame.entries)
|
|
96
|
+
entries.set(entry.roomId, entry);
|
|
97
|
+
Queue.offerUnsafe(queue, frame);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (frame?.type !== "registry.delta")
|
|
101
|
+
return;
|
|
102
|
+
if (frame.action === "remove") {
|
|
103
|
+
entries.delete(frame.roomId);
|
|
104
|
+
} else if (frame.entry) {
|
|
105
|
+
entries.set(frame.entry.roomId, frame.entry);
|
|
106
|
+
}
|
|
107
|
+
Queue.offerUnsafe(queue, snapshot(frame.sentAt));
|
|
108
|
+
} catch {}
|
|
109
|
+
});
|
|
110
|
+
ws.addEventListener("error", () => {});
|
|
111
|
+
return ws;
|
|
112
|
+
}).pipe(Effect.flatMap((ws) => Effect.addFinalizer(() => Effect.sync(() => ws.close())))), { bufferSize: 1, strategy: "sliding" });
|
|
113
|
+
}
|
|
114
|
+
function createRegistryClient(options) {
|
|
115
|
+
const requestFetch = options.fetch ?? fetch;
|
|
116
|
+
const headers = { "content-type": "application/json" };
|
|
117
|
+
async function post(pathname, body) {
|
|
118
|
+
const response = await requestFetch(registryUrl(options.baseUrl, pathname), {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers,
|
|
121
|
+
body: JSON.stringify(body)
|
|
122
|
+
});
|
|
123
|
+
return parseResponseJson(response);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
async registerRoom(room) {
|
|
127
|
+
const response = await post("register", room);
|
|
128
|
+
return response.entry;
|
|
129
|
+
},
|
|
130
|
+
async heartbeatRoom(roomId, status, projection) {
|
|
131
|
+
const body = projection === undefined ? { roomId, namespaceKey: options.namespaceKey, status } : { roomId, namespaceKey: options.namespaceKey, status, projection };
|
|
132
|
+
const response = await post("heartbeat", body);
|
|
133
|
+
return response.entry;
|
|
134
|
+
},
|
|
135
|
+
async ingestProjection(input) {
|
|
136
|
+
const response = await post("projection", {
|
|
137
|
+
...input,
|
|
138
|
+
namespaceKey: input.namespaceKey ?? options.namespaceKey
|
|
139
|
+
});
|
|
140
|
+
return response.entry;
|
|
141
|
+
},
|
|
142
|
+
async listRoomsByOwner(filter) {
|
|
143
|
+
const namespaceKey = filter.namespaceKey ?? options.namespaceKey;
|
|
144
|
+
const url = registryUrl(options.baseUrl, "list");
|
|
145
|
+
url.searchParams.set("namespaceKey", namespaceKey);
|
|
146
|
+
if (filter.selectedRepo)
|
|
147
|
+
url.searchParams.set("repo", filter.selectedRepo);
|
|
148
|
+
const response = await requestFetch(url);
|
|
149
|
+
return (await parseResponseJson(response)).entries;
|
|
150
|
+
},
|
|
151
|
+
async removeRoom(roomId) {
|
|
152
|
+
const response = await post("remove", { roomId, namespaceKey: options.namespaceKey });
|
|
153
|
+
return response.removed;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// packages/transport-plugin/relay-registry/src/schema.ts
|
|
158
|
+
import { RunStatus as ContractRunStatus, Schema } from "@rig/contracts";
|
|
159
|
+
var RegistryOwner = Schema.Struct({
|
|
160
|
+
githubUserId: Schema.String,
|
|
161
|
+
login: Schema.String,
|
|
162
|
+
namespaceKey: Schema.String
|
|
163
|
+
});
|
|
164
|
+
var REGISTRY_STATUS_ALIASES = ["starting", "waiting-input"];
|
|
165
|
+
var REGISTRY_STATUS_VALUES = [
|
|
166
|
+
...ContractRunStatus.members.map((member) => member.literal),
|
|
167
|
+
...REGISTRY_STATUS_ALIASES
|
|
168
|
+
];
|
|
169
|
+
var RegistryStatus = Schema.Literals(REGISTRY_STATUS_VALUES);
|
|
170
|
+
var REGISTRY_STATUS_SET = new Set(REGISTRY_STATUS_VALUES);
|
|
171
|
+
function isRegistryStatus(value) {
|
|
172
|
+
return typeof value === "string" && REGISTRY_STATUS_SET.has(value);
|
|
173
|
+
}
|
|
174
|
+
function coerceRegistryStatus(value, fallback) {
|
|
175
|
+
if (value === "starting")
|
|
176
|
+
return "preparing";
|
|
177
|
+
if (value === "waiting-input")
|
|
178
|
+
return "waiting-user-input";
|
|
179
|
+
return isRegistryStatus(value) ? value : fallback;
|
|
180
|
+
}
|
|
181
|
+
var RegistryEntry = Schema.Struct({
|
|
182
|
+
roomId: Schema.String,
|
|
183
|
+
owner: RegistryOwner,
|
|
184
|
+
repo: Schema.String,
|
|
185
|
+
title: Schema.String,
|
|
186
|
+
status: RegistryStatus,
|
|
187
|
+
joinLink: Schema.String,
|
|
188
|
+
webLink: Schema.String,
|
|
189
|
+
relayUrl: Schema.String,
|
|
190
|
+
startedAt: Schema.String,
|
|
191
|
+
cwd: Schema.optional(Schema.String),
|
|
192
|
+
sessionPath: Schema.optional(Schema.String),
|
|
193
|
+
heartbeatAt: Schema.String,
|
|
194
|
+
pid: Schema.optional(Schema.Number),
|
|
195
|
+
projection: Schema.optional(Schema.Unknown)
|
|
196
|
+
});
|
|
197
|
+
var RegisterRequest = Schema.Struct({
|
|
198
|
+
roomId: Schema.String,
|
|
199
|
+
owner: RegistryOwner,
|
|
200
|
+
repo: Schema.String,
|
|
201
|
+
title: Schema.String,
|
|
202
|
+
status: RegistryStatus,
|
|
203
|
+
joinLink: Schema.String,
|
|
204
|
+
webLink: Schema.String,
|
|
205
|
+
relayUrl: Schema.String,
|
|
206
|
+
startedAt: Schema.String,
|
|
207
|
+
cwd: Schema.optional(Schema.String),
|
|
208
|
+
sessionPath: Schema.optional(Schema.String),
|
|
209
|
+
pid: Schema.optional(Schema.Number),
|
|
210
|
+
projection: Schema.optional(Schema.Unknown)
|
|
211
|
+
});
|
|
212
|
+
var HeartbeatRequest = Schema.Struct({
|
|
213
|
+
roomId: Schema.String,
|
|
214
|
+
namespaceKey: Schema.String,
|
|
215
|
+
status: RegistryStatus,
|
|
216
|
+
projection: Schema.optional(Schema.Unknown)
|
|
217
|
+
});
|
|
218
|
+
var ProjectionIngestRequest = Schema.Struct({
|
|
219
|
+
namespaceKey: Schema.String,
|
|
220
|
+
roomId: Schema.optional(Schema.String),
|
|
221
|
+
owner: Schema.optional(RegistryOwner),
|
|
222
|
+
repo: Schema.optional(Schema.String),
|
|
223
|
+
projection: Schema.Unknown
|
|
224
|
+
});
|
|
225
|
+
var RemoveRequest = Schema.Struct({
|
|
226
|
+
namespaceKey: Schema.String,
|
|
227
|
+
roomId: Schema.String
|
|
228
|
+
});
|
|
229
|
+
var ListedRegistryEntry = Schema.Struct({
|
|
230
|
+
roomId: Schema.String,
|
|
231
|
+
owner: RegistryOwner,
|
|
232
|
+
repo: Schema.String,
|
|
233
|
+
title: Schema.String,
|
|
234
|
+
status: RegistryStatus,
|
|
235
|
+
joinLink: Schema.String,
|
|
236
|
+
webLink: Schema.String,
|
|
237
|
+
relayUrl: Schema.String,
|
|
238
|
+
startedAt: Schema.String,
|
|
239
|
+
heartbeatAt: Schema.String,
|
|
240
|
+
cwd: Schema.optional(Schema.String),
|
|
241
|
+
sessionPath: Schema.optional(Schema.String),
|
|
242
|
+
pid: Schema.optional(Schema.Number),
|
|
243
|
+
projection: Schema.optional(Schema.Unknown),
|
|
244
|
+
stale: Schema.Boolean
|
|
245
|
+
});
|
|
246
|
+
var ListResponse = Schema.Struct({
|
|
247
|
+
entries: Schema.Array(ListedRegistryEntry)
|
|
248
|
+
});
|
|
249
|
+
var RegisterResponse = Schema.Struct({
|
|
250
|
+
entry: RegistryEntry
|
|
251
|
+
});
|
|
252
|
+
var HeartbeatResponse = Schema.Struct({
|
|
253
|
+
entry: RegistryEntry
|
|
254
|
+
});
|
|
255
|
+
var ProjectionIngestResponse = Schema.Struct({
|
|
256
|
+
entry: RegistryEntry
|
|
257
|
+
});
|
|
258
|
+
var RemoveResponse = Schema.Struct({
|
|
259
|
+
removed: Schema.Boolean
|
|
260
|
+
});
|
|
261
|
+
var RegistrySnapshotFrame = Schema.Struct({
|
|
262
|
+
type: Schema.Literal("registry.snapshot"),
|
|
263
|
+
namespaceKey: Schema.String,
|
|
264
|
+
sentAt: Schema.String,
|
|
265
|
+
entries: Schema.Array(ListedRegistryEntry)
|
|
266
|
+
});
|
|
267
|
+
var RegistryDeltaFrame = Schema.Struct({
|
|
268
|
+
type: Schema.Literal("registry.delta"),
|
|
269
|
+
namespaceKey: Schema.String,
|
|
270
|
+
sentAt: Schema.String,
|
|
271
|
+
action: Schema.Literals(["upsert", "remove"]),
|
|
272
|
+
roomId: Schema.String,
|
|
273
|
+
entry: Schema.optional(ListedRegistryEntry)
|
|
274
|
+
});
|
|
275
|
+
var WorkerProjectionFrame = Schema.Struct({
|
|
276
|
+
type: Schema.Literal("projection"),
|
|
277
|
+
projection: Schema.Unknown
|
|
278
|
+
});
|
|
279
|
+
export {
|
|
280
|
+
subscribeDiscovery,
|
|
281
|
+
createRegistryClient,
|
|
282
|
+
connectWorkerProjection,
|
|
283
|
+
coerceRegistryStatus
|
|
284
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Effect, Layer, ServiceMap, Stream } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* RunDiscoveryBus — an in-process event bus for run-discovery, following the
|
|
4
|
+
* t3code `RuntimeReceiptBus` pattern (a `ServiceMap.Service` backed by an Effect
|
|
5
|
+
* `PubSub`, exposed as a `Stream`). Producers (`localRunChanges` fs.watch,
|
|
6
|
+
* `remoteRunChanges` registry SSE) `publish` change events; the cockpit
|
|
7
|
+
* subscribes to `events` once and re-discovers, decoupled from how many sources
|
|
8
|
+
* feed it. New sources merge by publishing to the same bus — no consumer change.
|
|
9
|
+
*/
|
|
10
|
+
export interface RunDiscoveryEvent {
|
|
11
|
+
readonly source: "local" | "remote";
|
|
12
|
+
readonly kind: "registered" | "heartbeat" | "removed" | "changed";
|
|
13
|
+
readonly roomId?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RunDiscoveryBusShape {
|
|
16
|
+
readonly publish: (event: RunDiscoveryEvent) => Effect.Effect<void>;
|
|
17
|
+
readonly events: Stream.Stream<RunDiscoveryEvent>;
|
|
18
|
+
}
|
|
19
|
+
declare const RunDiscoveryBus_base: ServiceMap.ServiceClass<RunDiscoveryBus, "@rig/runtime/control-plane/RunDiscoveryBus", RunDiscoveryBusShape>;
|
|
20
|
+
export declare class RunDiscoveryBus extends RunDiscoveryBus_base {
|
|
21
|
+
static readonly layer: Layer.Layer<RunDiscoveryBus, never, never>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Fork each source stream so its signals become bus events, then return the
|
|
25
|
+
* deduped/debounced event stream for the caller to consume — the whole bus
|
|
26
|
+
* lifecycle in one Effect (provide `RunDiscoveryBus.layer` to run it).
|
|
27
|
+
*/
|
|
28
|
+
export declare const forkDiscoverySources: (sources: ReadonlyArray<{
|
|
29
|
+
readonly source: "local" | "remote";
|
|
30
|
+
readonly stream: Stream.Stream<void>;
|
|
31
|
+
}>) => Effect.Effect<Stream.Stream<RunDiscoveryEvent>, never, RunDiscoveryBus>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/transport-plugin/src/run-discovery-bus.ts
|
|
3
|
+
import { Effect, Layer, PubSub, ServiceMap, Stream } from "effect";
|
|
4
|
+
|
|
5
|
+
class RunDiscoveryBus extends ServiceMap.Service()("@rig/runtime/control-plane/RunDiscoveryBus") {
|
|
6
|
+
static layer = Layer.effect(RunDiscoveryBus, Effect.gen(function* () {
|
|
7
|
+
const pubsub = yield* PubSub.unbounded();
|
|
8
|
+
return {
|
|
9
|
+
publish: (event) => PubSub.publish(pubsub, event).pipe(Effect.asVoid),
|
|
10
|
+
events: Stream.fromPubSub(pubsub)
|
|
11
|
+
};
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
var forkDiscoverySources = (sources) => Effect.gen(function* () {
|
|
15
|
+
const bus = yield* RunDiscoveryBus;
|
|
16
|
+
for (const { source, stream } of sources) {
|
|
17
|
+
yield* Effect.forkChild(Stream.runForEach(stream, () => bus.publish({ source, kind: "changed" })));
|
|
18
|
+
}
|
|
19
|
+
return bus.events;
|
|
20
|
+
});
|
|
21
|
+
export {
|
|
22
|
+
forkDiscoverySources,
|
|
23
|
+
RunDiscoveryBus
|
|
24
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CapabilityProviderPlugin } from "@rig/kernel-seed/plugin-abi";
|
|
2
|
+
import { type DispatchRunDeps } from "./dispatch";
|
|
3
|
+
/**
|
|
4
|
+
* Build the placement-aware `transport` capability provider plugin (kernel-seed
|
|
5
|
+
* shape). Pass it to `adopt({ transport })` / `bootDefaultKernel({ extraPlugins })`.
|
|
6
|
+
* `deps` are the dispatch overrides (spawn/uuid/env/timeouts) — empty in
|
|
7
|
+
* production; the defaults reproduce exactly the former in-kernel auto-binding.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createPlacementTransportProviderPlugin(deps?: DispatchRunDeps): CapabilityProviderPlugin;
|