@open-wa/runtime-core 5.0.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/LICENSE.md +67 -0
- package/README.md +21 -0
- package/dist/index.cjs +568 -0
- package/dist/index.d.cts +341 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +341 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +530 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import { Cause, Context, Data, Effect, Exit, Fiber, Layer, PubSub, Queue, Schema, Scope, Semaphore, Stream } from "effect";
|
|
2
|
+
import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
|
|
3
|
+
import { Rpc } from "effect/unstable/rpc";
|
|
4
|
+
//#region src/errors.ts
|
|
5
|
+
var RuntimeCapabilityError = class extends Data.TaggedError("RuntimeCapabilityError") {};
|
|
6
|
+
var QueueOverloadedError = class extends Data.TaggedError("QueueOverloadedError") {};
|
|
7
|
+
var QueueClosedError = class extends Data.TaggedError("QueueClosedError") {};
|
|
8
|
+
var TaskTimeoutError = class extends Data.TaggedError("TaskTimeoutError") {};
|
|
9
|
+
var TaskExecutionError = class extends Data.TaggedError("TaskExecutionError") {};
|
|
10
|
+
var StartupGraphError = class extends Data.TaggedError("StartupGraphError") {};
|
|
11
|
+
var SandboxPolicyError = class extends Data.TaggedError("SandboxPolicyError") {};
|
|
12
|
+
var SandboxExecutionError = class extends Data.TaggedError("SandboxExecutionError") {};
|
|
13
|
+
var SessionAdmissionError = class extends Data.TaggedError("SessionAdmissionError") {};
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/effect-spikes.ts
|
|
16
|
+
const RuntimeHealthSchema = Schema.Struct({
|
|
17
|
+
sessionId: Schema.String,
|
|
18
|
+
state: Schema.Literals([
|
|
19
|
+
"STARTING",
|
|
20
|
+
"AUTHENTICATING",
|
|
21
|
+
"READY",
|
|
22
|
+
"DISCONNECTED",
|
|
23
|
+
"STOPPED"
|
|
24
|
+
]),
|
|
25
|
+
ready: Schema.Boolean
|
|
26
|
+
});
|
|
27
|
+
const RuntimeEventSchema = Schema.Struct({
|
|
28
|
+
event: Schema.String,
|
|
29
|
+
sessionId: Schema.String,
|
|
30
|
+
timestamp: Schema.Number,
|
|
31
|
+
payload: Schema.Unknown
|
|
32
|
+
});
|
|
33
|
+
const OpenWaHttpApi = HttpApi.make("openwa").add(HttpApiGroup.make("runtime").add(HttpApiEndpoint.get("health", "/health", { success: RuntimeHealthSchema })));
|
|
34
|
+
const OpenWaAskRpc = Rpc.make("openwa.ask", {
|
|
35
|
+
payload: {
|
|
36
|
+
method: Schema.String,
|
|
37
|
+
args: Schema.Array(Schema.Unknown)
|
|
38
|
+
},
|
|
39
|
+
success: Schema.Unknown
|
|
40
|
+
});
|
|
41
|
+
const decodeRuntimeEvent = Schema.decodeUnknownEffect(RuntimeEventSchema);
|
|
42
|
+
const makeBoundedEventPipeline = (capacity) => Effect.gen(function* () {
|
|
43
|
+
const pubsub = yield* PubSub.bounded(capacity);
|
|
44
|
+
return {
|
|
45
|
+
publish: (event) => PubSub.publish(pubsub, event),
|
|
46
|
+
events: Stream.fromPubSub(pubsub),
|
|
47
|
+
shutdown: PubSub.shutdown(pubsub)
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/services.ts
|
|
52
|
+
const RuntimeCapabilities = Context.Service("@open-wa/RuntimeCapabilities");
|
|
53
|
+
const BrowserDriver = Context.Service("@open-wa/BrowserDriver");
|
|
54
|
+
const BrowserSession = Context.Service("@open-wa/BrowserSession");
|
|
55
|
+
const PatchSource = Context.Service("@open-wa/PatchSource");
|
|
56
|
+
const LicenseService = Context.Service("@open-wa/LicenseService");
|
|
57
|
+
const SessionStore = Context.Service("@open-wa/SessionStore");
|
|
58
|
+
const PluginRegistry = Context.Service("@open-wa/PluginRegistry");
|
|
59
|
+
const ChatExecutor = Context.Service("@open-wa/ChatExecutor");
|
|
60
|
+
const SessionAdmission = Context.Service("@open-wa/SessionAdmission");
|
|
61
|
+
const RuntimeObservability = Context.Service("@open-wa/RuntimeObservability");
|
|
62
|
+
const serviceLayer = (key, service) => Layer.succeed(key, service);
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/observability.ts
|
|
65
|
+
const serializeCause = (cause, seen = /* @__PURE__ */ new Set()) => {
|
|
66
|
+
if (!(cause instanceof Error)) return {
|
|
67
|
+
name: typeof cause,
|
|
68
|
+
message: String(cause)
|
|
69
|
+
};
|
|
70
|
+
if (seen.has(cause)) return {
|
|
71
|
+
name: cause.name,
|
|
72
|
+
message: `${cause.message} [circular cause]`
|
|
73
|
+
};
|
|
74
|
+
seen.add(cause);
|
|
75
|
+
const nested = "cause" in cause && cause.cause !== void 0 ? serializeCause(cause.cause, seen) : void 0;
|
|
76
|
+
const errors = cause instanceof AggregateError ? cause.errors.map((error) => serializeCause(error, seen)) : void 0;
|
|
77
|
+
return {
|
|
78
|
+
name: cause.name,
|
|
79
|
+
message: cause.message,
|
|
80
|
+
...cause.stack ? { stack: cause.stack } : {},
|
|
81
|
+
...nested ? { cause: nested } : {},
|
|
82
|
+
...errors ? { errors } : {}
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
const makeInMemoryObservability = () => {
|
|
86
|
+
const values = /* @__PURE__ */ new Map();
|
|
87
|
+
const causes = [];
|
|
88
|
+
const keyFor = (metric, attributes) => {
|
|
89
|
+
if (!attributes || Object.keys(attributes).length === 0) return metric;
|
|
90
|
+
return `${metric}{${Object.entries(attributes).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${String(value)}`).join(",")}}`;
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
increment: (metric, value = 1, attributes) => Effect.sync(() => {
|
|
94
|
+
const key = keyFor(metric, attributes);
|
|
95
|
+
values.set(key, (values.get(key) ?? 0) + value);
|
|
96
|
+
}),
|
|
97
|
+
gauge: (metric, value, attributes) => Effect.sync(() => {
|
|
98
|
+
values.set(keyFor(metric, attributes), value);
|
|
99
|
+
}),
|
|
100
|
+
snapshot: Effect.sync(() => Object.fromEntries(values)),
|
|
101
|
+
recordCause: (scope, cause, attributes) => Effect.sync(() => {
|
|
102
|
+
causes.push({
|
|
103
|
+
scope,
|
|
104
|
+
recordedAt: Date.now(),
|
|
105
|
+
...attributes ? { attributes } : {},
|
|
106
|
+
cause: serializeCause(cause)
|
|
107
|
+
});
|
|
108
|
+
const key = keyFor("cause_failures", {
|
|
109
|
+
scope,
|
|
110
|
+
...attributes
|
|
111
|
+
});
|
|
112
|
+
values.set(key, (values.get(key) ?? 0) + 1);
|
|
113
|
+
}),
|
|
114
|
+
causes: Effect.sync(() => causes.map((record) => ({ ...record })))
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
const InMemoryObservabilityLayer = Layer.sync(RuntimeObservability, makeInMemoryObservability);
|
|
118
|
+
const queueMetricsObserver = (observability) => {
|
|
119
|
+
let previousDropped = 0;
|
|
120
|
+
let previousFailed = 0;
|
|
121
|
+
let previousRateLimited = 0;
|
|
122
|
+
return (metrics) => {
|
|
123
|
+
const attributes = { queue: metrics.name };
|
|
124
|
+
Effect.runSync(Effect.all([
|
|
125
|
+
observability.gauge("queue_depth", metrics.depth, attributes),
|
|
126
|
+
observability.gauge("queue_active", metrics.active, attributes),
|
|
127
|
+
observability.gauge("active_fibers", metrics.active, attributes),
|
|
128
|
+
observability.increment("queue_drops", Math.max(0, metrics.dropped - previousDropped), attributes),
|
|
129
|
+
observability.increment("queue_failures", Math.max(0, metrics.failed - previousFailed), attributes),
|
|
130
|
+
observability.increment("rate_decisions", Math.max(0, metrics.rateLimited - previousRateLimited), attributes)
|
|
131
|
+
]));
|
|
132
|
+
previousDropped = metrics.dropped;
|
|
133
|
+
previousFailed = metrics.failed;
|
|
134
|
+
previousRateLimited = metrics.rateLimited;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/runtime-capabilities.ts
|
|
139
|
+
const makeRuntimeCapabilities = (runtime, capabilities) => {
|
|
140
|
+
const supported = new Set(capabilities);
|
|
141
|
+
return {
|
|
142
|
+
runtime,
|
|
143
|
+
supported,
|
|
144
|
+
has: (capability) => supported.has(capability),
|
|
145
|
+
require: (capability) => supported.has(capability) ? Effect.void : Effect.fail(new RuntimeCapabilityError({
|
|
146
|
+
capability,
|
|
147
|
+
runtime,
|
|
148
|
+
detail: `${runtime} does not provide ${capability}`
|
|
149
|
+
}))
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const runtimeCapabilitiesLayer = (runtime, capabilities) => Layer.succeed(RuntimeCapabilities, makeRuntimeCapabilities(runtime, capabilities));
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/sandbox.ts
|
|
155
|
+
const defaultChatSandboxPolicy = {
|
|
156
|
+
chats: false,
|
|
157
|
+
isolation: "process",
|
|
158
|
+
timeoutMs: 3e4,
|
|
159
|
+
idleTimeoutMs: 3e5,
|
|
160
|
+
memoryMb: 256,
|
|
161
|
+
concurrency: 1,
|
|
162
|
+
filesystem: "none",
|
|
163
|
+
network: "none",
|
|
164
|
+
networkAllowlist: [],
|
|
165
|
+
env: "none",
|
|
166
|
+
capabilities: ["sendText"]
|
|
167
|
+
};
|
|
168
|
+
const ExecutionSandbox = Context.Service("@open-wa/ExecutionSandbox");
|
|
169
|
+
/** Cause capture only. This function does not provide a security boundary. */
|
|
170
|
+
const captureSandboxCause = (effect) => Effect.sandbox(effect);
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/scoped-task-queue.ts
|
|
173
|
+
var ScopedTaskQueue = class ScopedTaskQueue {
|
|
174
|
+
policy;
|
|
175
|
+
queue;
|
|
176
|
+
scope;
|
|
177
|
+
activeTasks = /* @__PURE__ */ new Set();
|
|
178
|
+
metricsState = {
|
|
179
|
+
depth: 0,
|
|
180
|
+
active: 0,
|
|
181
|
+
completed: 0,
|
|
182
|
+
failed: 0,
|
|
183
|
+
dropped: 0,
|
|
184
|
+
rateLimited: 0,
|
|
185
|
+
closed: false
|
|
186
|
+
};
|
|
187
|
+
rateWindowStartedAt = Date.now();
|
|
188
|
+
rateWindowCount = 0;
|
|
189
|
+
constructor(policy, queue, scope) {
|
|
190
|
+
this.policy = policy;
|
|
191
|
+
this.queue = queue;
|
|
192
|
+
this.scope = scope;
|
|
193
|
+
}
|
|
194
|
+
static async make(policy) {
|
|
195
|
+
if (!Number.isInteger(policy.capacity) || policy.capacity < 1) throw new RangeError("queue capacity must be a positive integer");
|
|
196
|
+
if (!Number.isInteger(policy.concurrency) || policy.concurrency < 1) throw new RangeError("queue concurrency must be a positive integer");
|
|
197
|
+
if (policy.rate && (!Number.isInteger(policy.rate.limit) || policy.rate.limit < 1 || !Number.isFinite(policy.rate.intervalMs) || policy.rate.intervalMs <= 0)) throw new RangeError("queue rate limit and interval must be positive");
|
|
198
|
+
const normalized = {
|
|
199
|
+
...policy,
|
|
200
|
+
ordering: "fifo"
|
|
201
|
+
};
|
|
202
|
+
const scope = await Effect.runPromise(Scope.make());
|
|
203
|
+
const queue = await Effect.runPromise(normalized.overload === "dropping" ? Queue.dropping(normalized.capacity) : Queue.bounded(normalized.capacity));
|
|
204
|
+
const taskQueue = new ScopedTaskQueue(normalized, queue, scope);
|
|
205
|
+
for (let index = 0; index < normalized.concurrency; index += 1) await Effect.runPromise(Effect.forkIn(taskQueue.worker(index), scope));
|
|
206
|
+
return taskQueue;
|
|
207
|
+
}
|
|
208
|
+
submit(operation, label) {
|
|
209
|
+
if (this.metricsState.closed) return Promise.reject(new QueueClosedError({ queue: this.policy.name }));
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
const task = {
|
|
212
|
+
operation: async () => operation(),
|
|
213
|
+
resolve: (value) => resolve(value),
|
|
214
|
+
reject,
|
|
215
|
+
enqueuedAt: Date.now(),
|
|
216
|
+
label
|
|
217
|
+
};
|
|
218
|
+
this.metricsState.depth += 1;
|
|
219
|
+
this.notifyObserver();
|
|
220
|
+
Effect.runPromise(Queue.offer(this.queue, task)).then((accepted) => {
|
|
221
|
+
if (!accepted) {
|
|
222
|
+
this.metricsState.depth = Math.max(0, this.metricsState.depth - 1);
|
|
223
|
+
this.metricsState.dropped += 1;
|
|
224
|
+
this.notifyObserver();
|
|
225
|
+
reject(new QueueOverloadedError({
|
|
226
|
+
queue: this.policy.name,
|
|
227
|
+
capacity: this.policy.capacity
|
|
228
|
+
}));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}).catch((error) => {
|
|
232
|
+
this.metricsState.depth = Math.max(0, this.metricsState.depth - 1);
|
|
233
|
+
this.notifyObserver();
|
|
234
|
+
reject(this.metricsState.closed ? new QueueClosedError({ queue: this.policy.name }) : error);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
metrics() {
|
|
239
|
+
return {
|
|
240
|
+
name: this.policy.name,
|
|
241
|
+
capacity: this.policy.capacity,
|
|
242
|
+
...this.metricsState
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async close() {
|
|
246
|
+
if (this.metricsState.closed) return;
|
|
247
|
+
this.metricsState.closed = true;
|
|
248
|
+
this.notifyObserver();
|
|
249
|
+
while (true) {
|
|
250
|
+
const result = Queue.takeUnsafe(this.queue);
|
|
251
|
+
if (result === void 0) break;
|
|
252
|
+
if (Exit.isFailure(result)) continue;
|
|
253
|
+
this.metricsState.depth = Math.max(0, this.metricsState.depth - 1);
|
|
254
|
+
result.value.reject(new QueueClosedError({ queue: this.policy.name }));
|
|
255
|
+
this.notifyObserver();
|
|
256
|
+
}
|
|
257
|
+
for (const task of this.activeTasks) task.reject(new QueueClosedError({ queue: this.policy.name }));
|
|
258
|
+
this.activeTasks.clear();
|
|
259
|
+
this.metricsState.active = 0;
|
|
260
|
+
this.notifyObserver();
|
|
261
|
+
await Effect.runPromise(Queue.shutdown(this.queue));
|
|
262
|
+
await Effect.runPromise(Scope.close(this.scope, Exit.void));
|
|
263
|
+
}
|
|
264
|
+
async waitForIdle() {
|
|
265
|
+
while (this.metricsState.depth > 0 || this.metricsState.active > 0) await Effect.runPromise(Effect.sleep(5));
|
|
266
|
+
}
|
|
267
|
+
worker(index) {
|
|
268
|
+
const self = this;
|
|
269
|
+
return Effect.gen(function* () {
|
|
270
|
+
while (true) {
|
|
271
|
+
const task = yield* Queue.take(self.queue);
|
|
272
|
+
self.metricsState.depth = Math.max(0, self.metricsState.depth - 1);
|
|
273
|
+
yield* self.reserveRatePermit();
|
|
274
|
+
self.activeTasks.add(task);
|
|
275
|
+
self.metricsState.active += 1;
|
|
276
|
+
self.notifyObserver();
|
|
277
|
+
let execution = Effect.tryPromise({
|
|
278
|
+
try: task.operation,
|
|
279
|
+
catch: (cause) => new TaskExecutionError({
|
|
280
|
+
queue: self.policy.name,
|
|
281
|
+
cause
|
|
282
|
+
})
|
|
283
|
+
});
|
|
284
|
+
if (self.policy.timeoutMs !== void 0) execution = execution.pipe(Effect.timeoutOrElse({
|
|
285
|
+
duration: self.policy.timeoutMs,
|
|
286
|
+
orElse: () => Effect.fail(new TaskTimeoutError({
|
|
287
|
+
queue: self.policy.name,
|
|
288
|
+
timeoutMs: self.policy.timeoutMs
|
|
289
|
+
}))
|
|
290
|
+
}));
|
|
291
|
+
const result = yield* Effect.exit(execution.pipe(Effect.withSpan(`openwa.queue.${self.policy.name}`, { attributes: {
|
|
292
|
+
"queue.name": self.policy.name,
|
|
293
|
+
"queue.worker": index,
|
|
294
|
+
"queue.wait_ms": Date.now() - task.enqueuedAt,
|
|
295
|
+
...task.label ? { "task.label": task.label } : {}
|
|
296
|
+
} })));
|
|
297
|
+
self.activeTasks.delete(task);
|
|
298
|
+
self.metricsState.active = Math.max(0, self.metricsState.active - 1);
|
|
299
|
+
if (Exit.isSuccess(result)) {
|
|
300
|
+
self.metricsState.completed += 1;
|
|
301
|
+
task.resolve(result.value);
|
|
302
|
+
} else {
|
|
303
|
+
self.metricsState.failed += 1;
|
|
304
|
+
task.reject(Cause.squash(result.cause));
|
|
305
|
+
}
|
|
306
|
+
self.notifyObserver();
|
|
307
|
+
}
|
|
308
|
+
}).pipe(Effect.withSpan(`openwa.queue.worker.${self.policy.name}`));
|
|
309
|
+
}
|
|
310
|
+
reserveRatePermit() {
|
|
311
|
+
const rate = this.policy.rate;
|
|
312
|
+
if (!rate) return Effect.void;
|
|
313
|
+
const self = this;
|
|
314
|
+
return Effect.suspend(() => {
|
|
315
|
+
const now = Date.now();
|
|
316
|
+
const elapsed = now - self.rateWindowStartedAt;
|
|
317
|
+
if (elapsed >= rate.intervalMs) {
|
|
318
|
+
self.rateWindowStartedAt = now;
|
|
319
|
+
self.rateWindowCount = 0;
|
|
320
|
+
}
|
|
321
|
+
if (self.rateWindowCount < rate.limit) {
|
|
322
|
+
self.rateWindowCount += 1;
|
|
323
|
+
return Effect.void;
|
|
324
|
+
}
|
|
325
|
+
self.metricsState.rateLimited += 1;
|
|
326
|
+
self.notifyObserver();
|
|
327
|
+
return Effect.sleep(Math.max(1, rate.intervalMs - elapsed)).pipe(Effect.andThen(self.reserveRatePermit()));
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
notifyObserver() {
|
|
331
|
+
try {
|
|
332
|
+
this.policy.observe?.(this.metrics());
|
|
333
|
+
} catch {}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
//#endregion
|
|
337
|
+
//#region src/session-admission.ts
|
|
338
|
+
const makeSessionAdmission = (options) => Effect.gen(function* () {
|
|
339
|
+
const memory = yield* Semaphore.make(options.memoryCapacityMb);
|
|
340
|
+
const launches = yield* Semaphore.make(options.maxConcurrentLaunches);
|
|
341
|
+
let activeSessions = 0;
|
|
342
|
+
let availableMemoryMb = options.memoryCapacityMb;
|
|
343
|
+
return {
|
|
344
|
+
acquire: (memoryMb) => Effect.gen(function* () {
|
|
345
|
+
if (memoryMb > options.memoryCapacityMb) return yield* Effect.fail(new SessionAdmissionError({
|
|
346
|
+
requestedMemoryMb: memoryMb,
|
|
347
|
+
availableMemoryMb
|
|
348
|
+
}));
|
|
349
|
+
yield* Semaphore.take(memory, memoryMb);
|
|
350
|
+
yield* Semaphore.take(launches, 1);
|
|
351
|
+
availableMemoryMb -= memoryMb;
|
|
352
|
+
activeSessions += 1;
|
|
353
|
+
if (options.observability) yield* options.observability.gauge("session_browser_memory_mb", options.memoryCapacityMb - availableMemoryMb);
|
|
354
|
+
let released = false;
|
|
355
|
+
return {
|
|
356
|
+
memoryMb,
|
|
357
|
+
release: Effect.suspend(() => {
|
|
358
|
+
if (released) return Effect.void;
|
|
359
|
+
released = true;
|
|
360
|
+
activeSessions = Math.max(0, activeSessions - 1);
|
|
361
|
+
availableMemoryMb = Math.min(options.memoryCapacityMb, availableMemoryMb + memoryMb);
|
|
362
|
+
return Effect.all([
|
|
363
|
+
Semaphore.release(memory, memoryMb),
|
|
364
|
+
Semaphore.release(launches, 1),
|
|
365
|
+
...options.observability ? [options.observability.gauge("session_browser_memory_mb", options.memoryCapacityMb - availableMemoryMb)] : []
|
|
366
|
+
]).pipe(Effect.asVoid);
|
|
367
|
+
})
|
|
368
|
+
};
|
|
369
|
+
}),
|
|
370
|
+
snapshot: Effect.sync(() => ({
|
|
371
|
+
capacityMemoryMb: options.memoryCapacityMb,
|
|
372
|
+
availableMemoryMb,
|
|
373
|
+
activeSessions
|
|
374
|
+
}))
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
const sessionAdmissionLayer = (options) => Layer.effect(SessionAdmission, makeSessionAdmission(options));
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/session-scope.ts
|
|
380
|
+
var SessionScope = class SessionScope {
|
|
381
|
+
scope;
|
|
382
|
+
observability;
|
|
383
|
+
metricAttributes;
|
|
384
|
+
finalizerNames = /* @__PURE__ */ new Set();
|
|
385
|
+
closed = false;
|
|
386
|
+
activeFibers = 0;
|
|
387
|
+
constructor(scope, observability, metricAttributes = {}) {
|
|
388
|
+
this.scope = scope;
|
|
389
|
+
this.observability = observability;
|
|
390
|
+
this.metricAttributes = metricAttributes;
|
|
391
|
+
}
|
|
392
|
+
static async make(options = {}) {
|
|
393
|
+
return new SessionScope(await Effect.runPromise(Scope.make()), options.observability, options.metricAttributes);
|
|
394
|
+
}
|
|
395
|
+
async addFinalizer(name, finalizer) {
|
|
396
|
+
if (this.closed) throw new Error("session scope is already closed");
|
|
397
|
+
this.finalizerNames.add(name);
|
|
398
|
+
let active = true;
|
|
399
|
+
await Effect.runPromise(Scope.addFinalizer(this.scope, Effect.promise(async () => {
|
|
400
|
+
if (!active) return;
|
|
401
|
+
active = false;
|
|
402
|
+
this.finalizerNames.delete(name);
|
|
403
|
+
await finalizer();
|
|
404
|
+
})));
|
|
405
|
+
return () => {
|
|
406
|
+
active = false;
|
|
407
|
+
this.finalizerNames.delete(name);
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
snapshot() {
|
|
411
|
+
return {
|
|
412
|
+
closed: this.closed,
|
|
413
|
+
finalizers: [...this.finalizerNames]
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
async fork(effect) {
|
|
417
|
+
if (this.closed) throw new Error("session scope is already closed");
|
|
418
|
+
const fiber = await Effect.runPromise(Effect.forkIn(effect, this.scope));
|
|
419
|
+
this.activeFibers += 1;
|
|
420
|
+
this.publishActiveFibers();
|
|
421
|
+
Effect.runPromise(Fiber.await(fiber)).finally(() => {
|
|
422
|
+
this.activeFibers = Math.max(0, this.activeFibers - 1);
|
|
423
|
+
this.publishActiveFibers();
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
async close(reason = "normal-stop") {
|
|
427
|
+
if (this.closed) return;
|
|
428
|
+
this.closed = true;
|
|
429
|
+
await Effect.runPromise(Scope.close(this.scope, reason === "normal-stop" ? Exit.void : Exit.fail(/* @__PURE__ */ new Error(`session scope closed: ${reason}`))));
|
|
430
|
+
}
|
|
431
|
+
publishActiveFibers() {
|
|
432
|
+
if (!this.observability) return;
|
|
433
|
+
Effect.runSync(this.observability.gauge("active_fibers", this.activeFibers, {
|
|
434
|
+
owner: "session-scope",
|
|
435
|
+
...this.metricAttributes
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/startup-graph.ts
|
|
441
|
+
const validateStartupGraph = (nodes) => Effect.try({
|
|
442
|
+
try: () => {
|
|
443
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
444
|
+
if (ids.size !== nodes.length) throw new StartupGraphError({
|
|
445
|
+
detail: "startup graph contains duplicate node ids",
|
|
446
|
+
nodes: nodes.map((node) => node.id)
|
|
447
|
+
});
|
|
448
|
+
for (const node of nodes) for (const dependency of node.dependsOn ?? []) if (!ids.has(dependency)) throw new StartupGraphError({
|
|
449
|
+
detail: `${node.id} depends on missing node ${dependency}`,
|
|
450
|
+
nodes: [node.id, dependency]
|
|
451
|
+
});
|
|
452
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
453
|
+
const visited = /* @__PURE__ */ new Set();
|
|
454
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
455
|
+
const visit = (id) => {
|
|
456
|
+
if (visited.has(id)) return;
|
|
457
|
+
if (visiting.has(id)) throw new StartupGraphError({
|
|
458
|
+
detail: `startup graph contains a cycle at ${id}`,
|
|
459
|
+
nodes: [...visiting, id]
|
|
460
|
+
});
|
|
461
|
+
visiting.add(id);
|
|
462
|
+
for (const dependency of byId.get(id)?.dependsOn ?? []) visit(dependency);
|
|
463
|
+
visiting.delete(id);
|
|
464
|
+
visited.add(id);
|
|
465
|
+
};
|
|
466
|
+
for (const node of nodes) visit(node.id);
|
|
467
|
+
},
|
|
468
|
+
catch: (error) => error instanceof StartupGraphError ? error : new StartupGraphError({
|
|
469
|
+
detail: String(error),
|
|
470
|
+
nodes: nodes.map((node) => node.id)
|
|
471
|
+
})
|
|
472
|
+
});
|
|
473
|
+
const runStartupGraph = (nodes, options = {}) => Effect.gen(function* () {
|
|
474
|
+
yield* validateStartupGraph(nodes);
|
|
475
|
+
const startedAt = Date.now();
|
|
476
|
+
const pending = new Map(nodes.map((node) => [node.id, node]));
|
|
477
|
+
const values = /* @__PURE__ */ new Map();
|
|
478
|
+
const phases = [];
|
|
479
|
+
while (pending.size > 0) {
|
|
480
|
+
const ready = [...pending.values()].filter((node) => (node.dependsOn ?? []).every((dependency) => values.has(dependency)));
|
|
481
|
+
if (ready.length === 0) return yield* Effect.fail(new StartupGraphError({
|
|
482
|
+
detail: "startup graph made no progress",
|
|
483
|
+
nodes: [...pending.keys()]
|
|
484
|
+
}));
|
|
485
|
+
const results = yield* Effect.all(ready.map((node) => Effect.gen(function* () {
|
|
486
|
+
return {
|
|
487
|
+
node,
|
|
488
|
+
nodeStartedAt: Date.now(),
|
|
489
|
+
exit: yield* Effect.exit(node.run(values).pipe(Effect.withSpan(`openwa.startup.${node.id}`, { attributes: {
|
|
490
|
+
"startup.node": node.id,
|
|
491
|
+
"startup.optional": node.optional ?? false
|
|
492
|
+
} })))
|
|
493
|
+
};
|
|
494
|
+
})), { concurrency: options.concurrency ?? 4 });
|
|
495
|
+
for (const { node, nodeStartedAt, exit } of results) {
|
|
496
|
+
pending.delete(node.id);
|
|
497
|
+
if (exit._tag === "Failure") {
|
|
498
|
+
if (!node.optional) return yield* Effect.failCause(exit.cause);
|
|
499
|
+
values.set(node.id, exit);
|
|
500
|
+
phases.push({
|
|
501
|
+
id: node.id,
|
|
502
|
+
durationMs: Date.now() - nodeStartedAt,
|
|
503
|
+
optional: true,
|
|
504
|
+
result: exit
|
|
505
|
+
});
|
|
506
|
+
options.onPhase?.(phases.at(-1));
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
values.set(node.id, exit.value);
|
|
510
|
+
phases.push({
|
|
511
|
+
id: node.id,
|
|
512
|
+
durationMs: Date.now() - nodeStartedAt,
|
|
513
|
+
optional: node.optional ?? false,
|
|
514
|
+
result: exit.value
|
|
515
|
+
});
|
|
516
|
+
options.onPhase?.(phases.at(-1));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const result = {
|
|
520
|
+
values,
|
|
521
|
+
phases,
|
|
522
|
+
criticalPathMs: Date.now() - startedAt
|
|
523
|
+
};
|
|
524
|
+
options.onComplete?.(result);
|
|
525
|
+
return result;
|
|
526
|
+
});
|
|
527
|
+
//#endregion
|
|
528
|
+
export { BrowserDriver, BrowserSession, ChatExecutor, ExecutionSandbox, InMemoryObservabilityLayer, LicenseService, OpenWaAskRpc, OpenWaHttpApi, PatchSource, PluginRegistry, QueueClosedError, QueueOverloadedError, RuntimeCapabilities, RuntimeCapabilityError, RuntimeEventSchema, RuntimeHealthSchema, RuntimeObservability, SandboxExecutionError, SandboxPolicyError, ScopedTaskQueue, SessionAdmission, SessionAdmissionError, SessionScope, SessionStore, StartupGraphError, TaskExecutionError, TaskTimeoutError, captureSandboxCause, decodeRuntimeEvent, defaultChatSandboxPolicy, makeBoundedEventPipeline, makeInMemoryObservability, makeRuntimeCapabilities, makeSessionAdmission, queueMetricsObserver, runStartupGraph, runtimeCapabilitiesLayer, serviceLayer, sessionAdmissionLayer, validateStartupGraph };
|
|
529
|
+
|
|
530
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/errors.ts","../src/effect-spikes.ts","../src/services.ts","../src/observability.ts","../src/runtime-capabilities.ts","../src/sandbox.ts","../src/scoped-task-queue.ts","../src/session-admission.ts","../src/session-scope.ts","../src/startup-graph.ts"],"sourcesContent":["import { Data } from 'effect';\n\nexport class RuntimeCapabilityError extends Data.TaggedError(\n 'RuntimeCapabilityError',\n)<{\n readonly capability: RuntimeCapability;\n readonly runtime: RuntimeKind;\n readonly detail: string;\n}> {}\n\nexport class QueueOverloadedError extends Data.TaggedError(\n 'QueueOverloadedError',\n)<{\n readonly queue: string;\n readonly capacity: number;\n}> {}\n\nexport class QueueClosedError extends Data.TaggedError('QueueClosedError')<{\n readonly queue: string;\n}> {}\n\nexport class TaskTimeoutError extends Data.TaggedError('TaskTimeoutError')<{\n readonly queue: string;\n readonly timeoutMs: number;\n}> {}\n\nexport class TaskExecutionError extends Data.TaggedError(\n 'TaskExecutionError',\n)<{\n readonly queue: string;\n readonly cause: unknown;\n}> {}\n\nexport class StartupGraphError extends Data.TaggedError('StartupGraphError')<{\n readonly detail: string;\n readonly nodes: ReadonlyArray<string>;\n}> {}\n\nexport class SandboxPolicyError extends Data.TaggedError(\n 'SandboxPolicyError',\n)<{\n readonly detail: string;\n readonly field?: string;\n}> {}\n\nexport class SandboxExecutionError extends Data.TaggedError(\n 'SandboxExecutionError',\n)<{\n readonly chatId: string;\n readonly isolation: string;\n readonly cause: unknown;\n}> {}\n\nexport class SessionAdmissionError extends Data.TaggedError(\n 'SessionAdmissionError',\n)<{\n readonly requestedMemoryMb: number;\n readonly availableMemoryMb: number;\n}> {}\n\nexport type RuntimeKind = 'node' | 'bun' | 'browser' | 'edge' | 'test';\n\nexport type RuntimeCapability =\n | 'browser-client'\n | 'chromium-launch'\n | 'filesystem'\n | 'process'\n | 'web-fetch'\n | 'worker-bindings';\n","import { Effect, PubSub, Schema, Stream } from 'effect';\nimport {\n HttpApi,\n HttpApiEndpoint,\n HttpApiGroup,\n} from 'effect/unstable/httpapi';\nimport { Rpc } from 'effect/unstable/rpc';\n\nexport const RuntimeHealthSchema = Schema.Struct({\n sessionId: Schema.String,\n state: Schema.Literals(['STARTING', 'AUTHENTICATING', 'READY', 'DISCONNECTED', 'STOPPED']),\n ready: Schema.Boolean,\n});\n\nexport const RuntimeEventSchema = Schema.Struct({\n event: Schema.String,\n sessionId: Schema.String,\n timestamp: Schema.Number,\n payload: Schema.Unknown,\n});\n\nexport const OpenWaHttpApi = HttpApi.make('openwa').add(\n HttpApiGroup.make('runtime').add(\n HttpApiEndpoint.get('health', '/health', { success: RuntimeHealthSchema }),\n ),\n);\n\nexport const OpenWaAskRpc = Rpc.make('openwa.ask', {\n payload: {\n method: Schema.String,\n args: Schema.Array(Schema.Unknown),\n },\n success: Schema.Unknown,\n});\n\nexport const decodeRuntimeEvent = Schema.decodeUnknownEffect(RuntimeEventSchema);\n\nexport const makeBoundedEventPipeline = <A>(capacity: number) =>\n Effect.gen(function* () {\n const pubsub = yield* PubSub.bounded<A>(capacity);\n return {\n publish: (event: A) => PubSub.publish(pubsub, event),\n events: Stream.fromPubSub(pubsub),\n shutdown: PubSub.shutdown(pubsub),\n } as const;\n });\n","import { Context, Effect, Layer } from 'effect';\nimport type {\n RuntimeCapability,\n RuntimeCapabilityError,\n RuntimeKind,\n SessionAdmissionError,\n} from './errors';\n\nexport interface RuntimeCapabilitiesShape {\n readonly runtime: RuntimeKind;\n readonly supported: ReadonlySet<RuntimeCapability>;\n readonly has: (capability: RuntimeCapability) => boolean;\n readonly require: (\n capability: RuntimeCapability,\n ) => Effect.Effect<void, RuntimeCapabilityError>;\n}\n\nexport const RuntimeCapabilities =\n Context.Service<RuntimeCapabilitiesShape>('@open-wa/RuntimeCapabilities');\n\nexport interface BrowserDriverShape {\n readonly launch: (options?: unknown) => Effect.Effect<unknown, unknown>;\n}\n\nexport const BrowserDriver =\n Context.Service<BrowserDriverShape>('@open-wa/BrowserDriver');\n\nexport interface BrowserSessionShape {\n readonly page: unknown;\n readonly close: Effect.Effect<void>;\n}\n\nexport const BrowserSession =\n Context.Service<BrowserSessionShape>('@open-wa/BrowserSession');\n\nexport interface PatchSourceShape<Artifact = unknown> {\n readonly preload: Effect.Effect<Artifact, unknown>;\n}\n\nexport const PatchSource =\n Context.Service<PatchSourceShape>('@open-wa/PatchSource');\n\nexport interface LicenseServiceShape<Artifact = unknown, Identity = unknown> {\n readonly preflight: Effect.Effect<Artifact | null, unknown>;\n readonly validate: (\n identity: Identity,\n artifact: Artifact | null,\n ) => Effect.Effect<Artifact | null, unknown>;\n}\n\nexport const LicenseService =\n Context.Service<LicenseServiceShape>('@open-wa/LicenseService');\n\nexport interface SessionStoreShape {\n readonly get: (key: string) => Effect.Effect<unknown, unknown>;\n readonly set: (key: string, value: unknown) => Effect.Effect<void, unknown>;\n readonly remove: (key: string) => Effect.Effect<void, unknown>;\n}\n\nexport const SessionStore =\n Context.Service<SessionStoreShape>('@open-wa/SessionStore');\n\nexport interface PluginRegistryShape {\n readonly names: Effect.Effect<ReadonlyArray<string>>;\n readonly close: Effect.Effect<void>;\n}\n\nexport const PluginRegistry =\n Context.Service<PluginRegistryShape>('@open-wa/PluginRegistry');\n\nexport interface ChatExecutorShape {\n readonly execute: (\n chatId: string,\n source: string,\n input?: unknown,\n ) => Effect.Effect<unknown, unknown>;\n readonly closeChat: (chatId: string) => Effect.Effect<void>;\n}\n\nexport const ChatExecutor =\n Context.Service<ChatExecutorShape>('@open-wa/ChatExecutor');\n\nexport interface SessionAdmissionLease {\n readonly memoryMb: number;\n readonly release: Effect.Effect<void>;\n}\n\nexport interface SessionAdmissionShape {\n readonly acquire: (\n memoryMb: number,\n ) => Effect.Effect<SessionAdmissionLease, SessionAdmissionError>;\n readonly snapshot: Effect.Effect<{\n readonly capacityMemoryMb: number;\n readonly availableMemoryMb: number;\n readonly activeSessions: number;\n }>;\n}\n\nexport const SessionAdmission =\n Context.Service<SessionAdmissionShape>('@open-wa/SessionAdmission');\n\nexport interface RuntimeObservabilityShape {\n readonly increment: (\n metric: RuntimeMetric,\n value?: number,\n attributes?: Readonly<Record<string, string | number | boolean>>,\n ) => Effect.Effect<void>;\n readonly gauge: (\n metric: RuntimeMetric,\n value: number,\n attributes?: Readonly<Record<string, string | number | boolean>>,\n ) => Effect.Effect<void>;\n readonly snapshot: Effect.Effect<Readonly<Record<string, number>>>;\n readonly recordCause: (\n scope: string,\n cause: unknown,\n attributes?: Readonly<Record<string, string | number | boolean>>,\n ) => Effect.Effect<void>;\n readonly causes: Effect.Effect<ReadonlyArray<RuntimeCauseRecord>>;\n}\n\nexport interface SerializedRuntimeCause {\n readonly name: string;\n readonly message: string;\n readonly stack?: string;\n readonly cause?: SerializedRuntimeCause;\n readonly errors?: ReadonlyArray<SerializedRuntimeCause>;\n}\n\nexport interface RuntimeCauseRecord {\n readonly scope: string;\n readonly recordedAt: number;\n readonly attributes?: Readonly<Record<string, string | number | boolean>>;\n readonly cause: SerializedRuntimeCause;\n}\n\nexport type RuntimeMetric =\n | 'active_fibers'\n | 'cause_failures'\n | 'queue_active'\n | 'queue_depth'\n | 'queue_drops'\n | 'queue_failures'\n | 'rate_decisions'\n | 'retry_decisions'\n | 'session_browser_memory_mb'\n | 'startup_phase_ms'\n | 'startup_critical_path_ms';\n\nexport const RuntimeObservability =\n Context.Service<RuntimeObservabilityShape>('@open-wa/RuntimeObservability');\n\nexport const serviceLayer = <I, S>(key: Context.Key<I, S>, service: S) =>\n Layer.succeed(key, service);\n","import { Effect, Layer } from 'effect';\nimport {\n RuntimeObservability,\n type RuntimeObservabilityShape,\n type RuntimeMetric,\n type SerializedRuntimeCause,\n type RuntimeCauseRecord,\n} from './services';\n\nconst serializeCause = (cause: unknown, seen = new Set<unknown>()): SerializedRuntimeCause => {\n if (!(cause instanceof Error)) {\n return { name: typeof cause, message: String(cause) };\n }\n if (seen.has(cause)) {\n return { name: cause.name, message: `${cause.message} [circular cause]` };\n }\n seen.add(cause);\n const nested = 'cause' in cause && cause.cause !== undefined\n ? serializeCause(cause.cause, seen)\n : undefined;\n const errors = cause instanceof AggregateError\n ? cause.errors.map((error) => serializeCause(error, seen))\n : undefined;\n return {\n name: cause.name,\n message: cause.message,\n ...(cause.stack ? { stack: cause.stack } : {}),\n ...(nested ? { cause: nested } : {}),\n ...(errors ? { errors } : {}),\n };\n};\n\nexport const makeInMemoryObservability = (): RuntimeObservabilityShape => {\n const values = new Map<string, number>();\n const causes: RuntimeCauseRecord[] = [];\n\n const keyFor = (\n metric: RuntimeMetric,\n attributes?: Readonly<Record<string, string | number | boolean>>,\n ) => {\n if (!attributes || Object.keys(attributes).length === 0) return metric;\n const suffix = Object.entries(attributes)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, value]) => `${key}=${String(value)}`)\n .join(',');\n return `${metric}{${suffix}}`;\n };\n\n return {\n increment: (metric, value = 1, attributes) =>\n Effect.sync(() => {\n const key = keyFor(metric, attributes);\n values.set(key, (values.get(key) ?? 0) + value);\n }),\n gauge: (metric, value, attributes) =>\n Effect.sync(() => {\n values.set(keyFor(metric, attributes), value);\n }),\n snapshot: Effect.sync(() => Object.fromEntries(values)),\n recordCause: (scope, cause, attributes) => Effect.sync(() => {\n causes.push({\n scope,\n recordedAt: Date.now(),\n ...(attributes ? { attributes } : {}),\n cause: serializeCause(cause),\n });\n const key = keyFor('cause_failures', { scope, ...attributes });\n values.set(key, (values.get(key) ?? 0) + 1);\n }),\n causes: Effect.sync(() => causes.map((record) => ({ ...record }))),\n };\n};\n\nexport const InMemoryObservabilityLayer = Layer.sync(\n RuntimeObservability,\n makeInMemoryObservability,\n);\n\nexport const queueMetricsObserver = (observability: RuntimeObservabilityShape) => {\n let previousDropped = 0;\n let previousFailed = 0;\n let previousRateLimited = 0;\n return (metrics: {\n readonly name: string;\n readonly depth: number;\n readonly active: number;\n readonly dropped: number;\n readonly failed: number;\n readonly rateLimited: number;\n }) => {\n const attributes = { queue: metrics.name };\n Effect.runSync(Effect.all([\n observability.gauge('queue_depth', metrics.depth, attributes),\n observability.gauge('queue_active', metrics.active, attributes),\n observability.gauge('active_fibers', metrics.active, attributes),\n observability.increment('queue_drops', Math.max(0, metrics.dropped - previousDropped), attributes),\n observability.increment('queue_failures', Math.max(0, metrics.failed - previousFailed), attributes),\n observability.increment('rate_decisions', Math.max(0, metrics.rateLimited - previousRateLimited), attributes),\n ]));\n previousDropped = metrics.dropped;\n previousFailed = metrics.failed;\n previousRateLimited = metrics.rateLimited;\n };\n};\n","import { Effect, Layer } from 'effect';\nimport {\n RuntimeCapabilityError,\n type RuntimeCapability,\n type RuntimeKind,\n} from './errors';\nimport {\n RuntimeCapabilities,\n type RuntimeCapabilitiesShape,\n} from './services';\n\nexport const makeRuntimeCapabilities = (\n runtime: RuntimeKind,\n capabilities: Iterable<RuntimeCapability>,\n): RuntimeCapabilitiesShape => {\n const supported = new Set(capabilities);\n return {\n runtime,\n supported,\n has: (capability) => supported.has(capability),\n require: (capability) =>\n supported.has(capability)\n ? Effect.void\n : Effect.fail(\n new RuntimeCapabilityError({\n capability,\n runtime,\n detail: `${runtime} does not provide ${capability}`,\n }),\n ),\n };\n};\n\nexport const runtimeCapabilitiesLayer = (\n runtime: RuntimeKind,\n capabilities: Iterable<RuntimeCapability>,\n) => Layer.succeed(RuntimeCapabilities, makeRuntimeCapabilities(runtime, capabilities));\n","import { Context, Effect } from 'effect';\nimport type { SandboxExecutionError } from './errors';\n\nexport type SandboxIsolation = 'worker' | 'process' | 'container';\n\nexport interface ChatSandboxPolicy {\n readonly chats: boolean;\n readonly isolation: SandboxIsolation;\n readonly timeoutMs: number;\n readonly idleTimeoutMs: number;\n readonly memoryMb: number;\n readonly concurrency: number;\n readonly filesystem: 'none' | 'read-only' | 'workspace';\n readonly network: 'none' | 'allowlist';\n readonly networkAllowlist: ReadonlyArray<string>;\n readonly env: 'none' | ReadonlyArray<string>;\n readonly capabilities: ReadonlyArray<string>;\n}\n\nexport const defaultChatSandboxPolicy: ChatSandboxPolicy = {\n chats: false,\n isolation: 'process',\n timeoutMs: 30_000,\n idleTimeoutMs: 5 * 60_000,\n memoryMb: 256,\n concurrency: 1,\n filesystem: 'none',\n network: 'none',\n networkAllowlist: [],\n env: 'none',\n capabilities: ['sendText'],\n};\n\nexport type SandboxCapabilityHandler = (\n ...args: ReadonlyArray<unknown>\n) => unknown | Promise<unknown>;\n\nexport type SandboxCapabilityHandlers = Readonly<\n Record<string, SandboxCapabilityHandler>\n>;\n\nexport interface SandboxRequest {\n readonly chatId: string;\n readonly source: string;\n readonly input?: unknown;\n readonly policy: ChatSandboxPolicy;\n /** Parent-owned capability implementations. Functions never enter the sandbox. */\n readonly capabilityHandlers?: SandboxCapabilityHandlers;\n}\n\nexport interface ExecutionSandboxShape {\n readonly execute: (\n request: SandboxRequest,\n ) => Effect.Effect<unknown, SandboxExecutionError>;\n readonly closeChat: (chatId: string) => Effect.Effect<void>;\n readonly close: Effect.Effect<void>;\n}\n\nexport const ExecutionSandbox =\n Context.Service<ExecutionSandboxShape>('@open-wa/ExecutionSandbox');\n\n/** Cause capture only. This function does not provide a security boundary. */\nexport const captureSandboxCause = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n Effect.sandbox(effect);\n","import { Cause, Effect, Exit, Queue, Scope } from 'effect';\nimport {\n QueueClosedError,\n QueueOverloadedError,\n TaskExecutionError,\n TaskTimeoutError,\n} from './errors';\n\nexport type QueueOverloadStrategy = 'backpressure' | 'dropping';\n\nexport interface QueueRateLimit {\n readonly limit: number;\n readonly intervalMs: number;\n}\n\nexport interface TaskQueuePolicy {\n readonly name: string;\n readonly capacity: number;\n readonly concurrency: number;\n readonly overload: QueueOverloadStrategy;\n readonly ordering: 'fifo';\n readonly timeoutMs?: number;\n readonly rate?: QueueRateLimit;\n readonly observe?: (metrics: TaskQueueMetrics) => void;\n}\n\nexport interface TaskQueueMetrics {\n readonly name: string;\n readonly capacity: number;\n readonly depth: number;\n readonly active: number;\n readonly completed: number;\n readonly failed: number;\n readonly dropped: number;\n readonly rateLimited: number;\n readonly closed: boolean;\n}\n\ninterface QueuedTask {\n readonly operation: () => Promise<unknown>;\n readonly resolve: (value: unknown) => void;\n readonly reject: (error: unknown) => void;\n readonly enqueuedAt: number;\n readonly label?: string;\n}\n\nexport class ScopedTaskQueue {\n private readonly activeTasks = new Set<QueuedTask>();\n private readonly metricsState: {\n depth: number;\n active: number;\n completed: number;\n failed: number;\n dropped: number;\n rateLimited: number;\n closed: boolean;\n } = {\n depth: 0,\n active: 0,\n completed: 0,\n failed: 0,\n dropped: 0,\n rateLimited: 0,\n closed: false,\n };\n\n private rateWindowStartedAt = Date.now();\n private rateWindowCount = 0;\n\n private constructor(\n readonly policy: TaskQueuePolicy,\n private readonly queue: Queue.Queue<QueuedTask>,\n private readonly scope: Scope.Closeable,\n ) {}\n\n static async make(\n policy: Omit<TaskQueuePolicy, 'ordering'> & { readonly ordering?: 'fifo' },\n ): Promise<ScopedTaskQueue> {\n if (!Number.isInteger(policy.capacity) || policy.capacity < 1) {\n throw new RangeError('queue capacity must be a positive integer');\n }\n if (!Number.isInteger(policy.concurrency) || policy.concurrency < 1) {\n throw new RangeError('queue concurrency must be a positive integer');\n }\n if (\n policy.rate &&\n (!Number.isInteger(policy.rate.limit) ||\n policy.rate.limit < 1 ||\n !Number.isFinite(policy.rate.intervalMs) ||\n policy.rate.intervalMs <= 0)\n ) {\n throw new RangeError('queue rate limit and interval must be positive');\n }\n\n const normalized: TaskQueuePolicy = { ...policy, ordering: 'fifo' };\n const scope = await Effect.runPromise(Scope.make());\n const queue = await Effect.runPromise(\n normalized.overload === 'dropping'\n ? Queue.dropping<QueuedTask>(normalized.capacity)\n : Queue.bounded<QueuedTask>(normalized.capacity),\n );\n const taskQueue = new ScopedTaskQueue(normalized, queue, scope);\n\n for (let index = 0; index < normalized.concurrency; index += 1) {\n await Effect.runPromise(\n Effect.forkIn(taskQueue.worker(index), scope),\n );\n }\n\n return taskQueue;\n }\n\n submit<A>(operation: () => Promise<A> | A, label?: string): Promise<A> {\n if (this.metricsState.closed) {\n return Promise.reject(new QueueClosedError({ queue: this.policy.name }));\n }\n\n return new Promise<A>((resolve, reject) => {\n const task: QueuedTask = {\n operation: async () => operation(),\n resolve: (value) => resolve(value as A),\n reject,\n enqueuedAt: Date.now(),\n label,\n };\n\n this.metricsState.depth += 1;\n this.notifyObserver();\n void Effect.runPromise(Queue.offer(this.queue, task))\n .then((accepted) => {\n if (!accepted) {\n this.metricsState.depth = Math.max(0, this.metricsState.depth - 1);\n this.metricsState.dropped += 1;\n this.notifyObserver();\n reject(\n new QueueOverloadedError({\n queue: this.policy.name,\n capacity: this.policy.capacity,\n }),\n );\n return;\n }\n })\n .catch((error) => {\n this.metricsState.depth = Math.max(0, this.metricsState.depth - 1);\n this.notifyObserver();\n reject(\n this.metricsState.closed\n ? new QueueClosedError({ queue: this.policy.name })\n : error,\n );\n });\n });\n }\n\n metrics(): TaskQueueMetrics {\n return {\n name: this.policy.name,\n capacity: this.policy.capacity,\n ...this.metricsState,\n };\n }\n\n async close(): Promise<void> {\n if (this.metricsState.closed) return;\n this.metricsState.closed = true;\n this.notifyObserver();\n\n while (true) {\n const result = Queue.takeUnsafe(this.queue);\n if (result === undefined) break;\n if (Exit.isFailure(result)) continue;\n this.metricsState.depth = Math.max(0, this.metricsState.depth - 1);\n result.value.reject(new QueueClosedError({ queue: this.policy.name }));\n this.notifyObserver();\n }\n\n for (const task of this.activeTasks) {\n task.reject(new QueueClosedError({ queue: this.policy.name }));\n }\n this.activeTasks.clear();\n this.metricsState.active = 0;\n this.notifyObserver();\n\n await Effect.runPromise(Queue.shutdown(this.queue));\n await Effect.runPromise(Scope.close(this.scope, Exit.void));\n }\n\n async waitForIdle(): Promise<void> {\n while (this.metricsState.depth > 0 || this.metricsState.active > 0) {\n await Effect.runPromise(Effect.sleep(5));\n }\n }\n\n private worker(index: number) {\n const self = this;\n return Effect.gen(function* () {\n while (true) {\n const task = yield* Queue.take(self.queue);\n self.metricsState.depth = Math.max(0, self.metricsState.depth - 1);\n yield* self.reserveRatePermit();\n self.activeTasks.add(task);\n self.metricsState.active += 1;\n self.notifyObserver();\n\n let execution: Effect.Effect<\n unknown,\n TaskExecutionError | TaskTimeoutError\n > = Effect.tryPromise({\n try: task.operation,\n catch: (cause) =>\n new TaskExecutionError({ queue: self.policy.name, cause }),\n });\n\n if (self.policy.timeoutMs !== undefined) {\n execution = execution.pipe(\n Effect.timeoutOrElse({\n duration: self.policy.timeoutMs,\n orElse: () =>\n Effect.fail(\n new TaskTimeoutError({\n queue: self.policy.name,\n timeoutMs: self.policy.timeoutMs!,\n }),\n ),\n }),\n );\n }\n\n const result = yield* Effect.exit(\n execution.pipe(\n Effect.withSpan(`openwa.queue.${self.policy.name}`, {\n attributes: {\n 'queue.name': self.policy.name,\n 'queue.worker': index,\n 'queue.wait_ms': Date.now() - task.enqueuedAt,\n ...(task.label ? { 'task.label': task.label } : {}),\n },\n }),\n ),\n );\n\n self.activeTasks.delete(task);\n self.metricsState.active = Math.max(0, self.metricsState.active - 1);\n if (Exit.isSuccess(result)) {\n self.metricsState.completed += 1;\n task.resolve(result.value);\n } else {\n self.metricsState.failed += 1;\n task.reject(Cause.squash(result.cause));\n }\n self.notifyObserver();\n\n }\n }).pipe(Effect.withSpan(`openwa.queue.worker.${self.policy.name}`));\n }\n\n private reserveRatePermit(): Effect.Effect<void> {\n const rate = this.policy.rate;\n if (!rate) return Effect.void;\n\n const self = this;\n return Effect.suspend(() => {\n const now = Date.now();\n const elapsed = now - self.rateWindowStartedAt;\n\n if (elapsed >= rate.intervalMs) {\n self.rateWindowStartedAt = now;\n self.rateWindowCount = 0;\n }\n\n if (self.rateWindowCount < rate.limit) {\n self.rateWindowCount += 1;\n return Effect.void;\n }\n\n self.metricsState.rateLimited += 1;\n self.notifyObserver();\n return Effect.sleep(Math.max(1, rate.intervalMs - elapsed)).pipe(\n Effect.andThen(self.reserveRatePermit()),\n );\n });\n }\n\n private notifyObserver(): void {\n try {\n this.policy.observe?.(this.metrics());\n } catch {\n // Metrics must never change queue delivery semantics.\n }\n }\n}\n","import { Effect, Layer, Semaphore } from 'effect';\nimport { SessionAdmissionError } from './errors';\nimport {\n SessionAdmission,\n type SessionAdmissionShape,\n type RuntimeObservabilityShape,\n} from './services';\n\nexport interface SessionAdmissionOptions {\n readonly memoryCapacityMb: number;\n readonly maxConcurrentLaunches: number;\n readonly observability?: RuntimeObservabilityShape;\n}\n\nexport const makeSessionAdmission = (\n options: SessionAdmissionOptions,\n): Effect.Effect<SessionAdmissionShape> =>\n Effect.gen(function* () {\n const memory = yield* Semaphore.make(options.memoryCapacityMb);\n const launches = yield* Semaphore.make(options.maxConcurrentLaunches);\n let activeSessions = 0;\n let availableMemoryMb = options.memoryCapacityMb;\n\n return {\n acquire: (memoryMb) =>\n Effect.gen(function* () {\n if (memoryMb > options.memoryCapacityMb) {\n return yield* Effect.fail(\n new SessionAdmissionError({\n requestedMemoryMb: memoryMb,\n availableMemoryMb,\n }),\n );\n }\n yield* Semaphore.take(memory, memoryMb);\n yield* Semaphore.take(launches, 1);\n availableMemoryMb -= memoryMb;\n activeSessions += 1;\n if (options.observability) {\n yield* options.observability.gauge(\n 'session_browser_memory_mb',\n options.memoryCapacityMb - availableMemoryMb,\n );\n }\n let released = false;\n return {\n memoryMb,\n release: Effect.suspend(() => {\n if (released) return Effect.void;\n released = true;\n activeSessions = Math.max(0, activeSessions - 1);\n availableMemoryMb = Math.min(\n options.memoryCapacityMb,\n availableMemoryMb + memoryMb,\n );\n return Effect.all([\n Semaphore.release(memory, memoryMb),\n Semaphore.release(launches, 1),\n ...(options.observability\n ? [options.observability.gauge(\n 'session_browser_memory_mb',\n options.memoryCapacityMb - availableMemoryMb,\n )]\n : []),\n ]).pipe(Effect.asVoid);\n }),\n };\n }),\n snapshot: Effect.sync(() => ({\n capacityMemoryMb: options.memoryCapacityMb,\n availableMemoryMb,\n activeSessions,\n })),\n };\n });\n\nexport const sessionAdmissionLayer = (options: SessionAdmissionOptions) =>\n Layer.effect(SessionAdmission, makeSessionAdmission(options));\n","import { Effect, Exit, Fiber, Scope } from 'effect';\nimport type { RuntimeObservabilityShape } from './services';\n\nexport type SessionFinalizerReason =\n | 'auth-timeout'\n | 'browser-crash'\n | 'failure'\n | 'interruption'\n | 'normal-stop'\n | 'plugin-failure'\n | 'signal'\n | 'startup-failure';\n\nexport class SessionScope {\n private readonly finalizerNames = new Set<string>();\n private closed = false;\n private activeFibers = 0;\n\n private constructor(\n private readonly scope: Scope.Closeable,\n private readonly observability?: RuntimeObservabilityShape,\n private readonly metricAttributes: Readonly<Record<string, string | number | boolean>> = {},\n ) {}\n\n static async make(options: {\n readonly observability?: RuntimeObservabilityShape;\n readonly metricAttributes?: Readonly<Record<string, string | number | boolean>>;\n } = {}): Promise<SessionScope> {\n return new SessionScope(\n await Effect.runPromise(Scope.make()),\n options.observability,\n options.metricAttributes,\n );\n }\n\n async addFinalizer(\n name: string,\n finalizer: () => void | Promise<void>,\n ): Promise<() => void> {\n if (this.closed) throw new Error('session scope is already closed');\n this.finalizerNames.add(name);\n let active = true;\n await Effect.runPromise(\n Scope.addFinalizer(\n this.scope,\n Effect.promise(async () => {\n if (!active) return;\n active = false;\n this.finalizerNames.delete(name);\n await finalizer();\n }),\n ),\n );\n return () => {\n active = false;\n this.finalizerNames.delete(name);\n };\n }\n\n snapshot() {\n return {\n closed: this.closed,\n finalizers: [...this.finalizerNames],\n } as const;\n }\n\n async fork<A, E>(effect: Effect.Effect<A, E>): Promise<void> {\n if (this.closed) throw new Error('session scope is already closed');\n const fiber = await Effect.runPromise(Effect.forkIn(effect, this.scope));\n this.activeFibers += 1;\n this.publishActiveFibers();\n void Effect.runPromise(Fiber.await(fiber)).finally(() => {\n this.activeFibers = Math.max(0, this.activeFibers - 1);\n this.publishActiveFibers();\n });\n }\n\n async close(reason: SessionFinalizerReason = 'normal-stop'): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n await Effect.runPromise(\n Scope.close(\n this.scope,\n reason === 'normal-stop'\n ? Exit.void\n : Exit.fail(new Error(`session scope closed: ${reason}`)),\n ),\n );\n }\n\n private publishActiveFibers(): void {\n if (!this.observability) return;\n Effect.runSync(this.observability.gauge(\n 'active_fibers',\n this.activeFibers,\n { owner: 'session-scope', ...this.metricAttributes },\n ));\n }\n}\n","import { Effect } from 'effect';\nimport { StartupGraphError } from './errors';\n\nexport interface StartupNode<A = unknown> {\n readonly id: string;\n readonly dependsOn?: ReadonlyArray<string>;\n readonly optional?: boolean;\n readonly run: (\n completed: ReadonlyMap<string, unknown>,\n ) => Effect.Effect<A, unknown>;\n}\n\nexport interface StartupNodeResult {\n readonly id: string;\n readonly durationMs: number;\n readonly optional: boolean;\n readonly result: unknown;\n}\n\nexport interface StartupGraphResult {\n readonly values: ReadonlyMap<string, unknown>;\n readonly phases: ReadonlyArray<StartupNodeResult>;\n readonly criticalPathMs: number;\n}\n\nexport const validateStartupGraph = (\n nodes: ReadonlyArray<StartupNode>,\n): Effect.Effect<void, StartupGraphError> =>\n Effect.try({\n try: () => {\n const ids = new Set(nodes.map((node) => node.id));\n if (ids.size !== nodes.length) {\n throw new StartupGraphError({\n detail: 'startup graph contains duplicate node ids',\n nodes: nodes.map((node) => node.id),\n });\n }\n\n for (const node of nodes) {\n for (const dependency of node.dependsOn ?? []) {\n if (!ids.has(dependency)) {\n throw new StartupGraphError({\n detail: `${node.id} depends on missing node ${dependency}`,\n nodes: [node.id, dependency],\n });\n }\n }\n }\n\n const visiting = new Set<string>();\n const visited = new Set<string>();\n const byId = new Map(nodes.map((node) => [node.id, node]));\n const visit = (id: string) => {\n if (visited.has(id)) return;\n if (visiting.has(id)) {\n throw new StartupGraphError({\n detail: `startup graph contains a cycle at ${id}`,\n nodes: [...visiting, id],\n });\n }\n visiting.add(id);\n for (const dependency of byId.get(id)?.dependsOn ?? []) visit(dependency);\n visiting.delete(id);\n visited.add(id);\n };\n for (const node of nodes) visit(node.id);\n },\n catch: (error) =>\n error instanceof StartupGraphError\n ? error\n : new StartupGraphError({\n detail: String(error),\n nodes: nodes.map((node) => node.id),\n }),\n });\n\nexport const runStartupGraph = (\n nodes: ReadonlyArray<StartupNode>,\n options: {\n readonly concurrency?: number;\n readonly onPhase?: (phase: StartupNodeResult) => void;\n readonly onComplete?: (result: StartupGraphResult) => void;\n } = {},\n): Effect.Effect<StartupGraphResult, unknown> =>\n Effect.gen(function* () {\n yield* validateStartupGraph(nodes);\n const startedAt = Date.now();\n const pending = new Map(nodes.map((node) => [node.id, node]));\n const values = new Map<string, unknown>();\n const phases: StartupNodeResult[] = [];\n\n while (pending.size > 0) {\n const ready = [...pending.values()].filter((node) =>\n (node.dependsOn ?? []).every((dependency) => values.has(dependency)),\n );\n if (ready.length === 0) {\n return yield* Effect.fail(\n new StartupGraphError({\n detail: 'startup graph made no progress',\n nodes: [...pending.keys()],\n }),\n );\n }\n\n const results = yield* Effect.all(\n ready.map((node) =>\n Effect.gen(function* () {\n const nodeStartedAt = Date.now();\n const exit = yield* Effect.exit(\n node.run(values).pipe(\n Effect.withSpan(`openwa.startup.${node.id}`, {\n attributes: {\n 'startup.node': node.id,\n 'startup.optional': node.optional ?? false,\n },\n }),\n ),\n );\n return { node, nodeStartedAt, exit } as const;\n }),\n ),\n { concurrency: options.concurrency ?? 4 },\n );\n\n for (const { node, nodeStartedAt, exit } of results) {\n pending.delete(node.id);\n if (exit._tag === 'Failure') {\n if (!node.optional) return yield* Effect.failCause(exit.cause);\n values.set(node.id, exit);\n phases.push({\n id: node.id,\n durationMs: Date.now() - nodeStartedAt,\n optional: true,\n result: exit,\n });\n options.onPhase?.(phases.at(-1)!);\n continue;\n }\n values.set(node.id, exit.value);\n phases.push({\n id: node.id,\n durationMs: Date.now() - nodeStartedAt,\n optional: node.optional ?? false,\n result: exit.value,\n });\n options.onPhase?.(phases.at(-1)!);\n }\n }\n\n const result = {\n values,\n phases,\n criticalPathMs: Date.now() - startedAt,\n };\n options.onComplete?.(result);\n return result;\n });\n"],"mappings":"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"}
|