pi-harness-runtime 1.1.427 → 1.1.428

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 (153) hide show
  1. package/AGENTS.md +2 -2
  2. package/RULES.md +2 -3
  3. package/package.json +1 -1
  4. package/packages/a2a-adapter/package.json +1 -1
  5. package/packages/agent-policy/package.json +1 -1
  6. package/packages/architecture-generator/package.json +1 -1
  7. package/packages/auth/package.json +1 -1
  8. package/packages/autonomous-refactor/package.json +1 -1
  9. package/packages/autonomous-runtime/package.json +1 -1
  10. package/packages/cache-strategy/package.json +1 -1
  11. package/packages/capability-registry/package.json +1 -1
  12. package/packages/checkpoint/package.json +1 -1
  13. package/packages/cli-plugin-sdk/package.json +1 -1
  14. package/packages/clipboard/package.json +1 -1
  15. package/packages/clipboard-plugin/package.json +1 -1
  16. package/packages/code-generation/package.json +1 -1
  17. package/packages/code-review/package.json +1 -1
  18. package/packages/codex-adapter/package.json +1 -1
  19. package/packages/codex-watcher/package.json +1 -1
  20. package/packages/config-capture/package.json +1 -1
  21. package/packages/connection-runtime/package.json +1 -1
  22. package/packages/context-compiler/package.json +1 -1
  23. package/packages/context-discovery/package.json +1 -1
  24. package/packages/context-manager/package.json +1 -1
  25. package/packages/contracts/package.json +1 -1
  26. package/packages/cookie-sanitizer/package.json +1 -1
  27. package/packages/cost-optimizer/package.json +1 -1
  28. package/packages/dependency-analyzer/package.json +1 -1
  29. package/packages/django-plugin/package.json +1 -1
  30. package/packages/doc-generator/package.json +1 -1
  31. package/packages/durable-review/package.json +1 -1
  32. package/packages/e2e-tester/package.json +1 -1
  33. package/packages/evaluation-engine/package.json +1 -1
  34. package/packages/evaluation-runner/package.json +1 -1
  35. package/packages/event-bus/package.json +1 -1
  36. package/packages/event-sink/package.json +1 -1
  37. package/packages/event-store/package.json +1 -1
  38. package/packages/experience-replay/package.json +1 -1
  39. package/packages/feedback-collector/package.json +1 -1
  40. package/packages/file-copy-helper/package.json +1 -1
  41. package/packages/framework-detector/package.json +1 -1
  42. package/packages/framework-plugin-sdk/package.json +1 -1
  43. package/packages/frappe-plugin/package.json +1 -1
  44. package/packages/generic-web-plugin/package.json +1 -1
  45. package/packages/health-monitor/package.json +1 -1
  46. package/packages/intent-analyzer/package.json +1 -1
  47. package/packages/jev-judge/package.json +1 -1
  48. package/packages/knowledge-graph/package.json +1 -1
  49. package/packages/knowledge-retrieval/package.json +1 -1
  50. package/packages/laravel-plugin/package.json +1 -1
  51. package/packages/learning-engine/package.json +1 -1
  52. package/packages/logging/package.json +1 -1
  53. package/packages/mcp-adapter/package.json +1 -1
  54. package/packages/memory-engine/package.json +1 -1
  55. package/packages/milestone-manager/package.json +1 -1
  56. package/packages/model-registry/package.json +1 -1
  57. package/packages/nextjs-plugin/package.json +1 -1
  58. package/packages/notification/package.json +1 -1
  59. package/packages/observability/package.json +1 -1
  60. package/packages/okf-indexer/package.json +1 -1
  61. package/packages/paperclip-integration/package.json +1 -1
  62. package/packages/performance-optimizer/package.json +1 -1
  63. package/packages/permission-mode/package.json +1 -1
  64. package/packages/privilege-broker/package.json +1 -1
  65. package/packages/project-analyzer/package.json +1 -1
  66. package/packages/project-bootstrap/package.json +1 -1
  67. package/packages/projection-engine/package.json +1 -1
  68. package/packages/prompt-compiler/package.json +1 -1
  69. package/packages/prompt-versioning/package.json +1 -1
  70. package/packages/provider-adapter-sdk/package.json +1 -1
  71. package/packages/provider-router/package.json +1 -1
  72. package/packages/provider-selector/package.json +1 -1
  73. package/packages/providers/package.json +1 -1
  74. package/packages/qdrant-skills/package.json +1 -1
  75. package/packages/quota-manager/package.json +1 -1
  76. package/packages/rate-limiter/package.json +1 -1
  77. package/packages/react-vite-plugin/package.json +1 -1
  78. package/packages/relay/package.json +1 -1
  79. package/packages/release-manager/package.json +1 -1
  80. package/packages/requirement-compiler/package.json +1 -1
  81. package/packages/runtime/package.json +1 -1
  82. package/packages/scheduler/package.json +1 -1
  83. package/packages/scheduler-adapter/package.json +1 -1
  84. package/packages/session/package.json +1 -1
  85. package/packages/session-api/package.json +1 -1
  86. package/packages/session-export/package.json +1 -1
  87. package/packages/shared-context/package.json +1 -1
  88. package/packages/skill-mcp-client/package.json +1 -1
  89. package/packages/skill-registry/package.json +1 -1
  90. package/packages/skills/package.json +1 -1
  91. package/packages/sprint-planner/package.json +1 -1
  92. package/packages/subscription-engine/package.json +1 -1
  93. package/packages/task-compiler/package.json +1 -1
  94. package/packages/tencentdb-memory/package.json +1 -1
  95. package/packages/tencentdb-sync/package.json +1 -1
  96. package/packages/test-data-generator/package.json +1 -1
  97. package/packages/test-generator/package.json +1 -1
  98. package/packages/todo-bd-sync/package.json +1 -1
  99. package/packages/token-estimation/package.json +1 -1
  100. package/packages/token-optimizer/package.json +1 -1
  101. package/packages/tooling-preference/package.json +1 -1
  102. package/packages/trajectory/package.json +1 -1
  103. package/packages/tui/package.json +1 -1
  104. package/packages/two-phase-review/package.json +1 -1
  105. package/packages/types/package.json +1 -1
  106. package/packages/voice-api/package.json +1 -1
  107. package/packages/webmcp-tester/package.json +1 -1
  108. package/packages/wiki/package.json +1 -1
  109. package/packages/workflow-events/package.json +1 -1
  110. package/packages/workspace-scanner/package.json +1 -1
  111. package/packages/worktree/package.json +1 -1
  112. package/packages/write-review/package.json +1 -1
  113. package/scripts/lib/runtime-imports.ts +199 -0
  114. package/scripts/verify-package-contents.ts +70 -18
  115. package/packages/telegram/package.json +0 -22
  116. package/packages/telegram/src/agent-messages.d.ts +0 -28
  117. package/packages/telegram/src/agent-messages.js +0 -90
  118. package/packages/telegram/src/bindings.d.ts +0 -296
  119. package/packages/telegram/src/bindings.js +0 -1086
  120. package/packages/telegram/src/bus.d.ts +0 -759
  121. package/packages/telegram/src/bus.js +0 -2732
  122. package/packages/telegram/src/commands.d.ts +0 -761
  123. package/packages/telegram/src/commands.js +0 -2622
  124. package/packages/telegram/src/config.d.ts +0 -222
  125. package/packages/telegram/src/config.js +0 -844
  126. package/packages/telegram/src/delivery.d.ts +0 -161
  127. package/packages/telegram/src/delivery.js +0 -550
  128. package/packages/telegram/src/extension.d.ts +0 -7
  129. package/packages/telegram/src/extension.js +0 -2444
  130. package/packages/telegram/src/inbound.d.ts +0 -94
  131. package/packages/telegram/src/inbound.js +0 -478
  132. package/packages/telegram/src/index.d.ts +0 -6
  133. package/packages/telegram/src/index.js +0 -6
  134. package/packages/telegram/src/keyboard.d.ts +0 -21
  135. package/packages/telegram/src/keyboard.js +0 -37
  136. package/packages/telegram/src/lifecycle.d.ts +0 -210
  137. package/packages/telegram/src/lifecycle.js +0 -538
  138. package/packages/telegram/src/pi.d.ts +0 -76
  139. package/packages/telegram/src/pi.js +0 -130
  140. package/packages/telegram/src/polling.d.ts +0 -332
  141. package/packages/telegram/src/polling.js +0 -1219
  142. package/packages/telegram/src/replies.d.ts +0 -210
  143. package/packages/telegram/src/replies.js +0 -752
  144. package/packages/telegram/src/routing.d.ts +0 -388
  145. package/packages/telegram/src/routing.js +0 -7886
  146. package/packages/telegram/src/runtime.d.ts +0 -159
  147. package/packages/telegram/src/runtime.js +0 -351
  148. package/packages/telegram/src/telegram-api.d.ts +0 -564
  149. package/packages/telegram/src/telegram-api.js +0 -1332
  150. package/packages/telegram/src/turns.d.ts +0 -108
  151. package/packages/telegram/src/turns.js +0 -482
  152. package/packages/telegram/src/updates.d.ts +0 -1545
  153. package/packages/telegram/src/updates.js +0 -6420
@@ -1,2732 +0,0 @@
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 { createHash, randomBytes, timingSafeEqual } from "node:crypto";
8
- import { chmodSync, existsSync, lstatSync, mkdirSync, readlinkSync, renameSync, symlinkSync, unlinkSync, } from "node:fs";
9
- import { createRequire } from "node:module";
10
- import { createConnection, createServer, } from "node:net";
11
- import { platform as getPlatform, tmpdir } from "node:os";
12
- import { basename, dirname, join } from "node:path";
13
- import { isDeepStrictEqual } from "node:util";
14
- import { classifyTelegramBusTransportError, createTelegramBusTransportTimeoutError, delayTelegramBusTransportRetry, getTelegramBusEndpointDiagnostics, getTelegramBusFollowerEndpoint, getTelegramBusLeaderEndpoint, getTelegramBusPipePath, getTelegramBusTransportRetryPolicy, isRetryableTelegramBusTransportError, isTelegramBusPipePath, probeTelegramBusEndpoint, TELEGRAM_BUS_MAX_DIRECT_UNIX_ENDPOINT_BYTES, } from "./bus-transport.js";
15
- import { isSameTelegramLockOwner, TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, } from "./locks.js";
16
- import { resolveAgentDir } from "./paths.js";
17
- import { getTelegramProcessBirthIdentity } from "./process-identity.js";
18
- import { parseTelegramQueueHandoffPayload, TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES, } from "./queue.js";
19
- import { areTelegramTargetsEqual, parseTelegramTarget as parseTarget, } from "./target.js";
20
- import { normalizeTelegramSessionReplacementIntent, } from "./threads.js";
21
- import { hasOnlyWireKeys, isWireRecord as isRecord } from "./wire.js";
22
- export function createCurrentTelegramBusProcessRuntime(input) {
23
- return createTelegramBusProcessRuntime({
24
- getActiveProfileName: input.getActiveProfileName,
25
- endpointLayout: input.endpointLayout,
26
- pid: input.pid ?? process.pid,
27
- parentPid: input.parentPid ?? process.ppid,
28
- createdAtMs: input.createdAtMs ?? Date.now(),
29
- });
30
- }
31
- export function createTelegramBusProcessRuntime(input) {
32
- const getActiveProfileName = input.getActiveProfileName, endpointLayout = input.endpointLayout;
33
- const instanceId = `${input.pid}:${input.createdAtMs}`;
34
- const ownerPid = input.parentPid || input.pid;
35
- const manualFollowerOwnerId = input.parentProcessIdentity ??
36
- getTelegramProcessBirthIdentity(ownerPid, input.createdAtMs);
37
- return {
38
- instanceId,
39
- processId: input.pid,
40
- processBirthId: getTelegramProcessBirthIdentity(input.pid, instanceId),
41
- manualFollowerOwnerId,
42
- getLeaderSocketPath: () => getTelegramBusSocketPath(undefined, undefined, getActiveProfileName(), endpointLayout),
43
- getFollowerSocketPath: () => getTelegramBusFollowerSocketPath(instanceId, undefined, undefined, getActiveProfileName(), endpointLayout),
44
- };
45
- }
46
- export function createTelegramBusAuthSecret() {
47
- return randomBytes(32).toString("base64url");
48
- }
49
- // v3: journal source serialization left the config transaction; mixed v2/v3 peers would not exclude each other.
50
- const TELEGRAM_BUS_PROTOCOL_VERSION = 3;
51
- export const TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION = "durable-follower-admission-v1";
52
- export const TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF = "queue-handoff-v1";
53
- export const TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE = "input-custody-reference-v1";
54
- export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME = "workspace-thread-rename-v1";
55
- export const TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE = "thread-display-mode-v1";
56
- export const TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT = "directory-display-format-v1";
57
- export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT = "workspace-follower-auto-connect-v1";
58
- export const TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT = "session-replacement-intent-v1";
59
- export const TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE = "workspace-restore-v1";
60
- export const TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE = "live-thread-rebind-save-v1";
61
- export const TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY = "live-thread-rebind-apply-v1";
62
- export const TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE = "live-thread-rebind-settle-v1";
63
- /** Restricted recipient text/menu delivery; negotiated identity and captured effect authority remain required. */
64
- export const TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY = "selected-menu-delivery-v1";
65
- /** Versioned held-command set; syntax is shared, while the captured recipient Commands registry owns supported plans. */
66
- export const TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET = "live-thread-rebind-command-set-v1";
67
- /** Every live rebinding needs the save/apply/settle trio; held command selections additionally need these two. */
68
- export const TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES = [
69
- TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE,
70
- TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY,
71
- TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE,
72
- ];
73
- export const TELEGRAM_BUS_HELD_COMMAND_CAPABILITIES = [
74
- TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET,
75
- TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
76
- ];
77
- function parseSelectedCommandInput(value) {
78
- if (!isRecord(value) ||
79
- !hasOnlyWireKeys(value, ["name", "target"]) ||
80
- typeof value.name !== "string" ||
81
- !/^[a-z0-9_]{1,32}$/u.test(value.name) ||
82
- !isRecord(value.target) ||
83
- !hasOnlyWireKeys(value.target, ["chatId", "threadId"]))
84
- return undefined;
85
- const target = parseThreadTarget(value.target);
86
- return target &&
87
- Number.isSafeInteger(target.chatId) &&
88
- target.chatId > 0 &&
89
- Number.isSafeInteger(target.threadId) &&
90
- target.threadId > 0
91
- ? { name: value.name, target }
92
- : undefined;
93
- }
94
- /** Pure live-rebind recipient/operation identity shared by prepare/apply/settle envelopes. */
95
- function parseLiveRebindRecipient(value) {
96
- const { recipientInstanceId, recipientRegistrationGeneration, recipientSessionId, recipientBindingKey, operationId, } = value;
97
- return typeof recipientInstanceId === "string" &&
98
- recipientInstanceId &&
99
- typeof recipientRegistrationGeneration === "string" &&
100
- recipientRegistrationGeneration &&
101
- typeof recipientSessionId === "string" &&
102
- recipientSessionId &&
103
- typeof recipientBindingKey === "string" &&
104
- recipientBindingKey &&
105
- typeof operationId === "string" &&
106
- operationId.length > 0 &&
107
- operationId.length <= 128
108
- ? {
109
- recipientInstanceId,
110
- recipientRegistrationGeneration,
111
- recipientSessionId,
112
- recipientBindingKey,
113
- operationId,
114
- }
115
- : undefined;
116
- }
117
- /** Non-empty, duplicate-free, non-negative safe-integer Telegram update ids. */
118
- function isUniqueSourceUpdateIds(value) {
119
- return (Array.isArray(value) &&
120
- value.length > 0 &&
121
- value.every((id) => Number.isSafeInteger(id) && id >= 0) &&
122
- new Set(value).size === value.length);
123
- }
124
- function parsePreparedCommandSource(value) {
125
- if (!isRecord(value) ||
126
- !hasOnlyWireKeys(value, [
127
- "journalBindingKey",
128
- "updateId",
129
- "sourceSha256",
130
- ]) ||
131
- typeof value.journalBindingKey !== "string" ||
132
- !value.journalBindingKey ||
133
- !Number.isSafeInteger(value.updateId) ||
134
- value.updateId < 0 ||
135
- typeof value.sourceSha256 !== "string" ||
136
- !/^[a-f0-9]{64}$/u.test(value.sourceSha256))
137
- return undefined;
138
- return {
139
- journalBindingKey: value.journalBindingKey,
140
- updateId: value.updateId,
141
- sourceSha256: value.sourceSha256,
142
- };
143
- }
144
- export function createTelegramBusProtocolIdentity(input) {
145
- const runtimeBuild = input.runtimeBuild.trim();
146
- if (!runtimeBuild || runtimeBuild.length > 128) {
147
- throw new Error("Telegram bus runtime build identity is invalid.");
148
- }
149
- const capabilities = [...new Set(input.capabilities ?? [])].sort();
150
- if (capabilities.length > 32 ||
151
- capabilities.some((capability) => capability.length > 128 || !/^[a-z0-9][a-z0-9._-]*$/u.test(capability))) {
152
- throw new Error("Telegram bus capabilities must be canonical identifiers.");
153
- }
154
- return {
155
- protocolVersion: TELEGRAM_BUS_PROTOCOL_VERSION,
156
- runtimeBuild,
157
- capabilities,
158
- };
159
- }
160
- export function createTelegramCurrentBusProtocolIdentity(capabilities = []) {
161
- const packageMetadata = createRequire(import.meta.url)("../package.json");
162
- if (typeof packageMetadata.version !== "string") {
163
- throw new Error("Telegram package build identity is unavailable.");
164
- }
165
- return createTelegramBusProtocolIdentity({
166
- runtimeBuild: packageMetadata.version,
167
- capabilities,
168
- });
169
- }
170
- export function hasTelegramBusCapability(identity, capability) {
171
- return identity?.capabilities.includes(capability) ?? false;
172
- }
173
- export function getTelegramInputCustodyPeerReadiness(followers) {
174
- return followers.map((follower) => {
175
- if (!follower.registrationGeneration || !follower.protocol)
176
- return "unknown";
177
- return hasTelegramBusCapability(follower.protocol, TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION) &&
178
- hasTelegramBusCapability(follower.protocol, TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE)
179
- ? "ready"
180
- : "legacy";
181
- });
182
- }
183
- /** Both peers advertise every required capability over a compatible protocol; reads only the two identities. */
184
- export function hasTelegramBusSharedCapabilities(local, remote, required) {
185
- return (required.every((capability) => hasTelegramBusCapability(local, capability) &&
186
- hasTelegramBusCapability(remote, capability)) && getTelegramBusProtocolCompatibility({ local, remote }).compatible);
187
- }
188
- export function getTelegramBusProtocolCompatibility(input) {
189
- if (!input.remote) {
190
- return {
191
- compatible: false,
192
- reason: "missing-identity",
193
- missingCapabilities: [],
194
- };
195
- }
196
- if (input.remote.protocolVersion !== input.local.protocolVersion) {
197
- return {
198
- compatible: false,
199
- reason: "version-mismatch",
200
- missingCapabilities: [],
201
- };
202
- }
203
- const remoteCapabilities = new Set(input.remote.capabilities);
204
- const missingCapabilities = input.local.capabilities
205
- .filter((capability) => capability === TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION)
206
- .filter((capability) => !remoteCapabilities.has(capability));
207
- return missingCapabilities.length > 0
208
- ? {
209
- compatible: false,
210
- reason: "missing-capability",
211
- missingCapabilities,
212
- }
213
- : { compatible: true, missingCapabilities: [] };
214
- }
215
- export function getTelegramBusSocketPath(agentDir = resolveAgentDir(), platform = getPlatform(), profileName, layout) {
216
- return getTelegramBusLeaderEndpoint({
217
- agentDir,
218
- platform,
219
- profileName,
220
- layout,
221
- });
222
- }
223
- export function getTelegramBusFollowerSocketPath(instanceId, agentDir = resolveAgentDir(), platform = getPlatform(), profileName, layout) {
224
- return getTelegramBusFollowerEndpoint({
225
- agentDir,
226
- platform,
227
- instanceId,
228
- profileName,
229
- layout,
230
- });
231
- }
232
- /** Whether a live follower is still exactly the captured registration: identity, process, session, endpoint and negotiated protocol. */
233
- export function isSameTelegramBusFollowerRegistration(live, captured) {
234
- return (live.instanceId === captured.instanceId &&
235
- live.profileKey === captured.profileKey &&
236
- live.sessionId === captured.sessionId &&
237
- live.registrationGeneration === captured.registrationGeneration &&
238
- live.pid === captured.pid &&
239
- live.processBirthId === captured.processBirthId &&
240
- live.sessionGeneration === captured.sessionGeneration &&
241
- live.cwd === captured.cwd &&
242
- live.slot === captured.slot &&
243
- live.busSocketPath === captured.busSocketPath &&
244
- isDeepStrictEqual(live.protocol, captured.protocol));
245
- }
246
- export function getTelegramFollowerTargetOwnership(input) {
247
- const liveFollower = input.followers.find((follower) => {
248
- return (follower.target?.chatId === input.target.chatId &&
249
- follower.target.threadId === input.target.threadId);
250
- });
251
- if (liveFollower?.registrationGeneration &&
252
- liveFollower.profileKey &&
253
- liveFollower.protocol?.capabilities.includes(TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION)) {
254
- return {
255
- instanceId: liveFollower.instanceId,
256
- ownerGeneration: liveFollower.registrationGeneration,
257
- recipientBindingKey: liveFollower.profileKey,
258
- protocolIdentity: liveFollower.protocol,
259
- };
260
- }
261
- // Persisted records are restart hints, not live routing authority. Only an
262
- // authenticated current follower registration may receive forwarded work.
263
- return undefined;
264
- }
265
- const TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD = "__piTelegramAggregateDelivery";
266
- const TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD = "__piTelegramCrossTargetDelivery";
267
- function hasTelegramBusDeliveryMarker(body, marker) {
268
- return Boolean(body &&
269
- typeof body === "object" &&
270
- !Array.isArray(body) &&
271
- body[marker] === true);
272
- }
273
- export function markTelegramBusAggregateDelivery(body) {
274
- return { ...body, [TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD]: true };
275
- }
276
- export function markTelegramBusCrossTargetDelivery(body) {
277
- return { ...body, [TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD]: true };
278
- }
279
- export function stripTelegramBusApiMetadata(body) {
280
- if (!(TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD in body) &&
281
- !(TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD in body)) {
282
- return body;
283
- }
284
- const clean = { ...body };
285
- delete clean[TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD];
286
- delete clean[TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD];
287
- return clean;
288
- }
289
- export function isTelegramFollowerApiCallAllowed(input) {
290
- const allowedCallMethods = new Set([
291
- "answerCallbackQuery",
292
- "answerGuestQuery",
293
- "closeForumTopic",
294
- "deleteForumTopic",
295
- "deleteMessage",
296
- "editForumTopic",
297
- "editMessageReplyMarkup",
298
- "editMessageText",
299
- "sendChatAction",
300
- "sendMessage",
301
- "sendMessageDraft",
302
- "sendRichMessage",
303
- "sendRichMessageDraft",
304
- ]);
305
- const allowedMultipartMethods = new Set([
306
- "sendAudio",
307
- "sendDocument",
308
- "sendMediaGroup",
309
- "sendPhoto",
310
- "sendRichMessage",
311
- "sendVoice",
312
- ]);
313
- const target = input.follower.target;
314
- const matchesId = (value, expected) => value === expected || value === String(expected);
315
- const isTargetScoped = (body) => {
316
- if (!target)
317
- return false;
318
- if (!body || typeof body !== "object" || Array.isArray(body))
319
- return false;
320
- const record = body;
321
- if (!matchesId(record.chat_id, target.chatId))
322
- return false;
323
- if (target.threadId === undefined)
324
- return true;
325
- return matchesId(record.message_thread_id, target.threadId);
326
- };
327
- const isTargetChatScoped = (body) => {
328
- if (!target)
329
- return false;
330
- if (!body || typeof body !== "object" || Array.isArray(body))
331
- return false;
332
- const record = body;
333
- return matchesId(record.chat_id, target.chatId);
334
- };
335
- const isDifferentTargetScoped = (body) => {
336
- if (!target || !isTargetChatScoped(body))
337
- return false;
338
- const threadId = body.message_thread_id;
339
- if (threadId === undefined)
340
- return target.threadId !== undefined;
341
- const parsedThreadId = typeof threadId === "number" ? threadId : Number(threadId);
342
- return (Number.isInteger(parsedThreadId) &&
343
- (target.threadId === undefined || !matchesId(threadId, target.threadId)));
344
- };
345
- const isTargetMessageScoped = (body) => {
346
- if (!isTargetChatScoped(body))
347
- return false;
348
- const messageId = body.message_id;
349
- const parsedMessageId = typeof messageId === "number" ? messageId : Number(messageId);
350
- return (Number.isInteger(parsedMessageId) && matchesId(messageId, parsedMessageId));
351
- };
352
- const isBotCommandRegistration = (body) => {
353
- if (!body || typeof body !== "object" || Array.isArray(body))
354
- return false;
355
- const commands = body.commands;
356
- return (Array.isArray(commands) &&
357
- commands.every((command) => command &&
358
- typeof command === "object" &&
359
- !Array.isArray(command) &&
360
- typeof command.command === "string" &&
361
- typeof command.description === "string"));
362
- };
363
- if (input.method === "downloadFile")
364
- return true;
365
- if (input.method === "call") {
366
- const apiMethod = input.args[0];
367
- if (typeof apiMethod !== "string")
368
- return false;
369
- if (apiMethod === "answerCallbackQuery" ||
370
- apiMethod === "answerGuestQuery") {
371
- return true;
372
- }
373
- if (apiMethod === "getMe")
374
- return true;
375
- if (apiMethod === "setMyCommands")
376
- return isBotCommandRegistration(input.args[1]);
377
- if (apiMethod === "sendChatAction")
378
- return isTargetChatScoped(input.args[1]);
379
- if (apiMethod === "sendMessage" &&
380
- hasTelegramBusDeliveryMarker(input.args[1], TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD)) {
381
- const body = input.args[1];
382
- return body.message_thread_id === undefined && isTargetChatScoped(body);
383
- }
384
- if ((apiMethod === "sendMessage" || apiMethod === "sendRichMessage") &&
385
- hasTelegramBusDeliveryMarker(input.args[1], TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD)) {
386
- return isDifferentTargetScoped(input.args[1]);
387
- }
388
- if (apiMethod === "deleteMessage" ||
389
- apiMethod === "editMessageReplyMarkup" ||
390
- apiMethod === "editMessageText") {
391
- if (!isTargetMessageScoped(input.args[1]))
392
- return false;
393
- const body = input.args[1];
394
- const messageId = typeof body.message_id === "number"
395
- ? body.message_id
396
- : Number(body.message_id);
397
- return input.isMessageOwned?.(target.chatId, messageId) === true;
398
- }
399
- return allowedCallMethods.has(apiMethod) && isTargetScoped(input.args[1]);
400
- }
401
- if (input.method === "callMultipart") {
402
- const apiMethod = input.args[0];
403
- return (typeof apiMethod === "string" &&
404
- allowedMultipartMethods.has(apiMethod) &&
405
- isTargetScoped(input.args[1]));
406
- }
407
- return false;
408
- }
409
- export function createTelegramFollowerApiCallAuthorizer(deps) {
410
- return (input) => isTelegramFollowerApiCallAllowed({
411
- ...input,
412
- isMessageOwned(chatId, messageId) {
413
- return deps.isMessageOwned({
414
- chatId,
415
- messageId,
416
- follower: input.follower,
417
- });
418
- },
419
- });
420
- }
421
- export function createTelegramBusFollowerDeliveryIdentity(input) {
422
- if (!input.recipientBindingKey ||
423
- !Number.isSafeInteger(input.sourceUpdateId) ||
424
- input.sourceUpdateId < 0 ||
425
- (input.sourceClaim !== undefined &&
426
- (!input.sourceClaim.acquisitionId ||
427
- input.sourceClaim.acquisitionId.length > 256 ||
428
- !input.sourceClaim.handoffId ||
429
- input.sourceClaim.handoffId.length > 256))) {
430
- throw new Error("Telegram follower delivery identity is incomplete.");
431
- }
432
- const deliveryId = createHash("sha256")
433
- .update(JSON.stringify({
434
- version: 1,
435
- kind: input.kind,
436
- recipientBindingKey: input.recipientBindingKey,
437
- sourceUpdateId: input.sourceUpdateId,
438
- }))
439
- .digest("hex");
440
- return {
441
- deliveryId: `telegram-follower-v1-${deliveryId}`,
442
- sourceUpdateId: input.sourceUpdateId,
443
- recipientBindingKey: input.recipientBindingKey,
444
- ...(input.sourceRecoveryKey
445
- ? { sourceRecoveryKey: input.sourceRecoveryKey }
446
- : {}),
447
- ...(input.sourceClaim ? { sourceClaim: { ...input.sourceClaim } } : {}),
448
- };
449
- }
450
- export function canUseTelegramBusInputCustodyReference(input) {
451
- return (hasTelegramBusCapability(input.local, TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE) &&
452
- hasTelegramBusCapability(input.remote, TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE));
453
- }
454
- export function createTelegramBusFollowerSourceReferenceDeliveryIdentity(input) {
455
- if (!input.sourceRecoveryKey || !input.source.owner.handoffId)
456
- throw new Error("Telegram follower source-reference delivery requires an accepted handoff.");
457
- return createTelegramBusFollowerDeliveryIdentity({
458
- kind: input.kind,
459
- recipientBindingKey: input.recipientBindingKey,
460
- sourceUpdateId: input.source.updateId,
461
- sourceRecoveryKey: input.sourceRecoveryKey,
462
- sourceClaim: {
463
- acquisitionId: input.source.owner.acquisitionId,
464
- handoffId: input.source.owner.handoffId,
465
- },
466
- });
467
- }
468
- export function getTelegramBusEnvelopeTrafficClass(envelope) {
469
- if (envelope.kind === "follower.register" ||
470
- envelope.kind === "follower.restoreWorkspace" ||
471
- envelope.kind === "bus.probe")
472
- return "bootstrap";
473
- if (envelope.kind === "bus.ack")
474
- return "response";
475
- return "generation-fenced";
476
- }
477
- export function createTelegramBusRequestId(input) {
478
- return `${input.instanceId}:${input.sequence}`;
479
- }
480
- export function createTelegramBusRequestIdFactory(instanceId) {
481
- let sequence = 0;
482
- return () => {
483
- sequence += 1;
484
- return createTelegramBusRequestId({ instanceId, sequence });
485
- };
486
- }
487
- export function encodeTelegramBusEnvelope(envelope) {
488
- return `${JSON.stringify(envelope)}\n`;
489
- }
490
- export function parseTelegramBusEnvelope(line) {
491
- let value;
492
- try {
493
- value = JSON.parse(line);
494
- }
495
- catch {
496
- return undefined;
497
- }
498
- if (!isRecord(value))
499
- return undefined;
500
- const kind = value.kind;
501
- const requestId = value.requestId;
502
- if (typeof kind !== "string" || typeof requestId !== "string") {
503
- return undefined;
504
- }
505
- let envelope;
506
- switch (kind) {
507
- case "follower.register":
508
- case "follower.restoreWorkspace":
509
- envelope = parseRegisterEnvelope(value, requestId, kind);
510
- break;
511
- case "follower.heartbeat":
512
- case "follower.disconnect":
513
- envelope = parseFollowerPresenceEnvelope(value, requestId, kind);
514
- break;
515
- case "follower.setThreadDisplayMode":
516
- if (typeof value.instanceId === "string" &&
517
- typeof value.registrationGeneration === "string" &&
518
- (value.mode === "letters" ||
519
- value.mode === "names" ||
520
- value.mode === "directory-snake" ||
521
- value.mode === "directory-title")) {
522
- envelope = {
523
- kind,
524
- requestId,
525
- instanceId: value.instanceId,
526
- registrationGeneration: value.registrationGeneration,
527
- mode: value.mode,
528
- };
529
- }
530
- break;
531
- case "follower.renameThread":
532
- envelope = parseRenameThreadEnvelope(value, requestId);
533
- break;
534
- case "follower.resetThreadName": {
535
- const target = parseTarget(value.target);
536
- if (typeof value.instanceId === "string" &&
537
- typeof value.registrationGeneration === "string" &&
538
- typeof value.sentAtMs === "number" &&
539
- target?.threadId !== undefined) {
540
- envelope = {
541
- kind,
542
- requestId,
543
- instanceId: value.instanceId,
544
- registrationGeneration: value.registrationGeneration,
545
- target: { chatId: target.chatId, threadId: target.threadId },
546
- sentAtMs: value.sentAtMs,
547
- };
548
- }
549
- break;
550
- }
551
- case "follower.publishSessionReplacement":
552
- case "follower.settleSessionReplacement": {
553
- const intent = normalizeTelegramSessionReplacementIntent(value.intent);
554
- if (typeof value.instanceId === "string" &&
555
- typeof value.registrationGeneration === "string" &&
556
- typeof value.sentAtMs === "number" &&
557
- intent) {
558
- envelope = {
559
- kind,
560
- requestId,
561
- instanceId: value.instanceId,
562
- registrationGeneration: value.registrationGeneration,
563
- intent,
564
- sentAtMs: value.sentAtMs,
565
- };
566
- }
567
- break;
568
- }
569
- case "leader.forwardCallback":
570
- envelope = parseForwardCallbackEnvelope(value, requestId);
571
- break;
572
- case "leader.forwardReaction":
573
- envelope = parseForwardReactionEnvelope(value, requestId);
574
- break;
575
- case "leader.offerInputCustodyHandoff":
576
- envelope = parseOfferInputCustodyHandoffEnvelope(value, requestId);
577
- break;
578
- case "leader.wakeInputCustody":
579
- envelope = parseWakeInputCustodyEnvelope(value, requestId);
580
- break;
581
- case "leader.forwardMessage":
582
- envelope = parseForwardMessageEnvelope(value, requestId, "leader.forwardMessage");
583
- break;
584
- case "leader.forwardEditedMessage":
585
- envelope = parseForwardMessageEnvelope(value, requestId, "leader.forwardEditedMessage");
586
- break;
587
- case "leader.prepareLiveRebind": {
588
- const selectedCommand = parseSelectedCommandInput(value.selectedCommand);
589
- if (!hasOnlyWireKeys(value, [
590
- "kind",
591
- "requestId",
592
- "auth",
593
- "recipientInstanceId",
594
- "recipientRegistrationGeneration",
595
- "recipientSessionId",
596
- "recipientBindingKey",
597
- "operationId",
598
- "updates",
599
- "selectedCommand",
600
- "sentAtMs",
601
- ]) ||
602
- (Object.hasOwn(value, "selectedCommand") &&
603
- (!selectedCommand ||
604
- !Array.isArray(value.updates) ||
605
- value.updates.length !== 1)))
606
- break;
607
- const recipient = parseLiveRebindRecipient(value);
608
- if (recipient &&
609
- Array.isArray(value.updates) &&
610
- value.updates.every(isRecord) &&
611
- isUniqueSourceUpdateIds(value.updates.map((update) => update.update_id)) &&
612
- typeof value.sentAtMs === "number" &&
613
- Number.isFinite(value.sentAtMs)) {
614
- envelope = {
615
- kind: "leader.prepareLiveRebind",
616
- requestId,
617
- ...recipient,
618
- updates: value.updates,
619
- sentAtMs: value.sentAtMs,
620
- ...(selectedCommand ? { selectedCommand } : {}),
621
- };
622
- }
623
- break;
624
- }
625
- case "leader.applyLiveRebind":
626
- case "leader.settleLiveRebind": {
627
- const selectedCommand = parseSelectedCommandInput(value.selectedCommand), preparedSource = parsePreparedCommandSource(value.preparedSource);
628
- if (!hasOnlyWireKeys(value, [
629
- "kind",
630
- "requestId",
631
- "auth",
632
- "recipientInstanceId",
633
- "recipientRegistrationGeneration",
634
- "recipientSessionId",
635
- "recipientBindingKey",
636
- "operationId",
637
- "sourceUpdateIds",
638
- "mode",
639
- "oldTarget",
640
- "selectedCommand",
641
- "preparedSource",
642
- "sentAtMs",
643
- ]) ||
644
- (Object.hasOwn(value, "preparedSource") && !selectedCommand) ||
645
- (value.mode === "observe-command" &&
646
- (!selectedCommand || Object.hasOwn(value, "oldTarget"))))
647
- break;
648
- if (Object.hasOwn(value, "selectedCommand") &&
649
- (!selectedCommand ||
650
- !Array.isArray(value.sourceUpdateIds) ||
651
- value.sourceUpdateIds.length !== 1 ||
652
- !preparedSource ||
653
- preparedSource.updateId !== value.sourceUpdateIds[0] ||
654
- preparedSource.journalBindingKey !== value.recipientBindingKey))
655
- break;
656
- const recipient = parseLiveRebindRecipient(value);
657
- if (recipient &&
658
- (value.kind === "leader.applyLiveRebind"
659
- ? value.mode === "apply" || value.mode === "inspect"
660
- : value.mode === "release" ||
661
- value.mode === "discard" ||
662
- value.mode === "observe-command" ||
663
- (value.mode === "observe" &&
664
- !!parseThreadTarget(value.oldTarget))) &&
665
- isUniqueSourceUpdateIds(value.sourceUpdateIds) &&
666
- typeof value.sentAtMs === "number" &&
667
- Number.isFinite(value.sentAtMs)) {
668
- envelope = {
669
- kind: value.kind,
670
- requestId,
671
- ...recipient,
672
- sourceUpdateIds: value.sourceUpdateIds,
673
- mode: value.mode,
674
- sentAtMs: value.sentAtMs,
675
- ...(value.mode === "observe"
676
- ? { oldTarget: parseThreadTarget(value.oldTarget) }
677
- : {}),
678
- ...(selectedCommand ? { selectedCommand, preparedSource } : {}),
679
- };
680
- }
681
- break;
682
- }
683
- case "leader.workspaceRestore":
684
- if (typeof value.recipientInstanceId === "string" &&
685
- value.recipientInstanceId &&
686
- typeof value.recipientRegistrationGeneration === "string" &&
687
- value.recipientRegistrationGeneration &&
688
- typeof value.operationId === "string" &&
689
- value.operationId.length > 0 &&
690
- value.operationId.length <= 128 &&
691
- (value.mode === "apply" || value.mode === "inspect") &&
692
- typeof value.sentAtMs === "number" &&
693
- Number.isFinite(value.sentAtMs)) {
694
- envelope = {
695
- kind: "leader.workspaceRestore",
696
- requestId,
697
- recipientInstanceId: value.recipientInstanceId,
698
- recipientRegistrationGeneration: value.recipientRegistrationGeneration,
699
- operationId: value.operationId,
700
- mode: value.mode,
701
- sentAtMs: value.sentAtMs,
702
- };
703
- }
704
- break;
705
- case "leader.offerQueueHandoff":
706
- envelope = parseQueueHandoffEnvelope(value, requestId, "leader.offerQueueHandoff");
707
- break;
708
- case "follower.offerQueueHandoff":
709
- envelope = parseQueueHandoffEnvelope(value, requestId, "follower.offerQueueHandoff");
710
- break;
711
- case "follower.resolveAgentTarget":
712
- envelope = parseResolveAgentTargetEnvelope(value, requestId);
713
- break;
714
- case "follower.routeAgentMessage":
715
- envelope = parseRouteAgentMessageEnvelope(value, requestId);
716
- break;
717
- case "follower.deliverSelectedMenu":
718
- envelope = parseSelectedMenuDeliveryEnvelope(value, requestId);
719
- break;
720
- case "follower.callApi":
721
- envelope = parseCallApiEnvelope(value, requestId);
722
- break;
723
- case "bus.probe":
724
- envelope = { kind, requestId };
725
- break;
726
- case "bus.ack":
727
- envelope = parseAckEnvelope(value, requestId);
728
- break;
729
- default:
730
- return undefined;
731
- }
732
- const auth = value.auth;
733
- if (envelope && typeof auth === "string")
734
- envelope.auth = auth;
735
- return envelope;
736
- }
737
- export function probeTelegramBusLeader(input) {
738
- const timeoutMs = input.timeoutMs ?? TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS;
739
- return new Promise((resolve) => {
740
- let settled = false;
741
- let socket;
742
- const finish = (result) => {
743
- if (settled)
744
- return;
745
- settled = true;
746
- clearTimeout(timer);
747
- socket?.destroy();
748
- resolve(result);
749
- };
750
- const timer = setTimeout(() => finish("silent"), timeoutMs);
751
- let connected = false;
752
- try {
753
- socket = createConnection(resolveTelegramBusSocketPath(input.socketPath));
754
- }
755
- catch {
756
- finish("unknown");
757
- return;
758
- }
759
- socket.once("connect", () => {
760
- connected = true;
761
- socket.write(encodeTelegramBusEnvelope({
762
- kind: "bus.probe",
763
- requestId: `probe:${randomBytes(8).toString("hex")}`,
764
- ...(input.secret ? { auth: input.secret } : {}),
765
- }));
766
- });
767
- socket.once("data", () => finish("responsive"));
768
- socket.once("end", () => finish("responsive"));
769
- socket.once("error", (error) => {
770
- const code = error.code;
771
- finish(!connected && code === "ECONNREFUSED" ? "unreachable" : "unknown");
772
- });
773
- });
774
- }
775
- /**
776
- * Takeover evidence replacing the retired file heartbeat: a full silent window, or an unreachable endpoint
777
- * confirmed again after one more window (a just-started leader binds its socket well within it). The owner must
778
- * stay the same throughout; the lock acquisition then CAS-checks that exact owner.
779
- */
780
- export async function proveTelegramBusLeaderUnresponsive(input) {
781
- const first = await input.probe();
782
- if (first === "silent")
783
- return input.isSameOwner();
784
- if (first !== "unreachable")
785
- return false;
786
- await input.sleep(input.windowMs ?? TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS);
787
- if (!input.isSameOwner())
788
- return false;
789
- const second = await input.probe();
790
- return (second === "silent" || second === "unreachable") && input.isSameOwner();
791
- }
792
- /** Composition-ready takeover proof for one profile's leader lock and endpoint. */
793
- export function createTelegramBusLeaderUnresponsivenessProof(deps) {
794
- const probe = deps.probe ?? probeTelegramBusLeader;
795
- const sleep = deps.sleep ??
796
- ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
797
- return (owner) => proveTelegramBusLeaderUnresponsive({
798
- probe: () => probe({
799
- socketPath: owner.busSocketPath ?? deps.getLeaderSocketPath(),
800
- secret: owner.busSecret,
801
- }),
802
- isSameOwner: () => {
803
- const state = deps.getLeaderState();
804
- return (state.kind === "active-elsewhere" &&
805
- isSameTelegramLockOwner(state.lock, owner));
806
- },
807
- sleep,
808
- });
809
- }
810
- const TELEGRAM_ACTIVE_LOCAL_SERVERS = Symbol.for("@llblab/pi-telegram/active-local-servers");
811
- function getActiveTelegramBusLocalServers() {
812
- const root = globalThis;
813
- return (root[TELEGRAM_ACTIVE_LOCAL_SERVERS] ??= new Map());
814
- }
815
- export function resolveTelegramBusSocketPath(source, platform = getPlatform()) {
816
- const endpoint = typeof source === "function" ? source() : source;
817
- if (platform === "win32") {
818
- if (isTelegramBusPipePath(endpoint))
819
- return endpoint;
820
- return getTelegramBusPipePath({
821
- agentDir: dirname(endpoint),
822
- scope: basename(endpoint),
823
- });
824
- }
825
- const ownerScope = process.getuid?.() ?? "user";
826
- const fallbackDir = join(tmpdir(), `pi-telegram-${ownerScope}`);
827
- if (dirname(endpoint) === fallbackDir &&
828
- /^[0-9a-f]{16}\.sock$/u.test(basename(endpoint))) {
829
- return endpoint;
830
- }
831
- if (Buffer.byteLength(endpoint) <= TELEGRAM_BUS_MAX_DIRECT_UNIX_ENDPOINT_BYTES) {
832
- return endpoint;
833
- }
834
- const digest = createHash("sha256")
835
- .update(endpoint)
836
- .digest("hex")
837
- .slice(0, 16);
838
- return join(fallbackDir, `${digest}.sock`);
839
- }
840
- /** Local IPC issuance proof only: this callback/error never enters the envelope or certifies a remote API effect. */
841
- export class TelegramBusLocalAuthorityError extends Error {
842
- requestIssued;
843
- constructor(requestIssued, reason) {
844
- super(reason
845
- ? `Telegram bus local authority is unavailable: ${reason}`
846
- : "Telegram bus local authority is unavailable.");
847
- this.name = "TelegramBusLocalAuthorityError";
848
- this.requestIssued = requestIssued;
849
- }
850
- }
851
- export function isTelegramBusForwardOwnershipCurrent(expected, current) {
852
- return Boolean(current &&
853
- current.instanceId === expected.instanceId &&
854
- current.ownerGeneration === expected.ownerGeneration &&
855
- current.recipientBindingKey === expected.recipientBindingKey &&
856
- JSON.stringify(current.protocolIdentity) ===
857
- JSON.stringify(expected.protocolIdentity));
858
- }
859
- function getTelegramBusForwardSourceUpdateId(value) {
860
- if (!isRecord(value))
861
- return undefined;
862
- const updateId = value.pi_telegram_source_update_id;
863
- return Number.isSafeInteger(updateId) && updateId >= 0
864
- ? updateId
865
- : undefined;
866
- }
867
- function createTelegramBusForwardDelivery(kind, sourceUpdateId, recipientBindingKey) {
868
- return createTelegramBusFollowerDeliveryIdentity({
869
- kind,
870
- recipientBindingKey,
871
- sourceUpdateId,
872
- });
873
- }
874
- /**
875
- * Leader-to-follower forwards and control replies share the stale-liveness window: a follower doing journal work or stalled
876
- * by an ordinary multi-second Pi/TUI turn, or a slower Windows named pipe, is not a lost recipient.
877
- */
878
- const TELEGRAM_BUS_FOLLOWER_CONTROL_TIMEOUT_MS = TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS;
879
- export function createTelegramBusForeignOwnedUpdateForwarder(deps) {
880
- const getNowMs = deps.getNowMs ?? Date.now;
881
- const reject = (input) => {
882
- const settlement = {
883
- status: input.status,
884
- failureClass: input.failureClass,
885
- message: input.message,
886
- ...(input.delivery ? { delivery: input.delivery } : {}),
887
- ...(input.sourceUpdateId !== undefined
888
- ? { sourceUpdateId: input.sourceUpdateId }
889
- : {}),
890
- };
891
- deps.recordRuntimeEvent?.("bus", input.message, {
892
- phase: "foreign-update-forward-rejected",
893
- settlement: input.status,
894
- failureClass: input.failureClass,
895
- envelopeKind: input.envelopeKind,
896
- recipientInstanceId: input.ownership.instanceId,
897
- deliveryId: input.delivery?.deliveryId,
898
- sourceUpdateId: input.delivery?.sourceUpdateId ?? input.sourceUpdateId,
899
- });
900
- return settlement;
901
- };
902
- const prepare = (kind, value, ownership) => {
903
- const sourceUpdateId = getTelegramBusForwardSourceUpdateId(value);
904
- if (sourceUpdateId === undefined) {
905
- return {
906
- settlement: reject({
907
- status: "terminal-rejected",
908
- failureClass: "source-update-identity-missing",
909
- message: "Forwarded Telegram update has no durable source identity.",
910
- envelopeKind: kind,
911
- ownership,
912
- }),
913
- };
914
- }
915
- if (!ownership.recipientBindingKey) {
916
- return {
917
- settlement: reject({
918
- status: "terminal-rejected",
919
- failureClass: "recipient-binding-missing",
920
- message: "Forwarded Telegram update has no stable recipient binding.",
921
- envelopeKind: kind,
922
- ownership,
923
- sourceUpdateId,
924
- }),
925
- };
926
- }
927
- const sourceReference = canUseTelegramBusInputCustodyReference({
928
- local: deps.localProtocolIdentity,
929
- remote: ownership.protocolIdentity,
930
- });
931
- const reference = sourceReference
932
- ? deps.resolveInputCustodyReference?.({
933
- sourceUpdateId,
934
- recipientBindingKey: ownership.recipientBindingKey,
935
- })
936
- : undefined;
937
- if (sourceReference && !reference)
938
- return {
939
- settlement: reject({
940
- status: "retryable",
941
- failureClass: "source-reference-missing",
942
- message: "Forwarded Telegram update has no exact custody reference.",
943
- envelopeKind: kind,
944
- ownership,
945
- sourceUpdateId,
946
- }),
947
- };
948
- const delivery = reference
949
- ? createTelegramBusFollowerSourceReferenceDeliveryIdentity({
950
- kind: "leader.wakeInputCustody",
951
- recipientBindingKey: ownership.recipientBindingKey,
952
- sourceRecoveryKey: reference.sourceRecoveryKey,
953
- source: reference.source,
954
- })
955
- : createTelegramBusForwardDelivery(kind, sourceUpdateId, ownership.recipientBindingKey);
956
- if (!ownership.ownerGeneration) {
957
- return {
958
- settlement: reject({
959
- status: "retryable",
960
- failureClass: "recipient-generation-missing",
961
- message: "Forwarded Telegram update has no live recipient generation.",
962
- envelopeKind: kind,
963
- ownership,
964
- delivery,
965
- }),
966
- };
967
- }
968
- return {
969
- delivery,
970
- recipientRegistrationGeneration: ownership.ownerGeneration,
971
- sourceReference,
972
- };
973
- };
974
- const send = async (envelope, ownership) => {
975
- if (deps.validateForwardOwnership &&
976
- !deps.validateForwardOwnership(ownership))
977
- return reject({
978
- status: "retryable",
979
- failureClass: "recipient-ownership-stale",
980
- message: "Telegram follower ownership changed before forwarding.",
981
- envelopeKind: envelope.kind,
982
- ownership,
983
- delivery: envelope.delivery,
984
- });
985
- if (deps.getAuthSecret)
986
- envelope.auth = deps.getAuthSecret();
987
- const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
988
- let response;
989
- try {
990
- response = await sendTelegramBusLocalEnvelope({
991
- socketPath,
992
- envelope,
993
- timeoutMs: deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CONTROL_TIMEOUT_MS,
994
- retry: getTelegramBusTransportRetryPolicy({
995
- endpoint: socketPath,
996
- operation: "operation",
997
- }),
998
- });
999
- }
1000
- catch (error) {
1001
- return reject({
1002
- status: "retryable",
1003
- failureClass: "transport-failed",
1004
- message: error instanceof Error
1005
- ? error.message
1006
- : "Telegram follower forwarding transport failed.",
1007
- envelopeKind: envelope.kind,
1008
- ownership,
1009
- delivery: envelope.delivery,
1010
- });
1011
- }
1012
- if (response?.kind !== "bus.ack") {
1013
- return reject({
1014
- status: "retryable",
1015
- failureClass: "acknowledgement-missing",
1016
- message: "Follower returned no forwarding acknowledgement.",
1017
- envelopeKind: envelope.kind,
1018
- ownership,
1019
- delivery: envelope.delivery,
1020
- });
1021
- }
1022
- if (response.requestId !== envelope.requestId) {
1023
- return reject({
1024
- status: "terminal-rejected",
1025
- failureClass: "acknowledgement-mismatched",
1026
- message: "Follower returned a mismatched forwarding acknowledgement.",
1027
- envelopeKind: envelope.kind,
1028
- ownership,
1029
- delivery: envelope.delivery,
1030
- });
1031
- }
1032
- if (!response.ok) {
1033
- return reject({
1034
- status: "retryable",
1035
- failureClass: "acknowledgement-rejected",
1036
- message: response.message ?? "Follower rejected forwarded Telegram update.",
1037
- envelopeKind: envelope.kind,
1038
- ownership,
1039
- delivery: envelope.delivery,
1040
- });
1041
- }
1042
- const receipt = response.result;
1043
- if (!isRecord(receipt) ||
1044
- typeof receipt.deliveryId !== "string" ||
1045
- !Number.isSafeInteger(receipt.sourceUpdateId)) {
1046
- return reject({
1047
- status: "terminal-rejected",
1048
- failureClass: "durable-receipt-missing",
1049
- message: "Follower acknowledgement omitted the durable receipt.",
1050
- envelopeKind: envelope.kind,
1051
- ownership,
1052
- delivery: envelope.delivery,
1053
- });
1054
- }
1055
- if (receipt.deliveryId !== envelope.delivery.deliveryId ||
1056
- receipt.sourceUpdateId !== envelope.delivery.sourceUpdateId) {
1057
- return reject({
1058
- status: "terminal-rejected",
1059
- failureClass: "durable-receipt-mismatched",
1060
- message: "Follower acknowledgement returned a mismatched durable receipt.",
1061
- envelopeKind: envelope.kind,
1062
- ownership,
1063
- delivery: envelope.delivery,
1064
- });
1065
- }
1066
- return { status: "accepted", delivery: envelope.delivery };
1067
- };
1068
- // A recipient already holding the source reference is only woken; it never receives a second payload copy.
1069
- const forward = (kind, value, ownership, build) => {
1070
- const prepared = prepare(kind, value, ownership);
1071
- if ("settlement" in prepared)
1072
- return Promise.resolve(prepared.settlement);
1073
- const base = {
1074
- requestId: deps.createRequestId(),
1075
- recipientInstanceId: ownership.instanceId,
1076
- recipientRegistrationGeneration: prepared.recipientRegistrationGeneration,
1077
- delivery: prepared.delivery,
1078
- };
1079
- return send(prepared.sourceReference
1080
- ? { kind: "leader.wakeInputCustody", ...base, sentAtMs: getNowMs() }
1081
- : build(base, getNowMs()), ownership);
1082
- };
1083
- return {
1084
- forwardCallback: ({ query, ownership }) => forward("leader.forwardCallback", query, ownership, (base, sentAtMs) => ({
1085
- kind: "leader.forwardCallback",
1086
- ...base,
1087
- query,
1088
- sentAtMs,
1089
- })),
1090
- forwardReaction: ({ reactionUpdate, ownership }) => forward("leader.forwardReaction", reactionUpdate, ownership, (base, sentAtMs) => ({
1091
- kind: "leader.forwardReaction",
1092
- ...base,
1093
- reactionUpdate,
1094
- sentAtMs,
1095
- })),
1096
- forwardMessage: ({ message, ownership }) => forward("leader.forwardMessage", message, ownership, (base, sentAtMs) => {
1097
- const forwardCommentBatchPosition = deps.getForwardCommentBatchPosition?.(message);
1098
- return {
1099
- kind: "leader.forwardMessage",
1100
- ...base,
1101
- message,
1102
- ...(forwardCommentBatchPosition !== undefined
1103
- ? { forwardCommentBatchPosition }
1104
- : {}),
1105
- sentAtMs,
1106
- };
1107
- }),
1108
- forwardEditedMessage: ({ message, ownership }) => forward("leader.forwardEditedMessage", message, ownership, (base, sentAtMs) => ({
1109
- kind: "leader.forwardEditedMessage",
1110
- ...base,
1111
- message,
1112
- sentAtMs,
1113
- })),
1114
- };
1115
- }
1116
- export function listTelegramBusLiveThreadTargets(input) {
1117
- const targets = [];
1118
- if (input.leaderTarget?.threadId !== undefined) {
1119
- targets.push(input.leaderTarget);
1120
- }
1121
- for (const follower of input.followers) {
1122
- if (follower.target?.threadId !== undefined)
1123
- targets.push(follower.target);
1124
- }
1125
- return targets;
1126
- }
1127
- /** Caller owns the durable one-shot apply grant. Inspections never grant another apply. */
1128
- export function createTelegramBusWorkspaceRestoreController(deps) {
1129
- return async (input) => {
1130
- const isCurrent = input.isCurrent.bind(input);
1131
- if (!isCurrent())
1132
- return undefined;
1133
- const follower = deps.getFollower(input.instanceId);
1134
- const secret = deps.getAuthSecret();
1135
- if (!secret ||
1136
- !follower?.registrationGeneration ||
1137
- !follower.busSocketPath ||
1138
- follower.sessionId !== input.sessionId ||
1139
- follower.slot !== input.slot ||
1140
- !hasTelegramBusSharedCapabilities(deps.localProtocolIdentity, follower.protocol, [TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE]))
1141
- return undefined;
1142
- const captured = structuredClone(follower);
1143
- const expected = {
1144
- operationId: input.operationId,
1145
- sessionId: input.sessionId,
1146
- slot: input.slot,
1147
- target: { ...input.target },
1148
- oldTarget: { ...input.oldTarget },
1149
- mode: input.mode,
1150
- };
1151
- const current = () => {
1152
- if (!isCurrent() ||
1153
- deps.getAuthSecret() !== secret ||
1154
- !hasTelegramBusCapability(deps.localProtocolIdentity, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE))
1155
- return false;
1156
- const live = deps.getFollower(captured.instanceId);
1157
- return (!!live &&
1158
- live.registrationGeneration === captured.registrationGeneration &&
1159
- live.sessionId === captured.sessionId &&
1160
- live.cwd === captured.cwd &&
1161
- live.slot === captured.slot &&
1162
- live.busSocketPath === captured.busSocketPath &&
1163
- live.target?.chatId === captured.target?.chatId &&
1164
- live.target?.threadId === captured.target?.threadId &&
1165
- hasTelegramBusCapability(live.protocol, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE) &&
1166
- getTelegramBusProtocolCompatibility({
1167
- local: deps.localProtocolIdentity,
1168
- remote: live.protocol,
1169
- }).compatible);
1170
- };
1171
- if (!current())
1172
- return undefined;
1173
- const requestId = deps.createRequestId();
1174
- const response = await sendTelegramBusLocalEnvelope({
1175
- socketPath: captured.busSocketPath,
1176
- timeoutMs: deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CONTROL_TIMEOUT_MS,
1177
- retry: { attempts: 1, delayMs: 0 },
1178
- envelope: {
1179
- kind: "leader.workspaceRestore",
1180
- requestId,
1181
- recipientInstanceId: captured.instanceId,
1182
- recipientRegistrationGeneration: captured.registrationGeneration,
1183
- operationId: expected.operationId,
1184
- mode: expected.mode,
1185
- auth: secret,
1186
- sentAtMs: Date.now(),
1187
- },
1188
- });
1189
- if (!current() ||
1190
- response?.kind !== "bus.ack" ||
1191
- response.requestId !== requestId ||
1192
- response.ok !== true ||
1193
- !response.result ||
1194
- typeof response.result !== "object")
1195
- return undefined;
1196
- const result = response.result;
1197
- const recipient = result.recipient;
1198
- const target = parseThreadTarget(result.target);
1199
- const wanted = result.ready === true ? expected.target : expected.oldTarget;
1200
- if (result.operationId !== expected.operationId ||
1201
- typeof result.ready !== "boolean" ||
1202
- result.slot !== expected.slot ||
1203
- !target ||
1204
- target.chatId !== wanted.chatId ||
1205
- target.threadId !== wanted.threadId ||
1206
- recipient?.kind !== "follower" ||
1207
- recipient.instanceId !== captured.instanceId ||
1208
- recipient.sessionId !== expected.sessionId ||
1209
- recipient.generation !== captured.registrationGeneration)
1210
- return undefined;
1211
- return {
1212
- operationId: expected.operationId,
1213
- recipient: {
1214
- kind: "follower",
1215
- instanceId: captured.instanceId,
1216
- sessionId: expected.sessionId,
1217
- generation: captured.registrationGeneration,
1218
- },
1219
- target,
1220
- slot: expected.slot,
1221
- ready: result.ready,
1222
- };
1223
- };
1224
- }
1225
- /** Each capability has its own effect boundary; transport retries never replay save/dispatch/disposal. */
1226
- export function createTelegramBusLiveRebindController(deps) {
1227
- return async (input) => {
1228
- const isCurrent = input.isCurrent.bind(input), localProtocol = structuredClone(deps.localProtocolIdentity);
1229
- const selectedCommand = parseSelectedCommandInput(input.selectedCommand), held = selectedCommand, preparedSource = parsePreparedCommandSource(input.preparedSource);
1230
- if (!hasOnlyWireKeys(input, [
1231
- "operationId",
1232
- "instanceId",
1233
- "sessionId",
1234
- "recipientBindingKey",
1235
- "isCurrent",
1236
- "selectedCommand",
1237
- "preparedSource",
1238
- "updates",
1239
- "mode",
1240
- "sourceUpdateIds",
1241
- "slot",
1242
- "target",
1243
- "oldTarget",
1244
- ]) ||
1245
- ("mode" in input &&
1246
- (![
1247
- "apply",
1248
- "inspect",
1249
- "release",
1250
- "discard",
1251
- "observe",
1252
- "observe-command",
1253
- ].includes(input.mode) ||
1254
- (input.mode === "observe-command" && !selectedCommand))))
1255
- return undefined;
1256
- if ((input.preparedSource !== undefined &&
1257
- (!held ||
1258
- !preparedSource ||
1259
- preparedSource.journalBindingKey !== input.recipientBindingKey ||
1260
- preparedSource.updateId !==
1261
- ("mode" in input
1262
- ? input.sourceUpdateIds[0]
1263
- : input.updates[0]?.update_id))) ||
1264
- (held && "mode" in input && !preparedSource))
1265
- return undefined;
1266
- if (input.selectedCommand !== undefined &&
1267
- (!held ||
1268
- ("mode" in input
1269
- ? input.sourceUpdateIds.length !== 1 ||
1270
- !areTelegramTargetsEqual(input.target, held.target)
1271
- : input.updates.length !== 1)))
1272
- return undefined;
1273
- const marker = selectedCommand ? { selectedCommand } : {};
1274
- const expected = structuredClone({
1275
- operationId: input.operationId,
1276
- instanceId: input.instanceId,
1277
- sessionId: input.sessionId,
1278
- recipientBindingKey: input.recipientBindingKey,
1279
- ...(held
1280
- ? { ...marker, ...(preparedSource ? { preparedSource } : {}) }
1281
- : {}),
1282
- ...("mode" in input
1283
- ? {
1284
- mode: input.mode,
1285
- sourceUpdateIds: input.sourceUpdateIds,
1286
- slot: input.slot,
1287
- target: input.target,
1288
- oldTarget: input.oldTarget,
1289
- }
1290
- : { updates: input.updates }),
1291
- });
1292
- const observing = "mode" in expected && expected.mode === "observe";
1293
- const commandObserving = "mode" in expected && expected.mode === "observe-command";
1294
- const settling = "mode" in expected &&
1295
- (expected.mode === "release" ||
1296
- expected.mode === "discard" ||
1297
- observing ||
1298
- commandObserving);
1299
- const required = [
1300
- TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE,
1301
- ...("mode" in expected || held
1302
- ? [TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY]
1303
- : []),
1304
- ...(settling || held ? [TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE] : []),
1305
- ...(held
1306
- ? [
1307
- TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET,
1308
- TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
1309
- ]
1310
- : []),
1311
- ];
1312
- const follower = deps.getFollower(expected.instanceId), secret = deps.getAuthSecret();
1313
- if (!isCurrent() ||
1314
- !secret ||
1315
- !follower?.registrationGeneration ||
1316
- !follower.busSocketPath ||
1317
- follower.sessionId !== expected.sessionId ||
1318
- !hasTelegramBusSharedCapabilities(deps.localProtocolIdentity, follower.protocol, required))
1319
- return undefined;
1320
- const captured = structuredClone(follower);
1321
- /** The first failed authority condition, by fixed label; undefined while the captured recipient is current. */
1322
- const staleReason = () => {
1323
- if (!isCurrent())
1324
- return "caller authority";
1325
- if (deps.getAuthSecret() !== secret)
1326
- return "auth secret";
1327
- if (!isDeepStrictEqual(deps.localProtocolIdentity, localProtocol) ||
1328
- !required.every((cap) => hasTelegramBusCapability(deps.localProtocolIdentity, cap)))
1329
- return "local protocol";
1330
- const live = deps.getFollower(expected.instanceId);
1331
- if (!live)
1332
- return "follower registration";
1333
- if (live.sessionId !== captured.sessionId ||
1334
- live.registrationGeneration !== captured.registrationGeneration ||
1335
- live.sessionGeneration !== captured.sessionGeneration)
1336
- return "follower generation";
1337
- if (live.pid !== captured.pid ||
1338
- live.processBirthId !== captured.processBirthId)
1339
- return "follower process";
1340
- if (live.busSocketPath !== captured.busSocketPath ||
1341
- live.slot !== captured.slot ||
1342
- live.cwd !== captured.cwd)
1343
- return "follower endpoint";
1344
- if ("mode" in expected
1345
- ? live.slot !== expected.slot ||
1346
- !live.target ||
1347
- (!areTelegramTargetsEqual(live.target, expected.target) &&
1348
- !areTelegramTargetsEqual(live.target, expected.oldTarget))
1349
- : !isDeepStrictEqual(live.target, captured.target))
1350
- return "follower target";
1351
- if (!required.every((cap) => hasTelegramBusCapability(live.protocol, cap)) ||
1352
- !isDeepStrictEqual(live.protocol, captured.protocol) ||
1353
- !getTelegramBusProtocolCompatibility({
1354
- local: deps.localProtocolIdentity,
1355
- remote: live.protocol,
1356
- }).compatible)
1357
- return "follower protocol";
1358
- return undefined;
1359
- };
1360
- const current = () => staleReason() === undefined;
1361
- if (!current())
1362
- return undefined;
1363
- const requestId = deps.createRequestId();
1364
- const response = await sendTelegramBusLocalEnvelope({
1365
- socketPath: captured.busSocketPath,
1366
- timeoutMs: deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CONTROL_TIMEOUT_MS,
1367
- retry: { attempts: 1, delayMs: 0 },
1368
- ...(held
1369
- ? {
1370
- assertAuthority: () => {
1371
- const reason = staleReason();
1372
- if (reason)
1373
- throw new Error(`Selected held input sender authority changed (${reason}).`);
1374
- },
1375
- }
1376
- : {}),
1377
- envelope: {
1378
- requestId,
1379
- auth: secret,
1380
- ...(held
1381
- ? {
1382
- ...marker,
1383
- ...("mode" in expected
1384
- ? { preparedSource: expected.preparedSource }
1385
- : {}),
1386
- }
1387
- : {}),
1388
- recipientInstanceId: expected.instanceId,
1389
- recipientRegistrationGeneration: captured.registrationGeneration,
1390
- recipientSessionId: expected.sessionId,
1391
- recipientBindingKey: expected.recipientBindingKey,
1392
- operationId: expected.operationId,
1393
- sentAtMs: Date.now(),
1394
- ...("mode" in expected
1395
- ? settling
1396
- ? {
1397
- kind: "leader.settleLiveRebind",
1398
- mode: expected.mode,
1399
- sourceUpdateIds: expected.sourceUpdateIds,
1400
- ...(observing ? { oldTarget: expected.oldTarget } : {}),
1401
- }
1402
- : {
1403
- kind: "leader.applyLiveRebind",
1404
- mode: expected.mode,
1405
- sourceUpdateIds: expected.sourceUpdateIds,
1406
- }
1407
- : {
1408
- kind: "leader.prepareLiveRebind",
1409
- updates: expected.updates,
1410
- }),
1411
- },
1412
- });
1413
- if (!current() ||
1414
- response?.kind !== "bus.ack" ||
1415
- response.requestId !== requestId ||
1416
- response.ok !== true ||
1417
- !isRecord(response.result))
1418
- return undefined;
1419
- const result = response.result, recipient = result.recipient;
1420
- if (!hasOnlyWireKeys(result, [
1421
- "operationId",
1422
- "recipient",
1423
- "sourceUpdateIds",
1424
- "selectedCommand",
1425
- "preparedSource",
1426
- "status",
1427
- "command",
1428
- "sourceAck",
1429
- "target",
1430
- "slot",
1431
- "action",
1432
- "oldTarget",
1433
- "work",
1434
- ]))
1435
- return undefined;
1436
- if (selectedCommand
1437
- ? !isDeepStrictEqual(parseSelectedCommandInput(result.selectedCommand), selectedCommand)
1438
- : Object.hasOwn(result, "selectedCommand"))
1439
- return undefined;
1440
- const ids = ("mode" in expected
1441
- ? [...expected.sourceUpdateIds]
1442
- : expected.updates.map((update) => update.update_id)).sort((a, b) => a - b);
1443
- const observedSource = parsePreparedCommandSource(result.preparedSource);
1444
- if (held
1445
- ? !observedSource ||
1446
- observedSource.updateId !== ids[0] ||
1447
- observedSource.journalBindingKey !== expected.recipientBindingKey ||
1448
- (expected.preparedSource &&
1449
- !isDeepStrictEqual(observedSource, expected.preparedSource))
1450
- : Object.hasOwn(result, "preparedSource"))
1451
- return undefined;
1452
- if (result.operationId !== expected.operationId ||
1453
- !(commandObserving
1454
- ? result.status === "command-observed"
1455
- : observing
1456
- ? result.status === "observed"
1457
- : settling
1458
- ? result.status === "protected" ||
1459
- result.status === "unknown" ||
1460
- result.status ===
1461
- (expected.mode === "release" ? "released" : "discarded")
1462
- : "mode" in expected
1463
- ? result.status === "saved" || result.status === "applied"
1464
- : result.status === "saved") ||
1465
- !isRecord(recipient) ||
1466
- recipient.instanceId !== expected.instanceId ||
1467
- recipient.sessionId !== expected.sessionId ||
1468
- recipient.generation !== captured.registrationGeneration ||
1469
- recipient.bindingKey !== expected.recipientBindingKey ||
1470
- !Array.isArray(result.sourceUpdateIds) ||
1471
- result.sourceUpdateIds.length !== ids.length ||
1472
- result.sourceUpdateIds.some((id, index) => id !== ids[index]))
1473
- return undefined;
1474
- const observation = {
1475
- operationId: expected.operationId,
1476
- recipient: {
1477
- instanceId: expected.instanceId,
1478
- sessionId: expected.sessionId,
1479
- generation: captured.registrationGeneration,
1480
- bindingKey: expected.recipientBindingKey,
1481
- },
1482
- sourceUpdateIds: ids,
1483
- ...(held
1484
- ? { ...structuredClone(marker), preparedSource: { ...observedSource } }
1485
- : {}),
1486
- status: "saved",
1487
- };
1488
- if (!("mode" in expected))
1489
- return observation;
1490
- if (commandObserving) {
1491
- const sourceAck = parsePreparedCommandSource(result.sourceAck);
1492
- if (!hasOnlyWireKeys(result, [
1493
- "operationId",
1494
- "recipient",
1495
- "sourceUpdateIds",
1496
- "selectedCommand",
1497
- "preparedSource",
1498
- "status",
1499
- "command",
1500
- "sourceAck",
1501
- ]) ||
1502
- !hasOnlyWireKeys(recipient, [
1503
- "instanceId",
1504
- "sessionId",
1505
- "generation",
1506
- "bindingKey",
1507
- ]) ||
1508
- !["completed", "unknown", "not-issued"].includes(result.command) ||
1509
- (Object.hasOwn(result, "sourceAck") &&
1510
- (!sourceAck ||
1511
- !isDeepStrictEqual(sourceAck, expected.preparedSource) ||
1512
- result.command === "not-issued")))
1513
- return undefined;
1514
- const settled = {
1515
- command: result.command,
1516
- ...(sourceAck ? { sourceAck: { ...sourceAck } } : {}),
1517
- };
1518
- return {
1519
- ...observation,
1520
- status: "command-observed",
1521
- selectedCommand: structuredClone(selectedCommand),
1522
- preparedSource: { ...observedSource },
1523
- ...settled,
1524
- };
1525
- }
1526
- if (observing) {
1527
- const work = result.work, oldTarget = parseThreadTarget(result.oldTarget);
1528
- if (!oldTarget ||
1529
- !isDeepStrictEqual(oldTarget, expected.oldTarget) ||
1530
- !isRecord(work) ||
1531
- [
1532
- work.sessionBusy,
1533
- work.targetWork,
1534
- work.deliveryPending,
1535
- work.unknown,
1536
- ].some((flag) => typeof flag !== "boolean"))
1537
- return undefined;
1538
- return {
1539
- ...observation,
1540
- status: "observed",
1541
- oldTarget,
1542
- work: {
1543
- sessionBusy: work.sessionBusy,
1544
- targetWork: work.targetWork,
1545
- deliveryPending: work.deliveryPending,
1546
- unknown: work.unknown,
1547
- },
1548
- };
1549
- }
1550
- if (settling) {
1551
- if (result.action !== expected.mode)
1552
- return undefined;
1553
- return {
1554
- ...observation,
1555
- action: expected.mode,
1556
- status: result.status,
1557
- };
1558
- }
1559
- const target = parseThreadTarget(result.target), wanted = result.status === "applied" ? expected.target : expected.oldTarget;
1560
- if (!target ||
1561
- !isDeepStrictEqual(target, wanted) ||
1562
- result.slot !== expected.slot)
1563
- return undefined;
1564
- return {
1565
- ...observation,
1566
- status: result.status,
1567
- target,
1568
- slot: expected.slot,
1569
- };
1570
- };
1571
- }
1572
- export function isTelegramBusEnvelopeAuthorized(envelope, secret) {
1573
- if (!secret)
1574
- return true;
1575
- if (typeof envelope.auth !== "string")
1576
- return false;
1577
- const auth = Buffer.from(envelope.auth);
1578
- const expected = Buffer.from(secret);
1579
- return auth.length === expected.length && timingSafeEqual(auth, expected);
1580
- }
1581
- /** Negative acknowledgement carrying only a diagnostic message. */
1582
- export function rejectTelegramBusRequest(requestId, message) {
1583
- return { kind: "bus.ack", requestId, ok: false, message };
1584
- }
1585
- export function createUnauthorizedBusAck(requestId) {
1586
- return rejectTelegramBusRequest(requestId, "Unauthorized Telegram bus envelope.");
1587
- }
1588
- export function createTelegramBusLocalServer(deps) {
1589
- const requestLedger = new Map();
1590
- const requestLedgerMaxEntries = Math.max(1, deps.requestLedgerMaxEntries ?? 4096);
1591
- const getRequestLedgerKey = (envelope) => {
1592
- const identity = envelope.kind === "follower.register" ||
1593
- envelope.kind === "follower.restoreWorkspace"
1594
- ? envelope.registration.instanceId
1595
- : "instanceId" in envelope
1596
- ? envelope.instanceId
1597
- : "recipientInstanceId" in envelope
1598
- ? envelope.recipientInstanceId
1599
- : "ack";
1600
- return `${envelope.auth ?? ""}:${identity}:${envelope.requestId}`;
1601
- };
1602
- const handleEnvelopeOnce = (envelope) => {
1603
- const key = getRequestLedgerKey(envelope);
1604
- const fingerprint = JSON.stringify(envelope);
1605
- const existing = requestLedger.get(key);
1606
- if (existing) {
1607
- if (existing.fingerprint === fingerprint)
1608
- return existing.result;
1609
- return Promise.resolve({
1610
- kind: "bus.ack",
1611
- requestId: envelope.requestId,
1612
- ok: false,
1613
- message: "Telegram bus request id was reused with a different payload.",
1614
- error: { code: "request-id-collision" },
1615
- });
1616
- }
1617
- if (requestLedger.size >= requestLedgerMaxEntries) {
1618
- const settledKey = Array.from(requestLedger.entries()).find(([, entry]) => entry.settled)?.[0];
1619
- if (settledKey)
1620
- requestLedger.delete(settledKey);
1621
- }
1622
- if (requestLedger.size >= requestLedgerMaxEntries) {
1623
- return Promise.resolve({
1624
- kind: "bus.ack",
1625
- requestId: envelope.requestId,
1626
- ok: false,
1627
- message: "Telegram bus request ledger is full.",
1628
- error: { code: "ledger-overloaded" },
1629
- });
1630
- }
1631
- const entry = {
1632
- fingerprint,
1633
- settled: false,
1634
- result: Promise.resolve().then(() => deps.handleEnvelope(envelope)),
1635
- };
1636
- requestLedger.set(key, entry);
1637
- const settled = () => {
1638
- entry.settled = true;
1639
- };
1640
- void entry.result.then(settled, settled);
1641
- return entry.result;
1642
- };
1643
- let server;
1644
- let activeSocketPath;
1645
- let activeListenPath;
1646
- let endpointRecovery;
1647
- let stopGeneration = 0;
1648
- const sockets = new Set();
1649
- const closeSocket = (socket) => {
1650
- sockets.delete(socket);
1651
- socket.destroy();
1652
- };
1653
- const runtime = {
1654
- start: async () => {
1655
- if (server)
1656
- return;
1657
- const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
1658
- const activeServers = getActiveTelegramBusLocalServers();
1659
- const replacedServer = activeServers.get(socketPath);
1660
- if (replacedServer && replacedServer !== runtime) {
1661
- await replacedServer.stop();
1662
- }
1663
- const usesWindowsPipe = isTelegramBusPipePath(socketPath);
1664
- const endpointGeneration = randomBytes(8).toString("hex");
1665
- const listenPath = usesWindowsPipe
1666
- ? socketPath
1667
- : join(dirname(socketPath), `.pt-${endpointGeneration}.sock`);
1668
- activeSocketPath = socketPath;
1669
- activeListenPath = listenPath;
1670
- deps.recordTransportEvent?.("server-start", getTelegramBusEndpointDiagnostics(socketPath));
1671
- if (!usesWindowsPipe) {
1672
- const socketDir = dirname(socketPath);
1673
- mkdirSync(socketDir, { recursive: true, mode: 0o700 });
1674
- chmodSync(socketDir, 0o700);
1675
- if (existsSync(listenPath))
1676
- unlinkSync(listenPath);
1677
- const legacyDeadlineMs = Date.now() + 2000;
1678
- while (true) {
1679
- let isLegacySocket = false;
1680
- try {
1681
- isLegacySocket = lstatSync(socketPath).isSocket();
1682
- }
1683
- catch {
1684
- /* endpoint does not exist */
1685
- }
1686
- if (!isLegacySocket)
1687
- break;
1688
- const probe = await probeTelegramBusEndpoint({
1689
- endpoint: socketPath,
1690
- timeoutMs: 50,
1691
- });
1692
- if (!probe.reachable)
1693
- break;
1694
- if (Date.now() >= legacyDeadlineMs) {
1695
- throw new Error(`Timed out waiting for legacy Telegram bus endpoint: ${socketPath}`);
1696
- }
1697
- await delayTelegramBusTransportRetry(25);
1698
- }
1699
- }
1700
- if (usesWindowsPipe) {
1701
- await deps.beforeEndpointPublication?.();
1702
- const committed = deps.commitEndpointPublication
1703
- ? deps.commitEndpointPublication(() => { })
1704
- : true;
1705
- if (!committed) {
1706
- activeSocketPath = undefined;
1707
- activeListenPath = undefined;
1708
- throw new Error("Telegram bus endpoint publication lost transport ownership.");
1709
- }
1710
- }
1711
- server = createServer((socket) => {
1712
- sockets.add(socket);
1713
- let buffer = "";
1714
- socket.setEncoding("utf8");
1715
- socket.on("data", (chunk) => {
1716
- buffer += chunk;
1717
- const lines = buffer.split("\n");
1718
- buffer = lines.pop() ?? "";
1719
- for (const line of lines) {
1720
- void handleTelegramBusSocketLine(line, socket, handleEnvelopeOnce, deps.recordTransportEvent, socketPath, deps.shouldDropResponse);
1721
- }
1722
- });
1723
- socket.on("close", () => sockets.delete(socket));
1724
- socket.on("error", (error) => {
1725
- deps.recordTransportEvent?.("server-socket-error", {
1726
- ...getTelegramBusEndpointDiagnostics(socketPath),
1727
- ...classifyTelegramBusTransportError(error),
1728
- });
1729
- closeSocket(socket);
1730
- });
1731
- });
1732
- try {
1733
- await new Promise((resolve, reject) => {
1734
- server?.once("error", reject);
1735
- server?.listen(listenPath, resolve);
1736
- });
1737
- activeServers.set(socketPath, runtime);
1738
- deps.recordTransportEvent?.("server-started", getTelegramBusEndpointDiagnostics(socketPath));
1739
- }
1740
- catch (error) {
1741
- server = undefined;
1742
- activeSocketPath = undefined;
1743
- activeListenPath = undefined;
1744
- deps.recordTransportEvent?.("server-start-failed", {
1745
- ...getTelegramBusEndpointDiagnostics(socketPath),
1746
- ...classifyTelegramBusTransportError(error),
1747
- });
1748
- throw error;
1749
- }
1750
- if (!usesWindowsPipe) {
1751
- chmodSync(listenPath, 0o600);
1752
- const linkPath = `${socketPath}.link.${endpointGeneration}`;
1753
- try {
1754
- symlinkSync(basename(listenPath), linkPath);
1755
- await deps.beforeEndpointPublication?.();
1756
- const committed = deps.commitEndpointPublication
1757
- ? deps.commitEndpointPublication(() => renameSync(linkPath, socketPath))
1758
- : (renameSync(linkPath, socketPath), true);
1759
- if (!committed) {
1760
- throw new Error("Telegram bus endpoint publication lost transport ownership.");
1761
- }
1762
- }
1763
- catch (error) {
1764
- try {
1765
- if (lstatSync(linkPath).isSymbolicLink())
1766
- unlinkSync(linkPath);
1767
- }
1768
- catch {
1769
- /* no unpublished link to remove */
1770
- }
1771
- const failedServer = server;
1772
- server = undefined;
1773
- activeSocketPath = undefined;
1774
- activeListenPath = undefined;
1775
- if (activeServers.get(socketPath) === runtime) {
1776
- activeServers.delete(socketPath);
1777
- }
1778
- if (failedServer) {
1779
- await new Promise((resolve) => failedServer.close(() => resolve()));
1780
- }
1781
- throw error;
1782
- }
1783
- }
1784
- },
1785
- stop: async () => {
1786
- stopGeneration += 1;
1787
- requestLedger.clear();
1788
- const activeServer = server;
1789
- const socketPath = activeSocketPath;
1790
- const listenPath = activeListenPath;
1791
- if (socketPath &&
1792
- getActiveTelegramBusLocalServers().get(socketPath) === runtime) {
1793
- getActiveTelegramBusLocalServers().delete(socketPath);
1794
- }
1795
- server = undefined;
1796
- activeSocketPath = undefined;
1797
- activeListenPath = undefined;
1798
- for (const socket of sockets)
1799
- closeSocket(socket);
1800
- if (activeServer) {
1801
- await new Promise((resolve) => activeServer.close(() => resolve()));
1802
- }
1803
- if (socketPath && listenPath && !isTelegramBusPipePath(socketPath)) {
1804
- try {
1805
- if (lstatSync(socketPath).isSymbolicLink() &&
1806
- readlinkSync(socketPath) === basename(listenPath)) {
1807
- // Leave the generation link in place. Node removes only the unique
1808
- // listen path on close; a later generation atomically replaces this
1809
- // link, and existsSync() treats the stopped dangling link as missing.
1810
- }
1811
- }
1812
- catch {
1813
- /* endpoint already moved or removed */
1814
- }
1815
- }
1816
- if (socketPath) {
1817
- deps.recordTransportEvent?.("server-stopped", getTelegramBusEndpointDiagnostics(socketPath));
1818
- }
1819
- },
1820
- ensureEndpoint: async () => {
1821
- const socketPath = activeSocketPath;
1822
- if (!server ||
1823
- !socketPath ||
1824
- isTelegramBusPipePath(socketPath) ||
1825
- existsSync(socketPath)) {
1826
- return false;
1827
- }
1828
- if (endpointRecovery)
1829
- return endpointRecovery;
1830
- endpointRecovery = (async () => {
1831
- deps.recordTransportEvent?.("server-endpoint-missing", getTelegramBusEndpointDiagnostics(socketPath));
1832
- const recoveryStopGeneration = stopGeneration + 1;
1833
- await runtime.stop();
1834
- if (stopGeneration !== recoveryStopGeneration)
1835
- return false;
1836
- await runtime.start();
1837
- if (stopGeneration !== recoveryStopGeneration) {
1838
- await runtime.stop();
1839
- return false;
1840
- }
1841
- deps.recordTransportEvent?.("server-endpoint-recovered", getTelegramBusEndpointDiagnostics(socketPath));
1842
- return true;
1843
- })();
1844
- try {
1845
- return await endpointRecovery;
1846
- }
1847
- finally {
1848
- endpointRecovery = undefined;
1849
- }
1850
- },
1851
- };
1852
- return runtime;
1853
- }
1854
- function getTelegramBusEnvelopeDiagnostics(envelope) {
1855
- return {
1856
- envelopeKind: envelope.kind,
1857
- requestId: envelope.requestId,
1858
- };
1859
- }
1860
- function sendTelegramBusLocalEnvelopeOnce(options) {
1861
- const timeoutMs = options.timeoutMs ?? 1000;
1862
- return new Promise((resolve, reject) => {
1863
- const assertAuthority = options.assertAuthority;
1864
- let issued = false;
1865
- const assertCurrent = () => {
1866
- try {
1867
- assertAuthority?.();
1868
- }
1869
- catch (error) {
1870
- // The caller's reason is a fixed condition label, never request or credential content.
1871
- throw new TelegramBusLocalAuthorityError(issued, error instanceof Error ? error.message : undefined);
1872
- }
1873
- };
1874
- try {
1875
- assertCurrent();
1876
- }
1877
- catch (error) {
1878
- reject(error);
1879
- return;
1880
- }
1881
- const socket = createConnection(options.socketPath);
1882
- let settled = false;
1883
- let buffer = "";
1884
- let timeoutFinalizer;
1885
- const settle = (callback) => {
1886
- if (settled)
1887
- return;
1888
- settled = true;
1889
- clearTimeout(timeout);
1890
- if (timeoutFinalizer)
1891
- clearImmediate(timeoutFinalizer);
1892
- socket.destroy();
1893
- callback();
1894
- };
1895
- const timeout = setTimeout(() => {
1896
- // A long synchronous Pi/TUI turn can resume in the timers phase after
1897
- // the peer acknowledgement is already buffered. Give pending socket I/O
1898
- // two poll phases before converting elapsed wall time into a transport
1899
- // failure; Darwin can surface buffered Unix-socket input only on the
1900
- // second cycle after a long stall, while a silent peer stays bounded.
1901
- timeoutFinalizer = setImmediate(() => {
1902
- timeoutFinalizer = setImmediate(() => {
1903
- settle(() => reject(createTelegramBusTransportTimeoutError("Timed out waiting for Telegram bus response")));
1904
- });
1905
- });
1906
- }, timeoutMs);
1907
- socket.setEncoding("utf8");
1908
- socket.once("connect", () => {
1909
- try {
1910
- assertCurrent();
1911
- const encoded = encodeTelegramBusEnvelope(options.envelope);
1912
- assertCurrent();
1913
- issued = true;
1914
- socket.write(encoded);
1915
- }
1916
- catch (error) {
1917
- settle(() => reject(error));
1918
- }
1919
- });
1920
- socket.on("data", (chunk) => {
1921
- buffer += chunk;
1922
- const newlineIndex = buffer.indexOf("\n");
1923
- if (newlineIndex < 0)
1924
- return;
1925
- const line = buffer.slice(0, newlineIndex);
1926
- try {
1927
- const response = parseTelegramBusEnvelope(line);
1928
- assertCurrent();
1929
- settle(() => resolve(response));
1930
- }
1931
- catch (error) {
1932
- settle(() => reject(error));
1933
- }
1934
- });
1935
- socket.once("error", (error) => settle(() => reject(error)));
1936
- socket.once("end", () => settle(() => resolve(undefined)));
1937
- });
1938
- }
1939
- export async function sendTelegramBusLocalEnvelope(options) {
1940
- const resolvedOptions = {
1941
- ...options,
1942
- socketPath: resolveTelegramBusSocketPath(options.socketPath),
1943
- };
1944
- const attempts = Math.max(1, resolvedOptions.retry?.attempts ?? 1);
1945
- const delayMs = Math.max(0, resolvedOptions.retry?.delayMs ?? 0);
1946
- for (let attempt = 1;; attempt += 1) {
1947
- try {
1948
- return await sendTelegramBusLocalEnvelopeOnce(resolvedOptions);
1949
- }
1950
- catch (error) {
1951
- const info = classifyTelegramBusTransportError(error);
1952
- resolvedOptions.recordTransportEvent?.("client-failed", {
1953
- ...getTelegramBusEndpointDiagnostics(resolvedOptions.socketPath),
1954
- ...getTelegramBusEnvelopeDiagnostics(resolvedOptions.envelope),
1955
- attempt,
1956
- attempts,
1957
- ...info,
1958
- });
1959
- if (error instanceof TelegramBusLocalAuthorityError ||
1960
- attempt >= attempts ||
1961
- !isRetryableTelegramBusTransportError(error)) {
1962
- throw error;
1963
- }
1964
- resolvedOptions.recordTransportEvent?.("client-retry", {
1965
- ...getTelegramBusEndpointDiagnostics(resolvedOptions.socketPath),
1966
- ...getTelegramBusEnvelopeDiagnostics(resolvedOptions.envelope),
1967
- attempt,
1968
- attempts,
1969
- delayMs,
1970
- ...info,
1971
- });
1972
- await delayTelegramBusTransportRetry(delayMs);
1973
- }
1974
- }
1975
- }
1976
- export function createTelegramBusForwardOwnershipValidator(registry) {
1977
- return (ownership) => {
1978
- const follower = registry.get(ownership.instanceId);
1979
- return isTelegramBusForwardOwnershipCurrent(ownership, follower?.registrationGeneration &&
1980
- follower.profileKey &&
1981
- follower.protocol
1982
- ? {
1983
- instanceId: follower.instanceId,
1984
- ownerGeneration: follower.registrationGeneration,
1985
- recipientBindingKey: follower.profileKey,
1986
- protocolIdentity: follower.protocol,
1987
- }
1988
- : undefined);
1989
- };
1990
- }
1991
- function hasTelegramBusFollowerIdentityOverlap(first, second) {
1992
- return (first.instanceId === second.instanceId ||
1993
- (first.profileKey !== undefined &&
1994
- first.profileKey === second.profileKey) ||
1995
- (first.target !== undefined &&
1996
- first.target.chatId === second.target?.chatId &&
1997
- first.target.threadId === second.target.threadId));
1998
- }
1999
- export function createTelegramBusFollowerRegistry() {
2000
- const followers = new Map();
2001
- const observations = new Set();
2002
- const clone = (follower) => ({
2003
- ...follower,
2004
- target: follower.target ? { ...follower.target } : undefined,
2005
- ...(follower.protocol
2006
- ? {
2007
- protocol: {
2008
- ...follower.protocol,
2009
- capabilities: [...follower.protocol.capabilities],
2010
- },
2011
- }
2012
- : {}),
2013
- });
2014
- return {
2015
- register: (registration) => {
2016
- const existing = followers.get(registration.instanceId);
2017
- for (const [instanceId, follower] of followers.entries()) {
2018
- if (instanceId === registration.instanceId)
2019
- continue;
2020
- if (hasTelegramBusFollowerIdentityOverlap(registration, follower))
2021
- followers.delete(instanceId);
2022
- }
2023
- const next = {
2024
- ...registration,
2025
- target: registration.target ? { ...registration.target } : undefined,
2026
- ...(registration.protocol
2027
- ? {
2028
- protocol: {
2029
- ...registration.protocol,
2030
- capabilities: [...registration.protocol.capabilities],
2031
- },
2032
- }
2033
- : {}),
2034
- lastHeartbeatMs: existing?.lastHeartbeatMs ?? registration.connectedAtMs,
2035
- };
2036
- followers.set(registration.instanceId, next);
2037
- for (const observation of observations) {
2038
- if (!hasTelegramBusFollowerIdentityOverlap(next, observation.follower))
2039
- continue;
2040
- observation.current = false;
2041
- observations.delete(observation);
2042
- }
2043
- return clone(next);
2044
- },
2045
- heartbeat: (instanceId, nowMs) => {
2046
- const existing = followers.get(instanceId);
2047
- if (!existing)
2048
- return undefined;
2049
- const next = { ...existing, lastHeartbeatMs: nowMs };
2050
- followers.set(instanceId, next);
2051
- return clone(next);
2052
- },
2053
- get: (instanceId) => {
2054
- const existing = followers.get(instanceId);
2055
- return existing ? clone(existing) : undefined;
2056
- },
2057
- getByTarget: (target) => {
2058
- for (const follower of followers.values()) {
2059
- if (follower.target?.chatId === target.chatId &&
2060
- follower.target.threadId === target.threadId) {
2061
- return clone(follower);
2062
- }
2063
- }
2064
- return undefined;
2065
- },
2066
- list: () => [...followers.values()].map(clone),
2067
- remove: (instanceId) => followers.delete(instanceId),
2068
- clear: () => {
2069
- followers.clear();
2070
- for (const observation of observations)
2071
- observation.current = false;
2072
- observations.clear();
2073
- },
2074
- observeUnregistered: (follower) => {
2075
- // This watches replacement, not process death or delivery authority.
2076
- const observation = {
2077
- follower: clone(follower),
2078
- current: ![...followers.values()].some((current) => hasTelegramBusFollowerIdentityOverlap(current, follower)),
2079
- };
2080
- if (observation.current)
2081
- observations.add(observation);
2082
- return {
2083
- isCurrent: () => observation.current,
2084
- release() {
2085
- observation.current = false;
2086
- observations.delete(observation);
2087
- },
2088
- };
2089
- },
2090
- pruneStale: (nowMs, staleAfterMs) => {
2091
- const removed = [];
2092
- for (const [instanceId, follower] of followers.entries()) {
2093
- if (nowMs - follower.lastHeartbeatMs <= staleAfterMs)
2094
- continue;
2095
- followers.delete(instanceId);
2096
- removed.push(clone(follower));
2097
- }
2098
- return removed;
2099
- },
2100
- };
2101
- }
2102
- async function handleTelegramBusSocketLine(line, socket, handleEnvelope, recordTransportEvent, socketPath, shouldDropResponse) {
2103
- const envelope = parseTelegramBusEnvelope(line);
2104
- if (!envelope) {
2105
- recordTransportEvent?.("server-invalid-envelope", {
2106
- ...getTelegramBusEndpointDiagnostics(socketPath),
2107
- byteLength: Buffer.byteLength(line),
2108
- });
2109
- socket.write(encodeTelegramBusEnvelope({
2110
- kind: "bus.ack",
2111
- requestId: "invalid",
2112
- ok: false,
2113
- message: "Invalid Telegram bus envelope.",
2114
- }));
2115
- return;
2116
- }
2117
- try {
2118
- const response = await handleEnvelope(envelope);
2119
- if (response && !shouldDropResponse?.(envelope, response)) {
2120
- socket.write(encodeTelegramBusEnvelope(response));
2121
- }
2122
- }
2123
- catch (error) {
2124
- recordTransportEvent?.("server-handler-failed", {
2125
- ...getTelegramBusEndpointDiagnostics(socketPath),
2126
- ...getTelegramBusEnvelopeDiagnostics(envelope),
2127
- ...classifyTelegramBusTransportError(error),
2128
- });
2129
- socket.write(encodeTelegramBusEnvelope(rejectTelegramBusRequest(envelope.requestId, "Telegram bus handler failed.")));
2130
- }
2131
- }
2132
- function parseRegisterEnvelope(value, requestId, kind) {
2133
- const registration = parseRegistration(value.registration);
2134
- return registration ? { kind, requestId, registration } : undefined;
2135
- }
2136
- function parseFollowerPresenceEnvelope(value, requestId, kind) {
2137
- return typeof value.instanceId === "string" &&
2138
- typeof value.sentAtMs === "number"
2139
- ? {
2140
- kind,
2141
- requestId,
2142
- instanceId: value.instanceId,
2143
- ...(typeof value.registrationGeneration === "string"
2144
- ? { registrationGeneration: value.registrationGeneration }
2145
- : {}),
2146
- sentAtMs: value.sentAtMs,
2147
- }
2148
- : undefined;
2149
- }
2150
- function parseRenameThreadEnvelope(value, requestId) {
2151
- const target = parseTarget(value.target);
2152
- return typeof value.instanceId === "string" &&
2153
- typeof value.registrationGeneration === "string" &&
2154
- target?.threadId !== undefined &&
2155
- typeof value.threadName === "string" &&
2156
- typeof value.sentAtMs === "number"
2157
- ? {
2158
- kind: "follower.renameThread",
2159
- requestId,
2160
- instanceId: value.instanceId,
2161
- registrationGeneration: value.registrationGeneration,
2162
- target: { chatId: target.chatId, threadId: target.threadId },
2163
- threadName: value.threadName,
2164
- sentAtMs: value.sentAtMs,
2165
- }
2166
- : undefined;
2167
- }
2168
- function parseTelegramBusFollowerDeliveryIdentity(value) {
2169
- if (!isRecord(value))
2170
- return undefined;
2171
- if (typeof value.deliveryId !== "string" ||
2172
- !/^telegram-follower-v1-[a-f0-9]{64}$/u.test(value.deliveryId) ||
2173
- !Number.isSafeInteger(value.sourceUpdateId) ||
2174
- value.sourceUpdateId < 0 ||
2175
- typeof value.recipientBindingKey !== "string" ||
2176
- !value.recipientBindingKey ||
2177
- (value.sourceRecoveryKey !== undefined &&
2178
- (typeof value.sourceRecoveryKey !== "string" ||
2179
- !value.sourceRecoveryKey ||
2180
- value.sourceRecoveryKey.length > 1_024)) ||
2181
- (value.sourceClaim !== undefined &&
2182
- (!isRecord(value.sourceClaim) ||
2183
- typeof value.sourceClaim.acquisitionId !== "string" ||
2184
- !value.sourceClaim.acquisitionId ||
2185
- value.sourceClaim.acquisitionId.length > 256 ||
2186
- typeof value.sourceClaim.handoffId !== "string" ||
2187
- !value.sourceClaim.handoffId ||
2188
- value.sourceClaim.handoffId.length > 256))) {
2189
- return undefined;
2190
- }
2191
- return {
2192
- deliveryId: value.deliveryId,
2193
- sourceUpdateId: value.sourceUpdateId,
2194
- recipientBindingKey: value.recipientBindingKey,
2195
- ...(typeof value.sourceRecoveryKey === "string"
2196
- ? { sourceRecoveryKey: value.sourceRecoveryKey }
2197
- : {}),
2198
- ...(isRecord(value.sourceClaim)
2199
- ? {
2200
- sourceClaim: {
2201
- acquisitionId: value.sourceClaim.acquisitionId,
2202
- handoffId: value.sourceClaim.handoffId,
2203
- },
2204
- }
2205
- : {}),
2206
- };
2207
- }
2208
- function parseForwardCallbackEnvelope(value, requestId) {
2209
- const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
2210
- return delivery &&
2211
- typeof value.recipientInstanceId === "string" &&
2212
- typeof value.recipientRegistrationGeneration === "string" &&
2213
- typeof value.sentAtMs === "number"
2214
- ? {
2215
- kind: "leader.forwardCallback",
2216
- requestId,
2217
- recipientInstanceId: value.recipientInstanceId,
2218
- recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2219
- delivery,
2220
- query: value.query,
2221
- sentAtMs: value.sentAtMs,
2222
- }
2223
- : undefined;
2224
- }
2225
- function parseForwardReactionEnvelope(value, requestId) {
2226
- const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
2227
- return delivery &&
2228
- typeof value.recipientInstanceId === "string" &&
2229
- typeof value.recipientRegistrationGeneration === "string" &&
2230
- typeof value.sentAtMs === "number"
2231
- ? {
2232
- kind: "leader.forwardReaction",
2233
- requestId,
2234
- recipientInstanceId: value.recipientInstanceId,
2235
- recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2236
- delivery,
2237
- reactionUpdate: value.reactionUpdate,
2238
- sentAtMs: value.sentAtMs,
2239
- }
2240
- : undefined;
2241
- }
2242
- function parseForwardMessageEnvelope(value, requestId, kind) {
2243
- const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
2244
- return delivery &&
2245
- typeof value.recipientInstanceId === "string" &&
2246
- typeof value.recipientRegistrationGeneration === "string" &&
2247
- typeof value.sentAtMs === "number"
2248
- ? {
2249
- kind,
2250
- requestId,
2251
- recipientInstanceId: value.recipientInstanceId,
2252
- recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2253
- delivery,
2254
- message: value.message,
2255
- ...(kind === "leader.forwardMessage" &&
2256
- (value.forwardCommentBatchPosition === "comment" ||
2257
- value.forwardCommentBatchPosition === "forward")
2258
- ? {
2259
- forwardCommentBatchPosition: value.forwardCommentBatchPosition,
2260
- }
2261
- : {}),
2262
- sentAtMs: value.sentAtMs,
2263
- }
2264
- : undefined;
2265
- }
2266
- function parseOfferInputCustodyHandoffEnvelope(value, requestId) {
2267
- const source = value.source;
2268
- if (!isRecord(source) ||
2269
- typeof value.recipientInstanceId !== "string" ||
2270
- typeof value.recipientRegistrationGeneration !== "string" ||
2271
- typeof value.recipientBindingKey !== "string" ||
2272
- !value.recipientBindingKey ||
2273
- typeof value.sourceRecoveryKey !== "string" ||
2274
- !value.sourceRecoveryKey ||
2275
- value.sourceRecoveryKey.length > 1_024 ||
2276
- typeof source.journalBindingKey !== "string" ||
2277
- !source.journalBindingKey ||
2278
- source.journalBindingKey !== value.sourceRecoveryKey ||
2279
- typeof source.tokenSha256 !== "string" ||
2280
- !/^[a-f0-9]{64}$/u.test(source.tokenSha256) ||
2281
- !Number.isSafeInteger(source.updateId) ||
2282
- source.updateId < 0 ||
2283
- typeof value.handoffId !== "string" ||
2284
- !value.handoffId ||
2285
- value.handoffId.length > 256 ||
2286
- typeof value.sentAtMs !== "number")
2287
- return undefined;
2288
- return {
2289
- kind: "leader.offerInputCustodyHandoff",
2290
- requestId,
2291
- recipientInstanceId: value.recipientInstanceId,
2292
- recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2293
- recipientBindingKey: value.recipientBindingKey,
2294
- sourceRecoveryKey: value.sourceRecoveryKey,
2295
- source: {
2296
- journalBindingKey: source.journalBindingKey,
2297
- tokenSha256: source.tokenSha256,
2298
- updateId: source.updateId,
2299
- },
2300
- handoffId: value.handoffId,
2301
- sentAtMs: value.sentAtMs,
2302
- };
2303
- }
2304
- function parseWakeInputCustodyEnvelope(value, requestId) {
2305
- const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
2306
- return delivery &&
2307
- delivery.sourceRecoveryKey &&
2308
- delivery.sourceClaim &&
2309
- typeof value.recipientInstanceId === "string" &&
2310
- typeof value.recipientRegistrationGeneration === "string" &&
2311
- typeof value.sentAtMs === "number"
2312
- ? {
2313
- kind: "leader.wakeInputCustody",
2314
- requestId,
2315
- recipientInstanceId: value.recipientInstanceId,
2316
- recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2317
- delivery,
2318
- sentAtMs: value.sentAtMs,
2319
- }
2320
- : undefined;
2321
- }
2322
- function parseQueueHandoffEnvelope(value, requestId, kind) {
2323
- let serializedPayloadBytes;
2324
- try {
2325
- serializedPayloadBytes = Buffer.byteLength(JSON.stringify(value.payload));
2326
- }
2327
- catch {
2328
- return undefined;
2329
- }
2330
- if (serializedPayloadBytes > TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES) {
2331
- return undefined;
2332
- }
2333
- const payload = parseTelegramQueueHandoffPayload(value.payload);
2334
- if (!payload ||
2335
- typeof value.recipientInstanceId !== "string" ||
2336
- typeof value.recipientRegistrationGeneration !== "string" ||
2337
- !Number.isSafeInteger(value.donorProcessId) ||
2338
- value.donorProcessId <= 0 ||
2339
- typeof value.donorProcessBirthId !== "string" ||
2340
- !value.donorProcessBirthId ||
2341
- !Number.isSafeInteger(value.donorSessionGeneration) ||
2342
- value.donorSessionGeneration <= 0 ||
2343
- typeof value.donorAcquisitionId !== "string" ||
2344
- !value.donorAcquisitionId ||
2345
- !Number.isSafeInteger(value.donorAcquiredAtMs) ||
2346
- value.donorAcquiredAtMs < 0 ||
2347
- typeof value.handoffToken !== "string" ||
2348
- value.handoffToken.length < 32 ||
2349
- value.handoffToken.length > 256 ||
2350
- value.donorProcessBirthId.length > 256 ||
2351
- value.donorAcquisitionId.length > 256 ||
2352
- typeof value.sentAtMs !== "number") {
2353
- return undefined;
2354
- }
2355
- const fields = {
2356
- requestId,
2357
- recipientInstanceId: value.recipientInstanceId,
2358
- recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2359
- donorProcessId: value.donorProcessId,
2360
- donorProcessBirthId: value.donorProcessBirthId,
2361
- donorSessionGeneration: value.donorSessionGeneration,
2362
- donorAcquisitionId: value.donorAcquisitionId,
2363
- donorAcquiredAtMs: value.donorAcquiredAtMs,
2364
- handoffToken: value.handoffToken,
2365
- payload,
2366
- sentAtMs: value.sentAtMs,
2367
- };
2368
- if (kind === "leader.offerQueueHandoff") {
2369
- return typeof value.donorInstanceId === "string"
2370
- ? { kind, donorInstanceId: value.donorInstanceId, ...fields }
2371
- : undefined;
2372
- }
2373
- return typeof value.instanceId === "string" &&
2374
- typeof value.registrationGeneration === "string"
2375
- ? {
2376
- kind,
2377
- instanceId: value.instanceId,
2378
- registrationGeneration: value.registrationGeneration,
2379
- ...fields,
2380
- }
2381
- : undefined;
2382
- }
2383
- function parseResolveAgentTargetEnvelope(value, requestId) {
2384
- const selectorValue = isRecord(value.selector) ? value.selector : undefined;
2385
- if (typeof value.instanceId !== "string" ||
2386
- !selectorValue ||
2387
- typeof value.sentAtMs !== "number") {
2388
- return undefined;
2389
- }
2390
- const chatId = typeof selectorValue.chatId === "number" &&
2391
- Number.isInteger(selectorValue.chatId)
2392
- ? selectorValue.chatId
2393
- : undefined;
2394
- const threadId = typeof selectorValue.threadId === "number" &&
2395
- Number.isInteger(selectorValue.threadId) &&
2396
- selectorValue.threadId > 0
2397
- ? selectorValue.threadId
2398
- : undefined;
2399
- const threadName = typeof selectorValue.threadName === "string" &&
2400
- selectorValue.threadName.trim()
2401
- ? selectorValue.threadName.trim()
2402
- : undefined;
2403
- if ((threadId === undefined) === (threadName === undefined))
2404
- return undefined;
2405
- return {
2406
- kind: "follower.resolveAgentTarget",
2407
- requestId,
2408
- instanceId: value.instanceId,
2409
- ...(typeof value.registrationGeneration === "string"
2410
- ? { registrationGeneration: value.registrationGeneration }
2411
- : {}),
2412
- selector: {
2413
- ...(chatId !== undefined ? { chatId } : {}),
2414
- ...(threadId !== undefined ? { threadId } : {}),
2415
- ...(threadName !== undefined ? { threadName } : {}),
2416
- },
2417
- sentAtMs: value.sentAtMs,
2418
- };
2419
- }
2420
- function parseRouteAgentMessageEnvelope(value, requestId) {
2421
- const messageValue = isRecord(value.message) ? value.message : undefined;
2422
- const target = parseThreadTarget(messageValue?.target);
2423
- if (typeof value.instanceId !== "string" ||
2424
- !messageValue ||
2425
- !target ||
2426
- typeof messageValue.messageId !== "number" ||
2427
- !Number.isInteger(messageValue.messageId) ||
2428
- messageValue.messageId <= 0 ||
2429
- typeof messageValue.text !== "string" ||
2430
- !messageValue.text.trim() ||
2431
- typeof value.sentAtMs !== "number") {
2432
- return undefined;
2433
- }
2434
- return {
2435
- kind: "follower.routeAgentMessage",
2436
- requestId,
2437
- instanceId: value.instanceId,
2438
- ...(typeof value.registrationGeneration === "string"
2439
- ? { registrationGeneration: value.registrationGeneration }
2440
- : {}),
2441
- message: {
2442
- target,
2443
- messageId: messageValue.messageId,
2444
- text: messageValue.text,
2445
- },
2446
- sentAtMs: value.sentAtMs,
2447
- };
2448
- }
2449
- const nonempty = (input, limit = 128) => typeof input === "string" && !!input.trim() && input.length <= limit;
2450
- const positive = (input) => Number.isSafeInteger(input) && input > 0;
2451
- const nonnegative = (input) => Number.isSafeInteger(input) && input >= 0;
2452
- /** Shared closed text-effect codec; parsing alone supplies no recipient or issuance authority. */
2453
- export function parseTelegramBusSelectedMenuTextEffect(effect) {
2454
- if (!isRecord(effect) ||
2455
- (effect.kind !== "send-text" && effect.kind !== "edit-text") ||
2456
- !hasOnlyWireKeys(effect, [
2457
- "kind",
2458
- "text",
2459
- "parseMode",
2460
- "replyMarkup",
2461
- ...(effect.kind === "send-text" ? ["replyToMessageId"] : ["messageId"]),
2462
- ]) ||
2463
- !nonempty(effect.text, 4096) ||
2464
- (effect.parseMode !== undefined && effect.parseMode !== "HTML") ||
2465
- (effect.kind === "send-text"
2466
- ? effect.replyToMessageId !== undefined &&
2467
- !positive(effect.replyToMessageId)
2468
- : !positive(effect.messageId)))
2469
- return undefined;
2470
- let replyMarkup;
2471
- if (effect.replyMarkup !== undefined) {
2472
- const markup = effect.replyMarkup;
2473
- if (!isRecord(markup) ||
2474
- !hasOnlyWireKeys(markup, ["inline_keyboard"]) ||
2475
- !Array.isArray(markup.inline_keyboard) ||
2476
- markup.inline_keyboard.length > 32 ||
2477
- markup.inline_keyboard.some((row) => !Array.isArray(row) ||
2478
- !row.length ||
2479
- row.length > 8 ||
2480
- row.some((button) => !isRecord(button) ||
2481
- !hasOnlyWireKeys(button, ["text", "callback_data"]) ||
2482
- !nonempty(button.text) ||
2483
- !nonempty(button.callback_data, 64) ||
2484
- Buffer.byteLength(button.callback_data, "utf8") > 64)))
2485
- return undefined;
2486
- replyMarkup = {
2487
- inline_keyboard: markup.inline_keyboard.map((row) => row.map((button) => ({
2488
- text: button.text,
2489
- callback_data: button.callback_data,
2490
- }))),
2491
- };
2492
- }
2493
- const common = {
2494
- text: effect.text,
2495
- ...(effect.parseMode === "HTML" ? { parseMode: "HTML" } : {}),
2496
- ...(replyMarkup ? { replyMarkup } : {}),
2497
- };
2498
- return effect.kind === "send-text"
2499
- ? {
2500
- ...common,
2501
- kind: "send-text",
2502
- ...(effect.replyToMessageId !== undefined
2503
- ? { replyToMessageId: effect.replyToMessageId }
2504
- : {}),
2505
- }
2506
- : { ...common, kind: "edit-text", messageId: effect.messageId };
2507
- }
2508
- function parseSelectedMenuDeliveryEnvelope(value, requestId) {
2509
- const recipient = value.recipient, executor = value.executor, effect = parseTelegramBusSelectedMenuTextEffect(value.effect);
2510
- if (!hasOnlyWireKeys(value, [
2511
- "kind",
2512
- "requestId",
2513
- "auth",
2514
- "instanceId",
2515
- "registrationGeneration",
2516
- "operationId",
2517
- "recipient",
2518
- "executor",
2519
- "operatorUserId",
2520
- "effect",
2521
- "sentAtMs",
2522
- ]) ||
2523
- !nonempty(requestId) ||
2524
- !nonempty(value.instanceId) ||
2525
- !nonempty(value.registrationGeneration) ||
2526
- !nonempty(value.operationId) ||
2527
- !isRecord(recipient) ||
2528
- !hasOnlyWireKeys(recipient, [
2529
- "sessionId",
2530
- "sessionGeneration",
2531
- "processId",
2532
- "processBirthId",
2533
- "profileKey",
2534
- "journalBindingKey",
2535
- "target",
2536
- ]) ||
2537
- !nonempty(recipient.sessionId) ||
2538
- !nonnegative(recipient.sessionGeneration) ||
2539
- !positive(recipient.processId) ||
2540
- !nonempty(recipient.processBirthId) ||
2541
- !nonempty(recipient.profileKey, 1024) ||
2542
- !nonempty(recipient.journalBindingKey, 8192) ||
2543
- recipient.profileKey === recipient.journalBindingKey ||
2544
- !isRecord(recipient.target) ||
2545
- !hasOnlyWireKeys(recipient.target, ["chatId", "threadId"]) ||
2546
- !positive(recipient.target.chatId) ||
2547
- !positive(recipient.target.threadId) ||
2548
- !positive(value.operatorUserId) ||
2549
- value.operatorUserId !== recipient.target.chatId ||
2550
- !isRecord(executor) ||
2551
- !hasOnlyWireKeys(executor, ["instanceId", "leaderEpoch"]) ||
2552
- !nonempty(executor.instanceId) ||
2553
- !nonempty(executor.leaderEpoch) ||
2554
- !effect ||
2555
- typeof value.sentAtMs !== "number" ||
2556
- !Number.isFinite(value.sentAtMs) ||
2557
- value.sentAtMs < 0 ||
2558
- (value.auth !== undefined && typeof value.auth !== "string"))
2559
- return undefined;
2560
- return {
2561
- kind: "follower.deliverSelectedMenu",
2562
- requestId,
2563
- instanceId: value.instanceId,
2564
- registrationGeneration: value.registrationGeneration,
2565
- operationId: value.operationId,
2566
- recipient: {
2567
- sessionId: recipient.sessionId,
2568
- sessionGeneration: recipient.sessionGeneration,
2569
- processId: recipient.processId,
2570
- processBirthId: recipient.processBirthId,
2571
- profileKey: recipient.profileKey,
2572
- journalBindingKey: recipient.journalBindingKey,
2573
- target: {
2574
- chatId: recipient.target.chatId,
2575
- threadId: recipient.target.threadId,
2576
- },
2577
- },
2578
- executor: {
2579
- instanceId: executor.instanceId,
2580
- leaderEpoch: executor.leaderEpoch,
2581
- },
2582
- operatorUserId: value.operatorUserId,
2583
- effect,
2584
- sentAtMs: value.sentAtMs,
2585
- };
2586
- }
2587
- function parseCallApiEnvelope(value, requestId) {
2588
- return typeof value.instanceId === "string" &&
2589
- typeof value.method === "string" &&
2590
- Array.isArray(value.args) &&
2591
- typeof value.sentAtMs === "number"
2592
- ? {
2593
- kind: "follower.callApi",
2594
- requestId,
2595
- instanceId: value.instanceId,
2596
- ...(typeof value.registrationGeneration === "string"
2597
- ? { registrationGeneration: value.registrationGeneration }
2598
- : {}),
2599
- method: value.method,
2600
- args: value.args,
2601
- sentAtMs: value.sentAtMs,
2602
- }
2603
- : undefined;
2604
- }
2605
- function parseAckEnvelope(value, requestId) {
2606
- if (typeof value.ok !== "boolean")
2607
- return undefined;
2608
- const envelope = {
2609
- kind: "bus.ack",
2610
- requestId,
2611
- ok: value.ok,
2612
- message: typeof value.message === "string" ? value.message : undefined,
2613
- };
2614
- if (Object.hasOwn(value, "result"))
2615
- envelope.result = value.result;
2616
- const protocol = parseTelegramBusProtocolIdentity(value.protocol);
2617
- if (protocol)
2618
- envelope.protocol = protocol;
2619
- if (isRecord(value.error)) {
2620
- const code = value.error.code;
2621
- if (code === "commit-unknown" ||
2622
- code === "request-id-collision" ||
2623
- code === "ledger-overloaded" ||
2624
- code === "incompatible-protocol" ||
2625
- code === "stale-target" ||
2626
- code === "workspace-binding-unavailable") {
2627
- const chatId = value.error.chatId;
2628
- const threadId = value.error.threadId;
2629
- if (code === "stale-target" &&
2630
- (!Number.isSafeInteger(chatId) || !Number.isSafeInteger(threadId))) {
2631
- return undefined;
2632
- }
2633
- envelope.error = {
2634
- code,
2635
- ...(typeof value.error.method === "string"
2636
- ? { method: value.error.method }
2637
- : {}),
2638
- ...(typeof chatId === "number" ? { chatId } : {}),
2639
- ...(typeof threadId === "number" ? { threadId } : {}),
2640
- };
2641
- }
2642
- }
2643
- return envelope;
2644
- }
2645
- function parseTelegramBusProtocolIdentity(value) {
2646
- if (!isRecord(value))
2647
- return undefined;
2648
- if (!Number.isSafeInteger(value.protocolVersion) ||
2649
- value.protocolVersion <= 0 ||
2650
- typeof value.runtimeBuild !== "string" ||
2651
- !value.runtimeBuild.trim() ||
2652
- value.runtimeBuild !== value.runtimeBuild.trim() ||
2653
- value.runtimeBuild.length > 128 ||
2654
- !Array.isArray(value.capabilities) ||
2655
- value.capabilities.length > 32 ||
2656
- value.capabilities.some((capability) => typeof capability !== "string" ||
2657
- capability.length > 128 ||
2658
- !/^[a-z0-9][a-z0-9._-]*$/u.test(capability))) {
2659
- return undefined;
2660
- }
2661
- const capabilities = value.capabilities;
2662
- if (capabilities.some((capability, index) => index > 0 && capability <= capabilities[index - 1])) {
2663
- return undefined;
2664
- }
2665
- return {
2666
- protocolVersion: value.protocolVersion,
2667
- runtimeBuild: value.runtimeBuild,
2668
- capabilities: [...capabilities],
2669
- };
2670
- }
2671
- function parseRegistration(value) {
2672
- if (!isRecord(value))
2673
- return undefined;
2674
- if (typeof value.instanceId !== "string")
2675
- return undefined;
2676
- if (typeof value.connectedAtMs !== "number")
2677
- return undefined;
2678
- const target = parseTarget(value.target);
2679
- if (value.target !== undefined && !target)
2680
- return undefined;
2681
- const registration = {
2682
- instanceId: value.instanceId,
2683
- connectedAtMs: value.connectedAtMs,
2684
- };
2685
- if (typeof value.previousInstanceId === "string") {
2686
- registration.previousInstanceId = value.previousInstanceId;
2687
- }
2688
- if (typeof value.profileKey === "string")
2689
- registration.profileKey = value.profileKey;
2690
- if (typeof value.threadName === "string")
2691
- registration.threadName = value.threadName;
2692
- if (typeof value.slot === "string" && /^[A-Z]$/.test(value.slot)) {
2693
- registration.slot = value.slot;
2694
- }
2695
- if (typeof value.cwd === "string")
2696
- registration.cwd = value.cwd;
2697
- if (value.sessionId !== undefined) {
2698
- if (typeof value.sessionId !== "string" ||
2699
- !value.sessionId ||
2700
- value.sessionId !== value.sessionId.trim() ||
2701
- Buffer.byteLength(value.sessionId, "utf8") > 256)
2702
- return undefined;
2703
- registration.sessionId = value.sessionId;
2704
- }
2705
- if (typeof value.pid === "number")
2706
- registration.pid = value.pid;
2707
- if (typeof value.busSocketPath === "string") {
2708
- registration.busSocketPath = value.busSocketPath;
2709
- }
2710
- if (typeof value.registrationGeneration === "string") {
2711
- registration.registrationGeneration = value.registrationGeneration;
2712
- }
2713
- if (Number.isSafeInteger(value.sessionGeneration) &&
2714
- value.sessionGeneration > 0) {
2715
- registration.sessionGeneration = value.sessionGeneration;
2716
- }
2717
- if (typeof value.processBirthId === "string" && value.processBirthId) {
2718
- registration.processBirthId = value.processBirthId;
2719
- }
2720
- const protocol = parseTelegramBusProtocolIdentity(value.protocol);
2721
- if (protocol)
2722
- registration.protocol = protocol;
2723
- if (target)
2724
- registration.target = target;
2725
- return registration;
2726
- }
2727
- function parseThreadTarget(value) {
2728
- const target = parseTarget(value);
2729
- return target && typeof target.threadId === "number"
2730
- ? { chatId: target.chatId, threadId: target.threadId }
2731
- : undefined;
2732
- }