@llblab/pi-telegram 0.45.11 → 0.47.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.
Files changed (204) hide show
  1. package/BACKLOG.md +5 -1
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +6 -6
  4. package/dist/api/activity.d.ts +6 -0
  5. package/dist/api/activity.js +6 -0
  6. package/dist/api/commands.d.ts +6 -0
  7. package/dist/api/commands.js +6 -0
  8. package/dist/api/delivery.d.ts +6 -0
  9. package/dist/api/delivery.js +6 -0
  10. package/dist/api/inbound.d.ts +6 -0
  11. package/dist/api/inbound.js +6 -0
  12. package/dist/api/keyboard.d.ts +6 -0
  13. package/dist/api/keyboard.js +6 -0
  14. package/dist/api/outbound.d.ts +6 -0
  15. package/dist/api/outbound.js +6 -0
  16. package/dist/api/sections.d.ts +7 -0
  17. package/dist/api/sections.js +6 -0
  18. package/dist/api/status.d.ts +6 -0
  19. package/dist/api/status.js +6 -0
  20. package/dist/api/updates.d.ts +6 -0
  21. package/dist/api/updates.js +6 -0
  22. package/dist/api/voice.d.ts +6 -0
  23. package/dist/api/voice.js +6 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +6 -0
  26. package/dist/lib/activity-verbosity.d.ts +52 -0
  27. package/dist/lib/activity-verbosity.js +596 -0
  28. package/dist/lib/activity.d.ts +220 -0
  29. package/dist/lib/activity.js +574 -0
  30. package/dist/lib/agent-messages.d.ts +28 -0
  31. package/dist/lib/agent-messages.js +86 -0
  32. package/dist/lib/bindings.d.ts +250 -0
  33. package/dist/lib/bindings.js +936 -0
  34. package/dist/lib/bus-api.d.ts +18 -0
  35. package/dist/lib/bus-api.js +254 -0
  36. package/dist/lib/bus-follower.d.ts +464 -0
  37. package/dist/lib/bus-follower.js +1686 -0
  38. package/dist/lib/bus-leader.d.ts +292 -0
  39. package/dist/lib/bus-leader.js +2242 -0
  40. package/dist/lib/bus-transport.d.ts +64 -0
  41. package/dist/lib/bus-transport.js +140 -0
  42. package/dist/lib/bus.d.ts +518 -0
  43. package/dist/lib/bus.js +2055 -0
  44. package/dist/lib/channel-posts.d.ts +170 -0
  45. package/dist/lib/channel-posts.js +611 -0
  46. package/dist/lib/command-templates.d.ts +70 -0
  47. package/dist/lib/command-templates.js +744 -0
  48. package/dist/lib/commands.d.ts +541 -0
  49. package/dist/lib/commands.js +1155 -0
  50. package/dist/lib/config.d.ts +252 -0
  51. package/dist/lib/config.js +889 -0
  52. package/dist/lib/delivery.d.ts +163 -0
  53. package/dist/lib/delivery.js +542 -0
  54. package/dist/lib/extension.d.ts +7 -0
  55. package/dist/lib/extension.js +1608 -0
  56. package/dist/lib/generative-app-worker.mjs +104 -0
  57. package/dist/lib/generative-apps.d.ts +212 -0
  58. package/dist/lib/generative-apps.js +1006 -0
  59. package/dist/lib/inbound.d.ts +91 -0
  60. package/dist/lib/inbound.js +502 -0
  61. package/dist/lib/journal.d.ts +665 -0
  62. package/dist/lib/journal.js +3701 -0
  63. package/dist/lib/keyboard.d.ts +23 -0
  64. package/dist/lib/keyboard.js +37 -0
  65. package/dist/lib/lifecycle.d.ts +157 -0
  66. package/dist/lib/lifecycle.js +395 -0
  67. package/dist/lib/locks.d.ts +164 -0
  68. package/dist/lib/locks.js +1208 -0
  69. package/dist/lib/logging.d.ts +50 -0
  70. package/dist/lib/logging.js +274 -0
  71. package/dist/lib/media.d.ts +181 -0
  72. package/dist/lib/media.js +602 -0
  73. package/dist/lib/menu-model.d.ts +217 -0
  74. package/dist/lib/menu-model.js +663 -0
  75. package/dist/lib/menu-queue.d.ts +37 -0
  76. package/dist/lib/menu-queue.js +408 -0
  77. package/dist/lib/menu-settings.d.ts +116 -0
  78. package/dist/lib/menu-settings.js +595 -0
  79. package/dist/lib/menu-status.d.ts +32 -0
  80. package/dist/lib/menu-status.js +112 -0
  81. package/dist/lib/menu-thinking.d.ts +29 -0
  82. package/dist/lib/menu-thinking.js +82 -0
  83. package/dist/lib/menu.d.ts +171 -0
  84. package/dist/lib/menu.js +323 -0
  85. package/dist/lib/model.d.ts +127 -0
  86. package/dist/lib/model.js +409 -0
  87. package/dist/lib/outbound-attachments.d.ts +241 -0
  88. package/dist/lib/outbound-attachments.js +639 -0
  89. package/dist/lib/outbound-buttons.d.ts +69 -0
  90. package/dist/lib/outbound-buttons.js +248 -0
  91. package/dist/lib/outbound-markup.d.ts +42 -0
  92. package/dist/lib/outbound-markup.js +678 -0
  93. package/dist/lib/outbound-voice.d.ts +55 -0
  94. package/dist/lib/outbound-voice.js +152 -0
  95. package/dist/lib/outbound.d.ts +185 -0
  96. package/dist/lib/outbound.js +516 -0
  97. package/dist/lib/ownership.d.ts +77 -0
  98. package/dist/lib/ownership.js +174 -0
  99. package/dist/lib/paths.d.ts +40 -0
  100. package/dist/lib/paths.js +94 -0
  101. package/dist/lib/pi.d.ts +72 -0
  102. package/dist/lib/pi.js +121 -0
  103. package/dist/lib/polling.d.ts +351 -0
  104. package/dist/lib/polling.js +1196 -0
  105. package/dist/lib/preview.d.ts +192 -0
  106. package/dist/lib/preview.js +614 -0
  107. package/dist/lib/prompt-templates.d.ts +24 -0
  108. package/dist/lib/prompt-templates.js +118 -0
  109. package/dist/lib/prompts.d.ts +57 -0
  110. package/dist/lib/prompts.js +167 -0
  111. package/dist/lib/queue.d.ts +766 -0
  112. package/dist/lib/queue.js +1965 -0
  113. package/dist/lib/recovery.d.ts +86 -0
  114. package/dist/lib/recovery.js +285 -0
  115. package/dist/lib/rendering.d.ts +20 -0
  116. package/dist/lib/rendering.js +983 -0
  117. package/dist/lib/replies.d.ts +214 -0
  118. package/dist/lib/replies.js +737 -0
  119. package/dist/lib/routing.d.ts +207 -0
  120. package/dist/lib/routing.js +2282 -0
  121. package/dist/lib/runtime.d.ts +172 -0
  122. package/dist/lib/runtime.js +402 -0
  123. package/dist/lib/sections.d.ts +165 -0
  124. package/dist/lib/sections.js +327 -0
  125. package/dist/lib/setup.d.ts +82 -0
  126. package/dist/lib/setup.js +150 -0
  127. package/dist/lib/skills.d.ts +8 -0
  128. package/dist/lib/skills.js +12 -0
  129. package/dist/lib/status.d.ts +442 -0
  130. package/dist/lib/status.js +1006 -0
  131. package/dist/lib/sync.d.ts +179 -0
  132. package/dist/lib/sync.js +782 -0
  133. package/dist/lib/target.d.ts +20 -0
  134. package/dist/lib/target.js +27 -0
  135. package/dist/lib/telegram-api.d.ts +541 -0
  136. package/dist/lib/telegram-api.js +1159 -0
  137. package/dist/lib/text-groups.d.ts +89 -0
  138. package/dist/lib/text-groups.js +317 -0
  139. package/dist/lib/thread-cleanup-manager.d.ts +288 -0
  140. package/dist/lib/thread-cleanup-manager.js +560 -0
  141. package/dist/lib/thread-display.d.ts +46 -0
  142. package/dist/lib/thread-display.js +257 -0
  143. package/dist/lib/thread-naming.d.ts +46 -0
  144. package/dist/lib/thread-naming.js +78 -0
  145. package/dist/lib/thread-reconciler.d.ts +239 -0
  146. package/dist/lib/thread-reconciler.js +644 -0
  147. package/dist/lib/threads.d.ts +621 -0
  148. package/dist/lib/threads.js +3679 -0
  149. package/dist/lib/time-injection.d.ts +15 -0
  150. package/dist/lib/time-injection.js +56 -0
  151. package/dist/lib/turns.d.ts +109 -0
  152. package/dist/lib/turns.js +500 -0
  153. package/dist/lib/updates.d.ts +1290 -0
  154. package/dist/lib/updates.js +3634 -0
  155. package/dist/lib/voice.d.ts +117 -0
  156. package/dist/lib/voice.js +174 -0
  157. package/dist/lib/workspace-admission.d.ts +264 -0
  158. package/dist/lib/workspace-admission.js +1136 -0
  159. package/dist/lib/workspace-retirement.d.ts +219 -0
  160. package/dist/lib/workspace-retirement.js +587 -0
  161. package/dist/lib/workspace-slots.d.ts +30 -0
  162. package/dist/lib/workspace-slots.js +54 -0
  163. package/dist/package.json +126 -0
  164. package/dist/pi-telegram/index.js +1 -0
  165. package/dist/skills/generated-control-surface/SKILL.md +107 -0
  166. package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
  167. package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
  168. package/dist/skills/generative-apps/SKILL.md +115 -0
  169. package/dist/skills/show-me/SKILL.md +166 -0
  170. package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
  171. package/dist/skills/telegram-bridge/SKILL.md +129 -0
  172. package/dist/skills/telegram-bridge/references/configuration.md +15 -0
  173. package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
  174. package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
  175. package/docs/architecture.md +1 -1
  176. package/docs/compact-matrix-literal.md +10 -3
  177. package/docs/generative-apps.md +15 -13
  178. package/docs/multi-instance-bus.md +17 -3
  179. package/docs/outbound.md +1 -1
  180. package/docs/public-api.md +2 -2
  181. package/lib/bindings.ts +89 -6
  182. package/lib/bus-follower.ts +11 -3
  183. package/lib/bus-leader.ts +57 -20
  184. package/lib/bus.ts +12 -2
  185. package/lib/commands.ts +2 -2
  186. package/lib/config.ts +14 -7
  187. package/lib/delivery.ts +38 -6
  188. package/lib/extension.ts +26 -1
  189. package/lib/generative-apps.ts +379 -13
  190. package/lib/menu-settings.ts +21 -5
  191. package/lib/outbound-markup.ts +16 -7
  192. package/lib/pi.ts +4 -0
  193. package/lib/sync.ts +3 -0
  194. package/lib/telegram-api.ts +12 -1
  195. package/lib/thread-cleanup-manager.ts +36 -6
  196. package/lib/thread-display.ts +96 -18
  197. package/lib/threads.ts +150 -30
  198. package/package.json +56 -13
  199. package/scripts/build-dist.mjs +44 -0
  200. package/scripts/measure-bus.mjs +8 -1
  201. package/scripts/measure-workspace.mjs +8 -1
  202. package/skills/generative-apps/SKILL.md +1 -1
  203. package/skills/show-me/SKILL.md +1 -1
  204. package/skills/telegram-bridge/SKILL.md +1 -1
@@ -0,0 +1,2055 @@
1
+ /**
2
+ * Telegram multi-instance bus protocol and IPC helpers
3
+ * Zones: multi-instance bus, local IPC contract, live instance routing
4
+ * Owns serializable bus envelopes, socket/auth helpers, local IPC client/server primitives,
5
+ * cross-instance forwarding helpers, and the live follower registry model.
6
+ */
7
+ import { execFileSync } from "node:child_process";
8
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
9
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, renameSync, symlinkSync, unlinkSync, } from "node:fs";
10
+ import { createConnection, createServer, } from "node:net";
11
+ import { createRequire } from "node:module";
12
+ import { platform as getPlatform, tmpdir } from "node:os";
13
+ import { basename, dirname, join } from "node:path";
14
+ import { classifyTelegramBusTransportError, createTelegramBusTransportTimeoutError, delayTelegramBusTransportRetry, getTelegramBusEndpointDiagnostics, getTelegramBusFollowerEndpoint, getTelegramBusLeaderEndpoint, getTelegramBusPipePath, getTelegramBusTransportRetryPolicy, isTelegramBusPipePath, isRetryableTelegramBusTransportError, probeTelegramBusEndpoint, } from "./bus-transport.js";
15
+ import { TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS, TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES, } from "./queue.js";
16
+ import { isProcessAlive } from "./locks.js";
17
+ import { resolveAgentDir } from "./paths.js";
18
+ function readDarwinProcessStart(pid) {
19
+ return execFileSync("/bin/ps", ["-o", "lstart=", "-p", String(pid)], {
20
+ encoding: "utf8",
21
+ stdio: ["ignore", "pipe", "ignore"],
22
+ }).trim();
23
+ }
24
+ export function getTelegramProcessBirthProof(pid, options = {}) {
25
+ if (pid <= 0)
26
+ return { status: "unverifiable" };
27
+ const platform = options.platform ?? getPlatform();
28
+ if (platform === "linux") {
29
+ try {
30
+ const stat = (options.readProcStat ?? ((targetPid) => readFileSync(`/proc/${targetPid}/stat`, "utf8")))(pid);
31
+ const closeParen = stat.lastIndexOf(")");
32
+ const fields = stat
33
+ .slice(closeParen + 2)
34
+ .trim()
35
+ .split(/\s+/u);
36
+ const startTicks = fields[19];
37
+ if (startTicks) {
38
+ return { status: "proven", identity: `${pid}:start:${startTicks}` };
39
+ }
40
+ }
41
+ catch {
42
+ /* inaccessible process metadata */
43
+ }
44
+ }
45
+ else if (platform === "darwin") {
46
+ try {
47
+ const startedAt = (options.readDarwinProcessStart ?? readDarwinProcessStart)(pid);
48
+ if (startedAt) {
49
+ const fingerprint = createHash("sha256")
50
+ .update(startedAt)
51
+ .digest("hex")
52
+ .slice(0, 16);
53
+ return { status: "proven", identity: `${pid}:start:${fingerprint}` };
54
+ }
55
+ }
56
+ catch {
57
+ /* inaccessible process metadata */
58
+ }
59
+ }
60
+ return { status: "unverifiable" };
61
+ }
62
+ export function getTelegramProcessBirthIdentity(pid, fallbackGeneration, options = {}) {
63
+ const proof = getTelegramProcessBirthProof(pid, options);
64
+ return proof.status === "proven"
65
+ ? proof.identity
66
+ : `${pid}:generation:${fallbackGeneration}`;
67
+ }
68
+ export function getTelegramProcessLiveness(owner, options = {}) {
69
+ const processAlive = options.isProcessAlive ?? isProcessAlive;
70
+ if (!processAlive(owner.processId))
71
+ return "dead";
72
+ const proof = getTelegramProcessBirthProof(owner.processId, options);
73
+ if (proof.status === "unverifiable")
74
+ return "unverifiable";
75
+ return proof.identity === owner.processBirthId ? "alive" : "dead";
76
+ }
77
+ export function getTelegramProcessBirthIdentityLiveness(processBirthId, options = {}) {
78
+ const match = /^(\d+):(start|generation):(.+)$/u.exec(processBirthId);
79
+ if (!match)
80
+ return "unverifiable";
81
+ const processId = Number(match[1]);
82
+ if (!Number.isSafeInteger(processId) || processId <= 0)
83
+ return "unverifiable";
84
+ const processAlive = options.isProcessAlive ?? isProcessAlive;
85
+ if (!processAlive(processId))
86
+ return "dead";
87
+ if (match[2] === "generation")
88
+ return "unverifiable";
89
+ const proof = getTelegramProcessBirthProof(processId, options);
90
+ if (proof.status === "unverifiable")
91
+ return "unverifiable";
92
+ return proof.identity === processBirthId ? "alive" : "dead";
93
+ }
94
+ export function createCurrentTelegramBusProcessRuntime(input) {
95
+ return createTelegramBusProcessRuntime({
96
+ getActiveProfileName: input.getActiveProfileName,
97
+ pid: input.pid ?? process.pid,
98
+ parentPid: input.parentPid ?? process.ppid,
99
+ createdAtMs: input.createdAtMs ?? Date.now(),
100
+ });
101
+ }
102
+ export function createTelegramBusProcessRuntime(input) {
103
+ const instanceId = `${input.pid}:${input.createdAtMs}`;
104
+ const ownerPid = input.parentPid || input.pid;
105
+ const manualFollowerOwnerId = input.parentProcessIdentity ??
106
+ getTelegramProcessBirthIdentity(ownerPid, input.createdAtMs);
107
+ return {
108
+ instanceId,
109
+ processId: input.pid,
110
+ processBirthId: getTelegramProcessBirthIdentity(input.pid, instanceId),
111
+ manualFollowerOwnerId,
112
+ getLeaderSocketPath: () => getTelegramBusSocketPath(undefined, undefined, input.getActiveProfileName()),
113
+ getFollowerSocketPath: () => getTelegramBusFollowerSocketPath(instanceId, undefined, undefined, input.getActiveProfileName()),
114
+ };
115
+ }
116
+ export function createTelegramBusAuthSecret() {
117
+ return randomBytes(32).toString("base64url");
118
+ }
119
+ export const TELEGRAM_BUS_PROTOCOL_VERSION = 2;
120
+ export const TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION = "durable-follower-admission-v1";
121
+ export const TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF = "queue-handoff-v1";
122
+ export const TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE = "input-custody-reference-v1";
123
+ export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME = "workspace-thread-rename-v1";
124
+ export const TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE = "thread-display-mode-v1";
125
+ export const TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT = "directory-display-format-v1";
126
+ export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT = "workspace-follower-auto-connect-v1";
127
+ export function createTelegramBusProtocolIdentity(input) {
128
+ const runtimeBuild = input.runtimeBuild.trim();
129
+ if (!runtimeBuild || runtimeBuild.length > 128) {
130
+ throw new Error("Telegram bus runtime build identity is invalid.");
131
+ }
132
+ const capabilities = [...new Set(input.capabilities ?? [])].sort();
133
+ if (capabilities.length > 32 ||
134
+ capabilities.some((capability) => capability.length > 128 ||
135
+ !/^[a-z0-9][a-z0-9._-]*$/u.test(capability))) {
136
+ throw new Error("Telegram bus capabilities must be canonical identifiers.");
137
+ }
138
+ return {
139
+ protocolVersion: TELEGRAM_BUS_PROTOCOL_VERSION,
140
+ runtimeBuild,
141
+ capabilities,
142
+ };
143
+ }
144
+ export function createTelegramCurrentBusProtocolIdentity(capabilities = []) {
145
+ const packageMetadata = createRequire(import.meta.url)("../package.json");
146
+ if (typeof packageMetadata.version !== "string") {
147
+ throw new Error("Telegram package build identity is unavailable.");
148
+ }
149
+ return createTelegramBusProtocolIdentity({
150
+ runtimeBuild: packageMetadata.version,
151
+ capabilities,
152
+ });
153
+ }
154
+ export function hasTelegramBusCapability(identity, capability) {
155
+ return identity?.capabilities.includes(capability) ?? false;
156
+ }
157
+ export function getTelegramInputCustodyPeerReadiness(followers) {
158
+ return followers.map(follower => {
159
+ if (!follower.registrationGeneration || !follower.protocol)
160
+ return "unknown";
161
+ return hasTelegramBusCapability(follower.protocol, TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION) &&
162
+ hasTelegramBusCapability(follower.protocol, TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE) ? "ready" : "legacy";
163
+ });
164
+ }
165
+ export function getTelegramBusProtocolCompatibility(input) {
166
+ if (!input.remote) {
167
+ return {
168
+ compatible: false,
169
+ reason: "missing-identity",
170
+ missingCapabilities: [],
171
+ };
172
+ }
173
+ if (input.remote.protocolVersion !== input.local.protocolVersion) {
174
+ return {
175
+ compatible: false,
176
+ reason: "version-mismatch",
177
+ missingCapabilities: [],
178
+ };
179
+ }
180
+ const remoteCapabilities = new Set(input.remote.capabilities);
181
+ const missingCapabilities = input.local.capabilities
182
+ .filter((capability) => capability === TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION)
183
+ .filter((capability) => !remoteCapabilities.has(capability));
184
+ return missingCapabilities.length > 0
185
+ ? {
186
+ compatible: false,
187
+ reason: "missing-capability",
188
+ missingCapabilities,
189
+ }
190
+ : { compatible: true, missingCapabilities: [] };
191
+ }
192
+ export function getTelegramBusSocketPath(agentDir = resolveAgentDir(), platform = getPlatform(), profileName) {
193
+ return getTelegramBusLeaderEndpoint({ agentDir, platform, profileName });
194
+ }
195
+ export function getTelegramBusFollowerSocketPath(instanceId, agentDir = resolveAgentDir(), platform = getPlatform(), profileName) {
196
+ return getTelegramBusFollowerEndpoint({
197
+ agentDir,
198
+ platform,
199
+ instanceId,
200
+ profileName,
201
+ });
202
+ }
203
+ export function getTelegramFollowerTargetOwnership(input) {
204
+ const liveFollower = input.followers.find((follower) => {
205
+ return (follower.target?.chatId === input.target.chatId &&
206
+ follower.target.threadId === input.target.threadId);
207
+ });
208
+ if (liveFollower?.registrationGeneration &&
209
+ liveFollower.profileKey &&
210
+ liveFollower.protocol?.capabilities.includes(TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION)) {
211
+ return {
212
+ instanceId: liveFollower.instanceId,
213
+ ownerGeneration: liveFollower.registrationGeneration,
214
+ recipientBindingKey: liveFollower.profileKey,
215
+ protocolIdentity: liveFollower.protocol,
216
+ };
217
+ }
218
+ // Persisted records are restart hints, not live routing authority. Only an
219
+ // authenticated current follower registration may receive forwarded work.
220
+ return undefined;
221
+ }
222
+ const TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD = "__piTelegramAggregateDelivery";
223
+ const TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD = "__piTelegramCrossTargetDelivery";
224
+ export function markTelegramBusAggregateDelivery(body) {
225
+ return {
226
+ ...body,
227
+ [TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD]: true,
228
+ };
229
+ }
230
+ export function isTelegramBusAggregateDelivery(body) {
231
+ return Boolean(body &&
232
+ typeof body === "object" &&
233
+ !Array.isArray(body) &&
234
+ body[TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD] ===
235
+ true);
236
+ }
237
+ export function markTelegramBusCrossTargetDelivery(body) {
238
+ return {
239
+ ...body,
240
+ [TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD]: true,
241
+ };
242
+ }
243
+ export function isTelegramBusCrossTargetDelivery(body) {
244
+ return Boolean(body &&
245
+ typeof body === "object" &&
246
+ !Array.isArray(body) &&
247
+ body[TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD] === true);
248
+ }
249
+ export function stripTelegramBusApiMetadata(body) {
250
+ if (!(TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD in body) &&
251
+ !(TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD in body)) {
252
+ return body;
253
+ }
254
+ const clean = { ...body };
255
+ delete clean[TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD];
256
+ delete clean[TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD];
257
+ return clean;
258
+ }
259
+ export function isTelegramFollowerApiCallAllowed(input) {
260
+ const allowedCallMethods = new Set([
261
+ "answerCallbackQuery",
262
+ "answerGuestQuery",
263
+ "closeForumTopic",
264
+ "deleteForumTopic",
265
+ "deleteMessage",
266
+ "editForumTopic",
267
+ "editMessageReplyMarkup",
268
+ "editMessageText",
269
+ "sendChatAction",
270
+ "sendMessage",
271
+ "sendMessageDraft",
272
+ "sendRichMessage",
273
+ "sendRichMessageDraft",
274
+ ]);
275
+ const allowedMultipartMethods = new Set([
276
+ "sendAudio",
277
+ "sendDocument",
278
+ "sendMediaGroup",
279
+ "sendPhoto",
280
+ "sendRichMessage",
281
+ "sendVoice",
282
+ ]);
283
+ const target = input.follower.target;
284
+ const matchesId = (value, expected) => value === expected || value === String(expected);
285
+ const isTargetScoped = (body) => {
286
+ if (!target)
287
+ return false;
288
+ if (!body || typeof body !== "object" || Array.isArray(body))
289
+ return false;
290
+ const record = body;
291
+ if (!matchesId(record.chat_id, target.chatId))
292
+ return false;
293
+ if (target.threadId === undefined)
294
+ return true;
295
+ return matchesId(record.message_thread_id, target.threadId);
296
+ };
297
+ const isTargetChatScoped = (body) => {
298
+ if (!target)
299
+ return false;
300
+ if (!body || typeof body !== "object" || Array.isArray(body))
301
+ return false;
302
+ const record = body;
303
+ return matchesId(record.chat_id, target.chatId);
304
+ };
305
+ const isDifferentTargetScoped = (body) => {
306
+ if (!target || !isTargetChatScoped(body))
307
+ return false;
308
+ const threadId = body.message_thread_id;
309
+ if (threadId === undefined)
310
+ return target.threadId !== undefined;
311
+ const parsedThreadId = typeof threadId === "number" ? threadId : Number(threadId);
312
+ return (Number.isInteger(parsedThreadId) &&
313
+ (target.threadId === undefined ||
314
+ !matchesId(threadId, target.threadId)));
315
+ };
316
+ const isTargetMessageScoped = (body) => {
317
+ if (!isTargetChatScoped(body))
318
+ return false;
319
+ const messageId = body.message_id;
320
+ const parsedMessageId = typeof messageId === "number" ? messageId : Number(messageId);
321
+ return (Number.isInteger(parsedMessageId) && matchesId(messageId, parsedMessageId));
322
+ };
323
+ const isBotCommandRegistration = (body) => {
324
+ if (!body || typeof body !== "object" || Array.isArray(body))
325
+ return false;
326
+ const commands = body.commands;
327
+ return (Array.isArray(commands) &&
328
+ commands.every((command) => command &&
329
+ typeof command === "object" &&
330
+ !Array.isArray(command) &&
331
+ typeof command.command === "string" &&
332
+ typeof command.description === "string"));
333
+ };
334
+ if (input.method === "downloadFile")
335
+ return true;
336
+ if (input.method === "call") {
337
+ const apiMethod = input.args[0];
338
+ if (typeof apiMethod !== "string")
339
+ return false;
340
+ if (apiMethod === "answerCallbackQuery" ||
341
+ apiMethod === "answerGuestQuery") {
342
+ return true;
343
+ }
344
+ if (apiMethod === "getMe")
345
+ return true;
346
+ if (apiMethod === "setMyCommands")
347
+ return isBotCommandRegistration(input.args[1]);
348
+ if (apiMethod === "sendChatAction")
349
+ return isTargetChatScoped(input.args[1]);
350
+ if (apiMethod === "sendMessage" &&
351
+ isTelegramBusAggregateDelivery(input.args[1])) {
352
+ const body = input.args[1];
353
+ return body.message_thread_id === undefined && isTargetChatScoped(body);
354
+ }
355
+ if ((apiMethod === "sendMessage" || apiMethod === "sendRichMessage") &&
356
+ isTelegramBusCrossTargetDelivery(input.args[1])) {
357
+ return isDifferentTargetScoped(input.args[1]);
358
+ }
359
+ if (apiMethod === "deleteMessage" ||
360
+ apiMethod === "editMessageReplyMarkup" ||
361
+ apiMethod === "editMessageText") {
362
+ if (!isTargetMessageScoped(input.args[1]))
363
+ return false;
364
+ const body = input.args[1];
365
+ const messageId = typeof body.message_id === "number"
366
+ ? body.message_id
367
+ : Number(body.message_id);
368
+ return input.isMessageOwned?.(target.chatId, messageId) === true;
369
+ }
370
+ return allowedCallMethods.has(apiMethod) && isTargetScoped(input.args[1]);
371
+ }
372
+ if (input.method === "callMultipart") {
373
+ const apiMethod = input.args[0];
374
+ return (typeof apiMethod === "string" &&
375
+ allowedMultipartMethods.has(apiMethod) &&
376
+ isTargetScoped(input.args[1]));
377
+ }
378
+ return false;
379
+ }
380
+ export function createTelegramFollowerApiCallAuthorizer(deps) {
381
+ return (input) => isTelegramFollowerApiCallAllowed({
382
+ ...input,
383
+ isMessageOwned(chatId, messageId) {
384
+ return deps.isMessageOwned({
385
+ chatId,
386
+ messageId,
387
+ follower: input.follower,
388
+ });
389
+ },
390
+ });
391
+ }
392
+ export function createTelegramBusFollowerDeliveryIdentity(input) {
393
+ if (!input.recipientBindingKey ||
394
+ !Number.isSafeInteger(input.sourceUpdateId) ||
395
+ input.sourceUpdateId < 0 ||
396
+ (input.sourceClaim !== undefined &&
397
+ (!input.sourceClaim.acquisitionId || input.sourceClaim.acquisitionId.length > 256 ||
398
+ !input.sourceClaim.handoffId || input.sourceClaim.handoffId.length > 256))) {
399
+ throw new Error("Telegram follower delivery identity is incomplete.");
400
+ }
401
+ const deliveryId = createHash("sha256")
402
+ .update(JSON.stringify({
403
+ version: 1,
404
+ kind: input.kind,
405
+ recipientBindingKey: input.recipientBindingKey,
406
+ sourceUpdateId: input.sourceUpdateId,
407
+ }))
408
+ .digest("hex");
409
+ return {
410
+ deliveryId: `telegram-follower-v1-${deliveryId}`,
411
+ sourceUpdateId: input.sourceUpdateId,
412
+ recipientBindingKey: input.recipientBindingKey,
413
+ ...(input.sourceRecoveryKey ? { sourceRecoveryKey: input.sourceRecoveryKey } : {}),
414
+ ...(input.sourceClaim ? { sourceClaim: { ...input.sourceClaim } } : {}),
415
+ };
416
+ }
417
+ export function canUseTelegramBusInputCustodyReference(input) {
418
+ return hasTelegramBusCapability(input.local, TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE) &&
419
+ hasTelegramBusCapability(input.remote, TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE);
420
+ }
421
+ export function createTelegramBusFollowerSourceReferenceDeliveryIdentity(input) {
422
+ if (!input.sourceRecoveryKey || !input.source.owner.handoffId)
423
+ throw new Error("Telegram follower source-reference delivery requires an accepted handoff.");
424
+ return createTelegramBusFollowerDeliveryIdentity({ kind: input.kind,
425
+ recipientBindingKey: input.recipientBindingKey,
426
+ sourceUpdateId: input.source.updateId,
427
+ sourceRecoveryKey: input.sourceRecoveryKey,
428
+ sourceClaim: { acquisitionId: input.source.owner.acquisitionId,
429
+ handoffId: input.source.owner.handoffId } });
430
+ }
431
+ export function getTelegramBusEnvelopeTrafficClass(envelope) {
432
+ if (envelope.kind === "follower.register" ||
433
+ envelope.kind === "follower.restoreWorkspace")
434
+ return "bootstrap";
435
+ if (envelope.kind === "bus.ack")
436
+ return "response";
437
+ return "generation-fenced";
438
+ }
439
+ export function createTelegramBusRequestId(input) {
440
+ return `${input.instanceId}:${input.sequence}`;
441
+ }
442
+ export function createTelegramBusRequestIdFactory(instanceId) {
443
+ let sequence = 0;
444
+ return () => {
445
+ sequence += 1;
446
+ return createTelegramBusRequestId({ instanceId, sequence });
447
+ };
448
+ }
449
+ export function encodeTelegramBusEnvelope(envelope) {
450
+ return `${JSON.stringify(envelope)}\n`;
451
+ }
452
+ export function parseTelegramBusEnvelope(line) {
453
+ let value;
454
+ try {
455
+ value = JSON.parse(line);
456
+ }
457
+ catch {
458
+ return undefined;
459
+ }
460
+ if (!isRecord(value))
461
+ return undefined;
462
+ const kind = value.kind;
463
+ const requestId = value.requestId;
464
+ if (typeof kind !== "string" || typeof requestId !== "string") {
465
+ return undefined;
466
+ }
467
+ let envelope;
468
+ switch (kind) {
469
+ case "follower.register":
470
+ case "follower.restoreWorkspace":
471
+ envelope = parseRegisterEnvelope(value, requestId, kind);
472
+ break;
473
+ case "follower.heartbeat":
474
+ envelope = parseHeartbeatEnvelope(value, requestId);
475
+ break;
476
+ case "follower.disconnect":
477
+ envelope = parseDisconnectEnvelope(value, requestId);
478
+ break;
479
+ case "follower.setThreadDisplayMode":
480
+ if (typeof value.instanceId === "string" &&
481
+ typeof value.registrationGeneration === "string" &&
482
+ (value.mode === "letters" || value.mode === "names" || value.mode === "directories" ||
483
+ value.mode === "directory-snake" || value.mode === "directory-title")) {
484
+ envelope = { kind, requestId, instanceId: value.instanceId,
485
+ registrationGeneration: value.registrationGeneration, mode: value.mode };
486
+ }
487
+ break;
488
+ case "follower.renameThread":
489
+ envelope = parseRenameThreadEnvelope(value, requestId);
490
+ break;
491
+ case "follower.resetThreadName": {
492
+ const target = parseTarget(value.target);
493
+ if (typeof value.instanceId === "string" &&
494
+ typeof value.registrationGeneration === "string" &&
495
+ typeof value.sentAtMs === "number" &&
496
+ target?.threadId !== undefined) {
497
+ envelope = {
498
+ kind,
499
+ requestId,
500
+ instanceId: value.instanceId,
501
+ registrationGeneration: value.registrationGeneration,
502
+ target: { chatId: target.chatId, threadId: target.threadId },
503
+ sentAtMs: value.sentAtMs,
504
+ };
505
+ }
506
+ break;
507
+ }
508
+ case "leader.forwardCallback":
509
+ envelope = parseForwardCallbackEnvelope(value, requestId);
510
+ break;
511
+ case "leader.forwardReaction":
512
+ envelope = parseForwardReactionEnvelope(value, requestId);
513
+ break;
514
+ case "leader.offerInputCustodyHandoff":
515
+ envelope = parseOfferInputCustodyHandoffEnvelope(value, requestId);
516
+ break;
517
+ case "leader.wakeInputCustody":
518
+ envelope = parseWakeInputCustodyEnvelope(value, requestId);
519
+ break;
520
+ case "leader.forwardMessage":
521
+ envelope = parseForwardMessageEnvelope(value, requestId, "leader.forwardMessage");
522
+ break;
523
+ case "leader.forwardEditedMessage":
524
+ envelope = parseForwardMessageEnvelope(value, requestId, "leader.forwardEditedMessage");
525
+ break;
526
+ case "leader.replaceFollowerTarget":
527
+ envelope = parseReplaceFollowerTargetEnvelope(value, requestId);
528
+ break;
529
+ case "leader.offerQueueHandoff":
530
+ envelope = parseQueueHandoffEnvelope(value, requestId, "leader.offerQueueHandoff");
531
+ break;
532
+ case "follower.offerQueueHandoff":
533
+ envelope = parseQueueHandoffEnvelope(value, requestId, "follower.offerQueueHandoff");
534
+ break;
535
+ case "follower.resolveAgentTarget":
536
+ envelope = parseResolveAgentTargetEnvelope(value, requestId);
537
+ break;
538
+ case "follower.routeAgentMessage":
539
+ envelope = parseRouteAgentMessageEnvelope(value, requestId);
540
+ break;
541
+ case "follower.callApi":
542
+ envelope = parseCallApiEnvelope(value, requestId);
543
+ break;
544
+ case "bus.ack":
545
+ envelope = parseAckEnvelope(value, requestId);
546
+ break;
547
+ default:
548
+ return undefined;
549
+ }
550
+ const auth = value.auth;
551
+ if (envelope && typeof auth === "string")
552
+ envelope.auth = auth;
553
+ return envelope;
554
+ }
555
+ const TELEGRAM_ACTIVE_LOCAL_SERVERS = Symbol.for("@llblab/pi-telegram/active-local-servers");
556
+ function getActiveTelegramBusLocalServers() {
557
+ const root = globalThis;
558
+ return (root[TELEGRAM_ACTIVE_LOCAL_SERVERS] ??= new Map());
559
+ }
560
+ const TELEGRAM_BUS_MAX_DIRECT_UNIX_ENDPOINT_BYTES = 80;
561
+ export function resolveTelegramBusSocketPath(source, platform = getPlatform()) {
562
+ const endpoint = typeof source === "function" ? source() : source;
563
+ if (platform === "win32") {
564
+ if (isTelegramBusPipePath(endpoint))
565
+ return endpoint;
566
+ return getTelegramBusPipePath({
567
+ agentDir: dirname(endpoint),
568
+ scope: basename(endpoint),
569
+ });
570
+ }
571
+ const ownerScope = process.getuid?.() ?? "user";
572
+ const fallbackDir = join(tmpdir(), `pi-telegram-${ownerScope}`);
573
+ if (dirname(endpoint) === fallbackDir &&
574
+ /^[0-9a-f]{16}\.sock$/u.test(basename(endpoint))) {
575
+ return endpoint;
576
+ }
577
+ if (Buffer.byteLength(endpoint) <= TELEGRAM_BUS_MAX_DIRECT_UNIX_ENDPOINT_BYTES) {
578
+ return endpoint;
579
+ }
580
+ const digest = createHash("sha256")
581
+ .update(endpoint)
582
+ .digest("hex")
583
+ .slice(0, 16);
584
+ return join(fallbackDir, `${digest}.sock`);
585
+ }
586
+ export function isTelegramBusForwardOwnershipCurrent(expected, current) {
587
+ return Boolean(current && current.instanceId === expected.instanceId &&
588
+ current.ownerGeneration === expected.ownerGeneration &&
589
+ current.recipientBindingKey === expected.recipientBindingKey &&
590
+ JSON.stringify(current.protocolIdentity) === JSON.stringify(expected.protocolIdentity));
591
+ }
592
+ function getTelegramBusForwardSourceUpdateId(value) {
593
+ if (!isRecord(value))
594
+ return undefined;
595
+ const updateId = value.pi_telegram_source_update_id;
596
+ return Number.isSafeInteger(updateId) && updateId >= 0
597
+ ? updateId
598
+ : undefined;
599
+ }
600
+ function createTelegramBusForwardDelivery(kind, sourceUpdateId, recipientBindingKey) {
601
+ return createTelegramBusFollowerDeliveryIdentity({
602
+ kind,
603
+ recipientBindingKey,
604
+ sourceUpdateId,
605
+ });
606
+ }
607
+ export function createTelegramBusForeignOwnedUpdateForwarder(deps) {
608
+ const getNowMs = deps.getNowMs ?? Date.now;
609
+ const reject = (input) => {
610
+ const settlement = {
611
+ status: input.status,
612
+ failureClass: input.failureClass,
613
+ message: input.message,
614
+ ...(input.delivery ? { delivery: input.delivery } : {}),
615
+ ...(input.sourceUpdateId !== undefined
616
+ ? { sourceUpdateId: input.sourceUpdateId }
617
+ : {}),
618
+ };
619
+ deps.recordRuntimeEvent?.("bus", input.message, {
620
+ phase: "foreign-update-forward-rejected",
621
+ settlement: input.status,
622
+ failureClass: input.failureClass,
623
+ envelopeKind: input.envelopeKind,
624
+ recipientInstanceId: input.ownership.instanceId,
625
+ deliveryId: input.delivery?.deliveryId,
626
+ sourceUpdateId: input.delivery?.sourceUpdateId ?? input.sourceUpdateId,
627
+ });
628
+ return settlement;
629
+ };
630
+ const prepare = (kind, value, ownership) => {
631
+ const sourceUpdateId = getTelegramBusForwardSourceUpdateId(value);
632
+ if (sourceUpdateId === undefined) {
633
+ return {
634
+ settlement: reject({
635
+ status: "terminal-rejected",
636
+ failureClass: "source-update-identity-missing",
637
+ message: "Forwarded Telegram update has no durable source identity.",
638
+ envelopeKind: kind,
639
+ ownership,
640
+ }),
641
+ };
642
+ }
643
+ if (!ownership.recipientBindingKey) {
644
+ return {
645
+ settlement: reject({
646
+ status: "terminal-rejected",
647
+ failureClass: "recipient-binding-missing",
648
+ message: "Forwarded Telegram update has no stable recipient binding.",
649
+ envelopeKind: kind,
650
+ ownership,
651
+ sourceUpdateId,
652
+ }),
653
+ };
654
+ }
655
+ const sourceReference = canUseTelegramBusInputCustodyReference({
656
+ local: deps.localProtocolIdentity, remote: ownership.protocolIdentity
657
+ });
658
+ const reference = sourceReference ? deps.resolveInputCustodyReference?.({
659
+ sourceUpdateId, recipientBindingKey: ownership.recipientBindingKey
660
+ }) : undefined;
661
+ if (sourceReference && !reference)
662
+ return { settlement: reject({
663
+ status: "retryable", failureClass: "source-reference-missing",
664
+ message: "Forwarded Telegram update has no exact custody reference.",
665
+ envelopeKind: kind, ownership, sourceUpdateId
666
+ }) };
667
+ const delivery = reference
668
+ ? createTelegramBusFollowerSourceReferenceDeliveryIdentity({
669
+ kind: "leader.wakeInputCustody", recipientBindingKey: ownership.recipientBindingKey,
670
+ sourceRecoveryKey: reference.sourceRecoveryKey, source: reference.source
671
+ })
672
+ : createTelegramBusForwardDelivery(kind, sourceUpdateId, ownership.recipientBindingKey);
673
+ if (!ownership.ownerGeneration) {
674
+ return {
675
+ settlement: reject({
676
+ status: "retryable",
677
+ failureClass: "recipient-generation-missing",
678
+ message: "Forwarded Telegram update has no live recipient generation.",
679
+ envelopeKind: kind,
680
+ ownership,
681
+ delivery,
682
+ }),
683
+ };
684
+ }
685
+ return {
686
+ delivery,
687
+ recipientRegistrationGeneration: ownership.ownerGeneration,
688
+ sourceReference,
689
+ };
690
+ };
691
+ const send = async (envelope, ownership) => {
692
+ if (deps.validateForwardOwnership && !deps.validateForwardOwnership(ownership))
693
+ return reject({
694
+ status: "retryable", failureClass: "recipient-ownership-stale",
695
+ message: "Telegram follower ownership changed before forwarding.",
696
+ envelopeKind: envelope.kind, ownership, delivery: envelope.delivery
697
+ });
698
+ if (deps.getAuthSecret)
699
+ envelope.auth = deps.getAuthSecret();
700
+ const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
701
+ let response;
702
+ try {
703
+ response = await sendTelegramBusLocalEnvelope({
704
+ socketPath,
705
+ envelope,
706
+ timeoutMs: deps.timeoutMs,
707
+ retry: getTelegramBusTransportRetryPolicy({
708
+ endpoint: socketPath,
709
+ operation: "operation",
710
+ }),
711
+ });
712
+ }
713
+ catch (error) {
714
+ return reject({
715
+ status: "retryable",
716
+ failureClass: "transport-failed",
717
+ message: error instanceof Error
718
+ ? error.message
719
+ : "Telegram follower forwarding transport failed.",
720
+ envelopeKind: envelope.kind,
721
+ ownership,
722
+ delivery: envelope.delivery,
723
+ });
724
+ }
725
+ if (response?.kind !== "bus.ack") {
726
+ return reject({
727
+ status: "retryable",
728
+ failureClass: "acknowledgement-missing",
729
+ message: "Follower returned no forwarding acknowledgement.",
730
+ envelopeKind: envelope.kind,
731
+ ownership,
732
+ delivery: envelope.delivery,
733
+ });
734
+ }
735
+ if (response.requestId !== envelope.requestId) {
736
+ return reject({
737
+ status: "terminal-rejected",
738
+ failureClass: "acknowledgement-mismatched",
739
+ message: "Follower returned a mismatched forwarding acknowledgement.",
740
+ envelopeKind: envelope.kind,
741
+ ownership,
742
+ delivery: envelope.delivery,
743
+ });
744
+ }
745
+ if (!response.ok) {
746
+ return reject({
747
+ status: "retryable",
748
+ failureClass: "acknowledgement-rejected",
749
+ message: response.message ?? "Follower rejected forwarded Telegram update.",
750
+ envelopeKind: envelope.kind,
751
+ ownership,
752
+ delivery: envelope.delivery,
753
+ });
754
+ }
755
+ const receipt = response.result;
756
+ if (!isRecord(receipt) ||
757
+ typeof receipt.deliveryId !== "string" ||
758
+ !Number.isSafeInteger(receipt.sourceUpdateId)) {
759
+ return reject({
760
+ status: "terminal-rejected",
761
+ failureClass: "durable-receipt-missing",
762
+ message: "Follower acknowledgement omitted the durable receipt.",
763
+ envelopeKind: envelope.kind,
764
+ ownership,
765
+ delivery: envelope.delivery,
766
+ });
767
+ }
768
+ if (receipt.deliveryId !== envelope.delivery.deliveryId ||
769
+ receipt.sourceUpdateId !== envelope.delivery.sourceUpdateId) {
770
+ return reject({
771
+ status: "terminal-rejected",
772
+ failureClass: "durable-receipt-mismatched",
773
+ message: "Follower acknowledgement returned a mismatched durable receipt.",
774
+ envelopeKind: envelope.kind,
775
+ ownership,
776
+ delivery: envelope.delivery,
777
+ });
778
+ }
779
+ return { status: "accepted", delivery: envelope.delivery };
780
+ };
781
+ return {
782
+ forwardCallback: ({ query, ownership }) => {
783
+ const prepared = prepare("leader.forwardCallback", query, ownership);
784
+ if ("settlement" in prepared)
785
+ return Promise.resolve(prepared.settlement);
786
+ if (prepared.sourceReference)
787
+ return send({ kind: "leader.wakeInputCustody",
788
+ requestId: deps.createRequestId(), recipientInstanceId: ownership.instanceId,
789
+ recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
790
+ delivery: prepared.delivery, sentAtMs: getNowMs() }, ownership);
791
+ return send({
792
+ kind: "leader.forwardCallback",
793
+ requestId: deps.createRequestId(),
794
+ recipientInstanceId: ownership.instanceId,
795
+ recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
796
+ delivery: prepared.delivery,
797
+ query,
798
+ sentAtMs: getNowMs(),
799
+ }, ownership);
800
+ },
801
+ forwardReaction: ({ reactionUpdate, ownership }) => {
802
+ const prepared = prepare("leader.forwardReaction", reactionUpdate, ownership);
803
+ if ("settlement" in prepared)
804
+ return Promise.resolve(prepared.settlement);
805
+ if (prepared.sourceReference)
806
+ return send({ kind: "leader.wakeInputCustody",
807
+ requestId: deps.createRequestId(), recipientInstanceId: ownership.instanceId,
808
+ recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
809
+ delivery: prepared.delivery, sentAtMs: getNowMs() }, ownership);
810
+ return send({
811
+ kind: "leader.forwardReaction",
812
+ requestId: deps.createRequestId(),
813
+ recipientInstanceId: ownership.instanceId,
814
+ recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
815
+ delivery: prepared.delivery,
816
+ reactionUpdate,
817
+ sentAtMs: getNowMs(),
818
+ }, ownership);
819
+ },
820
+ forwardMessage: ({ message, ownership }) => {
821
+ const prepared = prepare("leader.forwardMessage", message, ownership);
822
+ if ("settlement" in prepared)
823
+ return Promise.resolve(prepared.settlement);
824
+ if (prepared.sourceReference)
825
+ return send({ kind: "leader.wakeInputCustody",
826
+ requestId: deps.createRequestId(), recipientInstanceId: ownership.instanceId,
827
+ recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
828
+ delivery: prepared.delivery, sentAtMs: getNowMs() }, ownership);
829
+ return send({
830
+ kind: "leader.forwardMessage",
831
+ requestId: deps.createRequestId(),
832
+ recipientInstanceId: ownership.instanceId,
833
+ recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
834
+ delivery: prepared.delivery,
835
+ message,
836
+ ...(deps.getForwardCommentBatchPosition?.(message) !== undefined
837
+ ? {
838
+ forwardCommentBatchPosition: deps.getForwardCommentBatchPosition(message),
839
+ }
840
+ : {}),
841
+ sentAtMs: getNowMs(),
842
+ }, ownership);
843
+ },
844
+ forwardEditedMessage: ({ message, ownership }) => {
845
+ const prepared = prepare("leader.forwardEditedMessage", message, ownership);
846
+ if ("settlement" in prepared)
847
+ return Promise.resolve(prepared.settlement);
848
+ if (prepared.sourceReference)
849
+ return send({ kind: "leader.wakeInputCustody",
850
+ requestId: deps.createRequestId(), recipientInstanceId: ownership.instanceId,
851
+ recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
852
+ delivery: prepared.delivery, sentAtMs: getNowMs() }, ownership);
853
+ return send({
854
+ kind: "leader.forwardEditedMessage",
855
+ requestId: deps.createRequestId(),
856
+ recipientInstanceId: ownership.instanceId,
857
+ recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
858
+ delivery: prepared.delivery,
859
+ message,
860
+ sentAtMs: getNowMs(),
861
+ }, ownership);
862
+ },
863
+ };
864
+ }
865
+ export function listTelegramBusLiveThreadTargets(input) {
866
+ const targets = [];
867
+ if (input.leaderTarget?.threadId !== undefined) {
868
+ targets.push(input.leaderTarget);
869
+ }
870
+ for (const follower of input.followers) {
871
+ if (follower.target?.threadId !== undefined)
872
+ targets.push(follower.target);
873
+ }
874
+ return targets;
875
+ }
876
+ export function createTelegramBusFollowerTargetController(deps) {
877
+ const getNowMs = deps.getNowMs ?? Date.now;
878
+ return {
879
+ async replaceTarget({ follower, target, oldTarget, reason }) {
880
+ if (!follower.busSocketPath || !follower.registrationGeneration) {
881
+ return false;
882
+ }
883
+ const envelope = {
884
+ kind: "leader.replaceFollowerTarget",
885
+ requestId: deps.createRequestId(),
886
+ recipientInstanceId: follower.instanceId,
887
+ recipientRegistrationGeneration: follower.registrationGeneration,
888
+ target,
889
+ ...(oldTarget ? { oldTarget } : {}),
890
+ reason,
891
+ sentAtMs: getNowMs(),
892
+ };
893
+ if (deps.getAuthSecret)
894
+ envelope.auth = deps.getAuthSecret();
895
+ const response = await sendTelegramBusLocalEnvelope({
896
+ socketPath: follower.busSocketPath,
897
+ envelope,
898
+ timeoutMs: deps.timeoutMs,
899
+ retry: getTelegramBusTransportRetryPolicy({
900
+ endpoint: follower.busSocketPath,
901
+ operation: "operation",
902
+ }),
903
+ });
904
+ return response?.kind === "bus.ack" && response.ok;
905
+ },
906
+ };
907
+ }
908
+ export function createTelegramBusFollowerThreadRestoreHandler(deps) {
909
+ return async ({ record, target, oldTarget }) => {
910
+ if (!record.instanceId)
911
+ return false;
912
+ const follower = deps.followerRegistry.get(record.instanceId);
913
+ if (!follower?.registrationGeneration || !oldTarget ||
914
+ follower.target?.chatId !== oldTarget.chatId ||
915
+ follower.target.threadId !== oldTarget.threadId ||
916
+ target.chatId !== oldTarget.chatId || target.threadId === oldTarget.threadId)
917
+ return false;
918
+ const replaced = await deps.followerTargetController.replaceTarget({
919
+ follower,
920
+ target,
921
+ oldTarget,
922
+ reason: "thread-restore",
923
+ });
924
+ const current = deps.followerRegistry.get(record.instanceId);
925
+ if (!replaced || !current ||
926
+ current.registrationGeneration !== follower.registrationGeneration ||
927
+ current.target?.chatId !== oldTarget.chatId ||
928
+ current.target.threadId !== oldTarget.threadId)
929
+ return false;
930
+ deps.followerRegistry.register({
931
+ ...current,
932
+ target,
933
+ connectedAtMs: current.connectedAtMs,
934
+ });
935
+ deps.onRestored?.();
936
+ return true;
937
+ };
938
+ }
939
+ export function isTelegramBusEnvelopeAuthorized(envelope, secret) {
940
+ if (!secret)
941
+ return true;
942
+ if (typeof envelope.auth !== "string")
943
+ return false;
944
+ const auth = Buffer.from(envelope.auth);
945
+ const expected = Buffer.from(secret);
946
+ return auth.length === expected.length && timingSafeEqual(auth, expected);
947
+ }
948
+ export function createUnauthorizedBusAck(requestId) {
949
+ return {
950
+ kind: "bus.ack",
951
+ requestId,
952
+ ok: false,
953
+ message: "Unauthorized Telegram bus envelope.",
954
+ };
955
+ }
956
+ export function createTelegramBusLocalServer(deps) {
957
+ const requestLedger = new Map();
958
+ const requestLedgerMaxEntries = Math.max(1, deps.requestLedgerMaxEntries ?? 4096);
959
+ const getRequestLedgerKey = (envelope) => {
960
+ const identity = envelope.kind === "follower.register" ||
961
+ envelope.kind === "follower.restoreWorkspace"
962
+ ? envelope.registration.instanceId
963
+ : "instanceId" in envelope
964
+ ? envelope.instanceId
965
+ : "recipientInstanceId" in envelope
966
+ ? envelope.recipientInstanceId
967
+ : "ack";
968
+ return `${envelope.auth ?? ""}:${identity}:${envelope.requestId}`;
969
+ };
970
+ const handleEnvelopeOnce = (envelope) => {
971
+ const key = getRequestLedgerKey(envelope);
972
+ const fingerprint = JSON.stringify(envelope);
973
+ const existing = requestLedger.get(key);
974
+ if (existing) {
975
+ if (existing.fingerprint === fingerprint)
976
+ return existing.result;
977
+ return Promise.resolve({
978
+ kind: "bus.ack",
979
+ requestId: envelope.requestId,
980
+ ok: false,
981
+ message: "Telegram bus request id was reused with a different payload.",
982
+ error: { code: "request-id-collision" },
983
+ });
984
+ }
985
+ if (requestLedger.size >= requestLedgerMaxEntries) {
986
+ const settledKey = Array.from(requestLedger.entries()).find(([, entry]) => entry.settled)?.[0];
987
+ if (settledKey)
988
+ requestLedger.delete(settledKey);
989
+ }
990
+ if (requestLedger.size >= requestLedgerMaxEntries) {
991
+ return Promise.resolve({
992
+ kind: "bus.ack",
993
+ requestId: envelope.requestId,
994
+ ok: false,
995
+ message: "Telegram bus request ledger is full.",
996
+ error: { code: "ledger-overloaded" },
997
+ });
998
+ }
999
+ const entry = {
1000
+ fingerprint,
1001
+ settled: false,
1002
+ result: Promise.resolve().then(() => deps.handleEnvelope(envelope)),
1003
+ };
1004
+ requestLedger.set(key, entry);
1005
+ void entry.result.then(() => {
1006
+ entry.settled = true;
1007
+ }, () => {
1008
+ entry.settled = true;
1009
+ });
1010
+ return entry.result;
1011
+ };
1012
+ let server;
1013
+ let activeSocketPath;
1014
+ let activeListenPath;
1015
+ let endpointRecovery;
1016
+ let stopGeneration = 0;
1017
+ const sockets = new Set();
1018
+ const closeSocket = (socket) => {
1019
+ sockets.delete(socket);
1020
+ socket.destroy();
1021
+ };
1022
+ const runtime = {
1023
+ start: async () => {
1024
+ if (server)
1025
+ return;
1026
+ const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
1027
+ const activeServers = getActiveTelegramBusLocalServers();
1028
+ const replacedServer = activeServers.get(socketPath);
1029
+ if (replacedServer && replacedServer !== runtime) {
1030
+ await replacedServer.stop();
1031
+ }
1032
+ const usesWindowsPipe = isTelegramBusPipePath(socketPath);
1033
+ const endpointGeneration = randomBytes(8).toString("hex");
1034
+ const listenPath = usesWindowsPipe
1035
+ ? socketPath
1036
+ : join(dirname(socketPath), `.pt-${endpointGeneration}.sock`);
1037
+ activeSocketPath = socketPath;
1038
+ activeListenPath = listenPath;
1039
+ deps.recordTransportEvent?.("server-start", getTelegramBusEndpointDiagnostics(socketPath));
1040
+ if (!usesWindowsPipe) {
1041
+ const socketDir = dirname(socketPath);
1042
+ mkdirSync(socketDir, { recursive: true, mode: 0o700 });
1043
+ chmodSync(socketDir, 0o700);
1044
+ if (existsSync(listenPath))
1045
+ unlinkSync(listenPath);
1046
+ const legacyDeadlineMs = Date.now() + 2000;
1047
+ while (true) {
1048
+ let isLegacySocket = false;
1049
+ try {
1050
+ isLegacySocket = lstatSync(socketPath).isSocket();
1051
+ }
1052
+ catch {
1053
+ /* endpoint does not exist */
1054
+ }
1055
+ if (!isLegacySocket)
1056
+ break;
1057
+ const probe = await probeTelegramBusEndpoint({
1058
+ endpoint: socketPath,
1059
+ timeoutMs: 50,
1060
+ });
1061
+ if (!probe.reachable)
1062
+ break;
1063
+ if (Date.now() >= legacyDeadlineMs) {
1064
+ throw new Error(`Timed out waiting for legacy Telegram bus endpoint: ${socketPath}`);
1065
+ }
1066
+ await delayTelegramBusTransportRetry(25);
1067
+ }
1068
+ }
1069
+ if (usesWindowsPipe) {
1070
+ await deps.beforeEndpointPublication?.();
1071
+ const committed = deps.commitEndpointPublication
1072
+ ? deps.commitEndpointPublication(() => { })
1073
+ : true;
1074
+ if (!committed) {
1075
+ activeSocketPath = undefined;
1076
+ activeListenPath = undefined;
1077
+ throw new Error("Telegram bus endpoint publication lost transport ownership.");
1078
+ }
1079
+ }
1080
+ server = createServer((socket) => {
1081
+ sockets.add(socket);
1082
+ let buffer = "";
1083
+ socket.setEncoding("utf8");
1084
+ socket.on("data", (chunk) => {
1085
+ buffer += chunk;
1086
+ const lines = buffer.split("\n");
1087
+ buffer = lines.pop() ?? "";
1088
+ for (const line of lines) {
1089
+ void handleTelegramBusSocketLine(line, socket, handleEnvelopeOnce, deps.recordTransportEvent, socketPath, deps.shouldDropResponse);
1090
+ }
1091
+ });
1092
+ socket.on("close", () => sockets.delete(socket));
1093
+ socket.on("error", (error) => {
1094
+ deps.recordTransportEvent?.("server-socket-error", {
1095
+ ...getTelegramBusEndpointDiagnostics(socketPath),
1096
+ ...classifyTelegramBusTransportError(error),
1097
+ });
1098
+ closeSocket(socket);
1099
+ });
1100
+ });
1101
+ try {
1102
+ await new Promise((resolve, reject) => {
1103
+ server?.once("error", reject);
1104
+ server?.listen(listenPath, resolve);
1105
+ });
1106
+ activeServers.set(socketPath, runtime);
1107
+ deps.recordTransportEvent?.("server-started", getTelegramBusEndpointDiagnostics(socketPath));
1108
+ }
1109
+ catch (error) {
1110
+ server = undefined;
1111
+ activeSocketPath = undefined;
1112
+ activeListenPath = undefined;
1113
+ deps.recordTransportEvent?.("server-start-failed", {
1114
+ ...getTelegramBusEndpointDiagnostics(socketPath),
1115
+ ...classifyTelegramBusTransportError(error),
1116
+ });
1117
+ throw error;
1118
+ }
1119
+ if (!usesWindowsPipe) {
1120
+ chmodSync(listenPath, 0o600);
1121
+ const linkPath = `${socketPath}.link.${endpointGeneration}`;
1122
+ try {
1123
+ symlinkSync(basename(listenPath), linkPath);
1124
+ await deps.beforeEndpointPublication?.();
1125
+ const committed = deps.commitEndpointPublication
1126
+ ? deps.commitEndpointPublication(() => renameSync(linkPath, socketPath))
1127
+ : (renameSync(linkPath, socketPath), true);
1128
+ if (!committed) {
1129
+ throw new Error("Telegram bus endpoint publication lost transport ownership.");
1130
+ }
1131
+ }
1132
+ catch (error) {
1133
+ try {
1134
+ if (lstatSync(linkPath).isSymbolicLink())
1135
+ unlinkSync(linkPath);
1136
+ }
1137
+ catch {
1138
+ /* no unpublished link to remove */
1139
+ }
1140
+ const failedServer = server;
1141
+ server = undefined;
1142
+ activeSocketPath = undefined;
1143
+ activeListenPath = undefined;
1144
+ if (activeServers.get(socketPath) === runtime) {
1145
+ activeServers.delete(socketPath);
1146
+ }
1147
+ if (failedServer) {
1148
+ await new Promise((resolve) => failedServer.close(() => resolve()));
1149
+ }
1150
+ throw error;
1151
+ }
1152
+ }
1153
+ },
1154
+ stop: async () => {
1155
+ stopGeneration += 1;
1156
+ requestLedger.clear();
1157
+ const activeServer = server;
1158
+ const socketPath = activeSocketPath;
1159
+ const listenPath = activeListenPath;
1160
+ if (socketPath &&
1161
+ getActiveTelegramBusLocalServers().get(socketPath) === runtime) {
1162
+ getActiveTelegramBusLocalServers().delete(socketPath);
1163
+ }
1164
+ server = undefined;
1165
+ activeSocketPath = undefined;
1166
+ activeListenPath = undefined;
1167
+ for (const socket of sockets)
1168
+ closeSocket(socket);
1169
+ if (activeServer) {
1170
+ await new Promise((resolve) => activeServer.close(() => resolve()));
1171
+ }
1172
+ if (socketPath && listenPath && !isTelegramBusPipePath(socketPath)) {
1173
+ try {
1174
+ if (lstatSync(socketPath).isSymbolicLink() &&
1175
+ readlinkSync(socketPath) === basename(listenPath)) {
1176
+ // Leave the generation link in place. Node removes only the unique
1177
+ // listen path on close; a later generation atomically replaces this
1178
+ // link, and existsSync() treats the stopped dangling link as missing.
1179
+ }
1180
+ }
1181
+ catch {
1182
+ /* endpoint already moved or removed */
1183
+ }
1184
+ }
1185
+ if (socketPath) {
1186
+ deps.recordTransportEvent?.("server-stopped", getTelegramBusEndpointDiagnostics(socketPath));
1187
+ }
1188
+ },
1189
+ ensureEndpoint: async () => {
1190
+ const socketPath = activeSocketPath;
1191
+ if (!server ||
1192
+ !socketPath ||
1193
+ isTelegramBusPipePath(socketPath) ||
1194
+ existsSync(socketPath)) {
1195
+ return false;
1196
+ }
1197
+ if (endpointRecovery)
1198
+ return endpointRecovery;
1199
+ endpointRecovery = (async () => {
1200
+ deps.recordTransportEvent?.("server-endpoint-missing", getTelegramBusEndpointDiagnostics(socketPath));
1201
+ const recoveryStopGeneration = stopGeneration + 1;
1202
+ await runtime.stop();
1203
+ if (stopGeneration !== recoveryStopGeneration)
1204
+ return false;
1205
+ await runtime.start();
1206
+ if (stopGeneration !== recoveryStopGeneration) {
1207
+ await runtime.stop();
1208
+ return false;
1209
+ }
1210
+ deps.recordTransportEvent?.("server-endpoint-recovered", getTelegramBusEndpointDiagnostics(socketPath));
1211
+ return true;
1212
+ })();
1213
+ try {
1214
+ return await endpointRecovery;
1215
+ }
1216
+ finally {
1217
+ endpointRecovery = undefined;
1218
+ }
1219
+ },
1220
+ };
1221
+ return runtime;
1222
+ }
1223
+ function getTelegramBusEnvelopeDiagnostics(envelope) {
1224
+ return {
1225
+ envelopeKind: envelope.kind,
1226
+ requestId: envelope.requestId,
1227
+ };
1228
+ }
1229
+ function sendTelegramBusLocalEnvelopeOnce(options) {
1230
+ const timeoutMs = options.timeoutMs ?? 1000;
1231
+ return new Promise((resolve, reject) => {
1232
+ const socket = createConnection(options.socketPath);
1233
+ let settled = false;
1234
+ let buffer = "";
1235
+ let timeoutFinalizer;
1236
+ const settle = (callback) => {
1237
+ if (settled)
1238
+ return;
1239
+ settled = true;
1240
+ clearTimeout(timeout);
1241
+ if (timeoutFinalizer)
1242
+ clearImmediate(timeoutFinalizer);
1243
+ socket.destroy();
1244
+ callback();
1245
+ };
1246
+ const timeout = setTimeout(() => {
1247
+ // A long synchronous Pi/TUI turn can resume in the timers phase after
1248
+ // the peer acknowledgement is already buffered. Give pending socket I/O
1249
+ // two poll phases before converting elapsed wall time into a transport
1250
+ // failure; Darwin can surface buffered Unix-socket input only on the
1251
+ // second cycle after a long stall, while a silent peer stays bounded.
1252
+ timeoutFinalizer = setImmediate(() => {
1253
+ timeoutFinalizer = setImmediate(() => {
1254
+ settle(() => reject(createTelegramBusTransportTimeoutError("Timed out waiting for Telegram bus response")));
1255
+ });
1256
+ });
1257
+ }, timeoutMs);
1258
+ socket.setEncoding("utf8");
1259
+ socket.once("connect", () => {
1260
+ socket.write(encodeTelegramBusEnvelope(options.envelope));
1261
+ });
1262
+ socket.on("data", (chunk) => {
1263
+ buffer += chunk;
1264
+ const newlineIndex = buffer.indexOf("\n");
1265
+ if (newlineIndex < 0)
1266
+ return;
1267
+ const line = buffer.slice(0, newlineIndex);
1268
+ settle(() => resolve(parseTelegramBusEnvelope(line)));
1269
+ });
1270
+ socket.once("error", (error) => settle(() => reject(error)));
1271
+ socket.once("end", () => settle(() => resolve(undefined)));
1272
+ });
1273
+ }
1274
+ export async function sendTelegramBusLocalEnvelope(options) {
1275
+ const resolvedOptions = {
1276
+ ...options,
1277
+ socketPath: resolveTelegramBusSocketPath(options.socketPath),
1278
+ };
1279
+ const attempts = Math.max(1, resolvedOptions.retry?.attempts ?? 1);
1280
+ const delayMs = Math.max(0, resolvedOptions.retry?.delayMs ?? 0);
1281
+ for (let attempt = 1;; attempt += 1) {
1282
+ try {
1283
+ return await sendTelegramBusLocalEnvelopeOnce(resolvedOptions);
1284
+ }
1285
+ catch (error) {
1286
+ const info = classifyTelegramBusTransportError(error);
1287
+ resolvedOptions.recordTransportEvent?.("client-failed", {
1288
+ ...getTelegramBusEndpointDiagnostics(resolvedOptions.socketPath),
1289
+ ...getTelegramBusEnvelopeDiagnostics(resolvedOptions.envelope),
1290
+ attempt,
1291
+ attempts,
1292
+ ...info,
1293
+ });
1294
+ if (attempt >= attempts || !isRetryableTelegramBusTransportError(error)) {
1295
+ throw error;
1296
+ }
1297
+ resolvedOptions.recordTransportEvent?.("client-retry", {
1298
+ ...getTelegramBusEndpointDiagnostics(resolvedOptions.socketPath),
1299
+ ...getTelegramBusEnvelopeDiagnostics(resolvedOptions.envelope),
1300
+ attempt,
1301
+ attempts,
1302
+ delayMs,
1303
+ ...info,
1304
+ });
1305
+ await delayTelegramBusTransportRetry(delayMs);
1306
+ }
1307
+ }
1308
+ }
1309
+ export function createTelegramBusForwardOwnershipValidator(registry) {
1310
+ return ownership => {
1311
+ const follower = registry.get(ownership.instanceId);
1312
+ return isTelegramBusForwardOwnershipCurrent(ownership, follower?.registrationGeneration && follower.profileKey && follower.protocol ? {
1313
+ instanceId: follower.instanceId,
1314
+ ownerGeneration: follower.registrationGeneration,
1315
+ recipientBindingKey: follower.profileKey,
1316
+ protocolIdentity: follower.protocol,
1317
+ } : undefined);
1318
+ };
1319
+ }
1320
+ export function createTelegramBusFollowerRegistry() {
1321
+ const followers = new Map();
1322
+ const clone = (follower) => ({
1323
+ ...follower,
1324
+ target: follower.target ? { ...follower.target } : undefined,
1325
+ ...(follower.protocol
1326
+ ? {
1327
+ protocol: {
1328
+ ...follower.protocol,
1329
+ capabilities: [...follower.protocol.capabilities],
1330
+ },
1331
+ }
1332
+ : {}),
1333
+ });
1334
+ return {
1335
+ register: (registration) => {
1336
+ const existing = followers.get(registration.instanceId);
1337
+ for (const [instanceId, follower] of followers.entries()) {
1338
+ if (instanceId === registration.instanceId)
1339
+ continue;
1340
+ const sameProfile = registration.profileKey !== undefined &&
1341
+ registration.profileKey === follower.profileKey;
1342
+ const sameTarget = registration.target !== undefined &&
1343
+ follower.target?.chatId === registration.target.chatId &&
1344
+ follower.target.threadId === registration.target.threadId;
1345
+ if (sameProfile || sameTarget)
1346
+ followers.delete(instanceId);
1347
+ }
1348
+ const next = {
1349
+ ...registration,
1350
+ target: registration.target ? { ...registration.target } : undefined,
1351
+ ...(registration.protocol
1352
+ ? {
1353
+ protocol: {
1354
+ ...registration.protocol,
1355
+ capabilities: [...registration.protocol.capabilities],
1356
+ },
1357
+ }
1358
+ : {}),
1359
+ lastHeartbeatMs: existing?.lastHeartbeatMs ?? registration.connectedAtMs,
1360
+ };
1361
+ followers.set(registration.instanceId, next);
1362
+ return clone(next);
1363
+ },
1364
+ heartbeat: (instanceId, nowMs) => {
1365
+ const existing = followers.get(instanceId);
1366
+ if (!existing)
1367
+ return undefined;
1368
+ const next = { ...existing, lastHeartbeatMs: nowMs };
1369
+ followers.set(instanceId, next);
1370
+ return clone(next);
1371
+ },
1372
+ get: (instanceId) => {
1373
+ const existing = followers.get(instanceId);
1374
+ return existing ? clone(existing) : undefined;
1375
+ },
1376
+ getByTarget: (target) => {
1377
+ for (const follower of followers.values()) {
1378
+ if (follower.target?.chatId === target.chatId &&
1379
+ follower.target.threadId === target.threadId) {
1380
+ return clone(follower);
1381
+ }
1382
+ }
1383
+ return undefined;
1384
+ },
1385
+ list: () => [...followers.values()].map(clone),
1386
+ remove: (instanceId) => followers.delete(instanceId),
1387
+ clear: () => followers.clear(),
1388
+ pruneStale: (nowMs, staleAfterMs) => {
1389
+ const removed = [];
1390
+ for (const [instanceId, follower] of followers.entries()) {
1391
+ if (nowMs - follower.lastHeartbeatMs <= staleAfterMs)
1392
+ continue;
1393
+ followers.delete(instanceId);
1394
+ removed.push(clone(follower));
1395
+ }
1396
+ return removed;
1397
+ },
1398
+ };
1399
+ }
1400
+ async function handleTelegramBusSocketLine(line, socket, handleEnvelope, recordTransportEvent, socketPath, shouldDropResponse) {
1401
+ const envelope = parseTelegramBusEnvelope(line);
1402
+ if (!envelope) {
1403
+ recordTransportEvent?.("server-invalid-envelope", {
1404
+ ...getTelegramBusEndpointDiagnostics(socketPath),
1405
+ byteLength: Buffer.byteLength(line),
1406
+ });
1407
+ socket.write(encodeTelegramBusEnvelope({
1408
+ kind: "bus.ack",
1409
+ requestId: "invalid",
1410
+ ok: false,
1411
+ message: "Invalid Telegram bus envelope.",
1412
+ }));
1413
+ return;
1414
+ }
1415
+ try {
1416
+ const response = await handleEnvelope(envelope);
1417
+ if (response && !shouldDropResponse?.(envelope, response)) {
1418
+ socket.write(encodeTelegramBusEnvelope(response));
1419
+ }
1420
+ }
1421
+ catch (error) {
1422
+ recordTransportEvent?.("server-handler-failed", {
1423
+ ...getTelegramBusEndpointDiagnostics(socketPath),
1424
+ ...getTelegramBusEnvelopeDiagnostics(envelope),
1425
+ ...classifyTelegramBusTransportError(error),
1426
+ });
1427
+ socket.write(encodeTelegramBusEnvelope({
1428
+ kind: "bus.ack",
1429
+ requestId: envelope.requestId,
1430
+ ok: false,
1431
+ message: "Telegram bus handler failed.",
1432
+ }));
1433
+ }
1434
+ }
1435
+ function parseRegisterEnvelope(value, requestId, kind) {
1436
+ const registration = parseRegistration(value.registration);
1437
+ return registration ? { kind, requestId, registration } : undefined;
1438
+ }
1439
+ function parseHeartbeatEnvelope(value, requestId) {
1440
+ return typeof value.instanceId === "string" &&
1441
+ typeof value.sentAtMs === "number"
1442
+ ? {
1443
+ kind: "follower.heartbeat",
1444
+ requestId,
1445
+ instanceId: value.instanceId,
1446
+ ...(typeof value.registrationGeneration === "string"
1447
+ ? { registrationGeneration: value.registrationGeneration }
1448
+ : {}),
1449
+ sentAtMs: value.sentAtMs,
1450
+ }
1451
+ : undefined;
1452
+ }
1453
+ function parseDisconnectEnvelope(value, requestId) {
1454
+ return typeof value.instanceId === "string" &&
1455
+ typeof value.sentAtMs === "number"
1456
+ ? {
1457
+ kind: "follower.disconnect",
1458
+ requestId,
1459
+ instanceId: value.instanceId,
1460
+ ...(typeof value.registrationGeneration === "string"
1461
+ ? { registrationGeneration: value.registrationGeneration }
1462
+ : {}),
1463
+ sentAtMs: value.sentAtMs,
1464
+ }
1465
+ : undefined;
1466
+ }
1467
+ function parseRenameThreadEnvelope(value, requestId) {
1468
+ const target = parseTarget(value.target);
1469
+ return typeof value.instanceId === "string" &&
1470
+ typeof value.registrationGeneration === "string" &&
1471
+ target?.threadId !== undefined &&
1472
+ typeof value.threadName === "string" &&
1473
+ typeof value.sentAtMs === "number"
1474
+ ? {
1475
+ kind: "follower.renameThread",
1476
+ requestId,
1477
+ instanceId: value.instanceId,
1478
+ registrationGeneration: value.registrationGeneration,
1479
+ target: { chatId: target.chatId, threadId: target.threadId },
1480
+ threadName: value.threadName,
1481
+ sentAtMs: value.sentAtMs,
1482
+ }
1483
+ : undefined;
1484
+ }
1485
+ function parseTelegramBusFollowerDeliveryIdentity(value) {
1486
+ if (!isRecord(value))
1487
+ return undefined;
1488
+ if (typeof value.deliveryId !== "string" ||
1489
+ !/^telegram-follower-v1-[a-f0-9]{64}$/u.test(value.deliveryId) ||
1490
+ !Number.isSafeInteger(value.sourceUpdateId) ||
1491
+ value.sourceUpdateId < 0 ||
1492
+ typeof value.recipientBindingKey !== "string" ||
1493
+ !value.recipientBindingKey ||
1494
+ (value.sourceRecoveryKey !== undefined &&
1495
+ (typeof value.sourceRecoveryKey !== "string" || !value.sourceRecoveryKey ||
1496
+ value.sourceRecoveryKey.length > 1_024)) ||
1497
+ (value.sourceClaim !== undefined &&
1498
+ (!isRecord(value.sourceClaim) ||
1499
+ typeof value.sourceClaim.acquisitionId !== "string" ||
1500
+ !value.sourceClaim.acquisitionId || value.sourceClaim.acquisitionId.length > 256 ||
1501
+ typeof value.sourceClaim.handoffId !== "string" ||
1502
+ !value.sourceClaim.handoffId || value.sourceClaim.handoffId.length > 256))) {
1503
+ return undefined;
1504
+ }
1505
+ return {
1506
+ deliveryId: value.deliveryId,
1507
+ sourceUpdateId: value.sourceUpdateId,
1508
+ recipientBindingKey: value.recipientBindingKey,
1509
+ ...(typeof value.sourceRecoveryKey === "string"
1510
+ ? { sourceRecoveryKey: value.sourceRecoveryKey } : {}),
1511
+ ...(isRecord(value.sourceClaim)
1512
+ ? { sourceClaim: { acquisitionId: value.sourceClaim.acquisitionId,
1513
+ handoffId: value.sourceClaim.handoffId } } : {}),
1514
+ };
1515
+ }
1516
+ function parseForwardCallbackEnvelope(value, requestId) {
1517
+ const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
1518
+ return delivery &&
1519
+ typeof value.recipientInstanceId === "string" &&
1520
+ typeof value.recipientRegistrationGeneration === "string" &&
1521
+ typeof value.sentAtMs === "number"
1522
+ ? {
1523
+ kind: "leader.forwardCallback",
1524
+ requestId,
1525
+ recipientInstanceId: value.recipientInstanceId,
1526
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
1527
+ delivery,
1528
+ query: value.query,
1529
+ sentAtMs: value.sentAtMs,
1530
+ }
1531
+ : undefined;
1532
+ }
1533
+ function parseForwardReactionEnvelope(value, requestId) {
1534
+ const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
1535
+ return delivery &&
1536
+ typeof value.recipientInstanceId === "string" &&
1537
+ typeof value.recipientRegistrationGeneration === "string" &&
1538
+ typeof value.sentAtMs === "number"
1539
+ ? {
1540
+ kind: "leader.forwardReaction",
1541
+ requestId,
1542
+ recipientInstanceId: value.recipientInstanceId,
1543
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
1544
+ delivery,
1545
+ reactionUpdate: value.reactionUpdate,
1546
+ sentAtMs: value.sentAtMs,
1547
+ }
1548
+ : undefined;
1549
+ }
1550
+ function parseForwardMessageEnvelope(value, requestId, kind) {
1551
+ const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
1552
+ return delivery &&
1553
+ typeof value.recipientInstanceId === "string" &&
1554
+ typeof value.recipientRegistrationGeneration === "string" &&
1555
+ typeof value.sentAtMs === "number"
1556
+ ? {
1557
+ kind,
1558
+ requestId,
1559
+ recipientInstanceId: value.recipientInstanceId,
1560
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
1561
+ delivery,
1562
+ message: value.message,
1563
+ ...(kind === "leader.forwardMessage" &&
1564
+ (value.forwardCommentBatchPosition === "comment" ||
1565
+ value.forwardCommentBatchPosition === "forward")
1566
+ ? {
1567
+ forwardCommentBatchPosition: value.forwardCommentBatchPosition,
1568
+ }
1569
+ : {}),
1570
+ sentAtMs: value.sentAtMs,
1571
+ }
1572
+ : undefined;
1573
+ }
1574
+ function parseOfferInputCustodyHandoffEnvelope(value, requestId) {
1575
+ const source = value.source;
1576
+ if (!isRecord(source) || typeof value.recipientInstanceId !== "string" ||
1577
+ typeof value.recipientRegistrationGeneration !== "string" ||
1578
+ typeof value.recipientBindingKey !== "string" || !value.recipientBindingKey ||
1579
+ typeof value.sourceRecoveryKey !== "string" || !value.sourceRecoveryKey ||
1580
+ value.sourceRecoveryKey.length > 1_024 ||
1581
+ typeof source.journalBindingKey !== "string" || !source.journalBindingKey ||
1582
+ source.journalBindingKey !== value.sourceRecoveryKey ||
1583
+ typeof source.tokenSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(source.tokenSha256) ||
1584
+ !Number.isSafeInteger(source.updateId) || source.updateId < 0 ||
1585
+ typeof value.handoffId !== "string" || !value.handoffId || value.handoffId.length > 256 ||
1586
+ typeof value.sentAtMs !== "number")
1587
+ return undefined;
1588
+ return { kind: "leader.offerInputCustodyHandoff", requestId,
1589
+ recipientInstanceId: value.recipientInstanceId,
1590
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
1591
+ recipientBindingKey: value.recipientBindingKey,
1592
+ sourceRecoveryKey: value.sourceRecoveryKey,
1593
+ source: { journalBindingKey: source.journalBindingKey,
1594
+ tokenSha256: source.tokenSha256, updateId: source.updateId },
1595
+ handoffId: value.handoffId, sentAtMs: value.sentAtMs };
1596
+ }
1597
+ function parseWakeInputCustodyEnvelope(value, requestId) {
1598
+ const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
1599
+ return delivery && delivery.sourceRecoveryKey && delivery.sourceClaim &&
1600
+ typeof value.recipientInstanceId === "string" &&
1601
+ typeof value.recipientRegistrationGeneration === "string" &&
1602
+ typeof value.sentAtMs === "number"
1603
+ ? { kind: "leader.wakeInputCustody", requestId,
1604
+ recipientInstanceId: value.recipientInstanceId,
1605
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
1606
+ delivery, sentAtMs: value.sentAtMs }
1607
+ : undefined;
1608
+ }
1609
+ function parseReplaceFollowerTargetEnvelope(value, requestId) {
1610
+ const target = parseThreadTarget(value.target);
1611
+ const oldTarget = parseThreadTarget(value.oldTarget);
1612
+ if (typeof value.recipientInstanceId !== "string" ||
1613
+ !target ||
1614
+ (value.oldTarget !== undefined && !oldTarget) ||
1615
+ value.reason !== "thread-restore" ||
1616
+ typeof value.sentAtMs !== "number") {
1617
+ return undefined;
1618
+ }
1619
+ return {
1620
+ kind: "leader.replaceFollowerTarget",
1621
+ requestId,
1622
+ recipientInstanceId: value.recipientInstanceId,
1623
+ ...(typeof value.recipientRegistrationGeneration === "string"
1624
+ ? {
1625
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
1626
+ }
1627
+ : {}),
1628
+ target,
1629
+ ...(oldTarget ? { oldTarget } : {}),
1630
+ reason: value.reason,
1631
+ sentAtMs: value.sentAtMs,
1632
+ };
1633
+ }
1634
+ function parseQueueAdmissionReceipt(value, queueKind) {
1635
+ if (!isRecord(value) || value.queueKind !== queueKind)
1636
+ return undefined;
1637
+ const sourceUpdateIds = Array.isArray(value.sourceUpdateIds)
1638
+ ? value.sourceUpdateIds
1639
+ : undefined;
1640
+ if (typeof value.receiptId !== "string" ||
1641
+ !value.receiptId ||
1642
+ (value.journalBindingKey !== undefined &&
1643
+ (typeof value.journalBindingKey !== "string" ||
1644
+ !value.journalBindingKey.trim())) ||
1645
+ !sourceUpdateIds ||
1646
+ sourceUpdateIds.length === 0 ||
1647
+ sourceUpdateIds.some((updateId, index) => !Number.isSafeInteger(updateId) ||
1648
+ updateId < 0 ||
1649
+ (index > 0 &&
1650
+ updateId <=
1651
+ sourceUpdateIds[index - 1]))) {
1652
+ return undefined;
1653
+ }
1654
+ return {
1655
+ queueKind,
1656
+ receiptId: value.receiptId,
1657
+ sourceUpdateIds: sourceUpdateIds,
1658
+ ...(typeof value.journalBindingKey === "string"
1659
+ ? { journalBindingKey: value.journalBindingKey }
1660
+ : {}),
1661
+ };
1662
+ }
1663
+ function parseQueueHandoffPayload(value) {
1664
+ if (!isRecord(value) || (value.kind !== "prompt" && value.kind !== "control")) {
1665
+ return undefined;
1666
+ }
1667
+ const queueKind = value.kind;
1668
+ const target = parseTarget(value.target);
1669
+ const transportStamp = isRecord(value.transportStamp) &&
1670
+ typeof value.transportStamp.profile === "string" &&
1671
+ typeof value.transportStamp.generation === "string"
1672
+ ? {
1673
+ profile: value.transportStamp.profile,
1674
+ generation: value.transportStamp.generation,
1675
+ }
1676
+ : undefined;
1677
+ if (!Number.isSafeInteger(value.chatId) ||
1678
+ (value.target !== undefined && !target) ||
1679
+ (value.transportStamp !== undefined && !transportStamp) ||
1680
+ !Number.isSafeInteger(value.replyToMessageId) ||
1681
+ (value.guestQueryId !== undefined &&
1682
+ typeof value.guestQueryId !== "string") ||
1683
+ (value.guestInlineMessageId !== undefined &&
1684
+ typeof value.guestInlineMessageId !== "string") ||
1685
+ !Number.isSafeInteger(value.queueOrder) ||
1686
+ (value.queueLane !== "control" &&
1687
+ value.queueLane !== "priority" &&
1688
+ value.queueLane !== "default") ||
1689
+ !Number.isSafeInteger(value.laneOrder) ||
1690
+ typeof value.statusSummary !== "string" ||
1691
+ !Array.isArray(value.admissionReceipts)) {
1692
+ return undefined;
1693
+ }
1694
+ const admissionReceipts = value.admissionReceipts.map((receipt) => parseQueueAdmissionReceipt(receipt, queueKind));
1695
+ if (admissionReceipts.length === 0 ||
1696
+ admissionReceipts.length > TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS ||
1697
+ admissionReceipts.some((receipt) => receipt === undefined)) {
1698
+ return undefined;
1699
+ }
1700
+ const queueLane = value.queueLane;
1701
+ const base = {
1702
+ chatId: value.chatId,
1703
+ ...(target ? { target } : {}),
1704
+ ...(transportStamp ? { transportStamp } : {}),
1705
+ replyToMessageId: value.replyToMessageId,
1706
+ ...(typeof value.guestQueryId === "string"
1707
+ ? { guestQueryId: value.guestQueryId }
1708
+ : {}),
1709
+ ...(typeof value.guestInlineMessageId === "string"
1710
+ ? { guestInlineMessageId: value.guestInlineMessageId }
1711
+ : {}),
1712
+ queueOrder: value.queueOrder,
1713
+ queueLane,
1714
+ laneOrder: value.laneOrder,
1715
+ statusSummary: value.statusSummary,
1716
+ admissionReceipts: admissionReceipts,
1717
+ };
1718
+ if (queueKind === "control") {
1719
+ if (value.queueLane !== "control" ||
1720
+ (value.controlType !== "status" && value.controlType !== "model")) {
1721
+ return undefined;
1722
+ }
1723
+ return { kind: "control", controlType: value.controlType, ...base };
1724
+ }
1725
+ if (value.queueLane === "control" ||
1726
+ !Array.isArray(value.sourceMessageIds) ||
1727
+ value.sourceMessageIds.some((id) => !Number.isSafeInteger(id)) ||
1728
+ !Array.isArray(value.queuedAttachments) ||
1729
+ value.queuedAttachments.some((attachment) => !isRecord(attachment) ||
1730
+ typeof attachment.path !== "string" ||
1731
+ typeof attachment.fileName !== "string") ||
1732
+ !Array.isArray(value.content) ||
1733
+ value.content.some((content) => !isRecord(content) ||
1734
+ (content.type === "text"
1735
+ ? typeof content.text !== "string"
1736
+ : content.type === "image"
1737
+ ? typeof content.data !== "string" ||
1738
+ typeof content.mimeType !== "string"
1739
+ : true)) ||
1740
+ typeof value.historyText !== "string" ||
1741
+ (value.priorityEmoji !== undefined &&
1742
+ typeof value.priorityEmoji !== "string") ||
1743
+ (value.reactionSuppressionEmoji !== undefined &&
1744
+ typeof value.reactionSuppressionEmoji !== "string") ||
1745
+ (value.voiceReplyPreferred !== undefined &&
1746
+ typeof value.voiceReplyPreferred !== "boolean") ||
1747
+ (value.voiceReplyRequired !== undefined &&
1748
+ typeof value.voiceReplyRequired !== "boolean")) {
1749
+ return undefined;
1750
+ }
1751
+ return {
1752
+ kind: "prompt",
1753
+ ...base,
1754
+ sourceMessageIds: value.sourceMessageIds,
1755
+ queuedAttachments: value.queuedAttachments,
1756
+ content: value.content,
1757
+ historyText: value.historyText,
1758
+ ...(typeof value.priorityEmoji === "string"
1759
+ ? { priorityEmoji: value.priorityEmoji }
1760
+ : {}),
1761
+ ...(typeof value.reactionSuppressionEmoji === "string"
1762
+ ? { reactionSuppressionEmoji: value.reactionSuppressionEmoji }
1763
+ : {}),
1764
+ ...(typeof value.voiceReplyPreferred === "boolean"
1765
+ ? { voiceReplyPreferred: value.voiceReplyPreferred }
1766
+ : {}),
1767
+ ...(typeof value.voiceReplyRequired === "boolean"
1768
+ ? { voiceReplyRequired: value.voiceReplyRequired }
1769
+ : {}),
1770
+ };
1771
+ }
1772
+ function parseQueueHandoffEnvelope(value, requestId, kind) {
1773
+ let serializedPayloadBytes;
1774
+ try {
1775
+ serializedPayloadBytes = Buffer.byteLength(JSON.stringify(value.payload));
1776
+ }
1777
+ catch {
1778
+ return undefined;
1779
+ }
1780
+ if (serializedPayloadBytes > TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES) {
1781
+ return undefined;
1782
+ }
1783
+ const payload = parseQueueHandoffPayload(value.payload);
1784
+ if (!payload ||
1785
+ typeof value.recipientInstanceId !== "string" ||
1786
+ typeof value.recipientRegistrationGeneration !== "string" ||
1787
+ !Number.isSafeInteger(value.donorProcessId) ||
1788
+ value.donorProcessId <= 0 ||
1789
+ typeof value.donorProcessBirthId !== "string" ||
1790
+ !value.donorProcessBirthId ||
1791
+ !Number.isSafeInteger(value.donorSessionGeneration) ||
1792
+ value.donorSessionGeneration <= 0 ||
1793
+ typeof value.donorAcquisitionId !== "string" ||
1794
+ !value.donorAcquisitionId ||
1795
+ !Number.isSafeInteger(value.donorAcquiredAtMs) ||
1796
+ value.donorAcquiredAtMs < 0 ||
1797
+ typeof value.handoffToken !== "string" ||
1798
+ value.handoffToken.length < 32 ||
1799
+ value.handoffToken.length > 256 ||
1800
+ value.donorProcessBirthId.length > 256 ||
1801
+ value.donorAcquisitionId.length > 256 ||
1802
+ typeof value.sentAtMs !== "number") {
1803
+ return undefined;
1804
+ }
1805
+ const fields = {
1806
+ requestId,
1807
+ recipientInstanceId: value.recipientInstanceId,
1808
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
1809
+ donorProcessId: value.donorProcessId,
1810
+ donorProcessBirthId: value.donorProcessBirthId,
1811
+ donorSessionGeneration: value.donorSessionGeneration,
1812
+ donorAcquisitionId: value.donorAcquisitionId,
1813
+ donorAcquiredAtMs: value.donorAcquiredAtMs,
1814
+ handoffToken: value.handoffToken,
1815
+ payload,
1816
+ sentAtMs: value.sentAtMs,
1817
+ };
1818
+ if (kind === "leader.offerQueueHandoff") {
1819
+ return typeof value.donorInstanceId === "string"
1820
+ ? { kind, donorInstanceId: value.donorInstanceId, ...fields }
1821
+ : undefined;
1822
+ }
1823
+ return typeof value.instanceId === "string" &&
1824
+ typeof value.registrationGeneration === "string"
1825
+ ? {
1826
+ kind,
1827
+ instanceId: value.instanceId,
1828
+ registrationGeneration: value.registrationGeneration,
1829
+ ...fields,
1830
+ }
1831
+ : undefined;
1832
+ }
1833
+ function parseResolveAgentTargetEnvelope(value, requestId) {
1834
+ const selectorValue = isRecord(value.selector) ? value.selector : undefined;
1835
+ if (typeof value.instanceId !== "string" ||
1836
+ !selectorValue ||
1837
+ typeof value.sentAtMs !== "number") {
1838
+ return undefined;
1839
+ }
1840
+ const chatId = typeof selectorValue.chatId === "number" &&
1841
+ Number.isInteger(selectorValue.chatId)
1842
+ ? selectorValue.chatId
1843
+ : undefined;
1844
+ const threadId = typeof selectorValue.threadId === "number" &&
1845
+ Number.isInteger(selectorValue.threadId) &&
1846
+ selectorValue.threadId > 0
1847
+ ? selectorValue.threadId
1848
+ : undefined;
1849
+ const threadName = typeof selectorValue.threadName === "string" &&
1850
+ selectorValue.threadName.trim()
1851
+ ? selectorValue.threadName.trim()
1852
+ : undefined;
1853
+ if ((threadId === undefined) === (threadName === undefined))
1854
+ return undefined;
1855
+ return {
1856
+ kind: "follower.resolveAgentTarget",
1857
+ requestId,
1858
+ instanceId: value.instanceId,
1859
+ ...(typeof value.registrationGeneration === "string"
1860
+ ? { registrationGeneration: value.registrationGeneration }
1861
+ : {}),
1862
+ selector: {
1863
+ ...(chatId !== undefined ? { chatId } : {}),
1864
+ ...(threadId !== undefined ? { threadId } : {}),
1865
+ ...(threadName !== undefined ? { threadName } : {}),
1866
+ },
1867
+ sentAtMs: value.sentAtMs,
1868
+ };
1869
+ }
1870
+ function parseRouteAgentMessageEnvelope(value, requestId) {
1871
+ const messageValue = isRecord(value.message) ? value.message : undefined;
1872
+ const target = parseThreadTarget(messageValue?.target);
1873
+ if (typeof value.instanceId !== "string" ||
1874
+ !messageValue ||
1875
+ !target ||
1876
+ typeof messageValue.messageId !== "number" ||
1877
+ !Number.isInteger(messageValue.messageId) ||
1878
+ messageValue.messageId <= 0 ||
1879
+ typeof messageValue.text !== "string" ||
1880
+ !messageValue.text.trim() ||
1881
+ typeof value.sentAtMs !== "number") {
1882
+ return undefined;
1883
+ }
1884
+ return {
1885
+ kind: "follower.routeAgentMessage",
1886
+ requestId,
1887
+ instanceId: value.instanceId,
1888
+ ...(typeof value.registrationGeneration === "string"
1889
+ ? { registrationGeneration: value.registrationGeneration }
1890
+ : {}),
1891
+ message: {
1892
+ target,
1893
+ messageId: messageValue.messageId,
1894
+ text: messageValue.text,
1895
+ },
1896
+ sentAtMs: value.sentAtMs,
1897
+ };
1898
+ }
1899
+ function parseCallApiEnvelope(value, requestId) {
1900
+ return typeof value.instanceId === "string" &&
1901
+ typeof value.method === "string" &&
1902
+ Array.isArray(value.args) &&
1903
+ typeof value.sentAtMs === "number"
1904
+ ? {
1905
+ kind: "follower.callApi",
1906
+ requestId,
1907
+ instanceId: value.instanceId,
1908
+ ...(typeof value.registrationGeneration === "string"
1909
+ ? { registrationGeneration: value.registrationGeneration }
1910
+ : {}),
1911
+ method: value.method,
1912
+ args: value.args,
1913
+ sentAtMs: value.sentAtMs,
1914
+ }
1915
+ : undefined;
1916
+ }
1917
+ function parseAckEnvelope(value, requestId) {
1918
+ if (typeof value.ok !== "boolean")
1919
+ return undefined;
1920
+ const envelope = {
1921
+ kind: "bus.ack",
1922
+ requestId,
1923
+ ok: value.ok,
1924
+ message: typeof value.message === "string" ? value.message : undefined,
1925
+ };
1926
+ if (Object.hasOwn(value, "result"))
1927
+ envelope.result = value.result;
1928
+ const protocol = parseTelegramBusProtocolIdentity(value.protocol);
1929
+ if (protocol)
1930
+ envelope.protocol = protocol;
1931
+ if (isRecord(value.error)) {
1932
+ const code = value.error.code;
1933
+ if (code === "commit-unknown" ||
1934
+ code === "request-id-collision" ||
1935
+ code === "ledger-overloaded" ||
1936
+ code === "incompatible-protocol" ||
1937
+ code === "stale-target" ||
1938
+ code === "workspace-binding-unavailable") {
1939
+ const chatId = value.error.chatId;
1940
+ const threadId = value.error.threadId;
1941
+ if (code === "stale-target" &&
1942
+ (!Number.isSafeInteger(chatId) || !Number.isSafeInteger(threadId))) {
1943
+ return undefined;
1944
+ }
1945
+ envelope.error = {
1946
+ code,
1947
+ ...(typeof value.error.method === "string"
1948
+ ? { method: value.error.method }
1949
+ : {}),
1950
+ ...(typeof chatId === "number" ? { chatId } : {}),
1951
+ ...(typeof threadId === "number" ? { threadId } : {}),
1952
+ };
1953
+ }
1954
+ }
1955
+ return envelope;
1956
+ }
1957
+ function parseTelegramBusProtocolIdentity(value) {
1958
+ if (!isRecord(value))
1959
+ return undefined;
1960
+ if (!Number.isSafeInteger(value.protocolVersion) ||
1961
+ value.protocolVersion <= 0 ||
1962
+ typeof value.runtimeBuild !== "string" ||
1963
+ !value.runtimeBuild.trim() ||
1964
+ value.runtimeBuild !== value.runtimeBuild.trim() ||
1965
+ value.runtimeBuild.length > 128 ||
1966
+ !Array.isArray(value.capabilities) ||
1967
+ value.capabilities.length > 32 ||
1968
+ value.capabilities.some((capability) => typeof capability !== "string" ||
1969
+ capability.length > 128 ||
1970
+ !/^[a-z0-9][a-z0-9._-]*$/u.test(capability))) {
1971
+ return undefined;
1972
+ }
1973
+ const capabilities = value.capabilities;
1974
+ if (capabilities.some((capability, index) => index > 0 && capability <= capabilities[index - 1])) {
1975
+ return undefined;
1976
+ }
1977
+ return {
1978
+ protocolVersion: value.protocolVersion,
1979
+ runtimeBuild: value.runtimeBuild,
1980
+ capabilities: [...capabilities],
1981
+ };
1982
+ }
1983
+ function parseRegistration(value) {
1984
+ if (!isRecord(value))
1985
+ return undefined;
1986
+ if (typeof value.instanceId !== "string")
1987
+ return undefined;
1988
+ if (typeof value.connectedAtMs !== "number")
1989
+ return undefined;
1990
+ const target = parseTarget(value.target);
1991
+ if (value.target !== undefined && !target)
1992
+ return undefined;
1993
+ const registration = {
1994
+ instanceId: value.instanceId,
1995
+ connectedAtMs: value.connectedAtMs,
1996
+ };
1997
+ if (typeof value.previousInstanceId === "string") {
1998
+ registration.previousInstanceId = value.previousInstanceId;
1999
+ }
2000
+ if (typeof value.profileKey === "string")
2001
+ registration.profileKey = value.profileKey;
2002
+ if (typeof value.threadName === "string")
2003
+ registration.threadName = value.threadName;
2004
+ if (typeof value.slot === "string" && /^[A-Z]$/.test(value.slot)) {
2005
+ registration.slot = value.slot;
2006
+ }
2007
+ if (typeof value.cwd === "string")
2008
+ registration.cwd = value.cwd;
2009
+ if (value.sessionId !== undefined) {
2010
+ if (typeof value.sessionId !== "string" || !value.sessionId ||
2011
+ value.sessionId !== value.sessionId.trim() ||
2012
+ Buffer.byteLength(value.sessionId, "utf8") > 256)
2013
+ return undefined;
2014
+ registration.sessionId = value.sessionId;
2015
+ }
2016
+ if (typeof value.pid === "number")
2017
+ registration.pid = value.pid;
2018
+ if (typeof value.busSocketPath === "string") {
2019
+ registration.busSocketPath = value.busSocketPath;
2020
+ }
2021
+ if (typeof value.registrationGeneration === "string") {
2022
+ registration.registrationGeneration = value.registrationGeneration;
2023
+ }
2024
+ if (Number.isSafeInteger(value.sessionGeneration) &&
2025
+ value.sessionGeneration > 0) {
2026
+ registration.sessionGeneration = value.sessionGeneration;
2027
+ }
2028
+ if (typeof value.processBirthId === "string" && value.processBirthId) {
2029
+ registration.processBirthId = value.processBirthId;
2030
+ }
2031
+ const protocol = parseTelegramBusProtocolIdentity(value.protocol);
2032
+ if (protocol)
2033
+ registration.protocol = protocol;
2034
+ if (target)
2035
+ registration.target = target;
2036
+ return registration;
2037
+ }
2038
+ function parseTarget(value) {
2039
+ if (value === undefined)
2040
+ return undefined;
2041
+ if (!isRecord(value) || typeof value.chatId !== "number")
2042
+ return undefined;
2043
+ return typeof value.threadId === "number"
2044
+ ? { chatId: value.chatId, threadId: value.threadId }
2045
+ : { chatId: value.chatId };
2046
+ }
2047
+ function parseThreadTarget(value) {
2048
+ const target = parseTarget(value);
2049
+ return target && typeof target.threadId === "number"
2050
+ ? { chatId: target.chatId, threadId: target.threadId }
2051
+ : undefined;
2052
+ }
2053
+ function isRecord(value) {
2054
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2055
+ }