@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.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { execFileSync } from "node:child_process";
|
|
9
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
9
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
10
10
|
import {
|
|
11
11
|
chmodSync,
|
|
12
12
|
existsSync,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type Server,
|
|
25
25
|
type Socket,
|
|
26
26
|
} from "node:net";
|
|
27
|
+
import { createRequire } from "node:module";
|
|
27
28
|
import { platform as getPlatform, tmpdir } from "node:os";
|
|
28
29
|
import { basename, dirname, join } from "node:path";
|
|
29
30
|
|
|
@@ -42,11 +43,19 @@ import {
|
|
|
42
43
|
type TelegramBusTransportEventRecorder,
|
|
43
44
|
type TelegramBusTransportRetryPolicy,
|
|
44
45
|
} from "./bus-transport.ts";
|
|
46
|
+
import {
|
|
47
|
+
TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS,
|
|
48
|
+
TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES,
|
|
49
|
+
type TelegramQueueHandoffPayload,
|
|
50
|
+
} from "./queue.ts";
|
|
45
51
|
import type { TelegramTarget } from "./target.ts";
|
|
52
|
+
import { isProcessAlive } from "./locks.ts";
|
|
46
53
|
import { resolveAgentDir } from "./paths.ts";
|
|
47
54
|
|
|
48
55
|
export interface TelegramBusProcessRuntime {
|
|
49
56
|
instanceId: string;
|
|
57
|
+
processId: number;
|
|
58
|
+
processBirthId: string;
|
|
50
59
|
manualFollowerOwnerId: string;
|
|
51
60
|
getLeaderSocketPath: () => string;
|
|
52
61
|
getFollowerSocketPath: () => string;
|
|
@@ -58,6 +67,17 @@ export interface TelegramProcessBirthIdentityOptions {
|
|
|
58
67
|
readDarwinProcessStart?: (pid: number) => string;
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
export type TelegramProcessBirthProof =
|
|
71
|
+
| { status: "proven"; identity: string }
|
|
72
|
+
| { status: "unverifiable" };
|
|
73
|
+
|
|
74
|
+
export type TelegramProcessLiveness = "alive" | "dead" | "unverifiable";
|
|
75
|
+
|
|
76
|
+
export interface TelegramProcessLivenessOptions
|
|
77
|
+
extends TelegramProcessBirthIdentityOptions {
|
|
78
|
+
isProcessAlive?: (pid: number) => boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
61
81
|
function readDarwinProcessStart(pid: number): string {
|
|
62
82
|
return execFileSync(
|
|
63
83
|
"/bin/ps",
|
|
@@ -69,45 +89,67 @@ function readDarwinProcessStart(pid: number): string {
|
|
|
69
89
|
).trim();
|
|
70
90
|
}
|
|
71
91
|
|
|
72
|
-
export function
|
|
92
|
+
export function getTelegramProcessBirthProof(
|
|
73
93
|
pid: number,
|
|
74
|
-
fallbackGeneration: number | string,
|
|
75
94
|
options: TelegramProcessBirthIdentityOptions = {},
|
|
76
|
-
):
|
|
77
|
-
if (pid
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
/* inaccessible process metadata */
|
|
95
|
+
): TelegramProcessBirthProof {
|
|
96
|
+
if (pid <= 0) return { status: "unverifiable" };
|
|
97
|
+
const platform = options.platform ?? getPlatform();
|
|
98
|
+
if (platform === "linux") {
|
|
99
|
+
try {
|
|
100
|
+
const stat = (options.readProcStat ?? ((targetPid) =>
|
|
101
|
+
readFileSync(`/proc/${targetPid}/stat`, "utf8")))(pid);
|
|
102
|
+
const closeParen = stat.lastIndexOf(")");
|
|
103
|
+
const fields = stat
|
|
104
|
+
.slice(closeParen + 2)
|
|
105
|
+
.trim()
|
|
106
|
+
.split(/\s+/u);
|
|
107
|
+
const startTicks = fields[19];
|
|
108
|
+
if (startTicks) {
|
|
109
|
+
return { status: "proven", identity: `${pid}:start:${startTicks}` };
|
|
92
110
|
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
} catch {
|
|
112
|
+
/* inaccessible process metadata */
|
|
113
|
+
}
|
|
114
|
+
} else if (platform === "darwin") {
|
|
115
|
+
try {
|
|
116
|
+
const startedAt = (
|
|
117
|
+
options.readDarwinProcessStart ?? readDarwinProcessStart
|
|
118
|
+
)(pid);
|
|
119
|
+
if (startedAt) {
|
|
120
|
+
const fingerprint = createHash("sha256")
|
|
121
|
+
.update(startedAt)
|
|
122
|
+
.digest("hex")
|
|
123
|
+
.slice(0, 16);
|
|
124
|
+
return { status: "proven", identity: `${pid}:start:${fingerprint}` };
|
|
107
125
|
}
|
|
126
|
+
} catch {
|
|
127
|
+
/* inaccessible process metadata */
|
|
108
128
|
}
|
|
109
129
|
}
|
|
110
|
-
return
|
|
130
|
+
return { status: "unverifiable" };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getTelegramProcessBirthIdentity(
|
|
134
|
+
pid: number,
|
|
135
|
+
fallbackGeneration: number | string,
|
|
136
|
+
options: TelegramProcessBirthIdentityOptions = {},
|
|
137
|
+
): string {
|
|
138
|
+
const proof = getTelegramProcessBirthProof(pid, options);
|
|
139
|
+
return proof.status === "proven"
|
|
140
|
+
? proof.identity
|
|
141
|
+
: `${pid}:generation:${fallbackGeneration}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function getTelegramProcessLiveness(
|
|
145
|
+
owner: { processId: number; processBirthId: string },
|
|
146
|
+
options: TelegramProcessLivenessOptions = {},
|
|
147
|
+
): TelegramProcessLiveness {
|
|
148
|
+
const processAlive = options.isProcessAlive ?? isProcessAlive;
|
|
149
|
+
if (!processAlive(owner.processId)) return "dead";
|
|
150
|
+
const proof = getTelegramProcessBirthProof(owner.processId, options);
|
|
151
|
+
if (proof.status === "unverifiable") return "unverifiable";
|
|
152
|
+
return proof.identity === owner.processBirthId ? "alive" : "dead";
|
|
111
153
|
}
|
|
112
154
|
|
|
113
155
|
export function createCurrentTelegramBusProcessRuntime(input: {
|
|
@@ -138,6 +180,8 @@ export function createTelegramBusProcessRuntime(input: {
|
|
|
138
180
|
getTelegramProcessBirthIdentity(ownerPid, input.createdAtMs);
|
|
139
181
|
return {
|
|
140
182
|
instanceId,
|
|
183
|
+
processId: input.pid,
|
|
184
|
+
processBirthId: getTelegramProcessBirthIdentity(input.pid, instanceId),
|
|
141
185
|
manualFollowerOwnerId,
|
|
142
186
|
getLeaderSocketPath: () =>
|
|
143
187
|
getTelegramBusSocketPath(
|
|
@@ -159,6 +203,106 @@ export function createTelegramBusAuthSecret(): string {
|
|
|
159
203
|
return randomBytes(32).toString("base64url");
|
|
160
204
|
}
|
|
161
205
|
|
|
206
|
+
export const TELEGRAM_BUS_PROTOCOL_VERSION = 1 as const;
|
|
207
|
+
export const TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION =
|
|
208
|
+
"durable-follower-admission-v1" as const;
|
|
209
|
+
export const TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF =
|
|
210
|
+
"queue-handoff-v1" as const;
|
|
211
|
+
|
|
212
|
+
export interface TelegramBusProtocolIdentity {
|
|
213
|
+
protocolVersion: number;
|
|
214
|
+
runtimeBuild: string;
|
|
215
|
+
capabilities: string[];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface TelegramBusProtocolCompatibility {
|
|
219
|
+
compatible: boolean;
|
|
220
|
+
reason?: "missing-identity" | "version-mismatch" | "missing-capability";
|
|
221
|
+
missingCapabilities: string[];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function createTelegramBusProtocolIdentity(input: {
|
|
225
|
+
runtimeBuild: string;
|
|
226
|
+
capabilities?: readonly string[];
|
|
227
|
+
}): TelegramBusProtocolIdentity {
|
|
228
|
+
const runtimeBuild = input.runtimeBuild.trim();
|
|
229
|
+
if (!runtimeBuild || runtimeBuild.length > 128) {
|
|
230
|
+
throw new Error("Telegram bus runtime build identity is invalid.");
|
|
231
|
+
}
|
|
232
|
+
const capabilities = [...new Set(input.capabilities ?? [])].sort();
|
|
233
|
+
if (
|
|
234
|
+
capabilities.length > 32 ||
|
|
235
|
+
capabilities.some(
|
|
236
|
+
(capability) =>
|
|
237
|
+
capability.length > 128 ||
|
|
238
|
+
!/^[a-z0-9][a-z0-9._-]*$/u.test(capability),
|
|
239
|
+
)
|
|
240
|
+
) {
|
|
241
|
+
throw new Error("Telegram bus capabilities must be canonical identifiers.");
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
protocolVersion: TELEGRAM_BUS_PROTOCOL_VERSION,
|
|
245
|
+
runtimeBuild,
|
|
246
|
+
capabilities,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function createTelegramCurrentBusProtocolIdentity(
|
|
251
|
+
capabilities: readonly string[] = [],
|
|
252
|
+
): TelegramBusProtocolIdentity {
|
|
253
|
+
const packageMetadata = createRequire(import.meta.url)("../package.json") as {
|
|
254
|
+
version?: unknown;
|
|
255
|
+
};
|
|
256
|
+
if (typeof packageMetadata.version !== "string") {
|
|
257
|
+
throw new Error("Telegram package build identity is unavailable.");
|
|
258
|
+
}
|
|
259
|
+
return createTelegramBusProtocolIdentity({
|
|
260
|
+
runtimeBuild: packageMetadata.version,
|
|
261
|
+
capabilities,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function hasTelegramBusCapability(
|
|
266
|
+
identity: TelegramBusProtocolIdentity | undefined,
|
|
267
|
+
capability: string,
|
|
268
|
+
): boolean {
|
|
269
|
+
return identity?.capabilities.includes(capability) ?? false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function getTelegramBusProtocolCompatibility(input: {
|
|
273
|
+
local: TelegramBusProtocolIdentity;
|
|
274
|
+
remote?: TelegramBusProtocolIdentity;
|
|
275
|
+
}): TelegramBusProtocolCompatibility {
|
|
276
|
+
if (!input.remote) {
|
|
277
|
+
return {
|
|
278
|
+
compatible: false,
|
|
279
|
+
reason: "missing-identity",
|
|
280
|
+
missingCapabilities: [],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (input.remote.protocolVersion !== input.local.protocolVersion) {
|
|
284
|
+
return {
|
|
285
|
+
compatible: false,
|
|
286
|
+
reason: "version-mismatch",
|
|
287
|
+
missingCapabilities: [],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const remoteCapabilities = new Set(input.remote.capabilities);
|
|
291
|
+
const missingCapabilities = input.local.capabilities
|
|
292
|
+
.filter(
|
|
293
|
+
(capability) =>
|
|
294
|
+
capability === TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
|
|
295
|
+
)
|
|
296
|
+
.filter((capability) => !remoteCapabilities.has(capability));
|
|
297
|
+
return missingCapabilities.length > 0
|
|
298
|
+
? {
|
|
299
|
+
compatible: false,
|
|
300
|
+
reason: "missing-capability",
|
|
301
|
+
missingCapabilities,
|
|
302
|
+
}
|
|
303
|
+
: { compatible: true, missingCapabilities: [] };
|
|
304
|
+
}
|
|
305
|
+
|
|
162
306
|
export function getTelegramBusSocketPath(
|
|
163
307
|
agentDir = resolveAgentDir(),
|
|
164
308
|
platform = getPlatform(),
|
|
@@ -192,6 +336,9 @@ export interface TelegramBusInstanceRegistration {
|
|
|
192
336
|
target?: TelegramTarget;
|
|
193
337
|
busSocketPath?: string;
|
|
194
338
|
registrationGeneration?: string;
|
|
339
|
+
protocol?: TelegramBusProtocolIdentity;
|
|
340
|
+
sessionGeneration?: number;
|
|
341
|
+
processBirthId?: string;
|
|
195
342
|
connectedAtMs: number;
|
|
196
343
|
}
|
|
197
344
|
|
|
@@ -210,17 +357,28 @@ export function getTelegramFollowerTargetOwnership(input: {
|
|
|
210
357
|
target: TelegramTarget;
|
|
211
358
|
}[];
|
|
212
359
|
currentInstanceId?: string;
|
|
213
|
-
}): {
|
|
360
|
+
}): {
|
|
361
|
+
instanceId: string;
|
|
362
|
+
ownerGeneration: string;
|
|
363
|
+
recipientBindingKey: string;
|
|
364
|
+
} | undefined {
|
|
214
365
|
const liveFollower = input.followers.find((follower) => {
|
|
215
366
|
return (
|
|
216
367
|
follower.target?.chatId === input.target.chatId &&
|
|
217
368
|
follower.target.threadId === input.target.threadId
|
|
218
369
|
);
|
|
219
370
|
});
|
|
220
|
-
if (
|
|
371
|
+
if (
|
|
372
|
+
liveFollower?.registrationGeneration &&
|
|
373
|
+
liveFollower.profileKey &&
|
|
374
|
+
liveFollower.protocol?.capabilities.includes(
|
|
375
|
+
TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
|
|
376
|
+
)
|
|
377
|
+
) {
|
|
221
378
|
return {
|
|
222
379
|
instanceId: liveFollower.instanceId,
|
|
223
380
|
ownerGeneration: liveFollower.registrationGeneration,
|
|
381
|
+
recipientBindingKey: liveFollower.profileKey,
|
|
224
382
|
};
|
|
225
383
|
}
|
|
226
384
|
// Persisted records are restart hints, not live routing authority. Only an
|
|
@@ -460,6 +618,69 @@ export interface TelegramBusAgentMessage {
|
|
|
460
618
|
text: string;
|
|
461
619
|
}
|
|
462
620
|
|
|
621
|
+
export interface TelegramBusFollowerDeliveryIdentity {
|
|
622
|
+
deliveryId: string;
|
|
623
|
+
sourceUpdateId: number;
|
|
624
|
+
recipientBindingKey: string;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export type TelegramBusForeignUpdateFailureClass =
|
|
628
|
+
| "source-update-identity-missing"
|
|
629
|
+
| "recipient-binding-missing"
|
|
630
|
+
| "recipient-generation-missing"
|
|
631
|
+
| "transport-failed"
|
|
632
|
+
| "acknowledgement-missing"
|
|
633
|
+
| "acknowledgement-rejected"
|
|
634
|
+
| "acknowledgement-mismatched"
|
|
635
|
+
| "durable-receipt-missing"
|
|
636
|
+
| "durable-receipt-mismatched";
|
|
637
|
+
|
|
638
|
+
export type TelegramBusForeignUpdateSettlement =
|
|
639
|
+
| {
|
|
640
|
+
status: "accepted";
|
|
641
|
+
delivery: TelegramBusFollowerDeliveryIdentity;
|
|
642
|
+
}
|
|
643
|
+
| {
|
|
644
|
+
status: "retryable" | "terminal-rejected";
|
|
645
|
+
failureClass: TelegramBusForeignUpdateFailureClass;
|
|
646
|
+
message: string;
|
|
647
|
+
delivery?: TelegramBusFollowerDeliveryIdentity;
|
|
648
|
+
sourceUpdateId?: number;
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
export function createTelegramBusFollowerDeliveryIdentity(input: {
|
|
652
|
+
kind:
|
|
653
|
+
| "leader.forwardCallback"
|
|
654
|
+
| "leader.forwardReaction"
|
|
655
|
+
| "leader.forwardMessage"
|
|
656
|
+
| "leader.forwardEditedMessage";
|
|
657
|
+
recipientBindingKey: string;
|
|
658
|
+
sourceUpdateId: number;
|
|
659
|
+
}): TelegramBusFollowerDeliveryIdentity {
|
|
660
|
+
if (
|
|
661
|
+
!input.recipientBindingKey ||
|
|
662
|
+
!Number.isSafeInteger(input.sourceUpdateId) ||
|
|
663
|
+
input.sourceUpdateId < 0
|
|
664
|
+
) {
|
|
665
|
+
throw new Error("Telegram follower delivery identity is incomplete.");
|
|
666
|
+
}
|
|
667
|
+
const deliveryId = createHash("sha256")
|
|
668
|
+
.update(
|
|
669
|
+
JSON.stringify({
|
|
670
|
+
version: 1,
|
|
671
|
+
kind: input.kind,
|
|
672
|
+
recipientBindingKey: input.recipientBindingKey,
|
|
673
|
+
sourceUpdateId: input.sourceUpdateId,
|
|
674
|
+
}),
|
|
675
|
+
)
|
|
676
|
+
.digest("hex");
|
|
677
|
+
return {
|
|
678
|
+
deliveryId: `telegram-follower-v1-${deliveryId}`,
|
|
679
|
+
sourceUpdateId: input.sourceUpdateId,
|
|
680
|
+
recipientBindingKey: input.recipientBindingKey,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
463
684
|
export type TelegramBusEnvelope = (
|
|
464
685
|
| {
|
|
465
686
|
kind: "follower.register";
|
|
@@ -484,7 +705,8 @@ export type TelegramBusEnvelope = (
|
|
|
484
705
|
kind: "leader.forwardCallback";
|
|
485
706
|
requestId: string;
|
|
486
707
|
recipientInstanceId: string;
|
|
487
|
-
recipientRegistrationGeneration
|
|
708
|
+
recipientRegistrationGeneration: string;
|
|
709
|
+
delivery: TelegramBusFollowerDeliveryIdentity;
|
|
488
710
|
query: unknown;
|
|
489
711
|
sentAtMs: number;
|
|
490
712
|
}
|
|
@@ -492,7 +714,8 @@ export type TelegramBusEnvelope = (
|
|
|
492
714
|
kind: "leader.forwardReaction";
|
|
493
715
|
requestId: string;
|
|
494
716
|
recipientInstanceId: string;
|
|
495
|
-
recipientRegistrationGeneration
|
|
717
|
+
recipientRegistrationGeneration: string;
|
|
718
|
+
delivery: TelegramBusFollowerDeliveryIdentity;
|
|
496
719
|
reactionUpdate: unknown;
|
|
497
720
|
sentAtMs: number;
|
|
498
721
|
}
|
|
@@ -500,7 +723,8 @@ export type TelegramBusEnvelope = (
|
|
|
500
723
|
kind: "leader.forwardMessage";
|
|
501
724
|
requestId: string;
|
|
502
725
|
recipientInstanceId: string;
|
|
503
|
-
recipientRegistrationGeneration
|
|
726
|
+
recipientRegistrationGeneration: string;
|
|
727
|
+
delivery: TelegramBusFollowerDeliveryIdentity;
|
|
504
728
|
message: unknown;
|
|
505
729
|
forwardCommentBatchPosition?: "comment" | "forward";
|
|
506
730
|
sentAtMs: number;
|
|
@@ -509,7 +733,8 @@ export type TelegramBusEnvelope = (
|
|
|
509
733
|
kind: "leader.forwardEditedMessage";
|
|
510
734
|
requestId: string;
|
|
511
735
|
recipientInstanceId: string;
|
|
512
|
-
recipientRegistrationGeneration
|
|
736
|
+
recipientRegistrationGeneration: string;
|
|
737
|
+
delivery: TelegramBusFollowerDeliveryIdentity;
|
|
513
738
|
message: unknown;
|
|
514
739
|
sentAtMs: number;
|
|
515
740
|
}
|
|
@@ -523,6 +748,37 @@ export type TelegramBusEnvelope = (
|
|
|
523
748
|
reason: "thread-restore";
|
|
524
749
|
sentAtMs: number;
|
|
525
750
|
}
|
|
751
|
+
| {
|
|
752
|
+
kind: "leader.offerQueueHandoff";
|
|
753
|
+
requestId: string;
|
|
754
|
+
recipientInstanceId: string;
|
|
755
|
+
recipientRegistrationGeneration: string;
|
|
756
|
+
donorInstanceId: string;
|
|
757
|
+
donorProcessId: number;
|
|
758
|
+
donorProcessBirthId: string;
|
|
759
|
+
donorSessionGeneration: number;
|
|
760
|
+
donorAcquisitionId: string;
|
|
761
|
+
donorAcquiredAtMs: number;
|
|
762
|
+
handoffToken: string;
|
|
763
|
+
payload: TelegramQueueHandoffPayload;
|
|
764
|
+
sentAtMs: number;
|
|
765
|
+
}
|
|
766
|
+
| {
|
|
767
|
+
kind: "follower.offerQueueHandoff";
|
|
768
|
+
requestId: string;
|
|
769
|
+
instanceId: string;
|
|
770
|
+
registrationGeneration: string;
|
|
771
|
+
recipientInstanceId: string;
|
|
772
|
+
recipientRegistrationGeneration: string;
|
|
773
|
+
donorProcessId: number;
|
|
774
|
+
donorProcessBirthId: string;
|
|
775
|
+
donorSessionGeneration: number;
|
|
776
|
+
donorAcquisitionId: string;
|
|
777
|
+
donorAcquiredAtMs: number;
|
|
778
|
+
handoffToken: string;
|
|
779
|
+
payload: TelegramQueueHandoffPayload;
|
|
780
|
+
sentAtMs: number;
|
|
781
|
+
}
|
|
526
782
|
| {
|
|
527
783
|
kind: "follower.resolveAgentTarget";
|
|
528
784
|
requestId: string;
|
|
@@ -554,13 +810,31 @@ export type TelegramBusEnvelope = (
|
|
|
554
810
|
ok: boolean;
|
|
555
811
|
message?: string;
|
|
556
812
|
result?: unknown;
|
|
813
|
+
protocol?: TelegramBusProtocolIdentity;
|
|
557
814
|
error?: {
|
|
558
|
-
code:
|
|
815
|
+
code:
|
|
816
|
+
| "commit-unknown"
|
|
817
|
+
| "request-id-collision"
|
|
818
|
+
| "ledger-overloaded"
|
|
819
|
+
| "incompatible-protocol";
|
|
559
820
|
method?: string;
|
|
560
821
|
};
|
|
561
822
|
}
|
|
562
823
|
) & { auth?: string };
|
|
563
824
|
|
|
825
|
+
export type TelegramBusEnvelopeTrafficClass =
|
|
826
|
+
| "bootstrap"
|
|
827
|
+
| "generation-fenced"
|
|
828
|
+
| "response";
|
|
829
|
+
|
|
830
|
+
export function getTelegramBusEnvelopeTrafficClass(
|
|
831
|
+
envelope: TelegramBusEnvelope,
|
|
832
|
+
): TelegramBusEnvelopeTrafficClass {
|
|
833
|
+
if (envelope.kind === "follower.register") return "bootstrap";
|
|
834
|
+
if (envelope.kind === "bus.ack") return "response";
|
|
835
|
+
return "generation-fenced";
|
|
836
|
+
}
|
|
837
|
+
|
|
564
838
|
export function createTelegramBusRequestId(input: {
|
|
565
839
|
instanceId: string;
|
|
566
840
|
sequence: number;
|
|
@@ -633,6 +907,20 @@ export function parseTelegramBusEnvelope(
|
|
|
633
907
|
case "leader.replaceFollowerTarget":
|
|
634
908
|
envelope = parseReplaceFollowerTargetEnvelope(value, requestId);
|
|
635
909
|
break;
|
|
910
|
+
case "leader.offerQueueHandoff":
|
|
911
|
+
envelope = parseQueueHandoffEnvelope(
|
|
912
|
+
value,
|
|
913
|
+
requestId,
|
|
914
|
+
"leader.offerQueueHandoff",
|
|
915
|
+
);
|
|
916
|
+
break;
|
|
917
|
+
case "follower.offerQueueHandoff":
|
|
918
|
+
envelope = parseQueueHandoffEnvelope(
|
|
919
|
+
value,
|
|
920
|
+
requestId,
|
|
921
|
+
"follower.offerQueueHandoff",
|
|
922
|
+
);
|
|
923
|
+
break;
|
|
636
924
|
case "follower.resolveAgentTarget":
|
|
637
925
|
envelope = parseResolveAgentTargetEnvelope(value, requestId);
|
|
638
926
|
break;
|
|
@@ -735,6 +1023,52 @@ export interface TelegramBusForeignOwnedForwarderDeps<TMessage = unknown> {
|
|
|
735
1023
|
) => void;
|
|
736
1024
|
}
|
|
737
1025
|
|
|
1026
|
+
interface TelegramBusForwardOwnership {
|
|
1027
|
+
instanceId: string;
|
|
1028
|
+
ownerGeneration?: string;
|
|
1029
|
+
recipientBindingKey?: string;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
type TelegramBusDurableForwardEnvelope = Extract<
|
|
1033
|
+
TelegramBusEnvelope,
|
|
1034
|
+
{
|
|
1035
|
+
kind:
|
|
1036
|
+
| "leader.forwardCallback"
|
|
1037
|
+
| "leader.forwardReaction"
|
|
1038
|
+
| "leader.forwardMessage"
|
|
1039
|
+
| "leader.forwardEditedMessage";
|
|
1040
|
+
}
|
|
1041
|
+
> & {
|
|
1042
|
+
recipientRegistrationGeneration: string;
|
|
1043
|
+
delivery: TelegramBusFollowerDeliveryIdentity;
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
function getTelegramBusForwardSourceUpdateId(
|
|
1047
|
+
value: unknown,
|
|
1048
|
+
): number | undefined {
|
|
1049
|
+
if (!isRecord(value)) return undefined;
|
|
1050
|
+
const updateId = value.pi_telegram_source_update_id;
|
|
1051
|
+
return Number.isSafeInteger(updateId) && (updateId as number) >= 0
|
|
1052
|
+
? (updateId as number)
|
|
1053
|
+
: undefined;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function createTelegramBusForwardDelivery(
|
|
1057
|
+
kind:
|
|
1058
|
+
| "leader.forwardCallback"
|
|
1059
|
+
| "leader.forwardReaction"
|
|
1060
|
+
| "leader.forwardMessage"
|
|
1061
|
+
| "leader.forwardEditedMessage",
|
|
1062
|
+
sourceUpdateId: number,
|
|
1063
|
+
recipientBindingKey: string,
|
|
1064
|
+
): TelegramBusFollowerDeliveryIdentity {
|
|
1065
|
+
return createTelegramBusFollowerDeliveryIdentity({
|
|
1066
|
+
kind,
|
|
1067
|
+
recipientBindingKey,
|
|
1068
|
+
sourceUpdateId,
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
|
|
738
1072
|
export function createTelegramBusForeignOwnedUpdateForwarder<
|
|
739
1073
|
TContext,
|
|
740
1074
|
TReactionUpdate,
|
|
@@ -745,108 +1079,286 @@ export function createTelegramBusForeignOwnedUpdateForwarder<
|
|
|
745
1079
|
): {
|
|
746
1080
|
forwardCallback: (input: {
|
|
747
1081
|
query: TCallbackQuery;
|
|
748
|
-
ownership:
|
|
1082
|
+
ownership: TelegramBusForwardOwnership;
|
|
749
1083
|
ctx: TContext;
|
|
750
|
-
}) => Promise<
|
|
1084
|
+
}) => Promise<TelegramBusForeignUpdateSettlement>;
|
|
751
1085
|
forwardReaction: (input: {
|
|
752
1086
|
reactionUpdate: TReactionUpdate;
|
|
753
|
-
ownership:
|
|
1087
|
+
ownership: TelegramBusForwardOwnership;
|
|
754
1088
|
ctx: TContext;
|
|
755
|
-
}) => Promise<
|
|
1089
|
+
}) => Promise<TelegramBusForeignUpdateSettlement>;
|
|
756
1090
|
forwardMessage: (input: {
|
|
757
1091
|
message: TMessage;
|
|
758
|
-
ownership:
|
|
1092
|
+
ownership: TelegramBusForwardOwnership;
|
|
759
1093
|
ctx: TContext;
|
|
760
|
-
}) => Promise<
|
|
1094
|
+
}) => Promise<TelegramBusForeignUpdateSettlement>;
|
|
761
1095
|
forwardEditedMessage: (input: {
|
|
762
1096
|
message: TMessage;
|
|
763
|
-
ownership:
|
|
1097
|
+
ownership: TelegramBusForwardOwnership;
|
|
764
1098
|
ctx: TContext;
|
|
765
|
-
}) => Promise<
|
|
1099
|
+
}) => Promise<TelegramBusForeignUpdateSettlement>;
|
|
766
1100
|
} {
|
|
767
1101
|
const getNowMs = deps.getNowMs ?? Date.now;
|
|
768
|
-
const
|
|
1102
|
+
const reject = (input: {
|
|
1103
|
+
status: "retryable" | "terminal-rejected";
|
|
1104
|
+
failureClass: TelegramBusForeignUpdateFailureClass;
|
|
1105
|
+
message: string;
|
|
1106
|
+
envelopeKind: TelegramBusDurableForwardEnvelope["kind"];
|
|
1107
|
+
ownership: TelegramBusForwardOwnership;
|
|
1108
|
+
delivery?: TelegramBusFollowerDeliveryIdentity;
|
|
1109
|
+
sourceUpdateId?: number;
|
|
1110
|
+
}): TelegramBusForeignUpdateSettlement => {
|
|
1111
|
+
const settlement: TelegramBusForeignUpdateSettlement = {
|
|
1112
|
+
status: input.status,
|
|
1113
|
+
failureClass: input.failureClass,
|
|
1114
|
+
message: input.message,
|
|
1115
|
+
...(input.delivery ? { delivery: input.delivery } : {}),
|
|
1116
|
+
...(input.sourceUpdateId !== undefined
|
|
1117
|
+
? { sourceUpdateId: input.sourceUpdateId }
|
|
1118
|
+
: {}),
|
|
1119
|
+
};
|
|
1120
|
+
deps.recordRuntimeEvent?.("bus", input.message, {
|
|
1121
|
+
phase: "foreign-update-forward-rejected",
|
|
1122
|
+
settlement: input.status,
|
|
1123
|
+
failureClass: input.failureClass,
|
|
1124
|
+
envelopeKind: input.envelopeKind,
|
|
1125
|
+
recipientInstanceId: input.ownership.instanceId,
|
|
1126
|
+
deliveryId: input.delivery?.deliveryId,
|
|
1127
|
+
sourceUpdateId:
|
|
1128
|
+
input.delivery?.sourceUpdateId ?? input.sourceUpdateId,
|
|
1129
|
+
});
|
|
1130
|
+
return settlement;
|
|
1131
|
+
};
|
|
1132
|
+
const prepare = (
|
|
1133
|
+
kind: TelegramBusDurableForwardEnvelope["kind"],
|
|
1134
|
+
value: unknown,
|
|
1135
|
+
ownership: TelegramBusForwardOwnership,
|
|
1136
|
+
):
|
|
1137
|
+
| {
|
|
1138
|
+
delivery: TelegramBusFollowerDeliveryIdentity;
|
|
1139
|
+
recipientRegistrationGeneration: string;
|
|
1140
|
+
}
|
|
1141
|
+
| { settlement: TelegramBusForeignUpdateSettlement } => {
|
|
1142
|
+
const sourceUpdateId = getTelegramBusForwardSourceUpdateId(value);
|
|
1143
|
+
if (sourceUpdateId === undefined) {
|
|
1144
|
+
return {
|
|
1145
|
+
settlement: reject({
|
|
1146
|
+
status: "terminal-rejected",
|
|
1147
|
+
failureClass: "source-update-identity-missing",
|
|
1148
|
+
message: "Forwarded Telegram update has no durable source identity.",
|
|
1149
|
+
envelopeKind: kind,
|
|
1150
|
+
ownership,
|
|
1151
|
+
}),
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
if (!ownership.recipientBindingKey) {
|
|
1155
|
+
return {
|
|
1156
|
+
settlement: reject({
|
|
1157
|
+
status: "terminal-rejected",
|
|
1158
|
+
failureClass: "recipient-binding-missing",
|
|
1159
|
+
message: "Forwarded Telegram update has no stable recipient binding.",
|
|
1160
|
+
envelopeKind: kind,
|
|
1161
|
+
ownership,
|
|
1162
|
+
sourceUpdateId,
|
|
1163
|
+
}),
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
const delivery = createTelegramBusForwardDelivery(
|
|
1167
|
+
kind,
|
|
1168
|
+
sourceUpdateId,
|
|
1169
|
+
ownership.recipientBindingKey,
|
|
1170
|
+
);
|
|
1171
|
+
if (!ownership.ownerGeneration) {
|
|
1172
|
+
return {
|
|
1173
|
+
settlement: reject({
|
|
1174
|
+
status: "retryable",
|
|
1175
|
+
failureClass: "recipient-generation-missing",
|
|
1176
|
+
message: "Forwarded Telegram update has no live recipient generation.",
|
|
1177
|
+
envelopeKind: kind,
|
|
1178
|
+
ownership,
|
|
1179
|
+
delivery,
|
|
1180
|
+
}),
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
return {
|
|
1184
|
+
delivery,
|
|
1185
|
+
recipientRegistrationGeneration: ownership.ownerGeneration,
|
|
1186
|
+
};
|
|
1187
|
+
};
|
|
1188
|
+
const send = async (
|
|
1189
|
+
envelope: TelegramBusDurableForwardEnvelope,
|
|
1190
|
+
ownership: TelegramBusForwardOwnership,
|
|
1191
|
+
): Promise<TelegramBusForeignUpdateSettlement> => {
|
|
769
1192
|
if (deps.getAuthSecret) envelope.auth = deps.getAuthSecret();
|
|
770
1193
|
const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1194
|
+
let response: TelegramBusEnvelope | undefined;
|
|
1195
|
+
try {
|
|
1196
|
+
response = await sendTelegramBusLocalEnvelope({
|
|
1197
|
+
socketPath,
|
|
1198
|
+
envelope,
|
|
1199
|
+
timeoutMs: deps.timeoutMs,
|
|
1200
|
+
retry: getTelegramBusTransportRetryPolicy({
|
|
1201
|
+
endpoint: socketPath,
|
|
1202
|
+
operation: "operation",
|
|
1203
|
+
}),
|
|
1204
|
+
});
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
return reject({
|
|
1207
|
+
status: "retryable",
|
|
1208
|
+
failureClass: "transport-failed",
|
|
1209
|
+
message:
|
|
1210
|
+
error instanceof Error
|
|
1211
|
+
? error.message
|
|
1212
|
+
: "Telegram follower forwarding transport failed.",
|
|
1213
|
+
envelopeKind: envelope.kind,
|
|
1214
|
+
ownership,
|
|
1215
|
+
delivery: envelope.delivery,
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
if (response?.kind !== "bus.ack") {
|
|
1219
|
+
return reject({
|
|
1220
|
+
status: "retryable",
|
|
1221
|
+
failureClass: "acknowledgement-missing",
|
|
1222
|
+
message: "Follower returned no forwarding acknowledgement.",
|
|
1223
|
+
envelopeKind: envelope.kind,
|
|
1224
|
+
ownership,
|
|
1225
|
+
delivery: envelope.delivery,
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
if (response.requestId !== envelope.requestId) {
|
|
1229
|
+
return reject({
|
|
1230
|
+
status: "terminal-rejected",
|
|
1231
|
+
failureClass: "acknowledgement-mismatched",
|
|
1232
|
+
message: "Follower returned a mismatched forwarding acknowledgement.",
|
|
1233
|
+
envelopeKind: envelope.kind,
|
|
1234
|
+
ownership,
|
|
1235
|
+
delivery: envelope.delivery,
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
if (!response.ok) {
|
|
1239
|
+
return reject({
|
|
1240
|
+
status: "retryable",
|
|
1241
|
+
failureClass: "acknowledgement-rejected",
|
|
1242
|
+
message:
|
|
1243
|
+
response.message ?? "Follower rejected forwarded Telegram update.",
|
|
1244
|
+
envelopeKind: envelope.kind,
|
|
1245
|
+
ownership,
|
|
1246
|
+
delivery: envelope.delivery,
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
const receipt = response.result;
|
|
1250
|
+
if (
|
|
1251
|
+
!isRecord(receipt) ||
|
|
1252
|
+
typeof receipt.deliveryId !== "string" ||
|
|
1253
|
+
!Number.isSafeInteger(receipt.sourceUpdateId)
|
|
1254
|
+
) {
|
|
1255
|
+
return reject({
|
|
1256
|
+
status: "terminal-rejected",
|
|
1257
|
+
failureClass: "durable-receipt-missing",
|
|
1258
|
+
message: "Follower acknowledgement omitted the durable receipt.",
|
|
1259
|
+
envelopeKind: envelope.kind,
|
|
1260
|
+
ownership,
|
|
1261
|
+
delivery: envelope.delivery,
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
if (
|
|
1265
|
+
receipt.deliveryId !== envelope.delivery.deliveryId ||
|
|
1266
|
+
receipt.sourceUpdateId !== envelope.delivery.sourceUpdateId
|
|
1267
|
+
) {
|
|
1268
|
+
return reject({
|
|
1269
|
+
status: "terminal-rejected",
|
|
1270
|
+
failureClass: "durable-receipt-mismatched",
|
|
1271
|
+
message: "Follower acknowledgement returned a mismatched durable receipt.",
|
|
1272
|
+
envelopeKind: envelope.kind,
|
|
1273
|
+
ownership,
|
|
1274
|
+
delivery: envelope.delivery,
|
|
1275
|
+
});
|
|
796
1276
|
}
|
|
797
|
-
return accepted;
|
|
1277
|
+
return { status: "accepted", delivery: envelope.delivery };
|
|
798
1278
|
};
|
|
799
1279
|
return {
|
|
800
|
-
forwardCallback: ({ query, ownership }) =>
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
:
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1280
|
+
forwardCallback: ({ query, ownership }) => {
|
|
1281
|
+
const prepared = prepare("leader.forwardCallback", query, ownership);
|
|
1282
|
+
if ("settlement" in prepared) return Promise.resolve(prepared.settlement);
|
|
1283
|
+
return send(
|
|
1284
|
+
{
|
|
1285
|
+
kind: "leader.forwardCallback",
|
|
1286
|
+
requestId: deps.createRequestId(),
|
|
1287
|
+
recipientInstanceId: ownership.instanceId,
|
|
1288
|
+
recipientRegistrationGeneration:
|
|
1289
|
+
prepared.recipientRegistrationGeneration,
|
|
1290
|
+
delivery: prepared.delivery,
|
|
1291
|
+
query,
|
|
1292
|
+
sentAtMs: getNowMs(),
|
|
1293
|
+
},
|
|
1294
|
+
ownership,
|
|
1295
|
+
);
|
|
1296
|
+
},
|
|
1297
|
+
forwardReaction: ({ reactionUpdate, ownership }) => {
|
|
1298
|
+
const prepared = prepare(
|
|
1299
|
+
"leader.forwardReaction",
|
|
819
1300
|
reactionUpdate,
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
send(
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
send(
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1301
|
+
ownership,
|
|
1302
|
+
);
|
|
1303
|
+
if ("settlement" in prepared) return Promise.resolve(prepared.settlement);
|
|
1304
|
+
return send(
|
|
1305
|
+
{
|
|
1306
|
+
kind: "leader.forwardReaction",
|
|
1307
|
+
requestId: deps.createRequestId(),
|
|
1308
|
+
recipientInstanceId: ownership.instanceId,
|
|
1309
|
+
recipientRegistrationGeneration:
|
|
1310
|
+
prepared.recipientRegistrationGeneration,
|
|
1311
|
+
delivery: prepared.delivery,
|
|
1312
|
+
reactionUpdate,
|
|
1313
|
+
sentAtMs: getNowMs(),
|
|
1314
|
+
},
|
|
1315
|
+
ownership,
|
|
1316
|
+
);
|
|
1317
|
+
},
|
|
1318
|
+
forwardMessage: ({ message, ownership }) => {
|
|
1319
|
+
const prepared = prepare("leader.forwardMessage", message, ownership);
|
|
1320
|
+
if ("settlement" in prepared) return Promise.resolve(prepared.settlement);
|
|
1321
|
+
return send(
|
|
1322
|
+
{
|
|
1323
|
+
kind: "leader.forwardMessage",
|
|
1324
|
+
requestId: deps.createRequestId(),
|
|
1325
|
+
recipientInstanceId: ownership.instanceId,
|
|
1326
|
+
recipientRegistrationGeneration:
|
|
1327
|
+
prepared.recipientRegistrationGeneration,
|
|
1328
|
+
delivery: prepared.delivery,
|
|
1329
|
+
message,
|
|
1330
|
+
...(deps.getForwardCommentBatchPosition?.(message) !== undefined
|
|
1331
|
+
? {
|
|
1332
|
+
forwardCommentBatchPosition:
|
|
1333
|
+
deps.getForwardCommentBatchPosition(message),
|
|
1334
|
+
}
|
|
1335
|
+
: {}),
|
|
1336
|
+
sentAtMs: getNowMs(),
|
|
1337
|
+
},
|
|
1338
|
+
ownership,
|
|
1339
|
+
);
|
|
1340
|
+
},
|
|
1341
|
+
forwardEditedMessage: ({ message, ownership }) => {
|
|
1342
|
+
const prepared = prepare(
|
|
1343
|
+
"leader.forwardEditedMessage",
|
|
847
1344
|
message,
|
|
848
|
-
|
|
849
|
-
|
|
1345
|
+
ownership,
|
|
1346
|
+
);
|
|
1347
|
+
if ("settlement" in prepared) return Promise.resolve(prepared.settlement);
|
|
1348
|
+
return send(
|
|
1349
|
+
{
|
|
1350
|
+
kind: "leader.forwardEditedMessage",
|
|
1351
|
+
requestId: deps.createRequestId(),
|
|
1352
|
+
recipientInstanceId: ownership.instanceId,
|
|
1353
|
+
recipientRegistrationGeneration:
|
|
1354
|
+
prepared.recipientRegistrationGeneration,
|
|
1355
|
+
delivery: prepared.delivery,
|
|
1356
|
+
message,
|
|
1357
|
+
sentAtMs: getNowMs(),
|
|
1358
|
+
},
|
|
1359
|
+
ownership,
|
|
1360
|
+
);
|
|
1361
|
+
},
|
|
850
1362
|
};
|
|
851
1363
|
}
|
|
852
1364
|
|
|
@@ -885,16 +1397,14 @@ export function createTelegramBusFollowerTargetController(
|
|
|
885
1397
|
const getNowMs = deps.getNowMs ?? Date.now;
|
|
886
1398
|
return {
|
|
887
1399
|
async replaceTarget({ follower, target, oldTarget, reason }) {
|
|
888
|
-
if (!follower.busSocketPath)
|
|
1400
|
+
if (!follower.busSocketPath || !follower.registrationGeneration) {
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
889
1403
|
const envelope: TelegramBusEnvelope = {
|
|
890
1404
|
kind: "leader.replaceFollowerTarget",
|
|
891
1405
|
requestId: deps.createRequestId(),
|
|
892
1406
|
recipientInstanceId: follower.instanceId,
|
|
893
|
-
|
|
894
|
-
? {
|
|
895
|
-
recipientRegistrationGeneration: follower.registrationGeneration,
|
|
896
|
-
}
|
|
897
|
-
: {}),
|
|
1407
|
+
recipientRegistrationGeneration: follower.registrationGeneration,
|
|
898
1408
|
target,
|
|
899
1409
|
...(oldTarget ? { oldTarget } : {}),
|
|
900
1410
|
reason,
|
|
@@ -947,7 +1457,11 @@ export function isTelegramBusEnvelopeAuthorized(
|
|
|
947
1457
|
envelope: TelegramBusEnvelope,
|
|
948
1458
|
secret: string | undefined,
|
|
949
1459
|
): boolean {
|
|
950
|
-
|
|
1460
|
+
if (!secret) return true;
|
|
1461
|
+
if (typeof envelope.auth !== "string") return false;
|
|
1462
|
+
const auth = Buffer.from(envelope.auth);
|
|
1463
|
+
const expected = Buffer.from(secret);
|
|
1464
|
+
return auth.length === expected.length && timingSafeEqual(auth, expected);
|
|
951
1465
|
}
|
|
952
1466
|
|
|
953
1467
|
export function createUnauthorizedBusAck(
|
|
@@ -1354,6 +1868,14 @@ export function createTelegramBusFollowerRegistry(): TelegramBusFollowerRegistry
|
|
|
1354
1868
|
): TelegramBusFollowerView => ({
|
|
1355
1869
|
...follower,
|
|
1356
1870
|
target: follower.target ? { ...follower.target } : undefined,
|
|
1871
|
+
...(follower.protocol
|
|
1872
|
+
? {
|
|
1873
|
+
protocol: {
|
|
1874
|
+
...follower.protocol,
|
|
1875
|
+
capabilities: [...follower.protocol.capabilities],
|
|
1876
|
+
},
|
|
1877
|
+
}
|
|
1878
|
+
: {}),
|
|
1357
1879
|
});
|
|
1358
1880
|
return {
|
|
1359
1881
|
register: (registration) => {
|
|
@@ -1372,6 +1894,14 @@ export function createTelegramBusFollowerRegistry(): TelegramBusFollowerRegistry
|
|
|
1372
1894
|
const next: TelegramBusFollowerView = {
|
|
1373
1895
|
...registration,
|
|
1374
1896
|
target: registration.target ? { ...registration.target } : undefined,
|
|
1897
|
+
...(registration.protocol
|
|
1898
|
+
? {
|
|
1899
|
+
protocol: {
|
|
1900
|
+
...registration.protocol,
|
|
1901
|
+
capabilities: [...registration.protocol.capabilities],
|
|
1902
|
+
},
|
|
1903
|
+
}
|
|
1904
|
+
: {}),
|
|
1375
1905
|
lastHeartbeatMs:
|
|
1376
1906
|
existing?.lastHeartbeatMs ?? registration.connectedAtMs,
|
|
1377
1907
|
};
|
|
@@ -1510,22 +2040,42 @@ function parseDisconnectEnvelope(
|
|
|
1510
2040
|
: undefined;
|
|
1511
2041
|
}
|
|
1512
2042
|
|
|
2043
|
+
function parseTelegramBusFollowerDeliveryIdentity(
|
|
2044
|
+
value: unknown,
|
|
2045
|
+
): TelegramBusFollowerDeliveryIdentity | undefined {
|
|
2046
|
+
if (!isRecord(value)) return undefined;
|
|
2047
|
+
if (
|
|
2048
|
+
typeof value.deliveryId !== "string" ||
|
|
2049
|
+
!/^telegram-follower-v1-[a-f0-9]{64}$/u.test(value.deliveryId) ||
|
|
2050
|
+
!Number.isSafeInteger(value.sourceUpdateId) ||
|
|
2051
|
+
(value.sourceUpdateId as number) < 0 ||
|
|
2052
|
+
typeof value.recipientBindingKey !== "string" ||
|
|
2053
|
+
!value.recipientBindingKey
|
|
2054
|
+
) {
|
|
2055
|
+
return undefined;
|
|
2056
|
+
}
|
|
2057
|
+
return {
|
|
2058
|
+
deliveryId: value.deliveryId,
|
|
2059
|
+
sourceUpdateId: value.sourceUpdateId as number,
|
|
2060
|
+
recipientBindingKey: value.recipientBindingKey,
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1513
2064
|
function parseForwardCallbackEnvelope(
|
|
1514
2065
|
value: Record<string, unknown>,
|
|
1515
2066
|
requestId: string,
|
|
1516
2067
|
): TelegramBusEnvelope | undefined {
|
|
1517
|
-
|
|
2068
|
+
const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
|
|
2069
|
+
return delivery &&
|
|
2070
|
+
typeof value.recipientInstanceId === "string" &&
|
|
2071
|
+
typeof value.recipientRegistrationGeneration === "string" &&
|
|
1518
2072
|
typeof value.sentAtMs === "number"
|
|
1519
2073
|
? {
|
|
1520
2074
|
kind: "leader.forwardCallback",
|
|
1521
2075
|
requestId,
|
|
1522
2076
|
recipientInstanceId: value.recipientInstanceId,
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
recipientRegistrationGeneration:
|
|
1526
|
-
value.recipientRegistrationGeneration,
|
|
1527
|
-
}
|
|
1528
|
-
: {}),
|
|
2077
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2078
|
+
delivery,
|
|
1529
2079
|
query: value.query,
|
|
1530
2080
|
sentAtMs: value.sentAtMs,
|
|
1531
2081
|
}
|
|
@@ -1536,18 +2086,17 @@ function parseForwardReactionEnvelope(
|
|
|
1536
2086
|
value: Record<string, unknown>,
|
|
1537
2087
|
requestId: string,
|
|
1538
2088
|
): TelegramBusEnvelope | undefined {
|
|
1539
|
-
|
|
2089
|
+
const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
|
|
2090
|
+
return delivery &&
|
|
2091
|
+
typeof value.recipientInstanceId === "string" &&
|
|
2092
|
+
typeof value.recipientRegistrationGeneration === "string" &&
|
|
1540
2093
|
typeof value.sentAtMs === "number"
|
|
1541
2094
|
? {
|
|
1542
2095
|
kind: "leader.forwardReaction",
|
|
1543
2096
|
requestId,
|
|
1544
2097
|
recipientInstanceId: value.recipientInstanceId,
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
recipientRegistrationGeneration:
|
|
1548
|
-
value.recipientRegistrationGeneration,
|
|
1549
|
-
}
|
|
1550
|
-
: {}),
|
|
2098
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2099
|
+
delivery,
|
|
1551
2100
|
reactionUpdate: value.reactionUpdate,
|
|
1552
2101
|
sentAtMs: value.sentAtMs,
|
|
1553
2102
|
}
|
|
@@ -1559,18 +2108,17 @@ function parseForwardMessageEnvelope(
|
|
|
1559
2108
|
requestId: string,
|
|
1560
2109
|
kind: "leader.forwardMessage" | "leader.forwardEditedMessage",
|
|
1561
2110
|
): TelegramBusEnvelope | undefined {
|
|
1562
|
-
|
|
2111
|
+
const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
|
|
2112
|
+
return delivery &&
|
|
2113
|
+
typeof value.recipientInstanceId === "string" &&
|
|
2114
|
+
typeof value.recipientRegistrationGeneration === "string" &&
|
|
1563
2115
|
typeof value.sentAtMs === "number"
|
|
1564
2116
|
? {
|
|
1565
2117
|
kind,
|
|
1566
2118
|
requestId,
|
|
1567
2119
|
recipientInstanceId: value.recipientInstanceId,
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
recipientRegistrationGeneration:
|
|
1571
|
-
value.recipientRegistrationGeneration,
|
|
1572
|
-
}
|
|
1573
|
-
: {}),
|
|
2120
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2121
|
+
delivery,
|
|
1574
2122
|
message: value.message,
|
|
1575
2123
|
...(kind === "leader.forwardMessage" &&
|
|
1576
2124
|
(value.forwardCommentBatchPosition === "comment" ||
|
|
@@ -1616,6 +2164,247 @@ function parseReplaceFollowerTargetEnvelope(
|
|
|
1616
2164
|
};
|
|
1617
2165
|
}
|
|
1618
2166
|
|
|
2167
|
+
function parseQueueAdmissionReceipt(
|
|
2168
|
+
value: unknown,
|
|
2169
|
+
queueKind: "prompt" | "control",
|
|
2170
|
+
):
|
|
2171
|
+
| {
|
|
2172
|
+
queueKind: "prompt" | "control";
|
|
2173
|
+
receiptId: string;
|
|
2174
|
+
sourceUpdateIds: number[];
|
|
2175
|
+
}
|
|
2176
|
+
| undefined {
|
|
2177
|
+
if (!isRecord(value) || value.queueKind !== queueKind) return undefined;
|
|
2178
|
+
const sourceUpdateIds = Array.isArray(value.sourceUpdateIds)
|
|
2179
|
+
? value.sourceUpdateIds
|
|
2180
|
+
: undefined;
|
|
2181
|
+
if (
|
|
2182
|
+
typeof value.receiptId !== "string" ||
|
|
2183
|
+
!value.receiptId ||
|
|
2184
|
+
(value.journalBindingKey !== undefined &&
|
|
2185
|
+
(typeof value.journalBindingKey !== "string" ||
|
|
2186
|
+
!value.journalBindingKey.trim())) ||
|
|
2187
|
+
!sourceUpdateIds ||
|
|
2188
|
+
sourceUpdateIds.length === 0 ||
|
|
2189
|
+
sourceUpdateIds.some(
|
|
2190
|
+
(updateId, index) =>
|
|
2191
|
+
!Number.isSafeInteger(updateId) ||
|
|
2192
|
+
(updateId as number) < 0 ||
|
|
2193
|
+
(index > 0 &&
|
|
2194
|
+
(updateId as number) <=
|
|
2195
|
+
(sourceUpdateIds[index - 1] as number)),
|
|
2196
|
+
)
|
|
2197
|
+
) {
|
|
2198
|
+
return undefined;
|
|
2199
|
+
}
|
|
2200
|
+
return {
|
|
2201
|
+
queueKind,
|
|
2202
|
+
receiptId: value.receiptId,
|
|
2203
|
+
sourceUpdateIds: sourceUpdateIds as number[],
|
|
2204
|
+
...(typeof value.journalBindingKey === "string"
|
|
2205
|
+
? { journalBindingKey: value.journalBindingKey }
|
|
2206
|
+
: {}),
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
function parseQueueHandoffPayload(
|
|
2211
|
+
value: unknown,
|
|
2212
|
+
): TelegramQueueHandoffPayload | undefined {
|
|
2213
|
+
if (!isRecord(value) || (value.kind !== "prompt" && value.kind !== "control")) {
|
|
2214
|
+
return undefined;
|
|
2215
|
+
}
|
|
2216
|
+
const queueKind = value.kind;
|
|
2217
|
+
const target = parseTarget(value.target);
|
|
2218
|
+
const transportStamp = isRecord(value.transportStamp) &&
|
|
2219
|
+
typeof value.transportStamp.profile === "string" &&
|
|
2220
|
+
typeof value.transportStamp.generation === "string"
|
|
2221
|
+
? {
|
|
2222
|
+
profile: value.transportStamp.profile,
|
|
2223
|
+
generation: value.transportStamp.generation,
|
|
2224
|
+
}
|
|
2225
|
+
: undefined;
|
|
2226
|
+
if (
|
|
2227
|
+
!Number.isSafeInteger(value.chatId) ||
|
|
2228
|
+
(value.target !== undefined && !target) ||
|
|
2229
|
+
(value.transportStamp !== undefined && !transportStamp) ||
|
|
2230
|
+
!Number.isSafeInteger(value.replyToMessageId) ||
|
|
2231
|
+
(value.guestQueryId !== undefined &&
|
|
2232
|
+
typeof value.guestQueryId !== "string") ||
|
|
2233
|
+
!Number.isSafeInteger(value.queueOrder) ||
|
|
2234
|
+
(value.queueLane !== "control" &&
|
|
2235
|
+
value.queueLane !== "priority" &&
|
|
2236
|
+
value.queueLane !== "default") ||
|
|
2237
|
+
!Number.isSafeInteger(value.laneOrder) ||
|
|
2238
|
+
typeof value.statusSummary !== "string" ||
|
|
2239
|
+
!Array.isArray(value.admissionReceipts)
|
|
2240
|
+
) {
|
|
2241
|
+
return undefined;
|
|
2242
|
+
}
|
|
2243
|
+
const admissionReceipts = value.admissionReceipts.map((receipt) =>
|
|
2244
|
+
parseQueueAdmissionReceipt(receipt, queueKind),
|
|
2245
|
+
);
|
|
2246
|
+
if (
|
|
2247
|
+
admissionReceipts.length === 0 ||
|
|
2248
|
+
admissionReceipts.length > TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS ||
|
|
2249
|
+
admissionReceipts.some((receipt) => receipt === undefined)
|
|
2250
|
+
) {
|
|
2251
|
+
return undefined;
|
|
2252
|
+
}
|
|
2253
|
+
const queueLane = value.queueLane as "control" | "priority" | "default";
|
|
2254
|
+
const base = {
|
|
2255
|
+
chatId: value.chatId as number,
|
|
2256
|
+
...(target ? { target } : {}),
|
|
2257
|
+
...(transportStamp ? { transportStamp } : {}),
|
|
2258
|
+
replyToMessageId: value.replyToMessageId as number,
|
|
2259
|
+
...(typeof value.guestQueryId === "string"
|
|
2260
|
+
? { guestQueryId: value.guestQueryId }
|
|
2261
|
+
: {}),
|
|
2262
|
+
queueOrder: value.queueOrder as number,
|
|
2263
|
+
queueLane,
|
|
2264
|
+
laneOrder: value.laneOrder as number,
|
|
2265
|
+
statusSummary: value.statusSummary,
|
|
2266
|
+
admissionReceipts: admissionReceipts as NonNullable<
|
|
2267
|
+
(typeof admissionReceipts)[number]
|
|
2268
|
+
>[],
|
|
2269
|
+
};
|
|
2270
|
+
if (queueKind === "control") {
|
|
2271
|
+
if (
|
|
2272
|
+
value.queueLane !== "control" ||
|
|
2273
|
+
(value.controlType !== "status" && value.controlType !== "model")
|
|
2274
|
+
) {
|
|
2275
|
+
return undefined;
|
|
2276
|
+
}
|
|
2277
|
+
return { kind: "control", controlType: value.controlType, ...base };
|
|
2278
|
+
}
|
|
2279
|
+
if (
|
|
2280
|
+
value.queueLane === "control" ||
|
|
2281
|
+
!Array.isArray(value.sourceMessageIds) ||
|
|
2282
|
+
value.sourceMessageIds.some((id) => !Number.isSafeInteger(id)) ||
|
|
2283
|
+
!Array.isArray(value.queuedAttachments) ||
|
|
2284
|
+
value.queuedAttachments.some(
|
|
2285
|
+
(attachment) =>
|
|
2286
|
+
!isRecord(attachment) ||
|
|
2287
|
+
typeof attachment.path !== "string" ||
|
|
2288
|
+
typeof attachment.fileName !== "string",
|
|
2289
|
+
) ||
|
|
2290
|
+
!Array.isArray(value.content) ||
|
|
2291
|
+
value.content.some(
|
|
2292
|
+
(content) =>
|
|
2293
|
+
!isRecord(content) ||
|
|
2294
|
+
(content.type === "text"
|
|
2295
|
+
? typeof content.text !== "string"
|
|
2296
|
+
: content.type === "image"
|
|
2297
|
+
? typeof content.data !== "string" ||
|
|
2298
|
+
typeof content.mimeType !== "string"
|
|
2299
|
+
: true),
|
|
2300
|
+
) ||
|
|
2301
|
+
typeof value.historyText !== "string" ||
|
|
2302
|
+
(value.priorityEmoji !== undefined &&
|
|
2303
|
+
typeof value.priorityEmoji !== "string") ||
|
|
2304
|
+
(value.reactionSuppressionEmoji !== undefined &&
|
|
2305
|
+
typeof value.reactionSuppressionEmoji !== "string") ||
|
|
2306
|
+
(value.voiceReplyPreferred !== undefined &&
|
|
2307
|
+
typeof value.voiceReplyPreferred !== "boolean") ||
|
|
2308
|
+
(value.voiceReplyRequired !== undefined &&
|
|
2309
|
+
typeof value.voiceReplyRequired !== "boolean")
|
|
2310
|
+
) {
|
|
2311
|
+
return undefined;
|
|
2312
|
+
}
|
|
2313
|
+
return {
|
|
2314
|
+
kind: "prompt",
|
|
2315
|
+
...base,
|
|
2316
|
+
sourceMessageIds: value.sourceMessageIds as number[],
|
|
2317
|
+
queuedAttachments: value.queuedAttachments as Array<{
|
|
2318
|
+
path: string;
|
|
2319
|
+
fileName: string;
|
|
2320
|
+
}>,
|
|
2321
|
+
content: value.content as Array<
|
|
2322
|
+
| { type: "text"; text: string }
|
|
2323
|
+
| { type: "image"; data: string; mimeType: string }
|
|
2324
|
+
>,
|
|
2325
|
+
historyText: value.historyText,
|
|
2326
|
+
...(typeof value.priorityEmoji === "string"
|
|
2327
|
+
? { priorityEmoji: value.priorityEmoji }
|
|
2328
|
+
: {}),
|
|
2329
|
+
...(typeof value.reactionSuppressionEmoji === "string"
|
|
2330
|
+
? { reactionSuppressionEmoji: value.reactionSuppressionEmoji }
|
|
2331
|
+
: {}),
|
|
2332
|
+
...(typeof value.voiceReplyPreferred === "boolean"
|
|
2333
|
+
? { voiceReplyPreferred: value.voiceReplyPreferred }
|
|
2334
|
+
: {}),
|
|
2335
|
+
...(typeof value.voiceReplyRequired === "boolean"
|
|
2336
|
+
? { voiceReplyRequired: value.voiceReplyRequired }
|
|
2337
|
+
: {}),
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
function parseQueueHandoffEnvelope(
|
|
2342
|
+
value: Record<string, unknown>,
|
|
2343
|
+
requestId: string,
|
|
2344
|
+
kind: "leader.offerQueueHandoff" | "follower.offerQueueHandoff",
|
|
2345
|
+
): TelegramBusEnvelope | undefined {
|
|
2346
|
+
let serializedPayloadBytes: number;
|
|
2347
|
+
try {
|
|
2348
|
+
serializedPayloadBytes = Buffer.byteLength(JSON.stringify(value.payload));
|
|
2349
|
+
} catch {
|
|
2350
|
+
return undefined;
|
|
2351
|
+
}
|
|
2352
|
+
if (serializedPayloadBytes > TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES) {
|
|
2353
|
+
return undefined;
|
|
2354
|
+
}
|
|
2355
|
+
const payload = parseQueueHandoffPayload(value.payload);
|
|
2356
|
+
if (
|
|
2357
|
+
!payload ||
|
|
2358
|
+
typeof value.recipientInstanceId !== "string" ||
|
|
2359
|
+
typeof value.recipientRegistrationGeneration !== "string" ||
|
|
2360
|
+
!Number.isSafeInteger(value.donorProcessId) ||
|
|
2361
|
+
(value.donorProcessId as number) <= 0 ||
|
|
2362
|
+
typeof value.donorProcessBirthId !== "string" ||
|
|
2363
|
+
!value.donorProcessBirthId ||
|
|
2364
|
+
!Number.isSafeInteger(value.donorSessionGeneration) ||
|
|
2365
|
+
(value.donorSessionGeneration as number) <= 0 ||
|
|
2366
|
+
typeof value.donorAcquisitionId !== "string" ||
|
|
2367
|
+
!value.donorAcquisitionId ||
|
|
2368
|
+
!Number.isSafeInteger(value.donorAcquiredAtMs) ||
|
|
2369
|
+
(value.donorAcquiredAtMs as number) < 0 ||
|
|
2370
|
+
typeof value.handoffToken !== "string" ||
|
|
2371
|
+
value.handoffToken.length < 32 ||
|
|
2372
|
+
value.handoffToken.length > 256 ||
|
|
2373
|
+
value.donorProcessBirthId.length > 256 ||
|
|
2374
|
+
value.donorAcquisitionId.length > 256 ||
|
|
2375
|
+
typeof value.sentAtMs !== "number"
|
|
2376
|
+
) {
|
|
2377
|
+
return undefined;
|
|
2378
|
+
}
|
|
2379
|
+
const fields = {
|
|
2380
|
+
requestId,
|
|
2381
|
+
recipientInstanceId: value.recipientInstanceId,
|
|
2382
|
+
recipientRegistrationGeneration: value.recipientRegistrationGeneration,
|
|
2383
|
+
donorProcessId: value.donorProcessId as number,
|
|
2384
|
+
donorProcessBirthId: value.donorProcessBirthId,
|
|
2385
|
+
donorSessionGeneration: value.donorSessionGeneration as number,
|
|
2386
|
+
donorAcquisitionId: value.donorAcquisitionId,
|
|
2387
|
+
donorAcquiredAtMs: value.donorAcquiredAtMs as number,
|
|
2388
|
+
handoffToken: value.handoffToken,
|
|
2389
|
+
payload,
|
|
2390
|
+
sentAtMs: value.sentAtMs,
|
|
2391
|
+
};
|
|
2392
|
+
if (kind === "leader.offerQueueHandoff") {
|
|
2393
|
+
return typeof value.donorInstanceId === "string"
|
|
2394
|
+
? { kind, donorInstanceId: value.donorInstanceId, ...fields }
|
|
2395
|
+
: undefined;
|
|
2396
|
+
}
|
|
2397
|
+
return typeof value.instanceId === "string" &&
|
|
2398
|
+
typeof value.registrationGeneration === "string"
|
|
2399
|
+
? {
|
|
2400
|
+
kind,
|
|
2401
|
+
instanceId: value.instanceId,
|
|
2402
|
+
registrationGeneration: value.registrationGeneration,
|
|
2403
|
+
...fields,
|
|
2404
|
+
}
|
|
2405
|
+
: undefined;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
1619
2408
|
function parseResolveAgentTargetEnvelope(
|
|
1620
2409
|
value: Record<string, unknown>,
|
|
1621
2410
|
requestId: string,
|
|
@@ -1730,12 +2519,15 @@ function parseAckEnvelope(
|
|
|
1730
2519
|
message: typeof value.message === "string" ? value.message : undefined,
|
|
1731
2520
|
};
|
|
1732
2521
|
if (Object.hasOwn(value, "result")) envelope.result = value.result;
|
|
2522
|
+
const protocol = parseTelegramBusProtocolIdentity(value.protocol);
|
|
2523
|
+
if (protocol) envelope.protocol = protocol;
|
|
1733
2524
|
if (isRecord(value.error)) {
|
|
1734
2525
|
const code = value.error.code;
|
|
1735
2526
|
if (
|
|
1736
2527
|
code === "commit-unknown" ||
|
|
1737
2528
|
code === "request-id-collision" ||
|
|
1738
|
-
code === "ledger-overloaded"
|
|
2529
|
+
code === "ledger-overloaded" ||
|
|
2530
|
+
code === "incompatible-protocol"
|
|
1739
2531
|
) {
|
|
1740
2532
|
envelope.error = {
|
|
1741
2533
|
code,
|
|
@@ -1748,6 +2540,44 @@ function parseAckEnvelope(
|
|
|
1748
2540
|
return envelope;
|
|
1749
2541
|
}
|
|
1750
2542
|
|
|
2543
|
+
function parseTelegramBusProtocolIdentity(
|
|
2544
|
+
value: unknown,
|
|
2545
|
+
): TelegramBusProtocolIdentity | undefined {
|
|
2546
|
+
if (!isRecord(value)) return undefined;
|
|
2547
|
+
if (
|
|
2548
|
+
!Number.isSafeInteger(value.protocolVersion) ||
|
|
2549
|
+
(value.protocolVersion as number) <= 0 ||
|
|
2550
|
+
typeof value.runtimeBuild !== "string" ||
|
|
2551
|
+
!value.runtimeBuild.trim() ||
|
|
2552
|
+
value.runtimeBuild !== value.runtimeBuild.trim() ||
|
|
2553
|
+
value.runtimeBuild.length > 128 ||
|
|
2554
|
+
!Array.isArray(value.capabilities) ||
|
|
2555
|
+
value.capabilities.length > 32 ||
|
|
2556
|
+
value.capabilities.some(
|
|
2557
|
+
(capability) =>
|
|
2558
|
+
typeof capability !== "string" ||
|
|
2559
|
+
capability.length > 128 ||
|
|
2560
|
+
!/^[a-z0-9][a-z0-9._-]*$/u.test(capability),
|
|
2561
|
+
)
|
|
2562
|
+
) {
|
|
2563
|
+
return undefined;
|
|
2564
|
+
}
|
|
2565
|
+
const capabilities = value.capabilities as string[];
|
|
2566
|
+
if (
|
|
2567
|
+
capabilities.some(
|
|
2568
|
+
(capability, index) =>
|
|
2569
|
+
index > 0 && capability <= capabilities[index - 1]!,
|
|
2570
|
+
)
|
|
2571
|
+
) {
|
|
2572
|
+
return undefined;
|
|
2573
|
+
}
|
|
2574
|
+
return {
|
|
2575
|
+
protocolVersion: value.protocolVersion as number,
|
|
2576
|
+
runtimeBuild: value.runtimeBuild,
|
|
2577
|
+
capabilities: [...capabilities],
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
|
|
1751
2581
|
function parseRegistration(
|
|
1752
2582
|
value: unknown,
|
|
1753
2583
|
): TelegramBusInstanceRegistration | undefined {
|
|
@@ -1778,6 +2608,17 @@ function parseRegistration(
|
|
|
1778
2608
|
if (typeof value.registrationGeneration === "string") {
|
|
1779
2609
|
registration.registrationGeneration = value.registrationGeneration;
|
|
1780
2610
|
}
|
|
2611
|
+
if (
|
|
2612
|
+
Number.isSafeInteger(value.sessionGeneration) &&
|
|
2613
|
+
(value.sessionGeneration as number) > 0
|
|
2614
|
+
) {
|
|
2615
|
+
registration.sessionGeneration = value.sessionGeneration as number;
|
|
2616
|
+
}
|
|
2617
|
+
if (typeof value.processBirthId === "string" && value.processBirthId) {
|
|
2618
|
+
registration.processBirthId = value.processBirthId;
|
|
2619
|
+
}
|
|
2620
|
+
const protocol = parseTelegramBusProtocolIdentity(value.protocol);
|
|
2621
|
+
if (protocol) registration.protocol = protocol;
|
|
1781
2622
|
if (target) registration.target = target;
|
|
1782
2623
|
return registration;
|
|
1783
2624
|
}
|