@masons/runtime-broker 0.2.1 → 0.2.2
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/broker/broker-daemon.d.ts +1 -0
- package/dist/broker/broker-daemon.d.ts.map +1 -1
- package/dist/broker/broker-daemon.js +569 -114
- package/dist/broker/control-event-dispatcher.d.ts.map +1 -1
- package/dist/broker/control-event-dispatcher.js +34 -16
- package/dist/broker/control-event-types.d.ts +12 -2
- package/dist/broker/control-event-types.d.ts.map +1 -1
- package/dist/broker/endpoint-registry.d.ts +7 -0
- package/dist/broker/endpoint-registry.d.ts.map +1 -1
- package/dist/broker/endpoint-registry.js +3 -0
- package/dist/broker/entry.d.ts.map +1 -1
- package/dist/broker/entry.js +4 -1
- package/dist/broker/ipc-server.d.ts +31 -0
- package/dist/broker/ipc-server.d.ts.map +1 -1
- package/dist/broker/ipc-server.js +6 -0
- package/dist/broker/reconnecting-buffer.d.ts +2 -0
- package/dist/broker/reconnecting-buffer.d.ts.map +1 -1
- package/dist/broker/reconnecting-buffer.js +3 -0
- package/dist/broker/runtime-endpoint-port.d.ts +2 -0
- package/dist/broker/runtime-endpoint-port.d.ts.map +1 -1
- package/dist/broker/runtime-processing-state-event-types.d.ts +26 -0
- package/dist/broker/runtime-processing-state-event-types.d.ts.map +1 -0
- package/dist/broker/runtime-processing-state-event-types.js +1 -0
- package/dist/broker-client/broker-client.d.ts +30 -0
- package/dist/broker-client/broker-client.d.ts.map +1 -1
- package/dist/broker-client/broker-client.js +27 -0
- package/dist/runtime-endpoint-client.d.ts +11 -1
- package/dist/runtime-endpoint-client.d.ts.map +1 -1
- package/dist/runtime-endpoint-client.js +33 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +12 -13
|
@@ -22,6 +22,15 @@ import { CONTENT_PREVIEW_MAX_CODEPOINTS, truncateContentPreview, } from "./undis
|
|
|
22
22
|
import { createUndispatchedChangedEmitter, postUndispatchedChangedViaPort, } from "./undispatched-emitter.js";
|
|
23
23
|
import { createUndispatchedInbox, } from "./undispatched-inbox.js";
|
|
24
24
|
const REMOTE_SPAWN_CAPABILITY = "remote_spawn_v1";
|
|
25
|
+
const DEFAULT_RUNTIME_ASSIGNMENT_ACCEPTANCE_TIMEOUT_MS = 30_000;
|
|
26
|
+
const RUNTIME_PROCESSING_RETRY_INITIAL_MS = 1_000;
|
|
27
|
+
const RUNTIME_PROCESSING_RETRY_MAX_MS = 30_000;
|
|
28
|
+
function isBackgroundExchangeMode(value) {
|
|
29
|
+
return value === "resident_endpoint" || value === "adapter_managed_turn";
|
|
30
|
+
}
|
|
31
|
+
function normalizeBackgroundExchangeMode(value) {
|
|
32
|
+
return isBackgroundExchangeMode(value) ? value : "resident_endpoint";
|
|
33
|
+
}
|
|
25
34
|
function derivePublicDisplayLabel(body) {
|
|
26
35
|
const parts = [
|
|
27
36
|
body.kind,
|
|
@@ -39,6 +48,8 @@ export async function startBrokerDaemon(opts) {
|
|
|
39
48
|
const { paths, asNodeId: _asNodeIdReservedForA3, connector, apiPort, logger, closedEndpointLookup, livenessProbe = defaultIsPluginAlive, spawnDriverRegistry, spawnFn = spawnChild, servicesEventClientFactory, } = opts;
|
|
40
49
|
const graceMs = opts.graceMs ?? DEFAULT_GRACE_MS;
|
|
41
50
|
const presenceGraceMs = opts.presenceGraceMs;
|
|
51
|
+
const runtimeAssignmentAcceptanceTimeoutMs = opts.runtimeAssignmentAcceptanceTimeoutMs ??
|
|
52
|
+
DEFAULT_RUNTIME_ASSIGNMENT_ACCEPTANCE_TIMEOUT_MS;
|
|
42
53
|
const postControlAck = opts.postControlAck ??
|
|
43
54
|
(async () => {
|
|
44
55
|
});
|
|
@@ -63,6 +74,8 @@ export async function startBrokerDaemon(opts) {
|
|
|
63
74
|
const reconnectingBuffers = createReconnectingBufferManager(paths.buffersDir);
|
|
64
75
|
const graceTimers = createGraceTimerManager();
|
|
65
76
|
const controlEventDispatcher = createControlEventDispatcher();
|
|
77
|
+
const runtimeAssignments = new Map();
|
|
78
|
+
const runtimeAssignmentByEndpointCorrelation = new Map();
|
|
66
79
|
const spawnCorrelation = createSpawnCorrelationManager({
|
|
67
80
|
timeoutMs: opts.spawnCorrelationTimeoutMs,
|
|
68
81
|
});
|
|
@@ -121,6 +134,71 @@ export async function startBrokerDaemon(opts) {
|
|
|
121
134
|
backoffMaxMs: opts.runtimeInboundRoutedBackoffMaxMs,
|
|
122
135
|
maxRetries: opts.runtimeInboundRoutedMaxRetries,
|
|
123
136
|
});
|
|
137
|
+
const runtimeAssignmentKey = (sourceMessageId, assignmentId) => `${sourceMessageId}\u0000${assignmentId}`;
|
|
138
|
+
const endpointCorrelationKey = (endpointId, correlationId) => `${endpointId}\u0000${correlationId}`;
|
|
139
|
+
const isBackgroundExchangeTarget = (entry) => Boolean(entry.runtime_capabilities?.includes("background_exchange_v1") &&
|
|
140
|
+
isBackgroundExchangeMode(entry.background_exchange_mode));
|
|
141
|
+
const rememberRuntimeAssignmentCorrelation = (endpointId, correlationId, assignmentKey) => {
|
|
142
|
+
if (typeof correlationId !== "string" || correlationId.length === 0) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
runtimeAssignmentByEndpointCorrelation.set(endpointCorrelationKey(endpointId, correlationId), assignmentKey);
|
|
146
|
+
};
|
|
147
|
+
const forgetRuntimeAssignment = (key) => {
|
|
148
|
+
const assignment = runtimeAssignments.get(key);
|
|
149
|
+
if (assignment?.acceptanceTimer) {
|
|
150
|
+
clearTimeout(assignment.acceptanceTimer);
|
|
151
|
+
}
|
|
152
|
+
if (assignment?.replyEmittedRetryTimer) {
|
|
153
|
+
clearTimeout(assignment.replyEmittedRetryTimer);
|
|
154
|
+
}
|
|
155
|
+
runtimeAssignments.delete(key);
|
|
156
|
+
for (const [correlationKey, assignmentKey] of Array.from(runtimeAssignmentByEndpointCorrelation.entries())) {
|
|
157
|
+
if (assignmentKey === key) {
|
|
158
|
+
runtimeAssignmentByEndpointCorrelation.delete(correlationKey);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const scheduleAcceptanceTimeout = (key) => {
|
|
163
|
+
if (runtimeAssignmentAcceptanceTimeoutMs <= 0)
|
|
164
|
+
return;
|
|
165
|
+
const assignment = runtimeAssignments.get(key);
|
|
166
|
+
if (!assignment || assignment.accepted || assignment.acceptanceTimer) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const timer = setTimeout(() => {
|
|
170
|
+
const current = runtimeAssignments.get(key);
|
|
171
|
+
if (!current || current.accepted)
|
|
172
|
+
return;
|
|
173
|
+
logger.warn("runtime_assignment_acceptance_timeout", {
|
|
174
|
+
undispatched_id: current.context.undispatchedId,
|
|
175
|
+
source_message_id: current.context.sourceMessageId,
|
|
176
|
+
target_endpoint_id: current.context.targetRef.projection_endpoint_id,
|
|
177
|
+
});
|
|
178
|
+
void restorePendingAssignmentsForEndpoint(current.context.targetRef.projection_endpoint_id, "adapter_acceptance_timeout");
|
|
179
|
+
}, runtimeAssignmentAcceptanceTimeoutMs);
|
|
180
|
+
timer.unref?.();
|
|
181
|
+
assignment.acceptanceTimer = timer;
|
|
182
|
+
};
|
|
183
|
+
const emitRuntimeProcessingState = async (event) => {
|
|
184
|
+
if (!apiPort.emitRuntimeProcessingState) {
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
terminal: true,
|
|
188
|
+
detail: "runtime_processing_state_port_not_configured",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
return await apiPort.emitRuntimeProcessingState(event);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
terminal: false,
|
|
198
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
};
|
|
124
202
|
const directPostUndispatched = async (event, opts = {}) => {
|
|
125
203
|
let outcome;
|
|
126
204
|
try {
|
|
@@ -195,6 +273,127 @@ export async function startBrokerDaemon(opts) {
|
|
|
195
273
|
return;
|
|
196
274
|
replyCorrelationCache.record(endpoint_id, c);
|
|
197
275
|
};
|
|
276
|
+
const restorePendingAssignmentsForEndpoint = (endpoint_id, reason) => {
|
|
277
|
+
const tasks = [];
|
|
278
|
+
for (const [key, assignment] of Array.from(runtimeAssignments.entries())) {
|
|
279
|
+
if (assignment.context.targetRef.projection_endpoint_id !== endpoint_id ||
|
|
280
|
+
assignment.accepted) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (assignment.acceptanceTimer) {
|
|
284
|
+
clearTimeout(assignment.acceptanceTimer);
|
|
285
|
+
assignment.acceptanceTimer = undefined;
|
|
286
|
+
}
|
|
287
|
+
const readded = undispatchedInbox.tryAdd(assignment.message);
|
|
288
|
+
if (!readded) {
|
|
289
|
+
logger.error("runtime_assignment_reinsert_failed_at_capacity", {
|
|
290
|
+
undispatched_id: assignment.context.undispatchedId,
|
|
291
|
+
source_message_id: assignment.context.sourceMessageId,
|
|
292
|
+
target_endpoint_id: endpoint_id,
|
|
293
|
+
inbox_size: undispatchedInbox.size(),
|
|
294
|
+
inbox_capacity: undispatchedInbox.capacity(),
|
|
295
|
+
});
|
|
296
|
+
scheduleAcceptanceTimeout(key);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const task = emitRuntimeProcessingState({
|
|
300
|
+
type: "runtime_processing_state",
|
|
301
|
+
version: 1,
|
|
302
|
+
source_message_id: assignment.context.sourceMessageId,
|
|
303
|
+
assignment_id: assignment.context.assignmentId,
|
|
304
|
+
state_event_id: `${assignment.context.assignmentId}:target_lost`,
|
|
305
|
+
state_sequence: 2,
|
|
306
|
+
target_ref: assignment.context.targetRef,
|
|
307
|
+
state: "target_lost_before_acceptance",
|
|
308
|
+
occurred_at: assignment.assignedAt + 1,
|
|
309
|
+
undispatched_id: assignment.context.undispatchedId,
|
|
310
|
+
reason_code: reason,
|
|
311
|
+
}).then((outcome) => {
|
|
312
|
+
if (outcome.ok) {
|
|
313
|
+
forgetRuntimeAssignment(key);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
logger.warn("runtime_assignment_target_lost_emit_failed", {
|
|
317
|
+
undispatched_id: assignment.context.undispatchedId,
|
|
318
|
+
source_message_id: assignment.context.sourceMessageId,
|
|
319
|
+
target_endpoint_id: endpoint_id,
|
|
320
|
+
status: outcome.status,
|
|
321
|
+
terminal: outcome.terminal,
|
|
322
|
+
detail: outcome.detail,
|
|
323
|
+
});
|
|
324
|
+
scheduleAcceptanceTimeout(key);
|
|
325
|
+
});
|
|
326
|
+
tasks.push(task);
|
|
327
|
+
}
|
|
328
|
+
return Promise.allSettled(tasks).then(() => undefined);
|
|
329
|
+
};
|
|
330
|
+
const emitReplyEmittedWithRetry = (assignmentKey, event, attempt = 0) => {
|
|
331
|
+
const assignment = runtimeAssignments.get(assignmentKey);
|
|
332
|
+
if (!assignment)
|
|
333
|
+
return;
|
|
334
|
+
assignment.replyEmittedEvent = event;
|
|
335
|
+
if (assignment.replyEmittedRetryTimer) {
|
|
336
|
+
clearTimeout(assignment.replyEmittedRetryTimer);
|
|
337
|
+
assignment.replyEmittedRetryTimer = undefined;
|
|
338
|
+
}
|
|
339
|
+
void emitRuntimeProcessingState(event).then((outcome) => {
|
|
340
|
+
const current = runtimeAssignments.get(assignmentKey);
|
|
341
|
+
if (!current)
|
|
342
|
+
return;
|
|
343
|
+
if (outcome.ok) {
|
|
344
|
+
forgetRuntimeAssignment(assignmentKey);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
logger.warn("runtime_reply_emitted_projection_failed", {
|
|
348
|
+
source_message_id: current.context.sourceMessageId,
|
|
349
|
+
assignment_id: current.context.assignmentId,
|
|
350
|
+
status: outcome.status,
|
|
351
|
+
terminal: outcome.terminal,
|
|
352
|
+
detail: outcome.detail,
|
|
353
|
+
});
|
|
354
|
+
if (outcome.terminal)
|
|
355
|
+
return;
|
|
356
|
+
const delay = Math.min(RUNTIME_PROCESSING_RETRY_INITIAL_MS * 2 ** attempt, RUNTIME_PROCESSING_RETRY_MAX_MS);
|
|
357
|
+
const timer = setTimeout(() => {
|
|
358
|
+
emitReplyEmittedWithRetry(assignmentKey, event, attempt + 1);
|
|
359
|
+
}, delay);
|
|
360
|
+
timer.unref?.();
|
|
361
|
+
current.replyEmittedRetryTimer = timer;
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
const flushRuntimeAssignmentsOnShutdown = async () => {
|
|
365
|
+
const endpointIds = new Set();
|
|
366
|
+
for (const assignment of runtimeAssignments.values()) {
|
|
367
|
+
endpointIds.add(assignment.context.targetRef.projection_endpoint_id);
|
|
368
|
+
}
|
|
369
|
+
for (const [key, assignment] of Array.from(runtimeAssignments.entries())) {
|
|
370
|
+
if (assignment.replyEmittedEvent) {
|
|
371
|
+
const outcome = await emitRuntimeProcessingState(assignment.replyEmittedEvent);
|
|
372
|
+
if (outcome.ok) {
|
|
373
|
+
forgetRuntimeAssignment(key);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
logger.warn("runtime_reply_emitted_shutdown_flush_failed", {
|
|
377
|
+
source_message_id: assignment.context.sourceMessageId,
|
|
378
|
+
assignment_id: assignment.context.assignmentId,
|
|
379
|
+
status: outcome.status,
|
|
380
|
+
terminal: outcome.terminal,
|
|
381
|
+
detail: outcome.detail,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
for (const endpointId of endpointIds) {
|
|
386
|
+
await restorePendingAssignmentsForEndpoint(endpointId, "broker_shutdown");
|
|
387
|
+
}
|
|
388
|
+
if (runtimeAssignments.size > 0) {
|
|
389
|
+
logger.warn("runtime_assignments_dropped_on_shutdown", {
|
|
390
|
+
size: runtimeAssignments.size,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
for (const key of Array.from(runtimeAssignments.keys())) {
|
|
394
|
+
forgetRuntimeAssignment(key);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
198
397
|
const channels = new Map();
|
|
199
398
|
let networkPresence = "offline";
|
|
200
399
|
let presenceGraceTimer;
|
|
@@ -357,11 +556,12 @@ export async function startBrokerDaemon(opts) {
|
|
|
357
556
|
break;
|
|
358
557
|
case "drain_buffer": {
|
|
359
558
|
const buf = reconnectingBuffers.forEndpoint(endpoint_id);
|
|
360
|
-
const drained = buf.
|
|
559
|
+
const drained = buf.read();
|
|
361
560
|
const e = registry.get(endpoint_id);
|
|
561
|
+
let allPushed = true;
|
|
362
562
|
if (e) {
|
|
363
563
|
for (const msg of drained) {
|
|
364
|
-
pushToPlugin(e.ipc_ws, {
|
|
564
|
+
const pushed = pushToPlugin(e.ipc_ws, {
|
|
365
565
|
event: "message_received",
|
|
366
566
|
from: msg.envelope.from,
|
|
367
567
|
content: msg.envelope.content,
|
|
@@ -370,15 +570,32 @@ export async function startBrokerDaemon(opts) {
|
|
|
370
570
|
...(msg.envelope.sourceMessageId !== undefined && {
|
|
371
571
|
sourceMessageId: msg.envelope.sourceMessageId,
|
|
372
572
|
}),
|
|
573
|
+
...(msg.envelope.runtimeAssignment !== undefined && {
|
|
574
|
+
runtimeAssignment: msg.envelope.runtimeAssignment,
|
|
575
|
+
}),
|
|
373
576
|
});
|
|
577
|
+
if (!pushed) {
|
|
578
|
+
allPushed = false;
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
374
581
|
}
|
|
375
582
|
}
|
|
583
|
+
if (allPushed) {
|
|
584
|
+
buf.delete();
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
logger.warn("reconnecting_buffer_drain_failed", {
|
|
588
|
+
endpoint_id,
|
|
589
|
+
buffered_count: drained.length,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
376
592
|
break;
|
|
377
593
|
}
|
|
378
594
|
case "delete_buffer":
|
|
379
595
|
reconnectingBuffers.cleanup(endpoint_id);
|
|
380
596
|
break;
|
|
381
597
|
case "remove_from_registry":
|
|
598
|
+
void restorePendingAssignmentsForEndpoint(endpoint_id, "endpoint_lost");
|
|
382
599
|
clearHeartbeat(endpoint_id);
|
|
383
600
|
registry.unregister(endpoint_id);
|
|
384
601
|
replyCorrelationCache.forgetEndpoint(endpoint_id);
|
|
@@ -389,20 +606,49 @@ export async function startBrokerDaemon(opts) {
|
|
|
389
606
|
}
|
|
390
607
|
};
|
|
391
608
|
controlEventDispatcher.on("dispatch_undispatched", async (event) => {
|
|
392
|
-
const
|
|
609
|
+
const targetEndpointId = event.target_ref?.projection_endpoint_id ?? event.target_endpoint_id;
|
|
610
|
+
if (!targetEndpointId) {
|
|
611
|
+
return {
|
|
612
|
+
ok: false,
|
|
613
|
+
detail: "dispatch target_ref missing",
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
const assignmentKey = runtimeAssignmentKey(event.source_message_id, event.idempotency_key);
|
|
617
|
+
const existingAssignment = runtimeAssignments.get(assignmentKey);
|
|
618
|
+
const taken = undispatchedInbox.take(event.undispatched_id) ??
|
|
619
|
+
(!existingAssignment?.accepted &&
|
|
620
|
+
existingAssignment?.context.undispatchedId === event.undispatched_id &&
|
|
621
|
+
existingAssignment.context.targetRef.projection_endpoint_id ===
|
|
622
|
+
targetEndpointId
|
|
623
|
+
? existingAssignment.message
|
|
624
|
+
: undefined);
|
|
393
625
|
if (!taken) {
|
|
394
626
|
return {
|
|
395
627
|
ok: false,
|
|
396
628
|
detail: `unknown undispatched_id: ${event.undispatched_id}`,
|
|
397
629
|
};
|
|
398
630
|
}
|
|
399
|
-
|
|
631
|
+
if (taken.source_message_id === undefined) {
|
|
632
|
+
undispatchedInbox.tryAdd(taken);
|
|
633
|
+
return {
|
|
634
|
+
ok: false,
|
|
635
|
+
detail: "dispatch source_message_id missing from inbox row",
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
if (event.source_message_id !== taken.source_message_id) {
|
|
639
|
+
undispatchedInbox.tryAdd(taken);
|
|
640
|
+
return {
|
|
641
|
+
ok: false,
|
|
642
|
+
detail: `source_message_id mismatch: ${event.source_message_id}`,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
const target = registry.get(targetEndpointId);
|
|
400
646
|
if (!target) {
|
|
401
647
|
const readded = undispatchedInbox.tryAdd(taken);
|
|
402
648
|
if (!readded) {
|
|
403
649
|
logger.error("dispatch_reinsert_failed_at_capacity", {
|
|
404
650
|
undispatched_id: event.undispatched_id,
|
|
405
|
-
target_endpoint_id:
|
|
651
|
+
target_endpoint_id: targetEndpointId,
|
|
406
652
|
inbox_size: undispatchedInbox.size(),
|
|
407
653
|
inbox_capacity: undispatchedInbox.capacity(),
|
|
408
654
|
});
|
|
@@ -419,9 +665,74 @@ export async function startBrokerDaemon(opts) {
|
|
|
419
665
|
}
|
|
420
666
|
return {
|
|
421
667
|
ok: false,
|
|
422
|
-
detail: `target endpoint not found: ${
|
|
668
|
+
detail: `target endpoint not found: ${targetEndpointId}`,
|
|
423
669
|
};
|
|
424
670
|
}
|
|
671
|
+
const targetRef = event.target_ref ?? {
|
|
672
|
+
kind: "projection_endpoint",
|
|
673
|
+
projection_endpoint_id: targetEndpointId,
|
|
674
|
+
background_exchange_mode: normalizeBackgroundExchangeMode(target.background_exchange_mode),
|
|
675
|
+
};
|
|
676
|
+
if (!target.runtime_capabilities?.includes("background_exchange_v1") ||
|
|
677
|
+
target.background_exchange_mode !== targetRef.background_exchange_mode) {
|
|
678
|
+
undispatchedInbox.tryAdd(taken);
|
|
679
|
+
return {
|
|
680
|
+
ok: false,
|
|
681
|
+
detail: `target endpoint is not background-exchange capable: ${targetEndpointId}`,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
const assignedAt = event.emitted_at;
|
|
685
|
+
const processingEvent = {
|
|
686
|
+
type: "runtime_processing_state",
|
|
687
|
+
version: 1,
|
|
688
|
+
source_message_id: event.source_message_id,
|
|
689
|
+
assignment_id: event.idempotency_key,
|
|
690
|
+
state_event_id: `${event.idempotency_key}:assigned`,
|
|
691
|
+
state_sequence: 1,
|
|
692
|
+
target_ref: targetRef,
|
|
693
|
+
state: "assigned_to_runtime_target",
|
|
694
|
+
occurred_at: assignedAt,
|
|
695
|
+
undispatched_id: event.undispatched_id,
|
|
696
|
+
};
|
|
697
|
+
const processingResult = await emitRuntimeProcessingState(processingEvent);
|
|
698
|
+
if (!processingResult.ok) {
|
|
699
|
+
undispatchedInbox.tryAdd(taken);
|
|
700
|
+
logger.warn("runtime_processing_state_emit_failed", {
|
|
701
|
+
undispatched_id: event.undispatched_id,
|
|
702
|
+
source_message_id: event.source_message_id,
|
|
703
|
+
target_endpoint_id: targetEndpointId,
|
|
704
|
+
status: processingResult.status,
|
|
705
|
+
terminal: processingResult.terminal,
|
|
706
|
+
detail: processingResult.detail,
|
|
707
|
+
});
|
|
708
|
+
return {
|
|
709
|
+
ok: false,
|
|
710
|
+
detail: processingResult.detail ??
|
|
711
|
+
"runtime_processing_state_assignment_failed",
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const runtimeAssignment = {
|
|
715
|
+
undispatchedId: event.undispatched_id,
|
|
716
|
+
sourceMessageId: event.source_message_id,
|
|
717
|
+
assignmentId: event.idempotency_key,
|
|
718
|
+
targetRef,
|
|
719
|
+
deliveryIntent: "external_handoff",
|
|
720
|
+
...(typeof taken.metadata.correlation_id === "string" && {
|
|
721
|
+
replyCorrelationId: taken.metadata.correlation_id,
|
|
722
|
+
}),
|
|
723
|
+
};
|
|
724
|
+
const storedAssignment = existingAssignment ?? {
|
|
725
|
+
context: runtimeAssignment,
|
|
726
|
+
message: taken,
|
|
727
|
+
accepted: false,
|
|
728
|
+
assignedAt,
|
|
729
|
+
};
|
|
730
|
+
storedAssignment.context = runtimeAssignment;
|
|
731
|
+
storedAssignment.message = taken;
|
|
732
|
+
storedAssignment.assignedAt = assignedAt;
|
|
733
|
+
runtimeAssignments.set(assignmentKey, storedAssignment);
|
|
734
|
+
scheduleAcceptanceTimeout(assignmentKey);
|
|
735
|
+
rememberRuntimeAssignmentCorrelation(target.endpoint_id, taken.metadata.correlation_id, assignmentKey);
|
|
425
736
|
const stamped = {
|
|
426
737
|
event: "message_received",
|
|
427
738
|
from: taken.sender_address,
|
|
@@ -430,47 +741,50 @@ export async function startBrokerDaemon(opts) {
|
|
|
430
741
|
metadata: {
|
|
431
742
|
...taken.metadata,
|
|
432
743
|
dispatched_from: event.undispatched_id,
|
|
433
|
-
|
|
744
|
+
delivery_intent: "external_handoff",
|
|
434
745
|
},
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}),
|
|
746
|
+
sourceMessageId: taken.source_message_id,
|
|
747
|
+
runtimeAssignment,
|
|
438
748
|
};
|
|
439
749
|
recordInboundCorrelation(target.endpoint_id, stamped.metadata);
|
|
440
750
|
if (target.state === "active") {
|
|
441
|
-
pushToPlugin(target.ipc_ws, stamped)
|
|
751
|
+
if (!pushToPlugin(target.ipc_ws, stamped)) {
|
|
752
|
+
await restorePendingAssignmentsForEndpoint(target.endpoint_id, "ipc_push_failed");
|
|
753
|
+
return {
|
|
754
|
+
ok: false,
|
|
755
|
+
detail: `target endpoint not writable: ${targetEndpointId}`,
|
|
756
|
+
};
|
|
757
|
+
}
|
|
442
758
|
}
|
|
443
759
|
else {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
760
|
+
try {
|
|
761
|
+
const buf = reconnectingBuffers.forEndpoint(target.endpoint_id);
|
|
762
|
+
buf.append({
|
|
763
|
+
id: randomUUID(),
|
|
764
|
+
arrived_at: new Date().toISOString(),
|
|
765
|
+
envelope: {
|
|
766
|
+
from: taken.sender_address,
|
|
767
|
+
content: taken.content,
|
|
768
|
+
contentType: taken.content_type,
|
|
769
|
+
metadata: stamped.metadata,
|
|
454
770
|
sourceMessageId: stamped.sourceMessageId,
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
771
|
+
runtimeAssignment,
|
|
772
|
+
},
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
catch (err) {
|
|
776
|
+
await restorePendingAssignmentsForEndpoint(target.endpoint_id, "reconnecting_buffer_failed");
|
|
777
|
+
return {
|
|
778
|
+
ok: false,
|
|
779
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
780
|
+
};
|
|
781
|
+
}
|
|
458
782
|
}
|
|
459
783
|
logger.info("undispatched_dispatched", {
|
|
460
784
|
undispatched_id: event.undispatched_id,
|
|
461
|
-
target_endpoint_id:
|
|
785
|
+
target_endpoint_id: targetEndpointId,
|
|
462
786
|
target_state: target.state,
|
|
463
787
|
});
|
|
464
|
-
emitUndispatched({
|
|
465
|
-
type: "undispatched_changed",
|
|
466
|
-
version: 1,
|
|
467
|
-
action: "dispatch",
|
|
468
|
-
undispatched_id: event.undispatched_id,
|
|
469
|
-
...(taken.source_message_id !== undefined && {
|
|
470
|
-
source_message_id: taken.source_message_id,
|
|
471
|
-
}),
|
|
472
|
-
dispatched_to_endpoint_id: event.target_endpoint_id,
|
|
473
|
-
});
|
|
474
788
|
return { ok: true };
|
|
475
789
|
});
|
|
476
790
|
controlEventDispatcher.on("spawn_request", async (event) => {
|
|
@@ -562,8 +876,12 @@ export async function startBrokerDaemon(opts) {
|
|
|
562
876
|
const spawnAvailability = spawnDriver
|
|
563
877
|
? await spawnDriver.isAvailable().catch(() => ({ available: false }))
|
|
564
878
|
: null;
|
|
565
|
-
const
|
|
566
|
-
|
|
879
|
+
const runtimeCapabilitiesSet = new Set(body.runtime_capabilities?.filter((capability) => capability !== REMOTE_SPAWN_CAPABILITY) ?? []);
|
|
880
|
+
if (spawnAvailability?.available) {
|
|
881
|
+
runtimeCapabilitiesSet.add(REMOTE_SPAWN_CAPABILITY);
|
|
882
|
+
}
|
|
883
|
+
const runtimeCapabilities = runtimeCapabilitiesSet.size > 0
|
|
884
|
+
? Array.from(runtimeCapabilitiesSet)
|
|
567
885
|
: undefined;
|
|
568
886
|
const apiResp = await apiPort.register({
|
|
569
887
|
runtime_kind: body.kind,
|
|
@@ -576,12 +894,17 @@ export async function startBrokerDaemon(opts) {
|
|
|
576
894
|
session_name: body.session_name,
|
|
577
895
|
task_hint: body.task_hint,
|
|
578
896
|
runtime_capabilities: runtimeCapabilities,
|
|
897
|
+
execution_surface: body.execution_surface,
|
|
898
|
+
background_exchange_mode: body.background_exchange_mode,
|
|
579
899
|
});
|
|
580
900
|
registry.register({
|
|
581
901
|
endpoint_id: apiResp.endpoint_id,
|
|
582
902
|
agent_id: body.agent_id,
|
|
583
903
|
plugin_pid: body.plugin_pid,
|
|
584
904
|
ipc_ws: boundWs,
|
|
905
|
+
runtime_capabilities: runtimeCapabilities,
|
|
906
|
+
execution_surface: body.execution_surface,
|
|
907
|
+
background_exchange_mode: body.background_exchange_mode,
|
|
585
908
|
display_metadata: {
|
|
586
909
|
session_name: body.session_name,
|
|
587
910
|
kind: body.kind,
|
|
@@ -698,8 +1021,86 @@ export async function startBrokerDaemon(opts) {
|
|
|
698
1021
|
}
|
|
699
1022
|
throw err;
|
|
700
1023
|
}
|
|
1024
|
+
if (inReplyTo) {
|
|
1025
|
+
const assignmentKey = runtimeAssignmentByEndpointCorrelation.get(endpointCorrelationKey(body.endpoint_id, inReplyTo));
|
|
1026
|
+
const assignment = assignmentKey !== undefined
|
|
1027
|
+
? runtimeAssignments.get(assignmentKey)
|
|
1028
|
+
: undefined;
|
|
1029
|
+
if (assignmentKey !== undefined && assignment?.accepted) {
|
|
1030
|
+
emitReplyEmittedWithRetry(assignmentKey, {
|
|
1031
|
+
type: "runtime_processing_state",
|
|
1032
|
+
version: 1,
|
|
1033
|
+
source_message_id: assignment.context.sourceMessageId,
|
|
1034
|
+
assignment_id: assignment.context.assignmentId,
|
|
1035
|
+
state_event_id: `${assignment.context.assignmentId}:reply:${ack.messageId}`,
|
|
1036
|
+
state_sequence: Number.MAX_SAFE_INTEGER,
|
|
1037
|
+
target_ref: assignment.context.targetRef,
|
|
1038
|
+
state: "reply_emitted",
|
|
1039
|
+
occurred_at: Date.now(),
|
|
1040
|
+
undispatched_id: assignment.context.undispatchedId,
|
|
1041
|
+
outbound_message_id: ack.messageId,
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
701
1045
|
return { messageId: ack.messageId, status: ack.status };
|
|
702
1046
|
},
|
|
1047
|
+
async reportProcessingState(body) {
|
|
1048
|
+
const entry = registry.get(body.endpoint_id);
|
|
1049
|
+
if (!entry) {
|
|
1050
|
+
throw new BrokerHttpError(404, "unknown_endpoint", `unknown endpoint_id: ${body.endpoint_id}`);
|
|
1051
|
+
}
|
|
1052
|
+
if (entry.plugin_pid !== body.plugin_pid) {
|
|
1053
|
+
throw new BrokerHttpError(403, "endpoint_pid_mismatch", "plugin_pid does not own endpoint_id");
|
|
1054
|
+
}
|
|
1055
|
+
const key = runtimeAssignmentKey(body.source_message_id, body.assignment_id);
|
|
1056
|
+
const assignment = runtimeAssignments.get(key);
|
|
1057
|
+
if (!assignment) {
|
|
1058
|
+
throw new BrokerHttpError(409, "runtime_assignment_unknown", "source assignment is not known to this broker");
|
|
1059
|
+
}
|
|
1060
|
+
if (assignment.context.targetRef.projection_endpoint_id !== body.endpoint_id) {
|
|
1061
|
+
throw new BrokerHttpError(403, "runtime_assignment_target_mismatch", "endpoint_id does not match the assigned target");
|
|
1062
|
+
}
|
|
1063
|
+
if (body.state === "reply_emitted") {
|
|
1064
|
+
throw new BrokerHttpError(403, "reply_emitted_broker_owned", "reply_emitted is emitted by the broker after outbound wire send");
|
|
1065
|
+
}
|
|
1066
|
+
const event = {
|
|
1067
|
+
type: "runtime_processing_state",
|
|
1068
|
+
version: 1,
|
|
1069
|
+
source_message_id: body.source_message_id,
|
|
1070
|
+
assignment_id: body.assignment_id,
|
|
1071
|
+
state_event_id: body.state_event_id,
|
|
1072
|
+
state_sequence: body.state_sequence,
|
|
1073
|
+
target_ref: assignment.context.targetRef,
|
|
1074
|
+
state: body.state,
|
|
1075
|
+
occurred_at: body.occurred_at ?? Date.now(),
|
|
1076
|
+
undispatched_id: body.undispatched_id ?? assignment.context.undispatchedId,
|
|
1077
|
+
...(body.retryable !== undefined && { retryable: body.retryable }),
|
|
1078
|
+
...(body.reason_code !== undefined && {
|
|
1079
|
+
reason_code: body.reason_code,
|
|
1080
|
+
}),
|
|
1081
|
+
...(body.detail !== undefined && { detail: body.detail }),
|
|
1082
|
+
...(body.reply_request_id !== undefined && {
|
|
1083
|
+
reply_request_id: body.reply_request_id,
|
|
1084
|
+
}),
|
|
1085
|
+
...(body.outbound_message_id !== undefined && {
|
|
1086
|
+
outbound_message_id: body.outbound_message_id,
|
|
1087
|
+
}),
|
|
1088
|
+
};
|
|
1089
|
+
const outcome = await emitRuntimeProcessingState(event);
|
|
1090
|
+
if (!outcome.ok) {
|
|
1091
|
+
throw new BrokerHttpError(outcome.terminal ? 409 : 503, "runtime_processing_state_rejected", outcome.detail ?? "runtime processing state was not accepted");
|
|
1092
|
+
}
|
|
1093
|
+
if (body.state === "adapter_accepted") {
|
|
1094
|
+
assignment.accepted = true;
|
|
1095
|
+
if (assignment.acceptanceTimer) {
|
|
1096
|
+
clearTimeout(assignment.acceptanceTimer);
|
|
1097
|
+
assignment.acceptanceTimer = undefined;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (body.state === "processing_failed" || body.state === "expired") {
|
|
1101
|
+
forgetRuntimeAssignment(key);
|
|
1102
|
+
}
|
|
1103
|
+
},
|
|
703
1104
|
async reattachEndpoint(endpoint_id, plugin_pid, _ipcWsHint) {
|
|
704
1105
|
const ipcWs = channels.get(plugin_pid);
|
|
705
1106
|
if (!ipcWs) {
|
|
@@ -722,11 +1123,12 @@ export async function startBrokerDaemon(opts) {
|
|
|
722
1123
|
}
|
|
723
1124
|
bound.add(endpoint_id);
|
|
724
1125
|
const buf = reconnectingBuffers.forEndpoint(endpoint_id);
|
|
725
|
-
const drained = buf.
|
|
1126
|
+
const drained = buf.read();
|
|
726
1127
|
graceTimers.cancel(endpoint_id);
|
|
727
1128
|
registry.markActive(endpoint_id, ipcWs);
|
|
1129
|
+
let allPushed = true;
|
|
728
1130
|
for (const msg of drained) {
|
|
729
|
-
pushToPlugin(ipcWs, {
|
|
1131
|
+
const pushed = pushToPlugin(ipcWs, {
|
|
730
1132
|
event: "message_received",
|
|
731
1133
|
from: msg.envelope.from,
|
|
732
1134
|
content: msg.envelope.content,
|
|
@@ -735,8 +1137,23 @@ export async function startBrokerDaemon(opts) {
|
|
|
735
1137
|
...(msg.envelope.sourceMessageId !== undefined && {
|
|
736
1138
|
sourceMessageId: msg.envelope.sourceMessageId,
|
|
737
1139
|
}),
|
|
1140
|
+
...(msg.envelope.runtimeAssignment !== undefined && {
|
|
1141
|
+
runtimeAssignment: msg.envelope.runtimeAssignment,
|
|
1142
|
+
}),
|
|
1143
|
+
});
|
|
1144
|
+
if (!pushed) {
|
|
1145
|
+
allPushed = false;
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
if (!allPushed) {
|
|
1150
|
+
handleTransition(endpoint_id, {
|
|
1151
|
+
type: "ipc_close",
|
|
1152
|
+
plugin_alive: livenessProbe(plugin_pid),
|
|
738
1153
|
});
|
|
1154
|
+
throw new BrokerHttpError(503, "reconnecting_buffer_drain_failed", "failed to push buffered messages to reattached endpoint", { retryable: true });
|
|
739
1155
|
}
|
|
1156
|
+
buf.delete();
|
|
740
1157
|
emitTransition(endpoint_id, "active", "reattach");
|
|
741
1158
|
logger.info("endpoint_reattached", {
|
|
742
1159
|
endpoint_id,
|
|
@@ -749,13 +1166,21 @@ export async function startBrokerDaemon(opts) {
|
|
|
749
1166
|
return undispatchedInbox.list();
|
|
750
1167
|
},
|
|
751
1168
|
async dispatch(undispatched_id, target_endpoint_id) {
|
|
1169
|
+
const pending = undispatchedInbox.get(undispatched_id);
|
|
1170
|
+
const target = registry.get(target_endpoint_id);
|
|
752
1171
|
const result = await controlEventDispatcher.dispatch({
|
|
753
1172
|
type: "dispatch_undispatched",
|
|
754
1173
|
version: 1,
|
|
755
1174
|
idempotency_key: `local-${randomUUID()}`,
|
|
756
1175
|
emitted_at: Date.now(),
|
|
757
1176
|
undispatched_id,
|
|
758
|
-
|
|
1177
|
+
source_message_id: pending?.source_message_id ?? undispatched_id,
|
|
1178
|
+
target_ref: {
|
|
1179
|
+
kind: "projection_endpoint",
|
|
1180
|
+
projection_endpoint_id: target_endpoint_id,
|
|
1181
|
+
background_exchange_mode: normalizeBackgroundExchangeMode(target?.background_exchange_mode),
|
|
1182
|
+
},
|
|
1183
|
+
delivery_intent: "external_handoff",
|
|
759
1184
|
});
|
|
760
1185
|
if (!result.ok) {
|
|
761
1186
|
throw new BrokerHttpError(400, "dispatch_failed", result.detail ?? "unknown");
|
|
@@ -841,21 +1266,71 @@ export async function startBrokerDaemon(opts) {
|
|
|
841
1266
|
? meta.target_endpoint_id
|
|
842
1267
|
: undefined;
|
|
843
1268
|
const correlation_id = typeof meta.correlation_id === "string" ? meta.correlation_id : undefined;
|
|
1269
|
+
const addUndispatchedFromPayload = async (opts) => {
|
|
1270
|
+
const undispatched = {
|
|
1271
|
+
id: payload.messageId ?? randomUUID(),
|
|
1272
|
+
...(payload.messageId !== undefined && {
|
|
1273
|
+
source_message_id: payload.messageId,
|
|
1274
|
+
}),
|
|
1275
|
+
arrived_at: new Date().toISOString(),
|
|
1276
|
+
sender_address: payload.from,
|
|
1277
|
+
content: payload.content,
|
|
1278
|
+
content_type: payload.contentType,
|
|
1279
|
+
metadata: meta,
|
|
1280
|
+
reason: opts.reason,
|
|
1281
|
+
original_target_endpoint_id: opts.originalTargetEndpointId,
|
|
1282
|
+
original_correlation_id: opts.originalCorrelationId,
|
|
1283
|
+
};
|
|
1284
|
+
const added = undispatchedInbox.tryAdd(undispatched);
|
|
1285
|
+
if (!added) {
|
|
1286
|
+
logger.warn("undispatched_inbox_at_capacity", {
|
|
1287
|
+
from: payload.from,
|
|
1288
|
+
size: undispatchedInbox.size(),
|
|
1289
|
+
capacity: undispatchedInbox.capacity(),
|
|
1290
|
+
});
|
|
1291
|
+
return false;
|
|
1292
|
+
}
|
|
1293
|
+
logger.info("undispatched_added", {
|
|
1294
|
+
id: undispatched.id,
|
|
1295
|
+
reason: opts.reason,
|
|
1296
|
+
});
|
|
1297
|
+
const wireReason = undispatched.reason === "unaddressed"
|
|
1298
|
+
? "no_target_endpoint"
|
|
1299
|
+
: undispatched.reason;
|
|
1300
|
+
const posted = await directPostUndispatched({
|
|
1301
|
+
type: "undispatched_changed",
|
|
1302
|
+
version: 1,
|
|
1303
|
+
action: "add",
|
|
1304
|
+
undispatched_id: undispatched.id,
|
|
1305
|
+
...(undispatched.source_message_id !== undefined && {
|
|
1306
|
+
source_message_id: undispatched.source_message_id,
|
|
1307
|
+
}),
|
|
1308
|
+
sender_address: undispatched.sender_address,
|
|
1309
|
+
content_preview: truncateContentPreview(undispatched.content, CONTENT_PREVIEW_MAX_CODEPOINTS),
|
|
1310
|
+
reason: wireReason,
|
|
1311
|
+
...(opts.originalTargetEndpointId !== undefined && {
|
|
1312
|
+
target_endpoint_id_hint: opts.originalTargetEndpointId,
|
|
1313
|
+
}),
|
|
1314
|
+
...(typeof correlation_id === "string" && {
|
|
1315
|
+
in_reply_to: correlation_id,
|
|
1316
|
+
}),
|
|
1317
|
+
arrived_at: Date.parse(undispatched.arrived_at),
|
|
1318
|
+
}, { queueOnTransientFailure: typeof payload.seq !== "number" });
|
|
1319
|
+
if (!posted)
|
|
1320
|
+
return false;
|
|
1321
|
+
return markDeliverySeqAccepted(payload.seq);
|
|
1322
|
+
};
|
|
844
1323
|
const decision = router.route({ target_endpoint_id, correlation_id });
|
|
845
1324
|
if (decision.kind === "endpoint") {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
routed_at: Date.now(),
|
|
856
|
-
}, { queueOnTransientFailure: typeof payload.seq !== "number" });
|
|
857
|
-
if (!posted && typeof payload.seq === "number")
|
|
858
|
-
return false;
|
|
1325
|
+
const isCorrelationContinuation = typeof correlation_id === "string" &&
|
|
1326
|
+
decision.entry.correlation_ring.has(correlation_id);
|
|
1327
|
+
if (isBackgroundExchangeTarget(decision.entry) &&
|
|
1328
|
+
!isCorrelationContinuation) {
|
|
1329
|
+
return addUndispatchedFromPayload({
|
|
1330
|
+
reason: "unaddressed",
|
|
1331
|
+
originalTargetEndpointId: decision.entry.endpoint_id,
|
|
1332
|
+
originalCorrelationId: correlation_id,
|
|
1333
|
+
});
|
|
859
1334
|
}
|
|
860
1335
|
recordInboundCorrelation(decision.entry.endpoint_id, meta);
|
|
861
1336
|
if (decision.entry.state === "active") {
|
|
@@ -874,24 +1349,47 @@ export async function startBrokerDaemon(opts) {
|
|
|
874
1349
|
}
|
|
875
1350
|
else {
|
|
876
1351
|
const buf = reconnectingBuffers.forEndpoint(decision.entry.endpoint_id);
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1352
|
+
try {
|
|
1353
|
+
buf.append({
|
|
1354
|
+
id: payload.messageId ?? randomUUID(),
|
|
1355
|
+
arrived_at: new Date().toISOString(),
|
|
1356
|
+
envelope: {
|
|
1357
|
+
from: payload.from,
|
|
1358
|
+
content: payload.content,
|
|
1359
|
+
contentType: payload.contentType,
|
|
1360
|
+
metadata: meta,
|
|
1361
|
+
...(payload.messageId !== undefined && {
|
|
1362
|
+
sourceMessageId: payload.messageId,
|
|
1363
|
+
}),
|
|
1364
|
+
},
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
catch (err) {
|
|
1368
|
+
logger.warn("buffer_for_reconnecting_endpoint_failed", {
|
|
1369
|
+
endpoint_id: decision.entry.endpoint_id,
|
|
1370
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1371
|
+
});
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
890
1374
|
logger.info("buffered_for_reconnecting_endpoint", {
|
|
891
1375
|
endpoint_id: decision.entry.endpoint_id,
|
|
892
1376
|
buffer_size: buf.size(),
|
|
893
1377
|
});
|
|
894
1378
|
}
|
|
1379
|
+
if (payload.messageId !== undefined) {
|
|
1380
|
+
const posted = await directPostRuntimeInboundRouted({
|
|
1381
|
+
type: "runtime_inbound_routed",
|
|
1382
|
+
version: 1,
|
|
1383
|
+
source_message_id: payload.messageId,
|
|
1384
|
+
routed_to_endpoint_id: decision.entry.endpoint_id,
|
|
1385
|
+
route_kind: decision.entry.state === "active"
|
|
1386
|
+
? "active_endpoint"
|
|
1387
|
+
: "reconnecting_endpoint",
|
|
1388
|
+
routed_at: Date.now(),
|
|
1389
|
+
}, { queueOnTransientFailure: typeof payload.seq !== "number" });
|
|
1390
|
+
if (!posted && typeof payload.seq === "number")
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
895
1393
|
return markDeliverySeqAccepted(payload.seq);
|
|
896
1394
|
}
|
|
897
1395
|
let reason = "unaddressed";
|
|
@@ -911,55 +1409,11 @@ export async function startBrokerDaemon(opts) {
|
|
|
911
1409
|
});
|
|
912
1410
|
}
|
|
913
1411
|
}
|
|
914
|
-
|
|
915
|
-
id: payload.messageId ?? randomUUID(),
|
|
916
|
-
...(payload.messageId !== undefined && {
|
|
917
|
-
source_message_id: payload.messageId,
|
|
918
|
-
}),
|
|
919
|
-
arrived_at: new Date().toISOString(),
|
|
920
|
-
sender_address: payload.from,
|
|
921
|
-
content: payload.content,
|
|
922
|
-
content_type: payload.contentType,
|
|
923
|
-
metadata: meta,
|
|
1412
|
+
return addUndispatchedFromPayload({
|
|
924
1413
|
reason,
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
};
|
|
928
|
-
const added = undispatchedInbox.tryAdd(undispatched);
|
|
929
|
-
if (!added) {
|
|
930
|
-
logger.warn("undispatched_inbox_at_capacity", {
|
|
931
|
-
from: payload.from,
|
|
932
|
-
size: undispatchedInbox.size(),
|
|
933
|
-
capacity: undispatchedInbox.capacity(),
|
|
934
|
-
});
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
|
-
logger.info("undispatched_added", { id: undispatched.id, reason });
|
|
938
|
-
const wireReason = undispatched.reason === "unaddressed"
|
|
939
|
-
? "no_target_endpoint"
|
|
940
|
-
: undispatched.reason;
|
|
941
|
-
const posted = await directPostUndispatched({
|
|
942
|
-
type: "undispatched_changed",
|
|
943
|
-
version: 1,
|
|
944
|
-
action: "add",
|
|
945
|
-
undispatched_id: undispatched.id,
|
|
946
|
-
...(undispatched.source_message_id !== undefined && {
|
|
947
|
-
source_message_id: undispatched.source_message_id,
|
|
948
|
-
}),
|
|
949
|
-
sender_address: undispatched.sender_address,
|
|
950
|
-
content_preview: truncateContentPreview(undispatched.content, CONTENT_PREVIEW_MAX_CODEPOINTS),
|
|
951
|
-
reason: wireReason,
|
|
952
|
-
...(undispatched.original_target_endpoint_id !== undefined && {
|
|
953
|
-
target_endpoint_id_hint: undispatched.original_target_endpoint_id,
|
|
954
|
-
}),
|
|
955
|
-
...(typeof correlation_id === "string" && {
|
|
956
|
-
in_reply_to: correlation_id,
|
|
957
|
-
}),
|
|
958
|
-
arrived_at: Date.parse(undispatched.arrived_at),
|
|
959
|
-
}, { queueOnTransientFailure: typeof payload.seq !== "number" });
|
|
960
|
-
if (!posted)
|
|
961
|
-
return false;
|
|
962
|
-
return markDeliverySeqAccepted(payload.seq);
|
|
1414
|
+
originalTargetEndpointId: originalTarget,
|
|
1415
|
+
originalCorrelationId: correlation_id,
|
|
1416
|
+
});
|
|
963
1417
|
};
|
|
964
1418
|
connector.on("message_received", ({ payload }) => {
|
|
965
1419
|
let task;
|
|
@@ -1081,6 +1535,7 @@ export async function startBrokerDaemon(opts) {
|
|
|
1081
1535
|
});
|
|
1082
1536
|
}
|
|
1083
1537
|
await runtimeInboundRoutedEmitter.shutdown();
|
|
1538
|
+
await flushRuntimeAssignmentsOnShutdown();
|
|
1084
1539
|
const endpoints = registry.list();
|
|
1085
1540
|
await Promise.all(endpoints.map(async (entry) => {
|
|
1086
1541
|
try {
|