@the-open-engine/zeroshot 6.6.0 → 6.7.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.
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ const { DEFAULT_BOUNDS } = require('./profiles');
4
+ const { reportLateFailure, stopEngineWithinBound } = require('./runtime-support');
5
+
6
+ function stopEngine(runtime) {
7
+ runtime.engineStopPromise ||= stopEngineWithinBound({
8
+ engineAdapter: runtime.engineAdapter,
9
+ timers: runtime.timers,
10
+ shutdownMs: runtime.profile?.bounds.shutdownMs || DEFAULT_BOUNDS.shutdownMs,
11
+ });
12
+ return runtime.engineStopPromise;
13
+ }
14
+
15
+ async function releaseEngine(runtime) {
16
+ if (typeof runtime.engineAdapter.close !== 'function') return;
17
+ if (!runtime.engineReleasePromise) {
18
+ try {
19
+ runtime.engineReleasePromise = Promise.resolve(runtime.engineAdapter.close()).then(
20
+ undefined,
21
+ (error) => {
22
+ reportLateFailure(runtime.cleanupFailureReporter, {
23
+ phase: 'engine_release',
24
+ kind: 'cleanup',
25
+ clusterId: runtime.machine.clusterId,
26
+ error,
27
+ });
28
+ }
29
+ );
30
+ } catch (error) {
31
+ reportLateFailure(runtime.cleanupFailureReporter, {
32
+ phase: 'engine_release',
33
+ kind: 'cleanup',
34
+ clusterId: runtime.machine.clusterId,
35
+ error,
36
+ });
37
+ runtime.engineReleasePromise = Promise.resolve();
38
+ }
39
+ }
40
+ await runtime.engineReleasePromise;
41
+ }
42
+
43
+ function waitForEngineCleanup(runtime) {
44
+ if (typeof runtime.engineAdapter.waitForCleanup === 'function') {
45
+ return Promise.resolve().then(() => runtime.engineAdapter.waitForCleanup());
46
+ }
47
+ return runtime.engineReleasePromise || runtime.engineStopPromise || Promise.resolve();
48
+ }
49
+
50
+ module.exports = { releaseEngine, stopEngine, waitForEngineCleanup };
@@ -0,0 +1,298 @@
1
+ 'use strict';
2
+
3
+ const { randomUUID } = require('crypto');
4
+ const { validateArtifactRef, validateWorkerOutcome } = require('./contracts');
5
+ const { createCurrentEngineAdapter } = require('./engine-adapter');
6
+ const { cloneJson, deepFreeze } = require('./object-utils');
7
+ const { createDeploymentProfileRegistry } = require('./profiles');
8
+ const { resolveRunPlan } = require('../run-plan');
9
+
10
+ const CANCELLED = Symbol('cancelled');
11
+
12
+ const DEPENDENCY_KEYS = new Set([
13
+ 'profileRegistry',
14
+ 'artifactResolver',
15
+ 'artifactReceiptSink',
16
+ 'engineAdapter',
17
+ 'clock',
18
+ 'timers',
19
+ 'idFactory',
20
+ 'cleanupFailureReporter',
21
+ ]);
22
+
23
+ async function stageArtifacts(resolver, artifacts, context) {
24
+ if (artifacts.length === 0) return deepFreeze({ artifacts: [] });
25
+ if (context.signal?.aborted) return CANCELLED;
26
+ for (const artifact of artifacts) validateArtifactRef(artifact);
27
+ const stage = typeof resolver === 'function' ? resolver : resolver?.stage;
28
+ if (typeof stage !== 'function') throw new Error('ArtifactResolver must expose stage()');
29
+ const manifest = await runCancellable(
30
+ Promise.resolve().then(() => stage.call(resolver, artifacts, context)),
31
+ {
32
+ signal: context.signal,
33
+ cleanup: (lateManifest) => resolver?.cleanup?.(lateManifest, context),
34
+ reportFailure: context.reportCleanupFailure,
35
+ failureContext: { phase: 'artifact_staging', clusterId: context.clusterId },
36
+ }
37
+ );
38
+ if (manifest === CANCELLED) return CANCELLED;
39
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
40
+ throw new Error('ArtifactResolver returned an invalid staged manifest');
41
+ }
42
+ return deepFreeze(manifest);
43
+ }
44
+
45
+ function createArtifactPreparation({
46
+ resolver,
47
+ artifacts,
48
+ clusterId,
49
+ getProfile,
50
+ signal,
51
+ reportFailure,
52
+ }) {
53
+ return async (isolation) => {
54
+ const manifest = await stageArtifacts(resolver, artifacts, {
55
+ clusterId,
56
+ profile: getProfile(),
57
+ isolation,
58
+ signal,
59
+ reportCleanupFailure: reportFailure,
60
+ });
61
+ if (manifest === CANCELLED) throw new Error('Artifact staging was cancelled');
62
+ return manifest;
63
+ };
64
+ }
65
+
66
+ function defaultCleanupFailureReporter(failure) {
67
+ const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
68
+ const cluster = failure.clusterId ? ` for cluster ${failure.clusterId}` : '';
69
+ process.emitWarning(
70
+ `Late ${failure.phase} ${failure.kind} failed${cluster}: ${message}`,
71
+ 'ZeroshotCleanupWarning',
72
+ 'ZEROSHOT_LEGACY_CLEANUP_FAILURE'
73
+ );
74
+ }
75
+
76
+ function reportReporterFailure(failure, reporterError) {
77
+ const original = failure.error instanceof Error ? failure.error.message : String(failure.error);
78
+ const reporting = reporterError instanceof Error ? reporterError.message : String(reporterError);
79
+ defaultCleanupFailureReporter({
80
+ ...failure,
81
+ error: new Error(`${original}; cleanup failure reporter also failed: ${reporting}`),
82
+ });
83
+ }
84
+
85
+ function reportLateFailure(reporter, failure) {
86
+ const selected = typeof reporter === 'function' ? reporter : defaultCleanupFailureReporter;
87
+ try {
88
+ const result = selected(Object.freeze(failure));
89
+ if (result && typeof result.then === 'function') {
90
+ result.then(undefined, (reporterError) => reportReporterFailure(failure, reporterError));
91
+ }
92
+ } catch (reporterError) {
93
+ reportReporterFailure(failure, reporterError);
94
+ }
95
+ }
96
+
97
+ async function observeCancelledOperation(operation, { cleanup, reportFailure, failureContext }) {
98
+ let lateValue;
99
+ try {
100
+ lateValue = await operation;
101
+ } catch (error) {
102
+ reportLateFailure(reportFailure, { ...failureContext, kind: 'operation', error });
103
+ return;
104
+ }
105
+ if (typeof cleanup !== 'function') return;
106
+ try {
107
+ await cleanup(lateValue);
108
+ } catch (error) {
109
+ reportLateFailure(reportFailure, { ...failureContext, kind: 'cleanup', error });
110
+ }
111
+ }
112
+
113
+ function observeLateSettlement(operation, options) {
114
+ observeCancelledOperation(operation, options).then(undefined, (error) =>
115
+ reportLateFailure(options.reportFailure, {
116
+ ...options.failureContext,
117
+ kind: 'operation',
118
+ error,
119
+ })
120
+ );
121
+ }
122
+ async function runCancellable(
123
+ value,
124
+ { signal, cleanup, reportFailure, failureContext = { phase: 'cancelled_operation' } } = {}
125
+ ) {
126
+ const operation = Promise.resolve(value);
127
+ if (!signal) return operation;
128
+ if (signal.aborted) {
129
+ observeLateSettlement(operation, { cleanup, reportFailure, failureContext });
130
+ return CANCELLED;
131
+ }
132
+
133
+ let removeAbortListener = () => {};
134
+ const cancellation = new Promise((resolve) => {
135
+ const onAbort = () => resolve(CANCELLED);
136
+ signal.addEventListener('abort', onAbort, { once: true });
137
+ removeAbortListener = () => signal.removeEventListener('abort', onAbort);
138
+ });
139
+ const completed = operation.then((resolved) => ({ resolved }));
140
+ const winner = await Promise.race([completed, cancellation]);
141
+ removeAbortListener();
142
+ if (winner === CANCELLED) {
143
+ observeLateSettlement(operation, { cleanup, reportFailure, failureContext });
144
+ return CANCELLED;
145
+ }
146
+ return winner.resolved;
147
+ }
148
+
149
+ async function collectReceipts(sink, declared, context) {
150
+ if (!sink) return [];
151
+ if (context.signal?.aborted) return CANCELLED;
152
+ const collect = typeof sink === 'function' ? sink : sink.collect || sink.write;
153
+ if (typeof collect !== 'function') throw new Error('ArtifactReceiptSink must expose collect()');
154
+ const receipts = await runCancellable(
155
+ Promise.resolve().then(() => collect.call(sink, declared || [], context)),
156
+ {
157
+ signal: context.signal,
158
+ cleanup: (lateReceipts) => sink?.cleanup?.(lateReceipts, context),
159
+ reportFailure: context.reportCleanupFailure,
160
+ failureContext: { phase: 'receipt_collection', clusterId: context.clusterId },
161
+ }
162
+ );
163
+ if (receipts === CANCELLED) return CANCELLED;
164
+ if (!Array.isArray(receipts)) throw new Error('ArtifactReceiptSink must return an array');
165
+ for (const receipt of receipts) validateArtifactRef(receipt);
166
+ return receipts.map((receipt) => deepFreeze(cloneJson(receipt)));
167
+ }
168
+
169
+ function errorOutcome(code, reason) {
170
+ const outcome = { status: 'error', code, reason };
171
+ validateWorkerOutcome(outcome);
172
+ return Object.freeze(outcome);
173
+ }
174
+
175
+ async function stopEngineWithinBound({ engineAdapter, timers, shutdownMs }) {
176
+ let deadlineHandle = null;
177
+ let effective = false;
178
+ try {
179
+ const engineStop = Promise.resolve()
180
+ .then(() => engineAdapter.stop())
181
+ .then(
182
+ (stopResult) => stopResult?.effective !== false,
183
+ () => false
184
+ );
185
+ const deadline = new Promise((resolve) => {
186
+ deadlineHandle = timers.setTimeout(() => resolve(false), shutdownMs);
187
+ });
188
+ effective = await Promise.race([engineStop, deadline]);
189
+ } catch {
190
+ effective = false;
191
+ }
192
+ if (deadlineHandle !== null) {
193
+ try {
194
+ timers.clearTimeout(deadlineHandle);
195
+ } catch {
196
+ effective = false;
197
+ }
198
+ }
199
+ return effective;
200
+ }
201
+
202
+ function resolveRuntimeDependencies(dependencies) {
203
+ const unknownDependencies = Object.keys(dependencies).filter((key) => !DEPENDENCY_KEYS.has(key));
204
+ if (unknownDependencies.length > 0) {
205
+ throw new Error(`Unsupported worker dependencies: ${unknownDependencies.join(', ')}`);
206
+ }
207
+ if (
208
+ dependencies.cleanupFailureReporter !== undefined &&
209
+ typeof dependencies.cleanupFailureReporter !== 'function'
210
+ ) {
211
+ throw new Error('cleanupFailureReporter must be a function');
212
+ }
213
+ return {
214
+ profileRegistry: dependencies.profileRegistry || createDeploymentProfileRegistry(),
215
+ artifactResolver: dependencies.artifactResolver || null,
216
+ artifactReceiptSink: dependencies.artifactReceiptSink || null,
217
+ engineAdapter: dependencies.engineAdapter || createCurrentEngineAdapter(),
218
+ clock: dependencies.clock || (() => Date.now()),
219
+ timers: dependencies.timers || { setTimeout, clearTimeout },
220
+ idFactory: dependencies.idFactory || (() => `legacy-worker-${randomUUID()}`),
221
+ cleanupFailureReporter: dependencies.cleanupFailureReporter || defaultCleanupFailureReporter,
222
+ };
223
+ }
224
+
225
+ function assertProfilePlan(profile) {
226
+ if (!profile?.plan || !Object.isFrozen(profile.plan)) {
227
+ throw new Error('DeploymentProfileRegistry must return a frozen canonical plan');
228
+ }
229
+ if (profile.plan.isolation !== 'worktree' && profile.plan.isolation !== 'docker') {
230
+ throw new Error('Deployment profile resolved to non-isolated execution');
231
+ }
232
+ }
233
+
234
+ function assertFrozenProfile(profile) {
235
+ if (
236
+ !Object.isFrozen(profile) ||
237
+ !Object.isFrozen(profile.deployment) ||
238
+ !Object.isFrozen(profile.provider) ||
239
+ !Object.isFrozen(profile.bounds)
240
+ ) {
241
+ throw new Error('DeploymentProfileRegistry must return a frozen deployment descriptor');
242
+ }
243
+ }
244
+
245
+ function assertMatchingProfileHandles(profile, request) {
246
+ if (
247
+ profile.isolationProfile !== request.isolationProfile ||
248
+ profile.providerProfile !== request.providerProfile
249
+ ) {
250
+ throw new Error('DeploymentProfileRegistry returned mismatched profile handles');
251
+ }
252
+ }
253
+
254
+ function assertCanonicalPlan(plan) {
255
+ const canonical = resolveRunPlan({
256
+ docker: plan.isolation === 'docker',
257
+ worktree: plan.isolation === 'worktree',
258
+ pr: plan.delivery === 'pr',
259
+ ship: plan.delivery === 'ship',
260
+ });
261
+ if (
262
+ canonical.isolation !== plan.isolation ||
263
+ canonical.delivery !== plan.delivery ||
264
+ canonical.autoMerge !== plan.autoMerge
265
+ ) {
266
+ throw new Error('DeploymentProfileRegistry returned a non-canonical run plan');
267
+ }
268
+ }
269
+
270
+ function assertProfileBounds(bounds) {
271
+ for (const name of ['executionMs', 'shutdownMs', 'frameBytes']) {
272
+ if (!Number.isSafeInteger(bounds[name]) || bounds[name] <= 0) {
273
+ throw new Error(`Deployment profile bound ${name} must be a positive safe integer`);
274
+ }
275
+ }
276
+ }
277
+
278
+ function assertIsolatedProfile(profile, request) {
279
+ assertProfilePlan(profile);
280
+ assertFrozenProfile(profile);
281
+ assertMatchingProfileHandles(profile, request);
282
+ assertCanonicalPlan(profile.plan);
283
+ assertProfileBounds(profile.bounds);
284
+ return profile;
285
+ }
286
+
287
+ module.exports = {
288
+ CANCELLED,
289
+ assertIsolatedProfile,
290
+ collectReceipts,
291
+ createArtifactPreparation,
292
+ errorOutcome,
293
+ reportLateFailure,
294
+ resolveRuntimeDependencies,
295
+ runCancellable,
296
+ stageArtifacts,
297
+ stopEngineWithinBound,
298
+ };
@@ -0,0 +1,147 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ TERMINAL_STATES,
5
+ validateLifecycleStatus,
6
+ validateTerminalReceipt,
7
+ } = require('./contracts');
8
+ const { deepFreeze } = require('./object-utils');
9
+
10
+ const ALLOWED = Object.freeze({
11
+ idle: new Set(['starting']),
12
+ starting: new Set([
13
+ 'running',
14
+ 'stopping',
15
+ 'completed',
16
+ 'failed',
17
+ 'timed_out',
18
+ 'stopped',
19
+ 'malformed',
20
+ ]),
21
+ running: new Set(['stopping', 'completed', 'failed', 'timed_out', 'stopped', 'malformed']),
22
+ stopping: new Set(['stopped', 'completed', 'failed', 'timed_out', 'malformed']),
23
+ });
24
+
25
+ class LiveEventIterator {
26
+ constructor(remove) {
27
+ this.remove = remove;
28
+ this.queue = [];
29
+ this.waiters = [];
30
+ this.closed = false;
31
+ }
32
+
33
+ push(value) {
34
+ if (this.closed) return;
35
+ const waiter = this.waiters.shift();
36
+ if (waiter) waiter({ value, done: false });
37
+ else this.queue.push(value);
38
+ }
39
+
40
+ next() {
41
+ if (this.queue.length > 0) return Promise.resolve({ value: this.queue.shift(), done: false });
42
+ if (this.closed) return Promise.resolve({ value: undefined, done: true });
43
+ return new Promise((resolve) => this.waiters.push(resolve));
44
+ }
45
+
46
+ return() {
47
+ if (!this.closed) {
48
+ this.closed = true;
49
+ this.remove();
50
+ for (const waiter of this.waiters.splice(0)) waiter({ value: undefined, done: true });
51
+ }
52
+ return Promise.resolve({ value: undefined, done: true });
53
+ }
54
+
55
+ close() {
56
+ if (this.closed) return;
57
+ this.closed = true;
58
+ this.remove();
59
+ for (const waiter of this.waiters.splice(0)) waiter({ value: undefined, done: true });
60
+ }
61
+
62
+ [Symbol.asyncIterator]() {
63
+ return this;
64
+ }
65
+ }
66
+
67
+ class LegacyWorkerStateMachine {
68
+ constructor({ clock = () => Date.now() } = {}) {
69
+ this.clock = clock;
70
+ this.state = 'idle';
71
+ this.clusterId = null;
72
+ this.sequence = 0;
73
+ this.stopRequested = false;
74
+ this.terminalReceipt = null;
75
+ this.subscribers = new Set();
76
+ this.resultPromise = new Promise((resolve) => {
77
+ this.resolveResult = resolve;
78
+ });
79
+ }
80
+
81
+ setClusterId(clusterId) {
82
+ if (this.clusterId && this.clusterId !== clusterId) {
83
+ throw new Error('Worker already owns a different cluster resource');
84
+ }
85
+ this.clusterId = clusterId;
86
+ }
87
+
88
+ status() {
89
+ return validateLifecycleStatus({
90
+ state: this.state,
91
+ clusterId: this.clusterId,
92
+ sequence: this.sequence,
93
+ stopRequested: this.stopRequested,
94
+ terminal: TERMINAL_STATES.includes(this.state),
95
+ });
96
+ }
97
+
98
+ events() {
99
+ let iterator;
100
+ iterator = new LiveEventIterator(() => this.subscribers.delete(iterator));
101
+ this.subscribers.add(iterator);
102
+ if (this.terminalReceipt) iterator.close();
103
+ return iterator;
104
+ }
105
+
106
+ transition(next, details) {
107
+ if (TERMINAL_STATES.includes(this.state)) return false;
108
+ const allowed = ALLOWED[this.state];
109
+ if (!allowed?.has(next))
110
+ throw new Error(`Invalid lifecycle transition ${this.state} -> ${next}`);
111
+ this.state = next;
112
+ this.sequence += 1;
113
+ const event = deepFreeze({
114
+ sequence: this.sequence,
115
+ state: next,
116
+ at: this.clock(),
117
+ ...(details === undefined ? {} : { details }),
118
+ });
119
+ for (const subscriber of this.subscribers) subscriber.push(event);
120
+ return true;
121
+ }
122
+
123
+ requestStop() {
124
+ this.stopRequested = true;
125
+ if (this.state === 'starting' || this.state === 'running') this.transition('stopping');
126
+ }
127
+
128
+ terminal(receipt) {
129
+ if (this.terminalReceipt) return false;
130
+ validateTerminalReceipt(receipt);
131
+ if (receipt.clusterId !== this.clusterId) {
132
+ throw new Error('Terminal receipt clusterId does not match owned resource');
133
+ }
134
+ const frozenReceipt = deepFreeze(receipt);
135
+ this.transition(receipt.state);
136
+ this.terminalReceipt = frozenReceipt;
137
+ this.resolveResult(this.terminalReceipt);
138
+ for (const subscriber of [...this.subscribers]) subscriber.close();
139
+ return true;
140
+ }
141
+
142
+ result() {
143
+ return this.resultPromise;
144
+ }
145
+ }
146
+
147
+ module.exports = { LegacyWorkerStateMachine };
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ const { validateLegacyShipResult, validateWorkerOutcome } = require('./contracts');
4
+ const { cloneJson, deepFreeze } = require('./object-utils');
5
+ const { collectReceipts } = require('./runtime-support');
6
+
7
+ async function resultFromEvent(event, options) {
8
+ let summary;
9
+ let status = 'succeeded';
10
+ let declaredArtifacts = event.artifacts;
11
+ if (Object.prototype.hasOwnProperty.call(event, 'result')) {
12
+ validateLegacyShipResult(event.result);
13
+ summary = event.result.summary;
14
+ status = event.result.status;
15
+ declaredArtifacts = event.result.artifacts;
16
+ } else {
17
+ summary = event.summary;
18
+ validateLegacyShipResult({ summary, status, artifacts: [] });
19
+ }
20
+ const artifacts = await collectReceipts(options.artifactReceiptSink, declaredArtifacts, {
21
+ clusterId: options.machine.clusterId,
22
+ profile: options.getProfile(),
23
+ signal: options.signal,
24
+ reportCleanupFailure: options.cleanupFailureReporter,
25
+ });
26
+ if (artifacts === options.cancelled) return options.cancelled;
27
+ const result = { summary, status, artifacts };
28
+ validateLegacyShipResult(result);
29
+ return result;
30
+ }
31
+
32
+ function createTerminalNormalizer(options) {
33
+ let completionPending = false;
34
+
35
+ function terminalFailure(authority, state, code, reason) {
36
+ if (options.claimTerminalAuthority(authority)) {
37
+ options.machine.terminal(options.failureReceipt(state, code, reason));
38
+ }
39
+ }
40
+
41
+ async function normalizeCompletion(event) {
42
+ try {
43
+ const result = await Promise.race([resultFromEvent(event, options), options.terminalClaimed]);
44
+ if (result === options.cancelled) return;
45
+ if (options.claimTerminalAuthority('engine:complete')) {
46
+ options.machine.terminal({
47
+ state: 'completed',
48
+ clusterId: options.machine.clusterId,
49
+ finishedAt: options.clock(),
50
+ result: deepFreeze(cloneJson(result)),
51
+ });
52
+ }
53
+ } catch {
54
+ terminalFailure('engine:complete:malformed', 'malformed', 'malformed', 'malformed_result');
55
+ } finally {
56
+ completionPending = false;
57
+ }
58
+ }
59
+
60
+ function normalizeFailure(event) {
61
+ try {
62
+ validateWorkerOutcome({ status: 'error', code: event.code, reason: event.reason });
63
+ terminalFailure('engine:failed', 'failed', event.code, event.reason);
64
+ } catch {
65
+ terminalFailure('engine:failed:malformed', 'malformed', 'malformed', 'malformed_result');
66
+ }
67
+ }
68
+
69
+ function beginCompletion(event) {
70
+ if (completionPending) return;
71
+ completionPending = true;
72
+ normalizeCompletion(event).catch(() => undefined);
73
+ }
74
+
75
+ const terminalHandlers = {
76
+ failed: normalizeFailure,
77
+ malformed() {
78
+ terminalFailure('engine:malformed', 'malformed', 'malformed', 'malformed_result');
79
+ },
80
+ };
81
+
82
+ return function onEngineEvent(event) {
83
+ if (!event || typeof event !== 'object' || Array.isArray(event)) return;
84
+ if (event.type === 'running') {
85
+ if (options.machine.state === 'starting') options.machine.transition('running');
86
+ return;
87
+ }
88
+ if (options.isTerminal()) return;
89
+ if (event.type === 'complete') return beginCompletion(event);
90
+ if (completionPending) return;
91
+ terminalHandlers[event.type]?.(event);
92
+ };
93
+ }
94
+
95
+ module.exports = { createTerminalNormalizer };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const { releaseEngine, waitForEngineCleanup } = require('./runtime-engine');
4
+
5
+ const RELEASE_WORKER = Symbol('releaseLegacyClusterWorker');
6
+ const WAIT_FOR_WORKER_CLEANUP = Symbol('waitForLegacyClusterWorkerCleanup');
7
+
8
+ function createWorkerFacade(runtime) {
9
+ return Object.freeze({
10
+ start: runtime.start.bind(runtime),
11
+ status: runtime.status.bind(runtime),
12
+ events: runtime.events.bind(runtime),
13
+ stop: runtime.stop.bind(runtime),
14
+ result: runtime.result.bind(runtime),
15
+ [RELEASE_WORKER]: () => releaseEngine(runtime),
16
+ [WAIT_FOR_WORKER_CLEANUP]: () => waitForEngineCleanup(runtime),
17
+ });
18
+ }
19
+
20
+ module.exports = { RELEASE_WORKER, WAIT_FOR_WORKER_CLEANUP, createWorkerFacade };