@llblab/pi-telegram 0.52.2 → 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 +9 -7
- package/BACKLOG.md +1 -18
- package/CHANGELOG.md +11 -0
- package/README.md +7 -11
- 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 +639 -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 +29 -3
- package/dist/lib/locks.js +289 -108
- 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 +21 -37
- package/dist/lib/polling.js +59 -50
- 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 +1 -3
- package/dist/lib/sync.js +67 -43
- 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 +148 -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 +31 -29
- 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 +1206 -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 +677 -203
- 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 +144 -147
- 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 +448 -361
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +302 -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
|
@@ -74,7 +74,9 @@ export function getLatestTelegramUpdateId(
|
|
|
74
74
|
export class TelegramPersistentGetUpdatesConflictError extends Error {
|
|
75
75
|
readonly count: number;
|
|
76
76
|
constructor(count: number) {
|
|
77
|
-
super(
|
|
77
|
+
super(
|
|
78
|
+
`Telegram polling stopped after ${count} consecutive getUpdates conflicts.`,
|
|
79
|
+
);
|
|
78
80
|
this.name = "TelegramPersistentGetUpdatesConflictError";
|
|
79
81
|
this.count = count;
|
|
80
82
|
}
|
|
@@ -134,22 +136,13 @@ export interface TelegramPollingStartState {
|
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
export type TelegramPollingWorkPhase =
|
|
137
|
-
|
|
138
|
-
| "persisting-journal"
|
|
139
|
-
| "persisting-offset"
|
|
140
|
-
| "retrying";
|
|
139
|
+
"long-poll" | "persisting-journal" | "persisting-offset" | "retrying";
|
|
141
140
|
|
|
142
141
|
export type TelegramPollingPhase =
|
|
143
|
-
| "
|
|
144
|
-
| "starting"
|
|
145
|
-
| TelegramPollingWorkPhase;
|
|
142
|
+
"stopped" | "starting" | TelegramPollingWorkPhase;
|
|
146
143
|
|
|
147
144
|
export type TelegramPollingStopReason =
|
|
148
|
-
|
|
149
|
-
| "requested"
|
|
150
|
-
| "completed"
|
|
151
|
-
| "failed"
|
|
152
|
-
| "persistent-conflict";
|
|
145
|
+
"not-started" | "requested" | "completed" | "failed" | "persistent-conflict";
|
|
153
146
|
|
|
154
147
|
export interface TelegramPollingStateSnapshot {
|
|
155
148
|
phase: TelegramPollingPhase;
|
|
@@ -162,8 +155,7 @@ export interface TelegramPollingStateSnapshot {
|
|
|
162
155
|
stopReason?: TelegramPollingStopReason;
|
|
163
156
|
}
|
|
164
157
|
|
|
165
|
-
export interface TelegramPollingControllerState
|
|
166
|
-
extends TelegramPollingStateSnapshot {
|
|
158
|
+
export interface TelegramPollingControllerState extends TelegramPollingStateSnapshot {
|
|
167
159
|
pollingPromise?: Promise<void>;
|
|
168
160
|
pollingController?: AbortController;
|
|
169
161
|
}
|
|
@@ -185,8 +177,7 @@ function getTelegramPollingStateSnapshot(
|
|
|
185
177
|
startedAtMs: state.startedAtMs,
|
|
186
178
|
stoppedAtMs: state.stoppedAtMs,
|
|
187
179
|
lastSuccessfulResponseAtMs: state.lastSuccessfulResponseAtMs,
|
|
188
|
-
lastSuccessfulResponseUpdateCount:
|
|
189
|
-
state.lastSuccessfulResponseUpdateCount,
|
|
180
|
+
lastSuccessfulResponseUpdateCount: state.lastSuccessfulResponseUpdateCount,
|
|
190
181
|
stopReason: state.stopReason,
|
|
191
182
|
};
|
|
192
183
|
}
|
|
@@ -263,7 +254,8 @@ export function createTelegramPollingAdmissionRuntime<TContext>(deps: {
|
|
|
263
254
|
async start(ctx) {
|
|
264
255
|
if (!(deps.canStart?.(ctx) ?? true)) return;
|
|
265
256
|
const expectedGeneration = ++generation;
|
|
266
|
-
const isCurrent = () =>
|
|
257
|
+
const isCurrent = () =>
|
|
258
|
+
expectedGeneration === generation && (deps.canStart?.(ctx) ?? true);
|
|
267
259
|
if (!isCurrent()) return;
|
|
268
260
|
await deps.prepareStart?.();
|
|
269
261
|
if (!isCurrent()) return;
|
|
@@ -333,7 +325,10 @@ export function createTelegramDurablePollingRuntimeAssembly<
|
|
|
333
325
|
if (batch.length > 0) deps.prepareUpdateBatch(batch);
|
|
334
326
|
} catch (error) {
|
|
335
327
|
try {
|
|
336
|
-
deps.recordRuntimeEvent?.("polling", error, {
|
|
328
|
+
deps.recordRuntimeEvent?.("polling", error, {
|
|
329
|
+
phase: "batch-preparation",
|
|
330
|
+
updateCount: updates.length,
|
|
331
|
+
});
|
|
337
332
|
} catch {
|
|
338
333
|
// Already-published input must still reach the worker if diagnostics fail.
|
|
339
334
|
}
|
|
@@ -592,8 +587,13 @@ export function startTelegramPollingRuntime<TContext>(
|
|
|
592
587
|
if (ownsController) deps.setPollingController(undefined);
|
|
593
588
|
if (!ownsPromise && !ownsController) return;
|
|
594
589
|
deps.onPollingStopped?.(
|
|
595
|
-
controller.signal.aborted
|
|
596
|
-
? "
|
|
590
|
+
controller.signal.aborted
|
|
591
|
+
? "requested"
|
|
592
|
+
: persistentConflict
|
|
593
|
+
? "persistent-conflict"
|
|
594
|
+
: failed
|
|
595
|
+
? "failed"
|
|
596
|
+
: "completed",
|
|
597
597
|
);
|
|
598
598
|
// Detach the inner promise before outer teardown calls polling.stop().
|
|
599
599
|
if (persistentConflict && !controller.signal.aborted) {
|
|
@@ -603,11 +603,14 @@ export function startTelegramPollingRuntime<TContext>(
|
|
|
603
603
|
} else {
|
|
604
604
|
deps.stopTypingLoop();
|
|
605
605
|
deps.recordRuntimeEvent?.("polling", persistentConflict, {
|
|
606
|
-
phase: "persistent-conflict",
|
|
606
|
+
phase: "persistent-conflict",
|
|
607
|
+
count: persistentConflict.count,
|
|
607
608
|
});
|
|
608
609
|
}
|
|
609
610
|
} catch (error) {
|
|
610
|
-
deps.recordRuntimeEvent?.("polling", error, {
|
|
611
|
+
deps.recordRuntimeEvent?.("polling", error, {
|
|
612
|
+
phase: "conflict-stand-down",
|
|
613
|
+
});
|
|
611
614
|
}
|
|
612
615
|
if (deps.getPollingController() || deps.getPollingPromise()) return;
|
|
613
616
|
}
|
|
@@ -731,7 +734,9 @@ export interface TelegramThreadTargetObservationBinding<TContext> {
|
|
|
731
734
|
set(handler: TelegramThreadTargetObservationHandler<TContext>): void;
|
|
732
735
|
}
|
|
733
736
|
|
|
734
|
-
export function createTelegramThreadTargetObservationBinding<
|
|
737
|
+
export function createTelegramThreadTargetObservationBinding<
|
|
738
|
+
TContext,
|
|
739
|
+
>(): TelegramThreadTargetObservationBinding<TContext> {
|
|
735
740
|
let handler: TelegramThreadTargetObservationHandler<TContext> | undefined;
|
|
736
741
|
return {
|
|
737
742
|
async handle(ctx) {
|
|
@@ -760,16 +765,8 @@ export interface TelegramThreadAwarePollingPorts<TContext, TOwner> {
|
|
|
760
765
|
stopFollowerRegistration: () => void;
|
|
761
766
|
}
|
|
762
767
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
TOwner,
|
|
766
|
-
> extends TelegramStartupThreadCapabilityProbeDeps {
|
|
767
|
-
lifecycle?: TelegramThreadCapabilityLifecycle;
|
|
768
|
-
isBusRuntimeEnabled: () => boolean;
|
|
769
|
-
isTopicModeUnavailableError: (error: unknown) => boolean;
|
|
770
|
-
getPollingStartedWithTelegramBus: () => boolean;
|
|
771
|
-
setPollingStartedWithTelegramBus: (started: boolean) => void;
|
|
772
|
-
setForceFreshLeaderThreadOnNextStart: (forceFresh: boolean) => void;
|
|
768
|
+
/** Classic/leader polling and follower registration controls that thread-aware polling switches between. */
|
|
769
|
+
export interface TelegramThreadAwarePollingControls<TContext, TOwner> {
|
|
773
770
|
startClassicPolling: (ctx: TContext) => MaybePromise<void>;
|
|
774
771
|
stopClassicPolling: () => Promise<void>;
|
|
775
772
|
startBusLeaderPolling: (ctx: TContext) => Promise<void>;
|
|
@@ -786,35 +783,29 @@ export interface TelegramThreadAwarePollingDeps<
|
|
|
786
783
|
) => Promise<boolean | undefined>;
|
|
787
784
|
hasRememberedWorkspaceBinding?: (ctx: TContext) => boolean;
|
|
788
785
|
stopFollowerRegistration: () => void;
|
|
786
|
+
isBusRuntimeEnabled: () => boolean;
|
|
787
|
+
isTopicModeUnavailableError: (error: unknown) => boolean;
|
|
789
788
|
}
|
|
790
789
|
|
|
791
|
-
export interface
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
>
|
|
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> {
|
|
795
804
|
state: TelegramThreadCapabilityStateRuntime;
|
|
796
805
|
topicTargetStore: TelegramThreadCapabilityStore;
|
|
797
|
-
isBusRuntimeEnabled: () => boolean;
|
|
798
806
|
ownsLock: (ctx: TContext) => boolean;
|
|
799
807
|
isFollowerRegistered?: () => boolean;
|
|
800
|
-
startClassicPolling: (ctx: TContext) => MaybePromise<void>;
|
|
801
|
-
stopClassicPolling: () => Promise<void>;
|
|
802
|
-
startBusLeaderPolling: (ctx: TContext) => Promise<void>;
|
|
803
|
-
stopBusLeaderPolling: () => Promise<void>;
|
|
804
|
-
startLeaderHealth: () => void;
|
|
805
|
-
stopLeaderHealth: () => void;
|
|
806
|
-
registerFollowerWithLeader: (
|
|
807
|
-
ctx: TContext,
|
|
808
|
-
owner: TOwner,
|
|
809
|
-
) => Promise<boolean | undefined>;
|
|
810
|
-
restoreFollowerWithLeader?: (
|
|
811
|
-
ctx: TContext,
|
|
812
|
-
owner: TOwner,
|
|
813
|
-
) => Promise<boolean | undefined>;
|
|
814
|
-
hasRememberedWorkspaceBinding?: (ctx: TContext) => boolean;
|
|
815
808
|
suspendLiveThreadTarget?: () => void;
|
|
816
|
-
stopFollowerRegistration: () => void;
|
|
817
|
-
isTopicModeUnavailableError: (error: unknown) => boolean;
|
|
818
809
|
updateStatus: (ctx: TContext) => void;
|
|
819
810
|
recordEvent: (
|
|
820
811
|
category: string,
|
|
@@ -864,7 +855,9 @@ export function createTelegramThreadCapabilityOrchestration<TContext, TOwner>(
|
|
|
864
855
|
const expected = generation;
|
|
865
856
|
return () => expected === generation;
|
|
866
857
|
},
|
|
867
|
-
invalidate() {
|
|
858
|
+
invalidate() {
|
|
859
|
+
generation++;
|
|
860
|
+
},
|
|
868
861
|
};
|
|
869
862
|
const capabilityDeps: TelegramThreadCapabilityRuntimeDeps<TContext> = {
|
|
870
863
|
lifecycle,
|
|
@@ -987,6 +980,32 @@ function hasTelegramThreadCapabilityBindings(
|
|
|
987
980
|
);
|
|
988
981
|
}
|
|
989
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
|
+
|
|
990
1009
|
export async function applyTelegramThreadCapability<TContext>(
|
|
991
1010
|
ctx: TContext,
|
|
992
1011
|
threadModeEnabled: boolean,
|
|
@@ -1029,22 +1048,8 @@ export async function applyTelegramThreadCapability<TContext>(
|
|
|
1029
1048
|
if (!isCurrent()) return;
|
|
1030
1049
|
deps.setPollingStartedWithTelegramBus(false);
|
|
1031
1050
|
if (hadLiveThreadTransport) deps.suspendLiveThreadTarget?.();
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
if (!isCurrent()) return;
|
|
1035
|
-
} catch (classicError) {
|
|
1036
|
-
if (!isCurrent()) return;
|
|
1037
|
-
deps.topicTargetStore.setBotState({
|
|
1038
|
-
threadMode: "disabled",
|
|
1039
|
-
updatedAtMs: (deps.getNowMs ?? Date.now)(),
|
|
1040
|
-
lastReconcileAction: `${phase}-classic-restore-failed`,
|
|
1041
|
-
});
|
|
1042
|
-
await deps.topicTargetStore.persist();
|
|
1043
|
-
if (!isCurrent()) return;
|
|
1044
|
-
deps.recordEvent("bus", classicError, {
|
|
1045
|
-
phase: `${phase}-classic-restore`,
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1051
|
+
if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
|
|
1052
|
+
return;
|
|
1048
1053
|
}
|
|
1049
1054
|
deps.updateStatus(ctx);
|
|
1050
1055
|
return;
|
|
@@ -1081,22 +1086,8 @@ export async function applyTelegramThreadCapability<TContext>(
|
|
|
1081
1086
|
if (!isCurrent()) return;
|
|
1082
1087
|
deps.setTopicModeUnavailable(true);
|
|
1083
1088
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
if (!isCurrent()) return;
|
|
1087
|
-
} catch (classicError) {
|
|
1088
|
-
if (!isCurrent()) return;
|
|
1089
|
-
deps.topicTargetStore.setBotState({
|
|
1090
|
-
threadMode: "disabled",
|
|
1091
|
-
updatedAtMs: (deps.getNowMs ?? Date.now)(),
|
|
1092
|
-
lastReconcileAction: `${phase}-classic-restore-failed`,
|
|
1093
|
-
});
|
|
1094
|
-
await deps.topicTargetStore.persist();
|
|
1095
|
-
if (!isCurrent()) return;
|
|
1096
|
-
deps.recordEvent("bus", classicError, {
|
|
1097
|
-
phase: `${phase}-classic-restore`,
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1089
|
+
if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
|
|
1090
|
+
return;
|
|
1100
1091
|
deps.updateStatus(ctx);
|
|
1101
1092
|
if (threadModeUnavailable) return;
|
|
1102
1093
|
throw error;
|
|
@@ -1116,12 +1107,16 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
|
|
|
1116
1107
|
const expectedGeneration = ++generation;
|
|
1117
1108
|
deps.lifecycle?.invalidate();
|
|
1118
1109
|
const isLifecycleCurrent = deps.lifecycle?.capture() ?? (() => true);
|
|
1119
|
-
const isCurrent = () =>
|
|
1110
|
+
const isCurrent = () =>
|
|
1111
|
+
expectedGeneration === generation && isLifecycleCurrent();
|
|
1120
1112
|
await deps.topicTargetStore.load();
|
|
1121
1113
|
if (!isCurrent()) return;
|
|
1122
1114
|
let startupThreadCapability: boolean | undefined;
|
|
1123
1115
|
try {
|
|
1124
|
-
startupThreadCapability = await probeTelegramStartupThreadCapability(
|
|
1116
|
+
startupThreadCapability = await probeTelegramStartupThreadCapability(
|
|
1117
|
+
deps,
|
|
1118
|
+
isCurrent,
|
|
1119
|
+
);
|
|
1125
1120
|
} catch (error) {
|
|
1126
1121
|
if (!isCurrent()) return;
|
|
1127
1122
|
deps.recordEvent("bus", error, { phase: "startup-thread-mode-probe" });
|
|
@@ -1168,7 +1163,8 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
|
|
|
1168
1163
|
deps.stopLeaderHealth();
|
|
1169
1164
|
if (deps.getPollingStartedWithTelegramBus()) {
|
|
1170
1165
|
await deps.stopBusLeaderPolling();
|
|
1171
|
-
if (expectedGeneration === generation)
|
|
1166
|
+
if (expectedGeneration === generation)
|
|
1167
|
+
deps.setPollingStartedWithTelegramBus(false);
|
|
1172
1168
|
return;
|
|
1173
1169
|
}
|
|
1174
1170
|
await deps.stopClassicPolling();
|
|
@@ -1193,13 +1189,25 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
|
|
|
1193
1189
|
owner: TOwner,
|
|
1194
1190
|
): Promise<boolean | undefined> => {
|
|
1195
1191
|
if (!(await refreshFollowerState())) {
|
|
1196
|
-
deps.recordEvent(
|
|
1197
|
-
|
|
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
|
+
);
|
|
1198
1200
|
return undefined;
|
|
1199
1201
|
}
|
|
1200
1202
|
if (!deps.hasRememberedWorkspaceBinding?.(ctx)) {
|
|
1201
|
-
deps.recordEvent(
|
|
1202
|
-
|
|
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
|
+
);
|
|
1203
1211
|
return undefined;
|
|
1204
1212
|
}
|
|
1205
1213
|
return deps.restoreFollowerWithLeader?.(ctx, owner);
|
|
@@ -1270,7 +1278,8 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
|
|
|
1270
1278
|
}
|
|
1271
1279
|
const expectedGeneration = generation;
|
|
1272
1280
|
const isLifecycleCurrent = deps.lifecycle?.capture() ?? (() => true);
|
|
1273
|
-
const isCurrent = (): boolean =>
|
|
1281
|
+
const isCurrent = (): boolean =>
|
|
1282
|
+
generation === expectedGeneration && isLifecycleCurrent();
|
|
1274
1283
|
let tracked: Promise<void>;
|
|
1275
1284
|
tracked = readTelegramThreadCapability(deps)
|
|
1276
1285
|
.then(async (threadModeEnabled) => {
|
|
@@ -1363,7 +1372,11 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
|
|
|
1363
1372
|
try {
|
|
1364
1373
|
check(ctx);
|
|
1365
1374
|
} catch (error) {
|
|
1366
|
-
try {
|
|
1375
|
+
try {
|
|
1376
|
+
stop();
|
|
1377
|
+
} catch {
|
|
1378
|
+
/* Timer shutdown must not escape the callback. */
|
|
1379
|
+
}
|
|
1367
1380
|
try {
|
|
1368
1381
|
deps.recordEvent("bus", error, { phase: "capability-monitor" });
|
|
1369
1382
|
} catch {
|
|
@@ -1487,13 +1500,10 @@ export async function admitTelegramPollingUpdateBatch<
|
|
|
1487
1500
|
return { updateCount: deps.updates.length, latestUpdateId };
|
|
1488
1501
|
}
|
|
1489
1502
|
|
|
1490
|
-
|
|
1503
|
+
/** Bot API, journal and progress ports shared by one poll loop and its restartable runner. */
|
|
1504
|
+
export interface TelegramPollLoopTransportPorts<
|
|
1491
1505
|
TUpdate extends TelegramUpdate,
|
|
1492
|
-
TContext = unknown,
|
|
1493
1506
|
> extends TelegramRuntimeEventRecorderPort {
|
|
1494
|
-
ctx: TContext;
|
|
1495
|
-
signal: AbortSignal;
|
|
1496
|
-
config: TelegramPollingConfig;
|
|
1497
1507
|
deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
|
|
1498
1508
|
getUpdates: (
|
|
1499
1509
|
body: Record<string, unknown>,
|
|
@@ -1508,9 +1518,6 @@ export interface TelegramPollLoopDeps<
|
|
|
1508
1518
|
getAcceptedThroughUpdateId?: () => number | undefined;
|
|
1509
1519
|
getJournalEntryCount: () => number;
|
|
1510
1520
|
signalUpdateWorker: () => void;
|
|
1511
|
-
onErrorStatus: (message: string) => void;
|
|
1512
|
-
onStatusReset: () => void;
|
|
1513
|
-
sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
1514
1521
|
onPhaseChange?: (
|
|
1515
1522
|
phase: TelegramPollingWorkPhase,
|
|
1516
1523
|
currentUpdateId?: number,
|
|
@@ -1518,32 +1525,25 @@ export interface TelegramPollLoopDeps<
|
|
|
1518
1525
|
onSuccessfulResponse?: (updateCount: number) => void;
|
|
1519
1526
|
}
|
|
1520
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
|
+
|
|
1521
1540
|
export interface TelegramPollLoopRunnerDeps<
|
|
1522
1541
|
TUpdate extends TelegramUpdate,
|
|
1523
1542
|
TContext = unknown,
|
|
1524
|
-
> extends
|
|
1543
|
+
> extends TelegramPollLoopTransportPorts<TUpdate> {
|
|
1525
1544
|
getConfig: () => TelegramPollingConfig;
|
|
1526
|
-
deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
|
|
1527
|
-
getUpdates: (
|
|
1528
|
-
body: Record<string, unknown>,
|
|
1529
|
-
signal: AbortSignal,
|
|
1530
|
-
) => Promise<TUpdate[]>;
|
|
1531
|
-
getUpdatesRequestBudgetMs?: (body: Record<string, unknown>) => number;
|
|
1532
|
-
persistConfig: (config: TelegramPollingConfig) => Promise<void>;
|
|
1533
|
-
appendUpdateBatch: (
|
|
1534
|
-
updates: readonly TUpdate[],
|
|
1535
|
-
acceptedThroughUpdateId?: number,
|
|
1536
|
-
) => MaybePromise<unknown>;
|
|
1537
|
-
getAcceptedThroughUpdateId?: () => number | undefined;
|
|
1538
|
-
getJournalEntryCount: () => number;
|
|
1539
|
-
signalUpdateWorker: () => void;
|
|
1540
1545
|
updateStatus: (ctx: TContext, message?: string) => void;
|
|
1541
1546
|
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
1542
|
-
onPhaseChange?: (
|
|
1543
|
-
phase: TelegramPollingWorkPhase,
|
|
1544
|
-
currentUpdateId?: number,
|
|
1545
|
-
) => void;
|
|
1546
|
-
onSuccessfulResponse?: (updateCount: number) => void;
|
|
1547
1547
|
}
|
|
1548
1548
|
|
|
1549
1549
|
export function sleepTelegramPollingRetry(
|
|
@@ -1641,10 +1641,7 @@ function reportTelegramPollingPhase(
|
|
|
1641
1641
|
function reportTelegramPollingResponse<
|
|
1642
1642
|
TUpdate extends TelegramUpdate,
|
|
1643
1643
|
TContext,
|
|
1644
|
-
>(
|
|
1645
|
-
deps: TelegramPollLoopDeps<TUpdate, TContext>,
|
|
1646
|
-
updateCount: number,
|
|
1647
|
-
): void {
|
|
1644
|
+
>(deps: TelegramPollLoopDeps<TUpdate, TContext>, updateCount: number): void {
|
|
1648
1645
|
try {
|
|
1649
1646
|
deps.onSuccessfulResponse?.(updateCount);
|
|
1650
1647
|
} catch (error) {
|
|
@@ -1724,11 +1721,16 @@ export async function runTelegramPollLoop<
|
|
|
1724
1721
|
let consecutiveFailures = 0;
|
|
1725
1722
|
const retryConflict = async () => {
|
|
1726
1723
|
consecutiveGetUpdatesConflicts += 1;
|
|
1727
|
-
if (
|
|
1728
|
-
|
|
1724
|
+
if (
|
|
1725
|
+
consecutiveGetUpdatesConflicts >= TELEGRAM_GET_UPDATES_CONFLICT_STOP_LIMIT
|
|
1726
|
+
) {
|
|
1727
|
+
throw new TelegramPersistentGetUpdatesConflictError(
|
|
1728
|
+
consecutiveGetUpdatesConflicts,
|
|
1729
|
+
);
|
|
1729
1730
|
}
|
|
1730
1731
|
await deps.sleep(
|
|
1731
|
-
consecutiveGetUpdatesConflicts <
|
|
1732
|
+
consecutiveGetUpdatesConflicts <
|
|
1733
|
+
TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT
|
|
1732
1734
|
? TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS
|
|
1733
1735
|
: TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS,
|
|
1734
1736
|
deps.signal,
|
|
@@ -1747,9 +1749,7 @@ export async function runTelegramPollLoop<
|
|
|
1747
1749
|
"Telegram polling cursor is missing while the durable update journal is non-empty.",
|
|
1748
1750
|
);
|
|
1749
1751
|
}
|
|
1750
|
-
if (
|
|
1751
|
-
deps.getAcceptedThroughUpdateId?.() === undefined
|
|
1752
|
-
) {
|
|
1752
|
+
if (deps.getAcceptedThroughUpdateId?.() === undefined) {
|
|
1753
1753
|
try {
|
|
1754
1754
|
const request = buildTelegramInitialSyncRequest();
|
|
1755
1755
|
reportTelegramPollingPhase(deps, "long-poll");
|
|
@@ -1757,11 +1757,7 @@ export async function runTelegramPollLoop<
|
|
|
1757
1757
|
reportTelegramPollingResponse(deps, updates.length);
|
|
1758
1758
|
const lastUpdateId = getLatestTelegramUpdateId(updates);
|
|
1759
1759
|
if (lastUpdateId !== undefined) {
|
|
1760
|
-
reportTelegramPollingPhase(
|
|
1761
|
-
deps,
|
|
1762
|
-
"persisting-offset",
|
|
1763
|
-
lastUpdateId,
|
|
1764
|
-
);
|
|
1760
|
+
reportTelegramPollingPhase(deps, "persisting-offset", lastUpdateId);
|
|
1765
1761
|
await deps.appendUpdateBatch([], lastUpdateId);
|
|
1766
1762
|
deps.recordRuntimeEvent?.(
|
|
1767
1763
|
"polling",
|
|
@@ -1774,12 +1770,13 @@ export async function runTelegramPollLoop<
|
|
|
1774
1770
|
reportTelegramPollingPhase(deps, "retrying");
|
|
1775
1771
|
if (isTelegramGetUpdatesConflictError(error)) {
|
|
1776
1772
|
await retryConflict();
|
|
1777
|
-
} else
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1773
|
+
} else
|
|
1774
|
+
deps.recordRuntimeEvent?.("polling", error, {
|
|
1775
|
+
phase: "initial-sync",
|
|
1776
|
+
...(error instanceof TelegramGetUpdatesTimeoutError
|
|
1777
|
+
? { timeoutMs: error.timeoutMs }
|
|
1778
|
+
: {}),
|
|
1779
|
+
});
|
|
1783
1780
|
}
|
|
1784
1781
|
}
|
|
1785
1782
|
let currentUpdateId: number | undefined;
|