@opengeni/api-router 0.5.2 → 0.5.4
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/dist/app.js +1 -1
- package/dist/{chunk-YY6OAEL6.js → chunk-DO2G3JSB.js} +5333 -2205
- package/dist/chunk-DO2G3JSB.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +297 -54
- package/dist/index.js.map +1 -1
- package/package.json +21 -21
- package/src/app.ts +415 -147
- package/src/auth/managed-auth.ts +32 -16
- package/src/http/auth.ts +8 -1
- package/src/http/common.ts +6 -2
- package/src/http/sse.ts +27 -6
- package/src/index.ts +196 -74
- package/src/integrations/oauth-client.ts +592 -131
- package/src/integrations/provider-domain.ts +4 -1
- package/src/mcp/documents.ts +173 -94
- package/src/mcp/server.ts +1517 -692
- package/src/mcp/session-view.ts +8 -2
- package/src/mcp/toolspace.ts +175 -84
- package/src/observability.ts +7 -1
- package/src/routes/api-keys.ts +39 -23
- package/src/routes/billing.ts +180 -65
- package/src/routes/capabilities.ts +17 -8
- package/src/routes/catalog-assets.ts +5 -2
- package/src/routes/codex.ts +244 -63
- package/src/routes/connections.ts +72 -34
- package/src/routes/documents.ts +242 -92
- package/src/routes/enrollments.ts +100 -70
- package/src/routes/environments.ts +205 -136
- package/src/routes/files.ts +164 -39
- package/src/routes/github.ts +123 -50
- package/src/routes/install.ts +9 -2
- package/src/routes/machines.ts +9 -8
- package/src/routes/packs.ts +141 -89
- package/src/routes/rigs.ts +189 -0
- package/src/routes/scheduled-tasks.ts +51 -9
- package/src/routes/sessions.ts +839 -329
- package/src/routes/social.ts +50 -38
- package/src/routes/workspace-capture.ts +238 -0
- package/src/routes/workspaces.ts +159 -13
- package/src/sandbox/access.ts +11 -3
- package/src/sandbox/auth-callout.ts +5 -1
- package/src/sandbox/channel-a.ts +104 -27
- package/src/sandbox/enrollment.ts +13 -3
- package/src/sandbox/machines.ts +68 -59
- package/src/sandbox/metrics-ingestion.ts +238 -17
- package/src/sandbox/viewer.ts +172 -46
- package/dist/chunk-YY6OAEL6.js.map +0 -1
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
// re-surfaced. Consuming the Hello's `capabilities.desktop` / `display` makes
|
|
22
22
|
// `has_display` track reality (both directions), which the desktop-capability
|
|
23
23
|
// gate (packages/runtime capabilities.ts) keys off.
|
|
24
|
+
// refreshEnrollmentOpStream — reconcile `enrollments.op_stream` to the LIVE
|
|
25
|
+
// runner capability the Hello reports, leaving legacy request/reply exec as the
|
|
26
|
+
// fallback unless the runner advertises the streaming engine.
|
|
24
27
|
//
|
|
25
28
|
// Both consumers are BEST-EFFORT and fail-soft: a decode/DB error for one message
|
|
26
29
|
// is logged + swallowed (the bus subscription already swallows handler throws) so
|
|
@@ -28,16 +31,27 @@
|
|
|
28
31
|
// back-pressures the agent, or breaks its connect.
|
|
29
32
|
|
|
30
33
|
import {
|
|
34
|
+
clearEnrollmentWentOffline,
|
|
31
35
|
getEnrollment,
|
|
32
36
|
ingestMachineMetricsSample,
|
|
37
|
+
sessionsWithActiveOpOnEnrollment,
|
|
33
38
|
setEnrollmentDisplayState,
|
|
39
|
+
setEnrollmentOpStreamState,
|
|
40
|
+
setEnrollmentWentOffline,
|
|
34
41
|
touchEnrollmentLastSeen,
|
|
42
|
+
type AppendEventInput,
|
|
35
43
|
type Database,
|
|
36
44
|
type MachineMetricsSample,
|
|
37
45
|
} from "@opengeni/db";
|
|
38
|
-
import type
|
|
46
|
+
import { appendAndPublishEvents, type EventBus } from "@opengeni/events";
|
|
39
47
|
import type { Observability } from "@opengeni/observability";
|
|
40
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
AgentEvent,
|
|
50
|
+
GoingOfflineReason,
|
|
51
|
+
Hello,
|
|
52
|
+
goingOfflineReasonToJSON,
|
|
53
|
+
type MetricsSample,
|
|
54
|
+
} from "@opengeni/agent-proto";
|
|
41
55
|
|
|
42
56
|
/** The wildcard subject the agent event plane publishes heartbeats on. */
|
|
43
57
|
export const AGENT_EVENTS_SUBJECT = "agent.*.*.events";
|
|
@@ -50,7 +64,10 @@ export const AGENT_HELLO_SUBJECT = "agent.*.*.hello";
|
|
|
50
64
|
* expected tail token. Returns null for a subject that does not match the shape
|
|
51
65
|
* (defensive — the subscription pattern already constrains it).
|
|
52
66
|
*/
|
|
53
|
-
function parseAgentSubject(
|
|
67
|
+
function parseAgentSubject(
|
|
68
|
+
subject: string,
|
|
69
|
+
tail: "events" | "hello",
|
|
70
|
+
): { workspaceId: string; agentId: string } | null {
|
|
54
71
|
const parts = subject.split(".");
|
|
55
72
|
if (parts.length !== 4 || parts[0] !== "agent" || parts[3] !== tail) {
|
|
56
73
|
return null;
|
|
@@ -59,12 +76,16 @@ function parseAgentSubject(subject: string, tail: "events" | "hello"): { workspa
|
|
|
59
76
|
}
|
|
60
77
|
|
|
61
78
|
/** Parse `agent.<ws>.<id>.events` → `{ workspaceId, agentId }` (heartbeat plane). */
|
|
62
|
-
export function parseAgentEventSubject(
|
|
79
|
+
export function parseAgentEventSubject(
|
|
80
|
+
subject: string,
|
|
81
|
+
): { workspaceId: string; agentId: string } | null {
|
|
63
82
|
return parseAgentSubject(subject, "events");
|
|
64
83
|
}
|
|
65
84
|
|
|
66
85
|
/** Parse `agent.<ws>.<id>.hello` → `{ workspaceId, agentId }` (connect plane). */
|
|
67
|
-
export function parseAgentHelloSubject(
|
|
86
|
+
export function parseAgentHelloSubject(
|
|
87
|
+
subject: string,
|
|
88
|
+
): { workspaceId: string; agentId: string } | null {
|
|
68
89
|
return parseAgentSubject(subject, "hello");
|
|
69
90
|
}
|
|
70
91
|
|
|
@@ -96,7 +117,10 @@ export function wireSampleToDbSample(wire: MetricsSample): MachineMetricsSample
|
|
|
96
117
|
contention: wire.runQueue,
|
|
97
118
|
// The sample carries its own wall-clock stamp (epoch ms); fall back to now on
|
|
98
119
|
// a missing/zero stamp so a series row is never NULL-dated.
|
|
99
|
-
sampledAt:
|
|
120
|
+
sampledAt:
|
|
121
|
+
wire.sampledAtMs && Number(wire.sampledAtMs) > 0
|
|
122
|
+
? new Date(Number(wire.sampledAtMs))
|
|
123
|
+
: new Date(),
|
|
100
124
|
};
|
|
101
125
|
}
|
|
102
126
|
|
|
@@ -132,16 +156,61 @@ export async function ingestHeartbeat(
|
|
|
132
156
|
return { ingested: true, seriesAppended: result.seriesAppended };
|
|
133
157
|
}
|
|
134
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Fan out one or more machine-LINK session events to the sessions that had an
|
|
161
|
+
* active op running on the machine when its control link changed (per
|
|
162
|
+
* `sessionsWithActiveOpOnEnrollment`) — the announce-only failure-visibility
|
|
163
|
+
* plane. Each session's events are stamped on its OWN active turn. No matching
|
|
164
|
+
* session ⇒ nothing is emitted (an idle-machine blip must never spam idle /
|
|
165
|
+
* historical sessions). Called best-effort inside the handlers' fail-soft blocks.
|
|
166
|
+
*
|
|
167
|
+
* Each session's emission is ISOLATED: one session's append failing (a
|
|
168
|
+
* session-specific constraint like a sequence collision from a racing writer, a
|
|
169
|
+
* transient write error) is logged with that sessionId and skipped, never
|
|
170
|
+
* aborting the fan-out — one session's failure must never cost the OTHER matching
|
|
171
|
+
* sessions their events. A partial fan-out stays visible per-session in the logs.
|
|
172
|
+
*/
|
|
173
|
+
async function fanOutMachineLinkEvents(
|
|
174
|
+
db: Database,
|
|
175
|
+
bus: EventBus,
|
|
176
|
+
observability: Observability | undefined,
|
|
177
|
+
workspaceId: string,
|
|
178
|
+
enrollmentId: string,
|
|
179
|
+
build: (activeTurnId: string) => AppendEventInput[],
|
|
180
|
+
): Promise<void> {
|
|
181
|
+
const sessions = await sessionsWithActiveOpOnEnrollment(db, { workspaceId, enrollmentId });
|
|
182
|
+
for (const session of sessions) {
|
|
183
|
+
try {
|
|
184
|
+
await appendAndPublishEvents(
|
|
185
|
+
db,
|
|
186
|
+
bus,
|
|
187
|
+
workspaceId,
|
|
188
|
+
session.sessionId,
|
|
189
|
+
build(session.activeTurnId),
|
|
190
|
+
);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
observability?.warn?.("Failed to fan out a machine-link event to a session", {
|
|
193
|
+
workspaceId,
|
|
194
|
+
sessionId: session.sessionId,
|
|
195
|
+
error: error instanceof Error ? error.message : String(error),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
135
201
|
/**
|
|
136
202
|
* Decode a raw `AgentEvent` payload + ingest it (the per-message handler). A
|
|
137
|
-
* heartbeat carrying a metrics sample is ingested; a going-offline
|
|
138
|
-
*
|
|
203
|
+
* heartbeat carrying a metrics sample is ingested; a going-offline records the
|
|
204
|
+
* machine-plane marker + fans out the link-plane session events. Decode failures
|
|
205
|
+
* are reported + swallowed. `bus` (when present) enables the session-event
|
|
206
|
+
* fan-out; the live consumer always supplies it, pure unit tests may omit it.
|
|
139
207
|
*/
|
|
140
208
|
export async function handleAgentEventPayload(
|
|
141
209
|
db: Database,
|
|
142
210
|
observability: Observability | undefined,
|
|
143
211
|
payload: Uint8Array,
|
|
144
212
|
subject: string,
|
|
213
|
+
bus?: EventBus,
|
|
145
214
|
): Promise<void> {
|
|
146
215
|
const ids = parseAgentEventSubject(subject);
|
|
147
216
|
if (!ids) {
|
|
@@ -157,15 +226,84 @@ export async function handleAgentEventPayload(
|
|
|
157
226
|
});
|
|
158
227
|
return;
|
|
159
228
|
}
|
|
229
|
+
// A clean GoingOffline is the machine-plane's typed shutdown signal. Two things
|
|
230
|
+
// happen, in this order:
|
|
231
|
+
// 1. Record it ALWAYS on the machine plane (a Prometheus counter keyed by the
|
|
232
|
+
// typed reason) so a fleet operator can see clean stops / self-updates /
|
|
233
|
+
// host shutdowns. This fires unconditionally, independent of the DB.
|
|
234
|
+
// 2. Stamp the enrollment's clean going-offline marker so the liveness
|
|
235
|
+
// derivation reads the machine OFFLINE immediately instead of waiting out
|
|
236
|
+
// the last_seen dead-detect window. Best-effort + fail-soft (like the rest
|
|
237
|
+
// of this module): an unknown enrollment is a no-op and a DB error is
|
|
238
|
+
// swallowed so a bad write never tears down the consumer. Deliberately does
|
|
239
|
+
// NOT touch last-seen (a shutdown must not look "more recently alive").
|
|
240
|
+
if (event.event?.$case === "goingOffline") {
|
|
241
|
+
const reason = goingOfflineReasonToJSON(event.event.goingOffline.reason);
|
|
242
|
+
observability?.incrementCounter({
|
|
243
|
+
name: "opengeni_machine_going_offline_total",
|
|
244
|
+
help: "Total Connected Machine clean GoingOffline signals by typed reason.",
|
|
245
|
+
labels: { reason },
|
|
246
|
+
});
|
|
247
|
+
try {
|
|
248
|
+
const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId);
|
|
249
|
+
if (enrollment) {
|
|
250
|
+
await setEnrollmentWentOffline(db, {
|
|
251
|
+
accountId: enrollment.accountId,
|
|
252
|
+
workspaceId: ids.workspaceId,
|
|
253
|
+
enrollmentId: ids.agentId,
|
|
254
|
+
reason,
|
|
255
|
+
});
|
|
256
|
+
// Fan out the link-plane events to the sessions with an active op on this
|
|
257
|
+
// machine: machine.link.lost (its control link is going away) for every
|
|
258
|
+
// clean going-offline, PLUS machine.runner.restarted when the reason is a
|
|
259
|
+
// self-update restart specifically (link.lost fires for it too; this
|
|
260
|
+
// distinguishes a restart from a plain stop / host shutdown).
|
|
261
|
+
if (bus) {
|
|
262
|
+
const isSelfUpdate =
|
|
263
|
+
event.event.goingOffline.reason === GoingOfflineReason.GOING_OFFLINE_REASON_UPDATE;
|
|
264
|
+
await fanOutMachineLinkEvents(
|
|
265
|
+
db,
|
|
266
|
+
bus,
|
|
267
|
+
observability,
|
|
268
|
+
ids.workspaceId,
|
|
269
|
+
ids.agentId,
|
|
270
|
+
(activeTurnId) => {
|
|
271
|
+
const events: AppendEventInput[] = [
|
|
272
|
+
{ type: "machine.link.lost", turnId: activeTurnId, payload: { reason } },
|
|
273
|
+
];
|
|
274
|
+
if (isSelfUpdate) {
|
|
275
|
+
events.push({
|
|
276
|
+
type: "machine.runner.restarted",
|
|
277
|
+
turnId: activeTurnId,
|
|
278
|
+
payload: {},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return events;
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
observability?.warn?.("Failed to record a machine clean going-offline", {
|
|
288
|
+
subject,
|
|
289
|
+
error: error instanceof Error ? error.message : String(error),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
160
294
|
if (event.event?.$case !== "heartbeat") {
|
|
161
|
-
return; //
|
|
295
|
+
return; // an unknown event kind → not a metrics point.
|
|
162
296
|
}
|
|
163
297
|
const metrics = event.event.heartbeat.metrics;
|
|
164
298
|
if (!metrics) {
|
|
165
299
|
return; // a heartbeat without a sample → liveness already touched elsewhere.
|
|
166
300
|
}
|
|
167
301
|
try {
|
|
168
|
-
await ingestHeartbeat(db, {
|
|
302
|
+
await ingestHeartbeat(db, {
|
|
303
|
+
workspaceId: ids.workspaceId,
|
|
304
|
+
agentId: ids.agentId,
|
|
305
|
+
sample: metrics,
|
|
306
|
+
});
|
|
169
307
|
} catch (error) {
|
|
170
308
|
observability?.warn?.("Failed to ingest a machine metrics heartbeat", {
|
|
171
309
|
subject,
|
|
@@ -185,7 +323,7 @@ export function startMetricsIngestion(deps: {
|
|
|
185
323
|
observability?: Observability;
|
|
186
324
|
}): () => void {
|
|
187
325
|
return deps.bus.subscribeAgentEvents(AGENT_EVENTS_SUBJECT, (payload, subject) =>
|
|
188
|
-
handleAgentEventPayload(deps.db, deps.observability, payload, subject),
|
|
326
|
+
handleAgentEventPayload(deps.db, deps.observability, payload, subject, deps.bus),
|
|
189
327
|
);
|
|
190
328
|
}
|
|
191
329
|
|
|
@@ -229,6 +367,11 @@ export function helloDesktopUnavailableReason(hello: Hello): string | null {
|
|
|
229
367
|
return reason ? reason : null;
|
|
230
368
|
}
|
|
231
369
|
|
|
370
|
+
/** Whether the runner's current Hello advertises the op-stream engine. */
|
|
371
|
+
export function helloReportsOpStream(hello: Hello): boolean {
|
|
372
|
+
return hello.capabilities?.opStream === true;
|
|
373
|
+
}
|
|
374
|
+
|
|
232
375
|
/**
|
|
233
376
|
* Reconcile `enrollments.has_display` (+ the capture-blocked reason) to what a Hello
|
|
234
377
|
* reports. Resolves the enrollment (the accountId is the RLS principal + the
|
|
@@ -239,7 +382,12 @@ export function helloDesktopUnavailableReason(hello: Hello): string | null {
|
|
|
239
382
|
*/
|
|
240
383
|
export async function refreshEnrollmentDisplay(
|
|
241
384
|
db: Database,
|
|
242
|
-
input: {
|
|
385
|
+
input: {
|
|
386
|
+
workspaceId: string;
|
|
387
|
+
agentId: string;
|
|
388
|
+
hasDisplay: boolean;
|
|
389
|
+
desktopUnavailableReason?: string | null;
|
|
390
|
+
},
|
|
243
391
|
): Promise<{ updated: boolean }> {
|
|
244
392
|
const desktopUnavailableReason = input.desktopUnavailableReason ?? null;
|
|
245
393
|
const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);
|
|
@@ -264,15 +412,50 @@ export async function refreshEnrollmentDisplay(
|
|
|
264
412
|
}
|
|
265
413
|
|
|
266
414
|
/**
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
415
|
+
* Reconcile `enrollments.op_stream` to what a Hello reports. Resolves the
|
|
416
|
+
* enrollment first so the accountId remains the RLS principal and so a no-change
|
|
417
|
+
* Hello short-circuits BEFORE issuing any write (the DB writer is itself
|
|
418
|
+
* change-guarded as a backstop). An unknown/cross-workspace agentId is a no-op.
|
|
419
|
+
*/
|
|
420
|
+
export async function refreshEnrollmentOpStream(
|
|
421
|
+
db: Database,
|
|
422
|
+
input: {
|
|
423
|
+
workspaceId: string;
|
|
424
|
+
agentId: string;
|
|
425
|
+
opStream: boolean;
|
|
426
|
+
},
|
|
427
|
+
): Promise<{ updated: boolean }> {
|
|
428
|
+
const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);
|
|
429
|
+
if (!enrollment) {
|
|
430
|
+
return { updated: false };
|
|
431
|
+
}
|
|
432
|
+
if (enrollment.opStream === input.opStream) {
|
|
433
|
+
// The capability is unchanged — do not even issue the UPDATE (no churn on a
|
|
434
|
+
// steady-state Hello).
|
|
435
|
+
return { updated: false };
|
|
436
|
+
}
|
|
437
|
+
return await setEnrollmentOpStreamState(db, {
|
|
438
|
+
accountId: enrollment.accountId,
|
|
439
|
+
workspaceId: input.workspaceId,
|
|
440
|
+
enrollmentId: input.agentId,
|
|
441
|
+
opStream: input.opStream,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Decode a raw `Hello` payload + refresh the enrollment's display cursor + clear
|
|
447
|
+
* any pending clean going-offline marker and, when the reconnect actually cleared
|
|
448
|
+
* one, fan out machine.link.restored to the sessions with an active op on the
|
|
449
|
+
* machine (the per-message handler for the hello plane). Decode failures + write
|
|
450
|
+
* failures are reported + swallowed — a Hello must NEVER break the agent's connect.
|
|
451
|
+
* `bus` (when present) enables the link.restored fan-out.
|
|
270
452
|
*/
|
|
271
453
|
export async function handleHelloPayload(
|
|
272
454
|
db: Database,
|
|
273
455
|
observability: Observability | undefined,
|
|
274
456
|
payload: Uint8Array,
|
|
275
457
|
subject: string,
|
|
458
|
+
bus?: EventBus,
|
|
276
459
|
): Promise<void> {
|
|
277
460
|
const ids = parseAgentHelloSubject(subject);
|
|
278
461
|
if (!ids) {
|
|
@@ -295,8 +478,46 @@ export async function handleHelloPayload(
|
|
|
295
478
|
hasDisplay: helloReportsDisplay(hello),
|
|
296
479
|
desktopUnavailableReason: helloDesktopUnavailableReason(hello),
|
|
297
480
|
});
|
|
481
|
+
await refreshEnrollmentOpStream(db, {
|
|
482
|
+
workspaceId: ids.workspaceId,
|
|
483
|
+
agentId: ids.agentId,
|
|
484
|
+
opStream: helloReportsOpStream(hello),
|
|
485
|
+
});
|
|
486
|
+
} catch (error) {
|
|
487
|
+
observability?.warn?.("Failed to refresh an enrollment's capabilities from a Hello", {
|
|
488
|
+
subject,
|
|
489
|
+
error: error instanceof Error ? error.message : String(error),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
// A reconnect Hello re-announces the machine, so any pending clean going-offline
|
|
493
|
+
// marker no longer holds — clear it so the liveness derivation stops reading the
|
|
494
|
+
// machine offline. Best-effort + fail-soft, and change-guarded in the DB (a
|
|
495
|
+
// steady-state Hello with no marker writes nothing), so this never breaks the
|
|
496
|
+
// agent's connect and never churns. When a marker was ACTUALLY cleared (the
|
|
497
|
+
// machine had been reported link.lost), fan out machine.link.restored to the
|
|
498
|
+
// sessions with an active op on it — a restored only ever pairs a prior lost, so
|
|
499
|
+
// a routine connect Hello (no marker) emits nothing.
|
|
500
|
+
try {
|
|
501
|
+
const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId);
|
|
502
|
+
if (enrollment) {
|
|
503
|
+
const { cleared } = await clearEnrollmentWentOffline(db, {
|
|
504
|
+
accountId: enrollment.accountId,
|
|
505
|
+
workspaceId: ids.workspaceId,
|
|
506
|
+
enrollmentId: ids.agentId,
|
|
507
|
+
});
|
|
508
|
+
if (cleared && bus) {
|
|
509
|
+
await fanOutMachineLinkEvents(
|
|
510
|
+
db,
|
|
511
|
+
bus,
|
|
512
|
+
observability,
|
|
513
|
+
ids.workspaceId,
|
|
514
|
+
ids.agentId,
|
|
515
|
+
(activeTurnId) => [{ type: "machine.link.restored", turnId: activeTurnId, payload: {} }],
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
298
519
|
} catch (error) {
|
|
299
|
-
observability?.warn?.("Failed to
|
|
520
|
+
observability?.warn?.("Failed to clear a machine going-offline marker on a Hello", {
|
|
300
521
|
subject,
|
|
301
522
|
error: error instanceof Error ? error.message : String(error),
|
|
302
523
|
});
|
|
@@ -315,6 +536,6 @@ export function startHelloIngestion(deps: {
|
|
|
315
536
|
observability?: Observability;
|
|
316
537
|
}): () => void {
|
|
317
538
|
return deps.bus.subscribeAgentEvents(AGENT_HELLO_SUBJECT, (payload, subject) =>
|
|
318
|
-
handleHelloPayload(deps.db, deps.observability, payload, subject),
|
|
539
|
+
handleHelloPayload(deps.db, deps.observability, payload, subject, deps.bus),
|
|
319
540
|
);
|
|
320
541
|
}
|