@llblab/pi-telegram 0.27.12 → 0.28.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/AGENTS.md +150 -258
- package/BACKLOG.md +1 -169
- package/CHANGELOG.md +393 -443
- package/README.md +9 -6
- package/api/updates.ts +5 -0
- package/docs/architecture.md +71 -26
- package/docs/multi-instance-bus.md +23 -5
- package/docs/public-api.md +7 -6
- package/docs/ui-style.md +6 -6
- package/docs/updates.md +29 -11
- package/index.ts +356 -246
- package/lib/activity-verbosity.ts +26 -0
- package/lib/bindings.ts +240 -2
- package/lib/bus-follower.ts +436 -238
- package/lib/bus-leader.ts +395 -42
- package/lib/bus.ts +994 -153
- package/lib/commands.ts +184 -30
- package/lib/config.ts +23 -2
- package/lib/journal.ts +3140 -0
- package/lib/lifecycle.ts +4 -0
- package/lib/locks.ts +4 -2
- package/lib/media.ts +71 -32
- package/lib/menu-queue.ts +31 -17
- package/lib/menu.ts +5 -3
- package/lib/model.ts +51 -24
- package/lib/ownership.ts +42 -7
- package/lib/paths.ts +35 -0
- package/lib/polling.ts +591 -106
- package/lib/prompts.ts +17 -0
- package/lib/queue.ts +732 -143
- package/lib/routing.ts +291 -64
- package/lib/runtime.ts +26 -10
- package/lib/status.ts +257 -18
- package/lib/sync.ts +131 -5
- package/lib/telegram-api.ts +41 -11
- package/lib/text-groups.ts +75 -35
- package/lib/threads.ts +112 -4
- package/lib/turns.ts +79 -14
- package/lib/updates.ts +3771 -223
- package/package.json +3 -3
- package/scripts/check-downgrade.mjs +435 -0
package/lib/bus-follower.ts
CHANGED
|
@@ -10,24 +10,33 @@ import { basename } from "node:path";
|
|
|
10
10
|
|
|
11
11
|
import * as Sync from "./sync.ts";
|
|
12
12
|
import * as Threads from "./threads.ts";
|
|
13
|
+
import { parseTelegramUpdateJournalQueueOwner } from "./journal.ts";
|
|
13
14
|
import type { TelegramLockEntry, TelegramLockState } from "./locks.ts";
|
|
15
|
+
import type {
|
|
16
|
+
TelegramQueueHandoffPayload,
|
|
17
|
+
TelegramQueueHandoffStageResult,
|
|
18
|
+
} from "./queue.ts";
|
|
14
19
|
import type { TelegramTarget } from "./target.ts";
|
|
15
20
|
import {
|
|
16
21
|
isTelegramApiMethodRetrySafe,
|
|
17
22
|
TelegramApiCommitUnknownError,
|
|
18
23
|
} from "./telegram-api.ts";
|
|
19
24
|
import {
|
|
25
|
+
createTelegramBusFollowerDeliveryIdentity,
|
|
20
26
|
createTelegramBusFollowerTargetController,
|
|
21
27
|
createTelegramBusForeignOwnedUpdateForwarder,
|
|
22
28
|
createTelegramBusLocalServer,
|
|
23
29
|
createTelegramBusRequestIdFactory,
|
|
24
30
|
createUnauthorizedBusAck,
|
|
31
|
+
getTelegramBusProtocolCompatibility,
|
|
25
32
|
getTelegramBusSocketPath,
|
|
33
|
+
isTelegramBusEnvelopeAuthorized,
|
|
26
34
|
resolveTelegramBusSocketPath,
|
|
27
35
|
sendTelegramBusLocalEnvelope,
|
|
28
36
|
type TelegramBusAgentMessage,
|
|
29
37
|
type TelegramBusAgentTargetSelector,
|
|
30
38
|
type TelegramBusEnvelope,
|
|
39
|
+
type TelegramBusProtocolIdentity,
|
|
31
40
|
type TelegramBusSocketPathSource,
|
|
32
41
|
} from "./bus.ts";
|
|
33
42
|
import {
|
|
@@ -158,12 +167,18 @@ export interface TelegramBusFollowerRegistrationState {
|
|
|
158
167
|
getSlot: () => string | undefined;
|
|
159
168
|
getThreadName: () => string | undefined;
|
|
160
169
|
getGeneration: () => string | undefined;
|
|
170
|
+
getLeaderProtocol: () => TelegramBusProtocolIdentity | undefined;
|
|
161
171
|
getEligibleElectionSlots: () => readonly string[];
|
|
162
172
|
setEligibleElectionSlots: (slots: readonly string[]) => void;
|
|
163
173
|
setRegistered: (
|
|
164
174
|
registered: boolean,
|
|
165
175
|
target?: TelegramTarget,
|
|
166
|
-
metadata?: {
|
|
176
|
+
metadata?: {
|
|
177
|
+
slot?: string;
|
|
178
|
+
threadName?: string;
|
|
179
|
+
generation?: string;
|
|
180
|
+
leaderProtocol?: TelegramBusProtocolIdentity;
|
|
181
|
+
},
|
|
167
182
|
) => void;
|
|
168
183
|
}
|
|
169
184
|
|
|
@@ -172,12 +187,33 @@ export interface TelegramBusForwardedUpdateReceiverRuntime {
|
|
|
172
187
|
stop: () => Promise<void>;
|
|
173
188
|
}
|
|
174
189
|
|
|
190
|
+
export interface TelegramBusFollowerDurableAdmissionResult {
|
|
191
|
+
deliveryId: string;
|
|
192
|
+
sourceUpdateId: number;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface TelegramBusFollowerDurableAdmissionPort<TContext> {
|
|
196
|
+
admit: (
|
|
197
|
+
envelope: Extract<
|
|
198
|
+
TelegramBusEnvelope,
|
|
199
|
+
{
|
|
200
|
+
kind:
|
|
201
|
+
| "leader.forwardCallback"
|
|
202
|
+
| "leader.forwardReaction"
|
|
203
|
+
| "leader.forwardMessage"
|
|
204
|
+
| "leader.forwardEditedMessage";
|
|
205
|
+
}
|
|
206
|
+
>,
|
|
207
|
+
ctx: TContext,
|
|
208
|
+
) => Promise<TelegramBusFollowerDurableAdmissionResult>;
|
|
209
|
+
}
|
|
210
|
+
|
|
175
211
|
export interface TelegramBusFollowerClientRuntimeDeps<TMessage = unknown> {
|
|
176
212
|
socketPath: TelegramBusSocketPathSource;
|
|
177
213
|
instanceId: string;
|
|
178
214
|
getApiAuthSecret?: () => string | undefined;
|
|
179
215
|
getForwardingAuthSecret?: () => string | undefined;
|
|
180
|
-
getRegistrationGeneration
|
|
216
|
+
getRegistrationGeneration: () => string | undefined;
|
|
181
217
|
getForwardCommentBatchPosition?: (
|
|
182
218
|
message: TMessage,
|
|
183
219
|
) => "comment" | "forward" | undefined;
|
|
@@ -194,7 +230,7 @@ export interface TelegramBusFollowerApiCallerDeps {
|
|
|
194
230
|
instanceId: string;
|
|
195
231
|
createRequestId: () => string;
|
|
196
232
|
getAuthSecret?: () => string | undefined;
|
|
197
|
-
getRegistrationGeneration
|
|
233
|
+
getRegistrationGeneration: () => string | undefined;
|
|
198
234
|
getNowMs?: () => number;
|
|
199
235
|
timeoutMs?: number;
|
|
200
236
|
}
|
|
@@ -204,6 +240,7 @@ export interface TelegramBusFollowerRegistrationRuntimeDeps<
|
|
|
204
240
|
> {
|
|
205
241
|
instanceId: string;
|
|
206
242
|
createRequestId: () => string;
|
|
243
|
+
protocolIdentity: TelegramBusProtocolIdentity;
|
|
207
244
|
getLeaderAuthSecret?: (leader: { busSecret?: string }) => string | undefined;
|
|
208
245
|
setActiveAuthSecret?: (secret: string | undefined) => void;
|
|
209
246
|
followerBusSocketPath?: string;
|
|
@@ -217,6 +254,8 @@ export interface TelegramBusFollowerRegistrationRuntimeDeps<
|
|
|
217
254
|
getThreadName?: (ctx: TContext) => string | undefined;
|
|
218
255
|
getNowMs?: () => number;
|
|
219
256
|
getPid?: () => number;
|
|
257
|
+
getProcessBirthId?: () => string;
|
|
258
|
+
getSessionGeneration?: () => number;
|
|
220
259
|
timeoutMs?: number;
|
|
221
260
|
registrationTimeoutMs?: number;
|
|
222
261
|
registrationRetryAttempts?: number;
|
|
@@ -228,6 +267,7 @@ export interface TelegramBusFollowerRegistrationRuntimeDeps<
|
|
|
228
267
|
details?: Record<string, unknown>,
|
|
229
268
|
) => void;
|
|
230
269
|
onHeartbeatFailure?: (error: unknown, ctx: TContext) => Promise<void> | void;
|
|
270
|
+
onRegistered?: (ctx: TContext) => Promise<void> | void;
|
|
231
271
|
}
|
|
232
272
|
|
|
233
273
|
export function createTelegramManualFollowerProfileKeyResolver(input: {
|
|
@@ -399,37 +439,21 @@ export interface TelegramBusFollowerHeartbeatRecoveryHandlerDeps<TContext> {
|
|
|
399
439
|
) => void;
|
|
400
440
|
}
|
|
401
441
|
|
|
402
|
-
export interface TelegramBusForwardedUpdateReceiverRuntimeDeps<
|
|
403
|
-
TContext,
|
|
404
|
-
TReactionUpdate,
|
|
405
|
-
TCallbackQuery,
|
|
406
|
-
TMessage = unknown,
|
|
407
|
-
> {
|
|
442
|
+
export interface TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext> {
|
|
408
443
|
socketPath: TelegramBusSocketPathSource;
|
|
409
444
|
instanceId: string;
|
|
410
445
|
getAuthSecret?: () => string | undefined;
|
|
411
|
-
getRegistrationGeneration
|
|
446
|
+
getRegistrationGeneration: () => string | undefined;
|
|
447
|
+
getRecipientBindingKey: () => string | undefined;
|
|
448
|
+
durableAdmission: TelegramBusFollowerDurableAdmissionPort<TContext>;
|
|
412
449
|
getContext: () => TContext | undefined;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
reactionUpdate: TReactionUpdate,
|
|
419
|
-
ctx: TContext,
|
|
420
|
-
) => Promise<void> | void;
|
|
421
|
-
prepareForwardedMessage?: (
|
|
422
|
-
message: TMessage,
|
|
423
|
-
position: "comment" | "forward",
|
|
424
|
-
) => void;
|
|
425
|
-
handleForwardedMessage?: (
|
|
426
|
-
message: TMessage,
|
|
427
|
-
ctx: TContext,
|
|
428
|
-
) => Promise<void> | void;
|
|
429
|
-
handleForwardedEditedMessage?: (
|
|
430
|
-
message: TMessage,
|
|
450
|
+
handleQueueHandoff?: (
|
|
451
|
+
envelope: Extract<
|
|
452
|
+
TelegramBusEnvelope,
|
|
453
|
+
{ kind: "leader.offerQueueHandoff" }
|
|
454
|
+
>,
|
|
431
455
|
ctx: TContext,
|
|
432
|
-
) => Promise<
|
|
456
|
+
) => Promise<TelegramQueueHandoffStageResult> | TelegramQueueHandoffStageResult;
|
|
433
457
|
handleReplaceTarget?: (
|
|
434
458
|
input: {
|
|
435
459
|
target: TelegramTarget & { threadId: number };
|
|
@@ -445,83 +469,13 @@ export interface TelegramBusForwardedUpdateReceiverRuntimeDeps<
|
|
|
445
469
|
) => void;
|
|
446
470
|
}
|
|
447
471
|
|
|
448
|
-
export interface TelegramBusFollowerRuntimeAssemblyDeps<
|
|
449
|
-
TContext extends { cwd?: string },
|
|
450
|
-
TReactionUpdate,
|
|
451
|
-
TCallbackQuery,
|
|
452
|
-
TMessage = unknown,
|
|
453
|
-
> {
|
|
454
|
-
receiver: Omit<
|
|
455
|
-
TelegramBusForwardedUpdateReceiverRuntimeDeps<
|
|
456
|
-
TContext,
|
|
457
|
-
TReactionUpdate,
|
|
458
|
-
TCallbackQuery,
|
|
459
|
-
TMessage
|
|
460
|
-
>,
|
|
461
|
-
"handleReplaceTarget"
|
|
462
|
-
>;
|
|
463
|
-
targetReplacement: TelegramBusFollowerTargetReplacementHandlerDeps<TContext>;
|
|
464
|
-
recovery: Omit<
|
|
465
|
-
TelegramBusFollowerHeartbeatRecoveryHandlerDeps<TContext>,
|
|
466
|
-
"getRegistrationRuntime"
|
|
467
|
-
>;
|
|
468
|
-
registration: Omit<
|
|
469
|
-
TelegramBusFollowerRegistrationRuntimeDeps<TContext>,
|
|
470
|
-
"startReceiving" | "stopReceiving" | "onHeartbeatFailure"
|
|
471
|
-
>;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
472
|
export interface TelegramBusFollowerRuntimeAssembly<TContext> {
|
|
475
473
|
receiver: TelegramBusForwardedUpdateReceiverRuntime;
|
|
476
474
|
registration: TelegramBusFollowerRegistrationRuntime<TContext>;
|
|
477
475
|
}
|
|
478
476
|
|
|
479
|
-
export function createTelegramBusForwardedRouteHandlers<
|
|
480
|
-
TContext,
|
|
481
|
-
TReactionUpdate,
|
|
482
|
-
TCallbackQuery,
|
|
483
|
-
TMessage,
|
|
484
|
-
>(route: {
|
|
485
|
-
handleUpdate(
|
|
486
|
-
update: {
|
|
487
|
-
callback_query?: TCallbackQuery;
|
|
488
|
-
message?: TMessage;
|
|
489
|
-
edited_message?: TMessage;
|
|
490
|
-
},
|
|
491
|
-
ctx: TContext,
|
|
492
|
-
): Promise<void> | void;
|
|
493
|
-
handleAuthorizedReactionUpdate(
|
|
494
|
-
reactionUpdate: TReactionUpdate,
|
|
495
|
-
ctx: TContext,
|
|
496
|
-
): Promise<void> | void;
|
|
497
|
-
}): Pick<
|
|
498
|
-
TelegramBusForwardedUpdateReceiverRuntimeDeps<
|
|
499
|
-
TContext,
|
|
500
|
-
TReactionUpdate,
|
|
501
|
-
TCallbackQuery,
|
|
502
|
-
TMessage
|
|
503
|
-
>,
|
|
504
|
-
| "handleForwardedCallback"
|
|
505
|
-
| "handleForwardedReaction"
|
|
506
|
-
| "handleForwardedMessage"
|
|
507
|
-
| "handleForwardedEditedMessage"
|
|
508
|
-
> {
|
|
509
|
-
return {
|
|
510
|
-
handleForwardedCallback: (query, ctx) =>
|
|
511
|
-
route.handleUpdate({ callback_query: query }, ctx),
|
|
512
|
-
handleForwardedReaction: route.handleAuthorizedReactionUpdate,
|
|
513
|
-
handleForwardedMessage: (message, ctx) =>
|
|
514
|
-
route.handleUpdate({ message }, ctx),
|
|
515
|
-
handleForwardedEditedMessage: (message, ctx) =>
|
|
516
|
-
route.handleUpdate({ edited_message: message }, ctx),
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
|
|
520
477
|
export interface TelegramBusFollowerRuntimeAssemblyPorts<
|
|
521
478
|
TContext extends { cwd?: string },
|
|
522
|
-
TReactionUpdate,
|
|
523
|
-
TCallbackQuery,
|
|
524
|
-
TMessage = unknown,
|
|
525
479
|
> {
|
|
526
480
|
instanceId: string;
|
|
527
481
|
registrationState: TelegramBusFollowerRegistrationState;
|
|
@@ -531,13 +485,11 @@ export interface TelegramBusFollowerRuntimeAssemblyPorts<
|
|
|
531
485
|
details?: Record<string, unknown>,
|
|
532
486
|
) => void;
|
|
533
487
|
receiver: Omit<
|
|
534
|
-
TelegramBusForwardedUpdateReceiverRuntimeDeps<
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
>,
|
|
540
|
-
"handleReplaceTarget" | "instanceId" | "recordRuntimeEvent"
|
|
488
|
+
TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext>,
|
|
489
|
+
| "handleReplaceTarget"
|
|
490
|
+
| "instanceId"
|
|
491
|
+
| "recordRuntimeEvent"
|
|
492
|
+
| "getRegistrationGeneration"
|
|
541
493
|
>;
|
|
542
494
|
targetReplacement: Omit<
|
|
543
495
|
TelegramBusFollowerTargetReplacementHandlerDeps<TContext>,
|
|
@@ -555,81 +507,42 @@ export interface TelegramBusFollowerRuntimeAssemblyPorts<
|
|
|
555
507
|
| "instanceId"
|
|
556
508
|
| "registrationState"
|
|
557
509
|
| "recordRuntimeEvent"
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
export function createTelegramBusFollowerRuntimeAssemblyDeps<
|
|
562
|
-
TContext extends { cwd?: string },
|
|
563
|
-
TReactionUpdate,
|
|
564
|
-
TCallbackQuery,
|
|
565
|
-
TMessage = unknown,
|
|
566
|
-
>(
|
|
567
|
-
ports: TelegramBusFollowerRuntimeAssemblyPorts<
|
|
568
|
-
TContext,
|
|
569
|
-
TReactionUpdate,
|
|
570
|
-
TCallbackQuery,
|
|
571
|
-
TMessage
|
|
572
|
-
>,
|
|
573
|
-
): TelegramBusFollowerRuntimeAssemblyDeps<
|
|
574
|
-
TContext,
|
|
575
|
-
TReactionUpdate,
|
|
576
|
-
TCallbackQuery,
|
|
577
|
-
TMessage
|
|
578
|
-
> {
|
|
579
|
-
return {
|
|
580
|
-
receiver: {
|
|
581
|
-
...ports.receiver,
|
|
582
|
-
instanceId: ports.instanceId,
|
|
583
|
-
recordRuntimeEvent: ports.recordRuntimeEvent,
|
|
584
|
-
},
|
|
585
|
-
targetReplacement: {
|
|
586
|
-
...ports.targetReplacement,
|
|
587
|
-
instanceId: ports.instanceId,
|
|
588
|
-
registrationState: ports.registrationState,
|
|
589
|
-
recordRuntimeEvent: ports.recordRuntimeEvent,
|
|
590
|
-
},
|
|
591
|
-
recovery: {
|
|
592
|
-
...ports.recovery,
|
|
593
|
-
registrationState: ports.registrationState,
|
|
594
|
-
recordRuntimeEvent: ports.recordRuntimeEvent,
|
|
595
|
-
},
|
|
596
|
-
registration: {
|
|
597
|
-
...ports.registration,
|
|
598
|
-
instanceId: ports.instanceId,
|
|
599
|
-
registrationState: ports.registrationState,
|
|
600
|
-
recordRuntimeEvent: ports.recordRuntimeEvent,
|
|
601
|
-
},
|
|
510
|
+
| "protocolIdentity"
|
|
511
|
+
> & {
|
|
512
|
+
protocolIdentity: TelegramBusProtocolIdentity;
|
|
602
513
|
};
|
|
603
514
|
}
|
|
604
515
|
|
|
605
516
|
export function createTelegramBusFollowerRuntimeAssembly<
|
|
606
517
|
TContext extends { cwd?: string },
|
|
607
|
-
TReactionUpdate,
|
|
608
|
-
TCallbackQuery,
|
|
609
|
-
TMessage = unknown,
|
|
610
518
|
>(
|
|
611
|
-
|
|
612
|
-
TContext,
|
|
613
|
-
TReactionUpdate,
|
|
614
|
-
TCallbackQuery,
|
|
615
|
-
TMessage
|
|
616
|
-
>,
|
|
519
|
+
ports: TelegramBusFollowerRuntimeAssemblyPorts<TContext>,
|
|
617
520
|
): TelegramBusFollowerRuntimeAssembly<TContext> {
|
|
521
|
+
const sharedRuntimeDeps = {
|
|
522
|
+
instanceId: ports.instanceId,
|
|
523
|
+
registrationState: ports.registrationState,
|
|
524
|
+
recordRuntimeEvent: ports.recordRuntimeEvent,
|
|
525
|
+
};
|
|
618
526
|
const receiver = createTelegramBusForwardedUpdateReceiverRuntime({
|
|
619
|
-
...
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
527
|
+
...ports.receiver,
|
|
528
|
+
instanceId: ports.instanceId,
|
|
529
|
+
recordRuntimeEvent: ports.recordRuntimeEvent,
|
|
530
|
+
getRegistrationGeneration: ports.registrationState.getGeneration,
|
|
531
|
+
handleReplaceTarget: createTelegramBusFollowerTargetReplacementHandler({
|
|
532
|
+
...ports.targetReplacement,
|
|
533
|
+
...sharedRuntimeDeps,
|
|
534
|
+
}),
|
|
625
535
|
});
|
|
626
536
|
let registration: TelegramBusFollowerRegistrationRuntime<TContext>;
|
|
627
537
|
const recovery = createTelegramBusFollowerHeartbeatRecoveryHandler({
|
|
628
|
-
...
|
|
538
|
+
...ports.recovery,
|
|
539
|
+
registrationState: ports.registrationState,
|
|
540
|
+
recordRuntimeEvent: ports.recordRuntimeEvent,
|
|
629
541
|
getRegistrationRuntime: () => registration,
|
|
630
542
|
});
|
|
631
543
|
registration = createTelegramBusFollowerRegistrationRuntime({
|
|
632
|
-
...
|
|
544
|
+
...ports.registration,
|
|
545
|
+
...sharedRuntimeDeps,
|
|
633
546
|
startReceiving: receiver.start,
|
|
634
547
|
stopReceiving: receiver.stop,
|
|
635
548
|
onHeartbeatFailure: recovery,
|
|
@@ -640,11 +553,7 @@ export function createTelegramBusFollowerRuntimeAssembly<
|
|
|
640
553
|
export function createTelegramBusFollowerTargetReplacementHandler<TContext>(
|
|
641
554
|
deps: TelegramBusFollowerTargetReplacementHandlerDeps<TContext>,
|
|
642
555
|
): NonNullable<
|
|
643
|
-
TelegramBusForwardedUpdateReceiverRuntimeDeps<
|
|
644
|
-
TContext,
|
|
645
|
-
unknown,
|
|
646
|
-
unknown
|
|
647
|
-
>["handleReplaceTarget"]
|
|
556
|
+
TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext>["handleReplaceTarget"]
|
|
648
557
|
> {
|
|
649
558
|
const getNowMs = deps.getNowMs ?? Date.now;
|
|
650
559
|
return async (input, ctx) => {
|
|
@@ -756,6 +665,12 @@ export function createTelegramBusFollowerClientRuntime<
|
|
|
756
665
|
deps.getForwardCommentBatchPosition,
|
|
757
666
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
758
667
|
}),
|
|
668
|
+
queueHandoff: createTelegramBusFollowerQueueHandoffClient({
|
|
669
|
+
...sharedClientDeps,
|
|
670
|
+
instanceId: deps.instanceId,
|
|
671
|
+
getAuthSecret: deps.getApiAuthSecret,
|
|
672
|
+
getRegistrationGeneration: deps.getRegistrationGeneration,
|
|
673
|
+
}),
|
|
759
674
|
targetController: createTelegramBusFollowerTargetController({
|
|
760
675
|
...sharedClientDeps,
|
|
761
676
|
getAuthSecret: deps.getForwardingAuthSecret,
|
|
@@ -763,6 +678,83 @@ export function createTelegramBusFollowerClientRuntime<
|
|
|
763
678
|
};
|
|
764
679
|
}
|
|
765
680
|
|
|
681
|
+
export function createTelegramBusFollowerQueueHandoffClient(
|
|
682
|
+
deps: TelegramBusFollowerApiCallerDeps,
|
|
683
|
+
): (input: {
|
|
684
|
+
recipientInstanceId: string;
|
|
685
|
+
recipientRegistrationGeneration: string;
|
|
686
|
+
donorProcessId: number;
|
|
687
|
+
donorProcessBirthId: string;
|
|
688
|
+
donorSessionGeneration: number;
|
|
689
|
+
donorAcquisitionId: string;
|
|
690
|
+
donorAcquiredAtMs: number;
|
|
691
|
+
handoffToken: string;
|
|
692
|
+
payload: TelegramQueueHandoffPayload;
|
|
693
|
+
}) => Promise<TelegramQueueHandoffStageResult> {
|
|
694
|
+
const getNowMs = deps.getNowMs ?? Date.now;
|
|
695
|
+
const timeoutMs =
|
|
696
|
+
deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
|
|
697
|
+
return async (input) => {
|
|
698
|
+
const registrationGeneration = deps.getRegistrationGeneration();
|
|
699
|
+
if (!registrationGeneration) {
|
|
700
|
+
throw new Error("Telegram bus follower is not registered.");
|
|
701
|
+
}
|
|
702
|
+
const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
|
|
703
|
+
const response = await sendTelegramBusLocalEnvelope({
|
|
704
|
+
socketPath,
|
|
705
|
+
timeoutMs,
|
|
706
|
+
retry: getTelegramBusTransportRetryPolicy({
|
|
707
|
+
endpoint: socketPath,
|
|
708
|
+
operation: "operation",
|
|
709
|
+
}),
|
|
710
|
+
envelope: {
|
|
711
|
+
kind: "follower.offerQueueHandoff",
|
|
712
|
+
requestId: deps.createRequestId(),
|
|
713
|
+
auth: deps.getAuthSecret?.(),
|
|
714
|
+
instanceId: deps.instanceId,
|
|
715
|
+
registrationGeneration,
|
|
716
|
+
...input,
|
|
717
|
+
sentAtMs: getNowMs(),
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
const queueOwner =
|
|
721
|
+
response?.kind === "bus.ack" && isRecord(response.result)
|
|
722
|
+
? parseTelegramUpdateJournalQueueOwner(response.result.queueOwner)
|
|
723
|
+
: undefined;
|
|
724
|
+
if (
|
|
725
|
+
response?.kind === "bus.ack" &&
|
|
726
|
+
response.ok &&
|
|
727
|
+
isRecord(response.result) &&
|
|
728
|
+
response.result.status === "staged" &&
|
|
729
|
+
typeof response.result.receiptId === "string" &&
|
|
730
|
+
Array.isArray(response.result.sourceUpdateIds) &&
|
|
731
|
+
response.result.sourceUpdateIds.every(Number.isSafeInteger) &&
|
|
732
|
+
input.payload.admissionReceipts.length === 1 &&
|
|
733
|
+
response.result.receiptId ===
|
|
734
|
+
input.payload.admissionReceipts[0]?.receiptId &&
|
|
735
|
+
response.result.sourceUpdateIds.length ===
|
|
736
|
+
input.payload.admissionReceipts[0].sourceUpdateIds.length &&
|
|
737
|
+
response.result.sourceUpdateIds.every(
|
|
738
|
+
(updateId, index) =>
|
|
739
|
+
updateId === input.payload.admissionReceipts[0]!.sourceUpdateIds[index],
|
|
740
|
+
) &&
|
|
741
|
+
queueOwner
|
|
742
|
+
) {
|
|
743
|
+
return {
|
|
744
|
+
status: "staged",
|
|
745
|
+
receiptId: response.result.receiptId,
|
|
746
|
+
sourceUpdateIds: response.result.sourceUpdateIds as number[],
|
|
747
|
+
queueOwner,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
throw new Error(
|
|
751
|
+
response?.kind === "bus.ack"
|
|
752
|
+
? response.message ?? "Telegram queue handoff was rejected."
|
|
753
|
+
: "Telegram queue handoff did not return an acknowledgement.",
|
|
754
|
+
);
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
766
758
|
export function createTelegramBusAgentMessageClient(
|
|
767
759
|
deps: TelegramBusFollowerApiCallerDeps,
|
|
768
760
|
): {
|
|
@@ -796,13 +788,17 @@ export function createTelegramBusAgentMessageClient(
|
|
|
796
788
|
: "Telegram bus agent message did not return an acknowledgement.",
|
|
797
789
|
);
|
|
798
790
|
};
|
|
799
|
-
const registrationFields = () =>
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
791
|
+
const registrationFields = () => {
|
|
792
|
+
const registrationGeneration = deps.getRegistrationGeneration();
|
|
793
|
+
if (!registrationGeneration) {
|
|
794
|
+
throw new Error("Telegram bus follower is not registered.");
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
auth: deps.getAuthSecret?.(),
|
|
798
|
+
instanceId: deps.instanceId,
|
|
799
|
+
registrationGeneration,
|
|
800
|
+
};
|
|
801
|
+
};
|
|
806
802
|
return {
|
|
807
803
|
async resolveTarget(selector) {
|
|
808
804
|
const result = await request({
|
|
@@ -844,6 +840,10 @@ export function createTelegramBusFollowerApiCaller(
|
|
|
844
840
|
deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
|
|
845
841
|
return async (method, args) => {
|
|
846
842
|
const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
|
|
843
|
+
const registrationGeneration = deps.getRegistrationGeneration();
|
|
844
|
+
if (!registrationGeneration) {
|
|
845
|
+
throw new Error("Telegram bus follower is not registered.");
|
|
846
|
+
}
|
|
847
847
|
let response: TelegramBusEnvelope | undefined;
|
|
848
848
|
try {
|
|
849
849
|
response = await sendTelegramBusLocalEnvelope({
|
|
@@ -858,11 +858,7 @@ export function createTelegramBusFollowerApiCaller(
|
|
|
858
858
|
requestId: deps.createRequestId(),
|
|
859
859
|
auth: deps.getAuthSecret?.(),
|
|
860
860
|
instanceId: deps.instanceId,
|
|
861
|
-
|
|
862
|
-
? {
|
|
863
|
-
registrationGeneration: deps.getRegistrationGeneration?.(),
|
|
864
|
-
}
|
|
865
|
-
: {}),
|
|
861
|
+
registrationGeneration,
|
|
866
862
|
method,
|
|
867
863
|
args,
|
|
868
864
|
sentAtMs: getNowMs(),
|
|
@@ -1048,6 +1044,7 @@ export function createTelegramBusFollowerRegistrationState(
|
|
|
1048
1044
|
let slot: string | undefined;
|
|
1049
1045
|
let threadName: string | undefined;
|
|
1050
1046
|
let generation: string | undefined;
|
|
1047
|
+
let leaderProtocol: TelegramBusProtocolIdentity | undefined;
|
|
1051
1048
|
let eligibleElectionSlots: string[] = [];
|
|
1052
1049
|
return {
|
|
1053
1050
|
isRegistered: () => registered,
|
|
@@ -1055,6 +1052,10 @@ export function createTelegramBusFollowerRegistrationState(
|
|
|
1055
1052
|
getSlot: () => slot,
|
|
1056
1053
|
getThreadName: () => threadName,
|
|
1057
1054
|
getGeneration: () => generation,
|
|
1055
|
+
getLeaderProtocol: () =>
|
|
1056
|
+
leaderProtocol
|
|
1057
|
+
? { ...leaderProtocol, capabilities: [...leaderProtocol.capabilities] }
|
|
1058
|
+
: undefined,
|
|
1058
1059
|
getEligibleElectionSlots: () => [...eligibleElectionSlots],
|
|
1059
1060
|
setEligibleElectionSlots: (slots) => {
|
|
1060
1061
|
eligibleElectionSlots = Array.from(
|
|
@@ -1068,6 +1069,13 @@ export function createTelegramBusFollowerRegistrationState(
|
|
|
1068
1069
|
slot = next ? metadata?.slot : undefined;
|
|
1069
1070
|
threadName = next ? metadata?.threadName : undefined;
|
|
1070
1071
|
generation = next ? metadata?.generation : undefined;
|
|
1072
|
+
leaderProtocol =
|
|
1073
|
+
next && metadata?.leaderProtocol
|
|
1074
|
+
? {
|
|
1075
|
+
...metadata.leaderProtocol,
|
|
1076
|
+
capabilities: [...metadata.leaderProtocol.capabilities],
|
|
1077
|
+
}
|
|
1078
|
+
: undefined;
|
|
1071
1079
|
if (availabilityChanged) options.onAvailabilityChanged?.();
|
|
1072
1080
|
},
|
|
1073
1081
|
};
|
|
@@ -1337,6 +1345,8 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1337
1345
|
deps.registrationRetryDelayMs ??
|
|
1338
1346
|
TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_DELAY_MS;
|
|
1339
1347
|
let heartbeatInterval: ReturnType<typeof setInterval> | undefined;
|
|
1348
|
+
let heartbeatPromise: Promise<void> | undefined;
|
|
1349
|
+
let heartbeatPromiseGeneration: string | undefined;
|
|
1340
1350
|
let activeLeaderSocketPath: string | undefined;
|
|
1341
1351
|
let activeAuthSecret: string | undefined;
|
|
1342
1352
|
let activeRegistrationGeneration: string | undefined;
|
|
@@ -1353,22 +1363,39 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1353
1363
|
stopHeartbeat();
|
|
1354
1364
|
activeAuthSecret = undefined;
|
|
1355
1365
|
activeRegistrationGeneration = undefined;
|
|
1366
|
+
heartbeatPromise = undefined;
|
|
1367
|
+
heartbeatPromiseGeneration = undefined;
|
|
1356
1368
|
deps.setActiveAuthSecret?.(undefined);
|
|
1357
1369
|
deps.registrationState?.setRegistered(false);
|
|
1358
1370
|
lastKnownTarget = undefined;
|
|
1359
1371
|
lastKnownSlot = undefined;
|
|
1360
1372
|
lastKnownThreadName = undefined;
|
|
1361
1373
|
activeContext = undefined;
|
|
1362
|
-
void deps.stopReceiving?.()
|
|
1374
|
+
void Promise.resolve(deps.stopReceiving?.()).catch((error) => {
|
|
1375
|
+
try {
|
|
1376
|
+
deps.recordRuntimeEvent?.("bus", error, {
|
|
1377
|
+
phase: "follower-receiver-stop",
|
|
1378
|
+
});
|
|
1379
|
+
} catch {
|
|
1380
|
+
// Stop diagnostics cannot create an unhandled Promise.
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1363
1383
|
};
|
|
1364
1384
|
const sendHeartbeat = async () => {
|
|
1365
|
-
|
|
1385
|
+
const leaderSocketPath = activeLeaderSocketPath;
|
|
1386
|
+
const registrationGeneration = activeRegistrationGeneration;
|
|
1387
|
+
const heartbeatContext = activeContext;
|
|
1388
|
+
if (!leaderSocketPath || !registrationGeneration) return;
|
|
1389
|
+
const isCurrentHeartbeat = (): boolean =>
|
|
1390
|
+
activeLeaderSocketPath === leaderSocketPath &&
|
|
1391
|
+
activeRegistrationGeneration === registrationGeneration &&
|
|
1392
|
+
activeContext === heartbeatContext;
|
|
1366
1393
|
try {
|
|
1367
1394
|
const response = await sendTelegramBusLocalEnvelope({
|
|
1368
|
-
socketPath:
|
|
1395
|
+
socketPath: leaderSocketPath,
|
|
1369
1396
|
timeoutMs: deps.timeoutMs,
|
|
1370
1397
|
retry: getTelegramBusTransportRetryPolicy({
|
|
1371
|
-
endpoint:
|
|
1398
|
+
endpoint: leaderSocketPath,
|
|
1372
1399
|
operation: "operation",
|
|
1373
1400
|
}),
|
|
1374
1401
|
envelope: {
|
|
@@ -1376,12 +1403,11 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1376
1403
|
requestId: deps.createRequestId(),
|
|
1377
1404
|
auth: activeAuthSecret,
|
|
1378
1405
|
instanceId: deps.instanceId,
|
|
1379
|
-
|
|
1380
|
-
? { registrationGeneration: activeRegistrationGeneration }
|
|
1381
|
-
: {}),
|
|
1406
|
+
registrationGeneration,
|
|
1382
1407
|
sentAtMs: getNowMs(),
|
|
1383
1408
|
},
|
|
1384
1409
|
});
|
|
1410
|
+
if (!isCurrentHeartbeat()) return;
|
|
1385
1411
|
if (response?.kind === "bus.ack" && response.ok) {
|
|
1386
1412
|
const heartbeatResult = isRecord(response.result)
|
|
1387
1413
|
? response.result
|
|
@@ -1399,15 +1425,49 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1399
1425
|
);
|
|
1400
1426
|
}
|
|
1401
1427
|
} catch (error) {
|
|
1402
|
-
|
|
1403
|
-
|
|
1428
|
+
if (!isCurrentHeartbeat()) return;
|
|
1429
|
+
try {
|
|
1430
|
+
deps.recordRuntimeEvent?.("bus", error, {
|
|
1431
|
+
phase: "follower-heartbeat",
|
|
1432
|
+
});
|
|
1433
|
+
} catch {
|
|
1434
|
+
// Diagnostics cannot replace heartbeat recovery.
|
|
1435
|
+
}
|
|
1436
|
+
if (!heartbeatContext) return;
|
|
1437
|
+
try {
|
|
1438
|
+
await deps.onHeartbeatFailure?.(error, heartbeatContext);
|
|
1439
|
+
} catch (recoveryError) {
|
|
1440
|
+
try {
|
|
1441
|
+
deps.recordRuntimeEvent?.("bus", recoveryError, {
|
|
1442
|
+
phase: "follower-heartbeat-recovery",
|
|
1443
|
+
});
|
|
1444
|
+
} catch {
|
|
1445
|
+
// A diagnostic sink cannot create an unhandled interval rejection.
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
const requestHeartbeat = (): Promise<void> => {
|
|
1451
|
+
const generation = activeRegistrationGeneration;
|
|
1452
|
+
if (heartbeatPromise && heartbeatPromiseGeneration === generation) {
|
|
1453
|
+
return heartbeatPromise;
|
|
1404
1454
|
}
|
|
1455
|
+
let tracked: Promise<void>;
|
|
1456
|
+
tracked = sendHeartbeat().finally(() => {
|
|
1457
|
+
if (heartbeatPromise === tracked) {
|
|
1458
|
+
heartbeatPromise = undefined;
|
|
1459
|
+
heartbeatPromiseGeneration = undefined;
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
heartbeatPromise = tracked;
|
|
1463
|
+
heartbeatPromiseGeneration = generation;
|
|
1464
|
+
return tracked;
|
|
1405
1465
|
};
|
|
1406
1466
|
const startHeartbeat = (socketPath: string) => {
|
|
1407
1467
|
stopHeartbeat();
|
|
1408
1468
|
activeLeaderSocketPath = socketPath;
|
|
1409
1469
|
heartbeatInterval = setInterval(() => {
|
|
1410
|
-
void
|
|
1470
|
+
void requestHeartbeat();
|
|
1411
1471
|
}, heartbeatMs);
|
|
1412
1472
|
heartbeatInterval.unref?.();
|
|
1413
1473
|
};
|
|
@@ -1460,6 +1520,8 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1460
1520
|
: {}),
|
|
1461
1521
|
cwd: ctx.cwd,
|
|
1462
1522
|
pid: getPid(),
|
|
1523
|
+
processBirthId: deps.getProcessBirthId?.(),
|
|
1524
|
+
sessionGeneration: deps.getSessionGeneration?.(),
|
|
1463
1525
|
target:
|
|
1464
1526
|
registrationOptions?.target ??
|
|
1465
1527
|
deps.registrationState?.getTarget() ??
|
|
@@ -1467,6 +1529,7 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1467
1529
|
busSocketPath:
|
|
1468
1530
|
deps.getFollowerBusSocketPath?.() ?? deps.followerBusSocketPath,
|
|
1469
1531
|
registrationGeneration,
|
|
1532
|
+
protocol: deps.protocolIdentity,
|
|
1470
1533
|
connectedAtMs: getNowMs(),
|
|
1471
1534
|
},
|
|
1472
1535
|
};
|
|
@@ -1508,6 +1571,21 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1508
1571
|
await deps.stopReceiving?.();
|
|
1509
1572
|
return false;
|
|
1510
1573
|
}
|
|
1574
|
+
const compatibility = getTelegramBusProtocolCompatibility({
|
|
1575
|
+
local: deps.protocolIdentity,
|
|
1576
|
+
remote: response?.kind === "bus.ack" ? response.protocol : undefined,
|
|
1577
|
+
});
|
|
1578
|
+
if (!compatibility.compatible) {
|
|
1579
|
+
stopHeartbeat();
|
|
1580
|
+
activeLeaderSocketPath = undefined;
|
|
1581
|
+
activeAuthSecret = undefined;
|
|
1582
|
+
deps.registrationState?.setRegistered(false);
|
|
1583
|
+
deps.setActiveAuthSecret?.(undefined);
|
|
1584
|
+
await deps.stopReceiving?.();
|
|
1585
|
+
throw new Error(
|
|
1586
|
+
`Incompatible Telegram bus leader protocol: ${compatibility.reason}.`,
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1511
1589
|
if (response?.kind === "bus.ack" && !response.ok) {
|
|
1512
1590
|
stopHeartbeat();
|
|
1513
1591
|
activeLeaderSocketPath = undefined;
|
|
@@ -1525,6 +1603,9 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1525
1603
|
deps.registrationState?.setRegistered(true, registrationResult.target, {
|
|
1526
1604
|
...registrationResult,
|
|
1527
1605
|
generation: registrationGeneration,
|
|
1606
|
+
...(response.protocol
|
|
1607
|
+
? { leaderProtocol: response.protocol }
|
|
1608
|
+
: {}),
|
|
1528
1609
|
});
|
|
1529
1610
|
lastKnownTarget = registrationResult.target;
|
|
1530
1611
|
lastKnownSlot = registrationResult.slot;
|
|
@@ -1532,7 +1613,19 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1532
1613
|
activeLeaderSocketPath = leaderSocketPath;
|
|
1533
1614
|
activeRegistrationGeneration = registrationGeneration;
|
|
1534
1615
|
activeContext = ctx;
|
|
1535
|
-
|
|
1616
|
+
try {
|
|
1617
|
+
await deps.onRegistered?.(ctx);
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
stopHeartbeat();
|
|
1620
|
+
activeLeaderSocketPath = undefined;
|
|
1621
|
+
activeAuthSecret = undefined;
|
|
1622
|
+
activeRegistrationGeneration = undefined;
|
|
1623
|
+
deps.registrationState?.setRegistered(false);
|
|
1624
|
+
deps.setActiveAuthSecret?.(undefined);
|
|
1625
|
+
await deps.stopReceiving?.();
|
|
1626
|
+
throw error;
|
|
1627
|
+
}
|
|
1628
|
+
await requestHeartbeat();
|
|
1536
1629
|
startHeartbeat(leaderSocketPath);
|
|
1537
1630
|
if (
|
|
1538
1631
|
pendingHandoffOptions &&
|
|
@@ -1585,18 +1678,97 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1585
1678
|
};
|
|
1586
1679
|
}
|
|
1587
1680
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1681
|
+
const TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD =
|
|
1682
|
+
"pi_telegram_forward_comment_batch_position";
|
|
1683
|
+
|
|
1684
|
+
export function prepareTelegramBusFollowerJournaledUpdateForExecution<
|
|
1685
|
+
TUpdate extends { message?: unknown } & Record<string, unknown>,
|
|
1593
1686
|
>(
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1687
|
+
update: TUpdate,
|
|
1688
|
+
prepareForwardedMessage: (
|
|
1689
|
+
message: NonNullable<TUpdate["message"]>,
|
|
1690
|
+
position: "comment" | "forward",
|
|
1691
|
+
) => void,
|
|
1692
|
+
): TUpdate {
|
|
1693
|
+
const position = update[TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD];
|
|
1694
|
+
if (
|
|
1695
|
+
update.message !== undefined &&
|
|
1696
|
+
(position === "comment" || position === "forward")
|
|
1697
|
+
) {
|
|
1698
|
+
prepareForwardedMessage(
|
|
1699
|
+
update.message as NonNullable<TUpdate["message"]>,
|
|
1700
|
+
position,
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
if (position === undefined) return update;
|
|
1704
|
+
const prepared = { ...update };
|
|
1705
|
+
delete prepared[TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD];
|
|
1706
|
+
return prepared;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
export function createTelegramBusFollowerDurableAdmissionRuntime<TContext>(deps: {
|
|
1710
|
+
journal: {
|
|
1711
|
+
appendBatch(
|
|
1712
|
+
updates: readonly ({ update_id: number } & Record<string, unknown>)[],
|
|
1713
|
+
): unknown;
|
|
1714
|
+
};
|
|
1715
|
+
signalWorker: (ctx: TContext) => void;
|
|
1716
|
+
}): TelegramBusFollowerDurableAdmissionPort<TContext> {
|
|
1717
|
+
return {
|
|
1718
|
+
async admit(envelope, ctx) {
|
|
1719
|
+
const delivery = envelope.delivery;
|
|
1720
|
+
if (!delivery) {
|
|
1721
|
+
throw new Error("Telegram follower durable admission requires delivery identity.");
|
|
1722
|
+
}
|
|
1723
|
+
const expected = createTelegramBusFollowerDeliveryIdentity({
|
|
1724
|
+
kind: envelope.kind,
|
|
1725
|
+
recipientBindingKey: delivery.recipientBindingKey,
|
|
1726
|
+
sourceUpdateId: delivery.sourceUpdateId,
|
|
1727
|
+
});
|
|
1728
|
+
if (expected.deliveryId !== delivery.deliveryId) {
|
|
1729
|
+
throw new Error("Invalid Telegram follower delivery id.");
|
|
1730
|
+
}
|
|
1731
|
+
const carrier =
|
|
1732
|
+
envelope.kind === "leader.forwardCallback"
|
|
1733
|
+
? envelope.query
|
|
1734
|
+
: envelope.kind === "leader.forwardReaction"
|
|
1735
|
+
? envelope.reactionUpdate
|
|
1736
|
+
: envelope.message;
|
|
1737
|
+
if (
|
|
1738
|
+
!isRecord(carrier) ||
|
|
1739
|
+
carrier.pi_telegram_source_update_id !== delivery.sourceUpdateId
|
|
1740
|
+
) {
|
|
1741
|
+
throw new Error("Telegram follower delivery source update id mismatch.");
|
|
1742
|
+
}
|
|
1743
|
+
const update = {
|
|
1744
|
+
update_id: delivery.sourceUpdateId,
|
|
1745
|
+
...(envelope.kind === "leader.forwardMessage" &&
|
|
1746
|
+
envelope.forwardCommentBatchPosition
|
|
1747
|
+
? {
|
|
1748
|
+
[TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD]:
|
|
1749
|
+
envelope.forwardCommentBatchPosition,
|
|
1750
|
+
}
|
|
1751
|
+
: {}),
|
|
1752
|
+
...(envelope.kind === "leader.forwardCallback"
|
|
1753
|
+
? { callback_query: carrier }
|
|
1754
|
+
: envelope.kind === "leader.forwardReaction"
|
|
1755
|
+
? { message_reaction: carrier }
|
|
1756
|
+
: envelope.kind === "leader.forwardMessage"
|
|
1757
|
+
? { message: carrier }
|
|
1758
|
+
: { edited_message: carrier }),
|
|
1759
|
+
};
|
|
1760
|
+
deps.journal.appendBatch([update]);
|
|
1761
|
+
deps.signalWorker(ctx);
|
|
1762
|
+
return {
|
|
1763
|
+
deliveryId: delivery.deliveryId,
|
|
1764
|
+
sourceUpdateId: delivery.sourceUpdateId,
|
|
1765
|
+
};
|
|
1766
|
+
},
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
export function createTelegramBusForwardedUpdateReceiverRuntime<TContext>(
|
|
1771
|
+
deps: TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext>,
|
|
1600
1772
|
): TelegramBusForwardedUpdateReceiverRuntime {
|
|
1601
1773
|
const server = createTelegramBusLocalServer({
|
|
1602
1774
|
socketPath: deps.socketPath,
|
|
@@ -1608,7 +1780,10 @@ export function createTelegramBusForwardedUpdateReceiverRuntime<
|
|
|
1608
1780
|
},
|
|
1609
1781
|
async handleEnvelope(envelope) {
|
|
1610
1782
|
const authSecret = deps.getAuthSecret?.();
|
|
1611
|
-
if (
|
|
1783
|
+
if (
|
|
1784
|
+
deps.getAuthSecret &&
|
|
1785
|
+
(!authSecret || !isTelegramBusEnvelopeAuthorized(envelope, authSecret))
|
|
1786
|
+
) {
|
|
1612
1787
|
return createUnauthorizedBusAck(envelope.requestId);
|
|
1613
1788
|
}
|
|
1614
1789
|
if (
|
|
@@ -1616,7 +1791,8 @@ export function createTelegramBusForwardedUpdateReceiverRuntime<
|
|
|
1616
1791
|
envelope.kind !== "leader.forwardReaction" &&
|
|
1617
1792
|
envelope.kind !== "leader.forwardMessage" &&
|
|
1618
1793
|
envelope.kind !== "leader.forwardEditedMessage" &&
|
|
1619
|
-
envelope.kind !== "leader.replaceFollowerTarget"
|
|
1794
|
+
envelope.kind !== "leader.replaceFollowerTarget" &&
|
|
1795
|
+
envelope.kind !== "leader.offerQueueHandoff") ||
|
|
1620
1796
|
envelope.recipientInstanceId !== deps.instanceId
|
|
1621
1797
|
) {
|
|
1622
1798
|
return {
|
|
@@ -1626,9 +1802,9 @@ export function createTelegramBusForwardedUpdateReceiverRuntime<
|
|
|
1626
1802
|
message: "Telegram bus receiver cannot handle this envelope.",
|
|
1627
1803
|
};
|
|
1628
1804
|
}
|
|
1629
|
-
const registrationGeneration = deps.getRegistrationGeneration
|
|
1805
|
+
const registrationGeneration = deps.getRegistrationGeneration();
|
|
1630
1806
|
if (
|
|
1631
|
-
registrationGeneration
|
|
1807
|
+
!registrationGeneration ||
|
|
1632
1808
|
envelope.recipientRegistrationGeneration !== registrationGeneration
|
|
1633
1809
|
) {
|
|
1634
1810
|
return {
|
|
@@ -1638,6 +1814,20 @@ export function createTelegramBusForwardedUpdateReceiverRuntime<
|
|
|
1638
1814
|
message: "Stale Telegram bus follower registration generation.",
|
|
1639
1815
|
};
|
|
1640
1816
|
}
|
|
1817
|
+
if (
|
|
1818
|
+
envelope.kind !== "leader.replaceFollowerTarget" &&
|
|
1819
|
+
envelope.kind !== "leader.offerQueueHandoff" &&
|
|
1820
|
+
(!envelope.delivery ||
|
|
1821
|
+
envelope.delivery.recipientBindingKey !==
|
|
1822
|
+
deps.getRecipientBindingKey())
|
|
1823
|
+
) {
|
|
1824
|
+
return {
|
|
1825
|
+
kind: "bus.ack",
|
|
1826
|
+
requestId: envelope.requestId,
|
|
1827
|
+
ok: false,
|
|
1828
|
+
message: "Mismatched Telegram follower delivery identity.",
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1641
1831
|
const ctx = deps.getContext();
|
|
1642
1832
|
if (!ctx) {
|
|
1643
1833
|
return {
|
|
@@ -1648,40 +1838,48 @@ export function createTelegramBusForwardedUpdateReceiverRuntime<
|
|
|
1648
1838
|
};
|
|
1649
1839
|
}
|
|
1650
1840
|
try {
|
|
1651
|
-
if (
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
);
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
envelope.
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
envelope.forwardCommentBatchPosition,
|
|
1666
|
-
);
|
|
1667
|
-
}
|
|
1668
|
-
if (!deps.handleForwardedMessage) {
|
|
1841
|
+
if (
|
|
1842
|
+
envelope.kind !== "leader.replaceFollowerTarget" &&
|
|
1843
|
+
envelope.kind !== "leader.offerQueueHandoff"
|
|
1844
|
+
) {
|
|
1845
|
+
const receipt = await deps.durableAdmission.admit(envelope, ctx);
|
|
1846
|
+
return {
|
|
1847
|
+
kind: "bus.ack",
|
|
1848
|
+
requestId: envelope.requestId,
|
|
1849
|
+
ok: true,
|
|
1850
|
+
result: receipt,
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
if (envelope.kind === "leader.offerQueueHandoff") {
|
|
1854
|
+
if (!deps.handleQueueHandoff) {
|
|
1669
1855
|
throw new Error(
|
|
1670
|
-
"Telegram bus receiver cannot
|
|
1856
|
+
"Telegram bus receiver cannot accept queue handoff payloads.",
|
|
1671
1857
|
);
|
|
1672
1858
|
}
|
|
1673
|
-
await deps.
|
|
1674
|
-
|
|
1675
|
-
if (
|
|
1859
|
+
const result = await deps.handleQueueHandoff(envelope, ctx);
|
|
1860
|
+
const receipt = envelope.payload.admissionReceipts[0];
|
|
1861
|
+
if (
|
|
1862
|
+
envelope.payload.admissionReceipts.length !== 1 ||
|
|
1863
|
+
!receipt ||
|
|
1864
|
+
result.status !== "staged" ||
|
|
1865
|
+
result.receiptId !== receipt.receiptId ||
|
|
1866
|
+
result.sourceUpdateIds.length !== receipt.sourceUpdateIds.length ||
|
|
1867
|
+
result.sourceUpdateIds.some(
|
|
1868
|
+
(updateId, index) => updateId !== receipt.sourceUpdateIds[index],
|
|
1869
|
+
)
|
|
1870
|
+
) {
|
|
1676
1871
|
throw new Error(
|
|
1677
|
-
"Telegram
|
|
1872
|
+
"Telegram queue handoff staging returned a mismatched receipt.",
|
|
1678
1873
|
);
|
|
1679
1874
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1875
|
+
return {
|
|
1876
|
+
kind: "bus.ack",
|
|
1877
|
+
requestId: envelope.requestId,
|
|
1878
|
+
ok: true,
|
|
1879
|
+
result,
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
{
|
|
1685
1883
|
if (!deps.handleReplaceTarget) {
|
|
1686
1884
|
throw new Error(
|
|
1687
1885
|
"Telegram bus receiver cannot replace follower target.",
|