@llblab/pi-telegram 0.52.1 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +10 -8
- package/BACKLOG.md +1 -16
- package/CHANGELOG.md +17 -0
- package/README.md +8 -10
- package/api/commands.ts +3 -0
- package/dist/api/commands.d.ts +1 -1
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +15 -42
- package/dist/lib/activity.d.ts +9 -0
- package/dist/lib/activity.js +39 -10
- package/dist/lib/agent-messages.js +7 -3
- package/dist/lib/bindings.d.ts +43 -5
- package/dist/lib/bindings.js +163 -20
- package/dist/lib/bus-api.d.ts +12 -0
- package/dist/lib/bus-api.js +182 -79
- package/dist/lib/bus-follower.d.ts +111 -31
- package/dist/lib/bus-follower.js +1324 -359
- package/dist/lib/bus-leader.d.ts +41 -91
- package/dist/lib/bus-leader.js +969 -637
- package/dist/lib/bus-transport.js +16 -9
- package/dist/lib/bus.d.ts +215 -18
- package/dist/lib/bus.js +1048 -327
- package/dist/lib/channel-posts.d.ts +5 -0
- package/dist/lib/channel-posts.js +434 -261
- package/dist/lib/command-templates.d.ts +26 -5
- package/dist/lib/command-templates.js +92 -176
- package/dist/lib/commands.d.ts +168 -82
- package/dist/lib/commands.js +1409 -291
- package/dist/lib/config.d.ts +1 -8
- package/dist/lib/config.js +34 -76
- package/dist/lib/delivery.d.ts +2 -2
- package/dist/lib/delivery.js +16 -16
- package/dist/lib/extension.js +640 -171
- package/dist/lib/generative-app-worker.mjs +19 -7
- package/dist/lib/generative-apps.d.ts +0 -5
- package/dist/lib/generative-apps.js +61 -49
- package/dist/lib/inbound.d.ts +5 -0
- package/dist/lib/inbound.js +35 -59
- package/dist/lib/journal.d.ts +33 -3
- package/dist/lib/journal.js +2063 -782
- package/dist/lib/lifecycle.d.ts +2 -6
- package/dist/lib/lifecycle.js +75 -58
- package/dist/lib/locks.d.ts +34 -3
- package/dist/lib/locks.js +318 -119
- package/dist/lib/logging.js +50 -14
- package/dist/lib/media.d.ts +22 -1
- package/dist/lib/media.js +46 -28
- package/dist/lib/menu-model.d.ts +19 -5
- package/dist/lib/menu-model.js +53 -11
- package/dist/lib/menu-queue.d.ts +5 -4
- package/dist/lib/menu-queue.js +45 -63
- package/dist/lib/menu-settings.d.ts +5 -4
- package/dist/lib/menu-settings.js +114 -78
- package/dist/lib/menu-status.d.ts +1 -1
- package/dist/lib/menu-status.js +8 -25
- package/dist/lib/menu-thinking.d.ts +6 -1
- package/dist/lib/menu-thinking.js +14 -21
- package/dist/lib/menu.d.ts +8 -61
- package/dist/lib/menu.js +92 -164
- package/dist/lib/model.d.ts +1 -1
- package/dist/lib/model.js +2 -16
- package/dist/lib/outbound-attachments.d.ts +6 -27
- package/dist/lib/outbound-attachments.js +45 -38
- package/dist/lib/outbound-buttons.js +22 -16
- package/dist/lib/outbound-markup.d.ts +0 -2
- package/dist/lib/outbound-markup.js +51 -64
- package/dist/lib/outbound-voice.js +6 -3
- package/dist/lib/outbound.d.ts +6 -6
- package/dist/lib/outbound.js +61 -51
- package/dist/lib/ownership.js +6 -2
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +37 -21
- package/dist/lib/pi.d.ts +2 -0
- package/dist/lib/pi.js +8 -1
- package/dist/lib/polling.d.ts +23 -37
- package/dist/lib/polling.js +71 -52
- package/dist/lib/preview.js +39 -19
- package/dist/lib/process-identity.d.ts +24 -0
- package/dist/lib/process-identity.js +133 -0
- package/dist/lib/prompts.js +2 -7
- package/dist/lib/queue.d.ts +14 -16
- package/dist/lib/queue.js +249 -126
- package/dist/lib/recovery.d.ts +2 -83
- package/dist/lib/recovery.js +20 -274
- package/dist/lib/replies.d.ts +16 -24
- package/dist/lib/replies.js +99 -116
- package/dist/lib/routing.d.ts +120 -7
- package/dist/lib/routing.js +4667 -1152
- package/dist/lib/runtime.d.ts +1 -11
- package/dist/lib/sections.d.ts +4 -29
- package/dist/lib/sections.js +7 -20
- package/dist/lib/skills.js +4 -3
- package/dist/lib/status.js +86 -57
- package/dist/lib/sync.d.ts +6 -3
- package/dist/lib/sync.js +112 -45
- package/dist/lib/target.d.ts +4 -0
- package/dist/lib/target.js +18 -0
- package/dist/lib/telegram-api.d.ts +18 -13
- package/dist/lib/telegram-api.js +165 -53
- package/dist/lib/text-groups.js +4 -39
- package/dist/lib/thread-cleanup-manager.d.ts +6 -27
- package/dist/lib/thread-cleanup-manager.js +369 -186
- package/dist/lib/thread-display.d.ts +1 -2
- package/dist/lib/thread-display.js +58 -38
- package/dist/lib/thread-naming.d.ts +28 -8
- package/dist/lib/thread-naming.js +175 -66
- package/dist/lib/thread-reconciler.d.ts +42 -16
- package/dist/lib/thread-reconciler.js +90 -50
- package/dist/lib/threads.d.ts +82 -20
- package/dist/lib/threads.js +2247 -855
- package/dist/lib/time-injection.js +1 -2
- package/dist/lib/turns.js +1 -19
- package/dist/lib/updates.d.ts +169 -80
- package/dist/lib/updates.js +2418 -641
- package/dist/lib/voice.d.ts +7 -22
- package/dist/lib/voice.js +7 -22
- package/dist/lib/wire.js +1 -1
- package/dist/lib/workspace-admission.d.ts +3 -1
- package/dist/lib/workspace-admission.js +425 -211
- package/dist/lib/workspace-identity.js +8 -13
- package/dist/lib/workspace-retirement.d.ts +1 -1
- package/dist/lib/workspace-retirement.js +328 -140
- package/dist/lib/workspace-slots.js +8 -4
- package/dist/package.json +1 -1
- package/docs/architecture.md +36 -30
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +15 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +29 -1
- package/docs/ui-style.md +25 -8
- package/lib/activity-verbosity.ts +59 -82
- package/lib/activity.ts +146 -102
- package/lib/agent-messages.ts +12 -5
- package/lib/bindings.ts +389 -82
- package/lib/bus-api.ts +263 -102
- package/lib/bus-follower.ts +2440 -601
- package/lib/bus-leader.ts +2209 -1290
- package/lib/bus-transport.ts +46 -12
- package/lib/bus.ts +1673 -444
- package/lib/channel-posts.ts +848 -327
- package/lib/command-templates.ts +152 -265
- package/lib/commands.ts +2398 -555
- package/lib/config.ts +177 -170
- package/lib/delivery.ts +41 -29
- package/lib/extension.ts +1207 -433
- package/lib/generative-app-worker.mjs +19 -7
- package/lib/generative-apps.ts +324 -211
- package/lib/inbound.ts +95 -97
- package/lib/journal.ts +5311 -1763
- package/lib/keyboard.ts +1 -3
- package/lib/lifecycle.ts +128 -78
- package/lib/locks.ts +703 -212
- package/lib/logging.ts +147 -58
- package/lib/media.ts +82 -43
- package/lib/menu-model.ts +85 -18
- package/lib/menu-queue.ts +88 -129
- package/lib/menu-settings.ts +260 -155
- package/lib/menu-status.ts +20 -60
- package/lib/menu-thinking.ts +41 -56
- package/lib/menu.ts +112 -285
- package/lib/model.ts +8 -22
- package/lib/outbound-attachments.ts +156 -125
- package/lib/outbound-buttons.ts +76 -49
- package/lib/outbound-markup.ts +96 -94
- package/lib/outbound-voice.ts +33 -27
- package/lib/outbound.ts +125 -125
- package/lib/ownership.ts +19 -10
- package/lib/paths.ts +148 -49
- package/lib/pi.ts +35 -15
- package/lib/polling.ts +165 -149
- package/lib/preview.ts +93 -40
- package/lib/process-identity.ts +164 -0
- package/lib/prompts.ts +4 -11
- package/lib/queue.ts +511 -300
- package/lib/recovery.ts +50 -454
- package/lib/rendering.ts +1 -3
- package/lib/replies.ts +248 -209
- package/lib/routing.ts +8509 -2186
- package/lib/runtime.ts +1 -12
- package/lib/sections.ts +24 -89
- package/lib/setup.ts +1 -3
- package/lib/skills.ts +9 -4
- package/lib/status.ts +215 -110
- package/lib/sync.ts +488 -359
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +322 -127
- package/lib/text-groups.ts +5 -39
- package/lib/thread-cleanup-manager.ts +833 -331
- package/lib/thread-display.ts +213 -97
- package/lib/thread-naming.ts +232 -87
- package/lib/thread-reconciler.ts +155 -72
- package/lib/threads.ts +5318 -1845
- package/lib/time-injection.ts +1 -4
- package/lib/turns.ts +15 -28
- package/lib/updates.ts +5184 -1612
- package/lib/voice.ts +7 -35
- package/lib/wire.ts +1 -1
- package/lib/workspace-admission.ts +996 -416
- package/lib/workspace-identity.ts +14 -17
- package/lib/workspace-retirement.ts +878 -376
- package/lib/workspace-slots.ts +22 -13
- package/package.json +1 -1
- package/scripts/check-downgrade.mjs +0 -472
- package/scripts/measure-workspace.mjs +0 -108
package/lib/polling.ts
CHANGED
|
@@ -25,7 +25,8 @@ export const TELEGRAM_GET_UPDATES_CONFLICT_STOP_LIMIT = 10;
|
|
|
25
25
|
const TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT = 3;
|
|
26
26
|
const TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS = 1_000;
|
|
27
27
|
const TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS = 3_000;
|
|
28
|
-
const
|
|
28
|
+
const TELEGRAM_POLLING_RETRY_BASE_MS = 1_000;
|
|
29
|
+
const TELEGRAM_POLLING_RETRY_MAX_MS = 30_000;
|
|
29
30
|
const TELEGRAM_GET_UPDATES_GRACE_MS = 10_000;
|
|
30
31
|
|
|
31
32
|
// Standard Telegram DM polling does not expose ordinary message-deletion events.
|
|
@@ -73,12 +74,25 @@ export function getLatestTelegramUpdateId(
|
|
|
73
74
|
export class TelegramPersistentGetUpdatesConflictError extends Error {
|
|
74
75
|
readonly count: number;
|
|
75
76
|
constructor(count: number) {
|
|
76
|
-
super(
|
|
77
|
+
super(
|
|
78
|
+
`Telegram polling stopped after ${count} consecutive getUpdates conflicts.`,
|
|
79
|
+
);
|
|
77
80
|
this.name = "TelegramPersistentGetUpdatesConflictError";
|
|
78
81
|
this.count = count;
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
|
|
85
|
+
/** Exponential backoff for non-conflict polling/admission failures. */
|
|
86
|
+
export function getTelegramPollingRetryDelayMs(
|
|
87
|
+
consecutiveFailures: number,
|
|
88
|
+
): number {
|
|
89
|
+
const exponent = Math.max(0, Math.min(consecutiveFailures - 1, 16));
|
|
90
|
+
return Math.min(
|
|
91
|
+
TELEGRAM_POLLING_RETRY_BASE_MS * 2 ** exponent,
|
|
92
|
+
TELEGRAM_POLLING_RETRY_MAX_MS,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
export class TelegramGetUpdatesTimeoutError extends Error {
|
|
83
97
|
readonly timeoutMs: number;
|
|
84
98
|
|
|
@@ -122,22 +136,13 @@ export interface TelegramPollingStartState {
|
|
|
122
136
|
}
|
|
123
137
|
|
|
124
138
|
export type TelegramPollingWorkPhase =
|
|
125
|
-
|
|
126
|
-
| "persisting-journal"
|
|
127
|
-
| "persisting-offset"
|
|
128
|
-
| "retrying";
|
|
139
|
+
"long-poll" | "persisting-journal" | "persisting-offset" | "retrying";
|
|
129
140
|
|
|
130
141
|
export type TelegramPollingPhase =
|
|
131
|
-
| "
|
|
132
|
-
| "starting"
|
|
133
|
-
| TelegramPollingWorkPhase;
|
|
142
|
+
"stopped" | "starting" | TelegramPollingWorkPhase;
|
|
134
143
|
|
|
135
144
|
export type TelegramPollingStopReason =
|
|
136
|
-
|
|
137
|
-
| "requested"
|
|
138
|
-
| "completed"
|
|
139
|
-
| "failed"
|
|
140
|
-
| "persistent-conflict";
|
|
145
|
+
"not-started" | "requested" | "completed" | "failed" | "persistent-conflict";
|
|
141
146
|
|
|
142
147
|
export interface TelegramPollingStateSnapshot {
|
|
143
148
|
phase: TelegramPollingPhase;
|
|
@@ -150,8 +155,7 @@ export interface TelegramPollingStateSnapshot {
|
|
|
150
155
|
stopReason?: TelegramPollingStopReason;
|
|
151
156
|
}
|
|
152
157
|
|
|
153
|
-
export interface TelegramPollingControllerState
|
|
154
|
-
extends TelegramPollingStateSnapshot {
|
|
158
|
+
export interface TelegramPollingControllerState extends TelegramPollingStateSnapshot {
|
|
155
159
|
pollingPromise?: Promise<void>;
|
|
156
160
|
pollingController?: AbortController;
|
|
157
161
|
}
|
|
@@ -173,8 +177,7 @@ function getTelegramPollingStateSnapshot(
|
|
|
173
177
|
startedAtMs: state.startedAtMs,
|
|
174
178
|
stoppedAtMs: state.stoppedAtMs,
|
|
175
179
|
lastSuccessfulResponseAtMs: state.lastSuccessfulResponseAtMs,
|
|
176
|
-
lastSuccessfulResponseUpdateCount:
|
|
177
|
-
state.lastSuccessfulResponseUpdateCount,
|
|
180
|
+
lastSuccessfulResponseUpdateCount: state.lastSuccessfulResponseUpdateCount,
|
|
178
181
|
stopReason: state.stopReason,
|
|
179
182
|
};
|
|
180
183
|
}
|
|
@@ -251,7 +254,8 @@ export function createTelegramPollingAdmissionRuntime<TContext>(deps: {
|
|
|
251
254
|
async start(ctx) {
|
|
252
255
|
if (!(deps.canStart?.(ctx) ?? true)) return;
|
|
253
256
|
const expectedGeneration = ++generation;
|
|
254
|
-
const isCurrent = () =>
|
|
257
|
+
const isCurrent = () =>
|
|
258
|
+
expectedGeneration === generation && (deps.canStart?.(ctx) ?? true);
|
|
255
259
|
if (!isCurrent()) return;
|
|
256
260
|
await deps.prepareStart?.();
|
|
257
261
|
if (!isCurrent()) return;
|
|
@@ -321,7 +325,10 @@ export function createTelegramDurablePollingRuntimeAssembly<
|
|
|
321
325
|
if (batch.length > 0) deps.prepareUpdateBatch(batch);
|
|
322
326
|
} catch (error) {
|
|
323
327
|
try {
|
|
324
|
-
deps.recordRuntimeEvent?.("polling", error, {
|
|
328
|
+
deps.recordRuntimeEvent?.("polling", error, {
|
|
329
|
+
phase: "batch-preparation",
|
|
330
|
+
updateCount: updates.length,
|
|
331
|
+
});
|
|
325
332
|
} catch {
|
|
326
333
|
// Already-published input must still reach the worker if diagnostics fail.
|
|
327
334
|
}
|
|
@@ -580,8 +587,13 @@ export function startTelegramPollingRuntime<TContext>(
|
|
|
580
587
|
if (ownsController) deps.setPollingController(undefined);
|
|
581
588
|
if (!ownsPromise && !ownsController) return;
|
|
582
589
|
deps.onPollingStopped?.(
|
|
583
|
-
controller.signal.aborted
|
|
584
|
-
? "
|
|
590
|
+
controller.signal.aborted
|
|
591
|
+
? "requested"
|
|
592
|
+
: persistentConflict
|
|
593
|
+
? "persistent-conflict"
|
|
594
|
+
: failed
|
|
595
|
+
? "failed"
|
|
596
|
+
: "completed",
|
|
585
597
|
);
|
|
586
598
|
// Detach the inner promise before outer teardown calls polling.stop().
|
|
587
599
|
if (persistentConflict && !controller.signal.aborted) {
|
|
@@ -591,11 +603,14 @@ export function startTelegramPollingRuntime<TContext>(
|
|
|
591
603
|
} else {
|
|
592
604
|
deps.stopTypingLoop();
|
|
593
605
|
deps.recordRuntimeEvent?.("polling", persistentConflict, {
|
|
594
|
-
phase: "persistent-conflict",
|
|
606
|
+
phase: "persistent-conflict",
|
|
607
|
+
count: persistentConflict.count,
|
|
595
608
|
});
|
|
596
609
|
}
|
|
597
610
|
} catch (error) {
|
|
598
|
-
deps.recordRuntimeEvent?.("polling", error, {
|
|
611
|
+
deps.recordRuntimeEvent?.("polling", error, {
|
|
612
|
+
phase: "conflict-stand-down",
|
|
613
|
+
});
|
|
599
614
|
}
|
|
600
615
|
if (deps.getPollingController() || deps.getPollingPromise()) return;
|
|
601
616
|
}
|
|
@@ -719,7 +734,9 @@ export interface TelegramThreadTargetObservationBinding<TContext> {
|
|
|
719
734
|
set(handler: TelegramThreadTargetObservationHandler<TContext>): void;
|
|
720
735
|
}
|
|
721
736
|
|
|
722
|
-
export function createTelegramThreadTargetObservationBinding<
|
|
737
|
+
export function createTelegramThreadTargetObservationBinding<
|
|
738
|
+
TContext,
|
|
739
|
+
>(): TelegramThreadTargetObservationBinding<TContext> {
|
|
723
740
|
let handler: TelegramThreadTargetObservationHandler<TContext> | undefined;
|
|
724
741
|
return {
|
|
725
742
|
async handle(ctx) {
|
|
@@ -748,16 +765,8 @@ export interface TelegramThreadAwarePollingPorts<TContext, TOwner> {
|
|
|
748
765
|
stopFollowerRegistration: () => void;
|
|
749
766
|
}
|
|
750
767
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
TOwner,
|
|
754
|
-
> extends TelegramStartupThreadCapabilityProbeDeps {
|
|
755
|
-
lifecycle?: TelegramThreadCapabilityLifecycle;
|
|
756
|
-
isBusRuntimeEnabled: () => boolean;
|
|
757
|
-
isTopicModeUnavailableError: (error: unknown) => boolean;
|
|
758
|
-
getPollingStartedWithTelegramBus: () => boolean;
|
|
759
|
-
setPollingStartedWithTelegramBus: (started: boolean) => void;
|
|
760
|
-
setForceFreshLeaderThreadOnNextStart: (forceFresh: boolean) => void;
|
|
768
|
+
/** Classic/leader polling and follower registration controls that thread-aware polling switches between. */
|
|
769
|
+
export interface TelegramThreadAwarePollingControls<TContext, TOwner> {
|
|
761
770
|
startClassicPolling: (ctx: TContext) => MaybePromise<void>;
|
|
762
771
|
stopClassicPolling: () => Promise<void>;
|
|
763
772
|
startBusLeaderPolling: (ctx: TContext) => Promise<void>;
|
|
@@ -774,35 +783,29 @@ export interface TelegramThreadAwarePollingDeps<
|
|
|
774
783
|
) => Promise<boolean | undefined>;
|
|
775
784
|
hasRememberedWorkspaceBinding?: (ctx: TContext) => boolean;
|
|
776
785
|
stopFollowerRegistration: () => void;
|
|
786
|
+
isBusRuntimeEnabled: () => boolean;
|
|
787
|
+
isTopicModeUnavailableError: (error: unknown) => boolean;
|
|
777
788
|
}
|
|
778
789
|
|
|
779
|
-
export interface
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
>
|
|
790
|
+
export interface TelegramThreadAwarePollingDeps<TContext, TOwner>
|
|
791
|
+
extends
|
|
792
|
+
TelegramStartupThreadCapabilityProbeDeps,
|
|
793
|
+
TelegramThreadAwarePollingControls<TContext, TOwner> {
|
|
794
|
+
lifecycle?: TelegramThreadCapabilityLifecycle;
|
|
795
|
+
getPollingStartedWithTelegramBus: () => boolean;
|
|
796
|
+
setPollingStartedWithTelegramBus: (started: boolean) => void;
|
|
797
|
+
setForceFreshLeaderThreadOnNextStart: (forceFresh: boolean) => void;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
export interface TelegramThreadCapabilityOrchestrationDeps<TContext, TOwner>
|
|
801
|
+
extends
|
|
802
|
+
TelegramThreadCapabilityReaderDeps,
|
|
803
|
+
TelegramThreadAwarePollingControls<TContext, TOwner> {
|
|
783
804
|
state: TelegramThreadCapabilityStateRuntime;
|
|
784
805
|
topicTargetStore: TelegramThreadCapabilityStore;
|
|
785
|
-
isBusRuntimeEnabled: () => boolean;
|
|
786
806
|
ownsLock: (ctx: TContext) => boolean;
|
|
787
807
|
isFollowerRegistered?: () => boolean;
|
|
788
|
-
startClassicPolling: (ctx: TContext) => MaybePromise<void>;
|
|
789
|
-
stopClassicPolling: () => Promise<void>;
|
|
790
|
-
startBusLeaderPolling: (ctx: TContext) => Promise<void>;
|
|
791
|
-
stopBusLeaderPolling: () => Promise<void>;
|
|
792
|
-
startLeaderHealth: () => void;
|
|
793
|
-
stopLeaderHealth: () => void;
|
|
794
|
-
registerFollowerWithLeader: (
|
|
795
|
-
ctx: TContext,
|
|
796
|
-
owner: TOwner,
|
|
797
|
-
) => Promise<boolean | undefined>;
|
|
798
|
-
restoreFollowerWithLeader?: (
|
|
799
|
-
ctx: TContext,
|
|
800
|
-
owner: TOwner,
|
|
801
|
-
) => Promise<boolean | undefined>;
|
|
802
|
-
hasRememberedWorkspaceBinding?: (ctx: TContext) => boolean;
|
|
803
808
|
suspendLiveThreadTarget?: () => void;
|
|
804
|
-
stopFollowerRegistration: () => void;
|
|
805
|
-
isTopicModeUnavailableError: (error: unknown) => boolean;
|
|
806
809
|
updateStatus: (ctx: TContext) => void;
|
|
807
810
|
recordEvent: (
|
|
808
811
|
category: string,
|
|
@@ -852,7 +855,9 @@ export function createTelegramThreadCapabilityOrchestration<TContext, TOwner>(
|
|
|
852
855
|
const expected = generation;
|
|
853
856
|
return () => expected === generation;
|
|
854
857
|
},
|
|
855
|
-
invalidate() {
|
|
858
|
+
invalidate() {
|
|
859
|
+
generation++;
|
|
860
|
+
},
|
|
856
861
|
};
|
|
857
862
|
const capabilityDeps: TelegramThreadCapabilityRuntimeDeps<TContext> = {
|
|
858
863
|
lifecycle,
|
|
@@ -975,6 +980,32 @@ function hasTelegramThreadCapabilityBindings(
|
|
|
975
980
|
);
|
|
976
981
|
}
|
|
977
982
|
|
|
983
|
+
/** Falls back to classic polling, recording a failed restore durably; false once the attempt is stale. */
|
|
984
|
+
async function restoreTelegramClassicPolling<TContext>(
|
|
985
|
+
ctx: TContext,
|
|
986
|
+
phase: string,
|
|
987
|
+
deps: TelegramThreadCapabilityRuntimeDeps<TContext>,
|
|
988
|
+
isCurrent: () => boolean,
|
|
989
|
+
): Promise<boolean> {
|
|
990
|
+
try {
|
|
991
|
+
await deps.startClassicPolling(ctx);
|
|
992
|
+
return isCurrent();
|
|
993
|
+
} catch (classicError) {
|
|
994
|
+
if (!isCurrent()) return false;
|
|
995
|
+
deps.topicTargetStore.setBotState({
|
|
996
|
+
threadMode: "disabled",
|
|
997
|
+
updatedAtMs: (deps.getNowMs ?? Date.now)(),
|
|
998
|
+
lastReconcileAction: `${phase}-classic-restore-failed`,
|
|
999
|
+
});
|
|
1000
|
+
await deps.topicTargetStore.persist();
|
|
1001
|
+
if (!isCurrent()) return false;
|
|
1002
|
+
deps.recordEvent("bus", classicError, {
|
|
1003
|
+
phase: `${phase}-classic-restore`,
|
|
1004
|
+
});
|
|
1005
|
+
return true;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
978
1009
|
export async function applyTelegramThreadCapability<TContext>(
|
|
979
1010
|
ctx: TContext,
|
|
980
1011
|
threadModeEnabled: boolean,
|
|
@@ -1017,22 +1048,8 @@ export async function applyTelegramThreadCapability<TContext>(
|
|
|
1017
1048
|
if (!isCurrent()) return;
|
|
1018
1049
|
deps.setPollingStartedWithTelegramBus(false);
|
|
1019
1050
|
if (hadLiveThreadTransport) deps.suspendLiveThreadTarget?.();
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
if (!isCurrent()) return;
|
|
1023
|
-
} catch (classicError) {
|
|
1024
|
-
if (!isCurrent()) return;
|
|
1025
|
-
deps.topicTargetStore.setBotState({
|
|
1026
|
-
threadMode: "disabled",
|
|
1027
|
-
updatedAtMs: (deps.getNowMs ?? Date.now)(),
|
|
1028
|
-
lastReconcileAction: `${phase}-classic-restore-failed`,
|
|
1029
|
-
});
|
|
1030
|
-
await deps.topicTargetStore.persist();
|
|
1031
|
-
if (!isCurrent()) return;
|
|
1032
|
-
deps.recordEvent("bus", classicError, {
|
|
1033
|
-
phase: `${phase}-classic-restore`,
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1051
|
+
if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
|
|
1052
|
+
return;
|
|
1036
1053
|
}
|
|
1037
1054
|
deps.updateStatus(ctx);
|
|
1038
1055
|
return;
|
|
@@ -1069,22 +1086,8 @@ export async function applyTelegramThreadCapability<TContext>(
|
|
|
1069
1086
|
if (!isCurrent()) return;
|
|
1070
1087
|
deps.setTopicModeUnavailable(true);
|
|
1071
1088
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
if (!isCurrent()) return;
|
|
1075
|
-
} catch (classicError) {
|
|
1076
|
-
if (!isCurrent()) return;
|
|
1077
|
-
deps.topicTargetStore.setBotState({
|
|
1078
|
-
threadMode: "disabled",
|
|
1079
|
-
updatedAtMs: (deps.getNowMs ?? Date.now)(),
|
|
1080
|
-
lastReconcileAction: `${phase}-classic-restore-failed`,
|
|
1081
|
-
});
|
|
1082
|
-
await deps.topicTargetStore.persist();
|
|
1083
|
-
if (!isCurrent()) return;
|
|
1084
|
-
deps.recordEvent("bus", classicError, {
|
|
1085
|
-
phase: `${phase}-classic-restore`,
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1089
|
+
if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
|
|
1090
|
+
return;
|
|
1088
1091
|
deps.updateStatus(ctx);
|
|
1089
1092
|
if (threadModeUnavailable) return;
|
|
1090
1093
|
throw error;
|
|
@@ -1104,12 +1107,16 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
|
|
|
1104
1107
|
const expectedGeneration = ++generation;
|
|
1105
1108
|
deps.lifecycle?.invalidate();
|
|
1106
1109
|
const isLifecycleCurrent = deps.lifecycle?.capture() ?? (() => true);
|
|
1107
|
-
const isCurrent = () =>
|
|
1110
|
+
const isCurrent = () =>
|
|
1111
|
+
expectedGeneration === generation && isLifecycleCurrent();
|
|
1108
1112
|
await deps.topicTargetStore.load();
|
|
1109
1113
|
if (!isCurrent()) return;
|
|
1110
1114
|
let startupThreadCapability: boolean | undefined;
|
|
1111
1115
|
try {
|
|
1112
|
-
startupThreadCapability = await probeTelegramStartupThreadCapability(
|
|
1116
|
+
startupThreadCapability = await probeTelegramStartupThreadCapability(
|
|
1117
|
+
deps,
|
|
1118
|
+
isCurrent,
|
|
1119
|
+
);
|
|
1113
1120
|
} catch (error) {
|
|
1114
1121
|
if (!isCurrent()) return;
|
|
1115
1122
|
deps.recordEvent("bus", error, { phase: "startup-thread-mode-probe" });
|
|
@@ -1156,7 +1163,8 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
|
|
|
1156
1163
|
deps.stopLeaderHealth();
|
|
1157
1164
|
if (deps.getPollingStartedWithTelegramBus()) {
|
|
1158
1165
|
await deps.stopBusLeaderPolling();
|
|
1159
|
-
if (expectedGeneration === generation)
|
|
1166
|
+
if (expectedGeneration === generation)
|
|
1167
|
+
deps.setPollingStartedWithTelegramBus(false);
|
|
1160
1168
|
return;
|
|
1161
1169
|
}
|
|
1162
1170
|
await deps.stopClassicPolling();
|
|
@@ -1181,13 +1189,25 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
|
|
|
1181
1189
|
owner: TOwner,
|
|
1182
1190
|
): Promise<boolean | undefined> => {
|
|
1183
1191
|
if (!(await refreshFollowerState())) {
|
|
1184
|
-
deps.recordEvent(
|
|
1185
|
-
|
|
1192
|
+
deps.recordEvent(
|
|
1193
|
+
"bus",
|
|
1194
|
+
"Telegram follower auto-connect skipped: Threaded Mode is not enabled.",
|
|
1195
|
+
{
|
|
1196
|
+
phase: "follower-auto-connect-skip",
|
|
1197
|
+
reason: "thread-mode-unavailable",
|
|
1198
|
+
},
|
|
1199
|
+
);
|
|
1186
1200
|
return undefined;
|
|
1187
1201
|
}
|
|
1188
1202
|
if (!deps.hasRememberedWorkspaceBinding?.(ctx)) {
|
|
1189
|
-
deps.recordEvent(
|
|
1190
|
-
|
|
1203
|
+
deps.recordEvent(
|
|
1204
|
+
"bus",
|
|
1205
|
+
"Telegram follower auto-connect skipped: no binding for this session.",
|
|
1206
|
+
{
|
|
1207
|
+
phase: "follower-auto-connect-skip",
|
|
1208
|
+
reason: "session-binding-unavailable",
|
|
1209
|
+
},
|
|
1210
|
+
);
|
|
1191
1211
|
return undefined;
|
|
1192
1212
|
}
|
|
1193
1213
|
return deps.restoreFollowerWithLeader?.(ctx, owner);
|
|
@@ -1258,7 +1278,8 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
|
|
|
1258
1278
|
}
|
|
1259
1279
|
const expectedGeneration = generation;
|
|
1260
1280
|
const isLifecycleCurrent = deps.lifecycle?.capture() ?? (() => true);
|
|
1261
|
-
const isCurrent = (): boolean =>
|
|
1281
|
+
const isCurrent = (): boolean =>
|
|
1282
|
+
generation === expectedGeneration && isLifecycleCurrent();
|
|
1262
1283
|
let tracked: Promise<void>;
|
|
1263
1284
|
tracked = readTelegramThreadCapability(deps)
|
|
1264
1285
|
.then(async (threadModeEnabled) => {
|
|
@@ -1351,7 +1372,11 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
|
|
|
1351
1372
|
try {
|
|
1352
1373
|
check(ctx);
|
|
1353
1374
|
} catch (error) {
|
|
1354
|
-
try {
|
|
1375
|
+
try {
|
|
1376
|
+
stop();
|
|
1377
|
+
} catch {
|
|
1378
|
+
/* Timer shutdown must not escape the callback. */
|
|
1379
|
+
}
|
|
1355
1380
|
try {
|
|
1356
1381
|
deps.recordEvent("bus", error, { phase: "capability-monitor" });
|
|
1357
1382
|
} catch {
|
|
@@ -1475,13 +1500,10 @@ export async function admitTelegramPollingUpdateBatch<
|
|
|
1475
1500
|
return { updateCount: deps.updates.length, latestUpdateId };
|
|
1476
1501
|
}
|
|
1477
1502
|
|
|
1478
|
-
|
|
1503
|
+
/** Bot API, journal and progress ports shared by one poll loop and its restartable runner. */
|
|
1504
|
+
export interface TelegramPollLoopTransportPorts<
|
|
1479
1505
|
TUpdate extends TelegramUpdate,
|
|
1480
|
-
TContext = unknown,
|
|
1481
1506
|
> extends TelegramRuntimeEventRecorderPort {
|
|
1482
|
-
ctx: TContext;
|
|
1483
|
-
signal: AbortSignal;
|
|
1484
|
-
config: TelegramPollingConfig;
|
|
1485
1507
|
deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
|
|
1486
1508
|
getUpdates: (
|
|
1487
1509
|
body: Record<string, unknown>,
|
|
@@ -1496,9 +1518,6 @@ export interface TelegramPollLoopDeps<
|
|
|
1496
1518
|
getAcceptedThroughUpdateId?: () => number | undefined;
|
|
1497
1519
|
getJournalEntryCount: () => number;
|
|
1498
1520
|
signalUpdateWorker: () => void;
|
|
1499
|
-
onErrorStatus: (message: string) => void;
|
|
1500
|
-
onStatusReset: () => void;
|
|
1501
|
-
sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
1502
1521
|
onPhaseChange?: (
|
|
1503
1522
|
phase: TelegramPollingWorkPhase,
|
|
1504
1523
|
currentUpdateId?: number,
|
|
@@ -1506,32 +1525,25 @@ export interface TelegramPollLoopDeps<
|
|
|
1506
1525
|
onSuccessfulResponse?: (updateCount: number) => void;
|
|
1507
1526
|
}
|
|
1508
1527
|
|
|
1528
|
+
export interface TelegramPollLoopDeps<
|
|
1529
|
+
TUpdate extends TelegramUpdate,
|
|
1530
|
+
TContext = unknown,
|
|
1531
|
+
> extends TelegramPollLoopTransportPorts<TUpdate> {
|
|
1532
|
+
ctx: TContext;
|
|
1533
|
+
signal: AbortSignal;
|
|
1534
|
+
config: TelegramPollingConfig;
|
|
1535
|
+
onErrorStatus: (message: string) => void;
|
|
1536
|
+
onStatusReset: () => void;
|
|
1537
|
+
sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1509
1540
|
export interface TelegramPollLoopRunnerDeps<
|
|
1510
1541
|
TUpdate extends TelegramUpdate,
|
|
1511
1542
|
TContext = unknown,
|
|
1512
|
-
> extends
|
|
1543
|
+
> extends TelegramPollLoopTransportPorts<TUpdate> {
|
|
1513
1544
|
getConfig: () => TelegramPollingConfig;
|
|
1514
|
-
deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
|
|
1515
|
-
getUpdates: (
|
|
1516
|
-
body: Record<string, unknown>,
|
|
1517
|
-
signal: AbortSignal,
|
|
1518
|
-
) => Promise<TUpdate[]>;
|
|
1519
|
-
getUpdatesRequestBudgetMs?: (body: Record<string, unknown>) => number;
|
|
1520
|
-
persistConfig: (config: TelegramPollingConfig) => Promise<void>;
|
|
1521
|
-
appendUpdateBatch: (
|
|
1522
|
-
updates: readonly TUpdate[],
|
|
1523
|
-
acceptedThroughUpdateId?: number,
|
|
1524
|
-
) => MaybePromise<unknown>;
|
|
1525
|
-
getAcceptedThroughUpdateId?: () => number | undefined;
|
|
1526
|
-
getJournalEntryCount: () => number;
|
|
1527
|
-
signalUpdateWorker: () => void;
|
|
1528
1545
|
updateStatus: (ctx: TContext, message?: string) => void;
|
|
1529
1546
|
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
1530
|
-
onPhaseChange?: (
|
|
1531
|
-
phase: TelegramPollingWorkPhase,
|
|
1532
|
-
currentUpdateId?: number,
|
|
1533
|
-
) => void;
|
|
1534
|
-
onSuccessfulResponse?: (updateCount: number) => void;
|
|
1535
1547
|
}
|
|
1536
1548
|
|
|
1537
1549
|
export function sleepTelegramPollingRetry(
|
|
@@ -1629,10 +1641,7 @@ function reportTelegramPollingPhase(
|
|
|
1629
1641
|
function reportTelegramPollingResponse<
|
|
1630
1642
|
TUpdate extends TelegramUpdate,
|
|
1631
1643
|
TContext,
|
|
1632
|
-
>(
|
|
1633
|
-
deps: TelegramPollLoopDeps<TUpdate, TContext>,
|
|
1634
|
-
updateCount: number,
|
|
1635
|
-
): void {
|
|
1644
|
+
>(deps: TelegramPollLoopDeps<TUpdate, TContext>, updateCount: number): void {
|
|
1636
1645
|
try {
|
|
1637
1646
|
deps.onSuccessfulResponse?.(updateCount);
|
|
1638
1647
|
} catch (error) {
|
|
@@ -1709,13 +1718,19 @@ export async function runTelegramPollLoop<
|
|
|
1709
1718
|
>(deps: TelegramPollLoopDeps<TUpdate, TContext>): Promise<void> {
|
|
1710
1719
|
if (!deps.config.botToken) return;
|
|
1711
1720
|
let consecutiveGetUpdatesConflicts = 0;
|
|
1721
|
+
let consecutiveFailures = 0;
|
|
1712
1722
|
const retryConflict = async () => {
|
|
1713
1723
|
consecutiveGetUpdatesConflicts += 1;
|
|
1714
|
-
if (
|
|
1715
|
-
|
|
1724
|
+
if (
|
|
1725
|
+
consecutiveGetUpdatesConflicts >= TELEGRAM_GET_UPDATES_CONFLICT_STOP_LIMIT
|
|
1726
|
+
) {
|
|
1727
|
+
throw new TelegramPersistentGetUpdatesConflictError(
|
|
1728
|
+
consecutiveGetUpdatesConflicts,
|
|
1729
|
+
);
|
|
1716
1730
|
}
|
|
1717
1731
|
await deps.sleep(
|
|
1718
|
-
consecutiveGetUpdatesConflicts <
|
|
1732
|
+
consecutiveGetUpdatesConflicts <
|
|
1733
|
+
TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT
|
|
1719
1734
|
? TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS
|
|
1720
1735
|
: TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS,
|
|
1721
1736
|
deps.signal,
|
|
@@ -1734,9 +1749,7 @@ export async function runTelegramPollLoop<
|
|
|
1734
1749
|
"Telegram polling cursor is missing while the durable update journal is non-empty.",
|
|
1735
1750
|
);
|
|
1736
1751
|
}
|
|
1737
|
-
if (
|
|
1738
|
-
deps.getAcceptedThroughUpdateId?.() === undefined
|
|
1739
|
-
) {
|
|
1752
|
+
if (deps.getAcceptedThroughUpdateId?.() === undefined) {
|
|
1740
1753
|
try {
|
|
1741
1754
|
const request = buildTelegramInitialSyncRequest();
|
|
1742
1755
|
reportTelegramPollingPhase(deps, "long-poll");
|
|
@@ -1744,11 +1757,7 @@ export async function runTelegramPollLoop<
|
|
|
1744
1757
|
reportTelegramPollingResponse(deps, updates.length);
|
|
1745
1758
|
const lastUpdateId = getLatestTelegramUpdateId(updates);
|
|
1746
1759
|
if (lastUpdateId !== undefined) {
|
|
1747
|
-
reportTelegramPollingPhase(
|
|
1748
|
-
deps,
|
|
1749
|
-
"persisting-offset",
|
|
1750
|
-
lastUpdateId,
|
|
1751
|
-
);
|
|
1760
|
+
reportTelegramPollingPhase(deps, "persisting-offset", lastUpdateId);
|
|
1752
1761
|
await deps.appendUpdateBatch([], lastUpdateId);
|
|
1753
1762
|
deps.recordRuntimeEvent?.(
|
|
1754
1763
|
"polling",
|
|
@@ -1761,12 +1770,13 @@ export async function runTelegramPollLoop<
|
|
|
1761
1770
|
reportTelegramPollingPhase(deps, "retrying");
|
|
1762
1771
|
if (isTelegramGetUpdatesConflictError(error)) {
|
|
1763
1772
|
await retryConflict();
|
|
1764
|
-
} else
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1773
|
+
} else
|
|
1774
|
+
deps.recordRuntimeEvent?.("polling", error, {
|
|
1775
|
+
phase: "initial-sync",
|
|
1776
|
+
...(error instanceof TelegramGetUpdatesTimeoutError
|
|
1777
|
+
? { timeoutMs: error.timeoutMs }
|
|
1778
|
+
: {}),
|
|
1779
|
+
});
|
|
1770
1780
|
}
|
|
1771
1781
|
}
|
|
1772
1782
|
let currentUpdateId: number | undefined;
|
|
@@ -1791,6 +1801,7 @@ export async function runTelegramPollLoop<
|
|
|
1791
1801
|
onPhaseChange: deps.onPhaseChange,
|
|
1792
1802
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
1793
1803
|
});
|
|
1804
|
+
consecutiveFailures = 0;
|
|
1794
1805
|
currentUpdateId = undefined;
|
|
1795
1806
|
} catch (error) {
|
|
1796
1807
|
if (shouldStopTelegramPolling(deps.signal.aborted, error)) return;
|
|
@@ -1809,8 +1820,13 @@ export async function runTelegramPollLoop<
|
|
|
1809
1820
|
: {}),
|
|
1810
1821
|
});
|
|
1811
1822
|
consecutiveGetUpdatesConflicts = 0;
|
|
1823
|
+
consecutiveFailures += 1;
|
|
1812
1824
|
deps.onErrorStatus(getTelegramPollingErrorMessage(error));
|
|
1813
|
-
|
|
1825
|
+
// Cap the pause, not retry count: outages must recover without manual reconnect.
|
|
1826
|
+
await deps.sleep(
|
|
1827
|
+
getTelegramPollingRetryDelayMs(consecutiveFailures),
|
|
1828
|
+
deps.signal,
|
|
1829
|
+
);
|
|
1814
1830
|
if (deps.signal.aborted) return;
|
|
1815
1831
|
deps.onStatusReset();
|
|
1816
1832
|
}
|