@llblab/pi-kit 0.20.0 → 0.21.1
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/CHANGELOG.md +13 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +6 -6
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +9 -0
- package/node_modules/@llblab/pi-state-flow/README.md +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/index.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +7 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +41 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +94 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +2 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +4 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +8 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +6 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +19 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +5 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +27 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +10 -8
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +4 -3
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +5 -5
- package/node_modules/@llblab/pi-state-flow/index.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +8 -3
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +41 -9
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +83 -1
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +2 -4
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +10 -4
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +23 -6
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +29 -3
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +2 -2
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +3 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +6 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +5 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +8 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +63 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +4 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +14 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +5 -0
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +7 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +13 -2
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +19 -0
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +9 -0
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +75 -16
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +18 -0
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +7 -0
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
|
@@ -9,6 +9,7 @@ import { basename } from "node:path";
|
|
|
9
9
|
import * as Sync from "./sync.js";
|
|
10
10
|
import * as Threads from "./threads.js";
|
|
11
11
|
import { parseTelegramUpdateJournalQueueOwner } from "./journal.js";
|
|
12
|
+
import { TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, } from "./locks.js";
|
|
12
13
|
import { isTelegramApiMethodRetrySafe, TelegramApiCommitUnknownError, TelegramApiStaleTargetError, } from "./telegram-api.js";
|
|
13
14
|
import { createTelegramBusFollowerDeliveryIdentity, createTelegramBusFollowerTargetController, createTelegramBusForeignOwnedUpdateForwarder, createTelegramBusLocalServer, createTelegramBusRequestIdFactory, createUnauthorizedBusAck, getTelegramBusProtocolCompatibility, getTelegramBusSocketPath, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, resolveTelegramBusSocketPath, sendTelegramBusLocalEnvelope, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, } from "./bus.js";
|
|
14
15
|
import { getTelegramBusTransportRetryPolicy, TELEGRAM_BUS_REGISTRATION_RETRY, } from "./bus-transport.js";
|
|
@@ -17,6 +18,7 @@ export const TELEGRAM_BUS_FOLLOWER_PROMOTION_GRACE_MS = 2_500;
|
|
|
17
18
|
export const TELEGRAM_FOLLOWER_SESSION_HANDOFF_TTL_MS = 30_000;
|
|
18
19
|
export const TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS = 30_000;
|
|
19
20
|
export const TELEGRAM_BUS_FOLLOWER_REGISTRATION_WAIT_MS = 30_000;
|
|
21
|
+
export const TELEGRAM_BUS_FOLLOWER_HEARTBEAT_TIMEOUT_MS = TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS;
|
|
20
22
|
export const TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_ATTEMPTS = TELEGRAM_BUS_REGISTRATION_RETRY.attempts;
|
|
21
23
|
export const TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_DELAY_MS = TELEGRAM_BUS_REGISTRATION_RETRY.delayMs;
|
|
22
24
|
const TELEGRAM_FOLLOWER_SESSION_HANDOFF_KEY = "__piTelegramFollowerSessionHandoff";
|
|
@@ -920,6 +922,9 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
920
922
|
const getNowMs = deps.getNowMs ?? Date.now;
|
|
921
923
|
const getPid = deps.getPid ?? (() => process.pid);
|
|
922
924
|
const heartbeatMs = deps.heartbeatMs ?? 1000;
|
|
925
|
+
const heartbeatTimeoutMs = deps.heartbeatTimeoutMs ??
|
|
926
|
+
deps.timeoutMs ??
|
|
927
|
+
TELEGRAM_BUS_FOLLOWER_HEARTBEAT_TIMEOUT_MS;
|
|
923
928
|
const registrationTimeoutMs = deps.registrationTimeoutMs ?? deps.timeoutMs ?? 30000;
|
|
924
929
|
const registrationRetryAttempts = deps.registrationRetryAttempts ??
|
|
925
930
|
TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_ATTEMPTS;
|
|
@@ -979,7 +984,7 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
979
984
|
try {
|
|
980
985
|
const response = await sendTelegramBusLocalEnvelope({
|
|
981
986
|
socketPath: leaderSocketPath,
|
|
982
|
-
timeoutMs:
|
|
987
|
+
timeoutMs: heartbeatTimeoutMs,
|
|
983
988
|
retry: getTelegramBusTransportRetryPolicy({
|
|
984
989
|
endpoint: leaderSocketPath,
|
|
985
990
|
operation: "operation",
|
|
@@ -210,6 +210,7 @@ export interface TelegramCommandActionDeps<TMessage, TContext> {
|
|
|
210
210
|
export interface TelegramStopCommandDeps {
|
|
211
211
|
hasAbortHandler: () => boolean;
|
|
212
212
|
clearPendingModelSwitch: () => void;
|
|
213
|
+
cancelNextTransitionAnnouncements?: () => void;
|
|
213
214
|
clearQueuedTelegramItems: () => number;
|
|
214
215
|
setFoldQueuedPromptsIntoHistory: (fold: boolean) => void;
|
|
215
216
|
abortCurrentTurn: () => void;
|
|
@@ -392,6 +393,8 @@ export interface TelegramCommandRuntimeDeps<TMessage extends TelegramCommandRunt
|
|
|
392
393
|
isContextActive?: (ctx: TContext) => boolean;
|
|
393
394
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
394
395
|
requestNextDispatchAnnouncement?: () => void;
|
|
396
|
+
markActiveTurnNextAbortAnnouncement?: () => boolean;
|
|
397
|
+
cancelNextTransitionAnnouncements?: () => void;
|
|
395
398
|
requestDeferredDispatchNextQueuedTelegramTurn?: (dispatch: (ctx: TContext) => void) => void;
|
|
396
399
|
startTypingLoop?: (ctx: TContext, chatId?: number, options?: {
|
|
397
400
|
target?: {
|
|
@@ -504,6 +507,7 @@ export declare function handleTelegramAbortCommand(deps: {
|
|
|
504
507
|
hasAbortHandler: () => boolean;
|
|
505
508
|
hasActiveTelegramTurn: () => boolean;
|
|
506
509
|
clearPendingModelSwitch: () => void;
|
|
510
|
+
cancelNextTransitionAnnouncements?: () => void;
|
|
507
511
|
abortCurrentTurn: () => void;
|
|
508
512
|
setFoldQueuedPromptsIntoHistory: (fold: boolean) => void;
|
|
509
513
|
updateStatus: () => void;
|
|
@@ -519,6 +523,7 @@ export declare function handleTelegramNextCommand(deps: {
|
|
|
519
523
|
abortCurrentTurn: () => void;
|
|
520
524
|
dispatchNextQueuedTurn: () => void;
|
|
521
525
|
requestNextDispatchAnnouncement?: () => void;
|
|
526
|
+
markActiveTurnNextAbortAnnouncement?: () => boolean;
|
|
522
527
|
clearFoldForDispatch: () => void;
|
|
523
528
|
updateStatus: () => void;
|
|
524
529
|
sendTextReply: (text: string, options?: {
|
|
@@ -631,6 +631,7 @@ function formatTelegramQueuedTurnCount(count) {
|
|
|
631
631
|
}
|
|
632
632
|
export async function handleTelegramStopCommand(deps) {
|
|
633
633
|
deps.clearPendingModelSwitch();
|
|
634
|
+
deps.cancelNextTransitionAnnouncements?.();
|
|
634
635
|
const clearedCount = deps.clearQueuedTelegramItems();
|
|
635
636
|
deps.setFoldQueuedPromptsIntoHistory(false);
|
|
636
637
|
if (!deps.hasAbortHandler()) {
|
|
@@ -651,6 +652,7 @@ export async function handleTelegramStopCommand(deps) {
|
|
|
651
652
|
}
|
|
652
653
|
export async function handleTelegramAbortCommand(deps) {
|
|
653
654
|
deps.clearPendingModelSwitch();
|
|
655
|
+
deps.cancelNextTransitionAnnouncements?.();
|
|
654
656
|
if (!deps.hasAbortHandler()) {
|
|
655
657
|
await deps.sendTextReply(formatTelegramInformationHeading("💤", "No active turn."), { parseMode: "HTML" });
|
|
656
658
|
return;
|
|
@@ -669,6 +671,7 @@ export async function handleTelegramNextCommand(deps) {
|
|
|
669
671
|
if (!deps.isIdle() && deps.hasAbortHandler()) {
|
|
670
672
|
deps.clearFoldForDispatch();
|
|
671
673
|
deps.requestNextDispatchAnnouncement?.();
|
|
674
|
+
deps.markActiveTurnNextAbortAnnouncement?.();
|
|
672
675
|
deps.abortCurrentTurn();
|
|
673
676
|
deps.updateStatus();
|
|
674
677
|
return;
|
|
@@ -934,6 +937,9 @@ export function createTelegramCommandHandlerTargetRuntime(deps) {
|
|
|
934
937
|
updateStatus: deps.updateStatus,
|
|
935
938
|
isContextActive: deps.isContextActive,
|
|
936
939
|
dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
|
|
940
|
+
requestNextDispatchAnnouncement: deps.requestNextDispatchAnnouncement,
|
|
941
|
+
markActiveTurnNextAbortAnnouncement: deps.markActiveTurnNextAbortAnnouncement,
|
|
942
|
+
cancelNextTransitionAnnouncements: deps.cancelNextTransitionAnnouncements,
|
|
937
943
|
startTypingLoop: deps.startTypingLoop,
|
|
938
944
|
stopTypingLoop: deps.stopTypingLoop,
|
|
939
945
|
enqueueContinueTurn: deps.enqueueContinueTurn,
|
|
@@ -1039,6 +1045,7 @@ async function handleTelegramCommandRuntime(commandName, message, ctx, deps, com
|
|
|
1039
1045
|
await handleTelegramStopCommand({
|
|
1040
1046
|
hasAbortHandler: deps.hasAbortHandler,
|
|
1041
1047
|
clearPendingModelSwitch: deps.clearPendingModelSwitch,
|
|
1048
|
+
cancelNextTransitionAnnouncements: deps.cancelNextTransitionAnnouncements,
|
|
1042
1049
|
clearQueuedTelegramItems: () => deps.clearQueuedTelegramItems(commandCtx),
|
|
1043
1050
|
setFoldQueuedPromptsIntoHistory: deps.setFoldQueuedPromptsIntoHistory,
|
|
1044
1051
|
abortCurrentTurn: deps.abortCurrentTurn,
|
|
@@ -1085,6 +1092,7 @@ async function handleTelegramCommandRuntime(commandName, message, ctx, deps, com
|
|
|
1085
1092
|
hasAbortHandler: deps.hasAbortHandler,
|
|
1086
1093
|
hasActiveTelegramTurn: deps.hasActiveTelegramTurn,
|
|
1087
1094
|
clearPendingModelSwitch: deps.clearPendingModelSwitch,
|
|
1095
|
+
cancelNextTransitionAnnouncements: deps.cancelNextTransitionAnnouncements,
|
|
1088
1096
|
abortCurrentTurn: deps.abortCurrentTurn,
|
|
1089
1097
|
setFoldQueuedPromptsIntoHistory: deps.setFoldQueuedPromptsIntoHistory,
|
|
1090
1098
|
updateStatus: updateStatusFor(commandCtx),
|
|
@@ -1100,6 +1108,7 @@ async function handleTelegramCommandRuntime(commandName, message, ctx, deps, com
|
|
|
1100
1108
|
abortCurrentTurn: deps.abortCurrentTurn,
|
|
1101
1109
|
dispatchNextQueuedTurn: () => deps.dispatchNextQueuedTelegramTurn(commandCtx),
|
|
1102
1110
|
requestNextDispatchAnnouncement: deps.requestNextDispatchAnnouncement,
|
|
1111
|
+
markActiveTurnNextAbortAnnouncement: deps.markActiveTurnNextAbortAnnouncement,
|
|
1103
1112
|
clearFoldForDispatch: () => deps.setFoldQueuedPromptsIntoHistory(false),
|
|
1104
1113
|
updateStatus: updateStatusFor(commandCtx),
|
|
1105
1114
|
sendTextReply: sendReplyFor(nextMessage),
|
|
@@ -642,7 +642,7 @@ export default function (pi) {
|
|
|
642
642
|
editMessageText: editTelegramMessageText,
|
|
643
643
|
},
|
|
644
644
|
});
|
|
645
|
-
const { mutation: queueMutationRuntime, dispatchNext: dispatchNextQueuedTelegramTurn, requestNextDispatchAnnouncement, watchdog: queueDispatchWatchdogRuntime, } = Bindings.createTelegramQueueBindingRuntime({
|
|
645
|
+
const { mutation: queueMutationRuntime, dispatchNext: dispatchNextQueuedTelegramTurn, requestNextDispatchAnnouncement, cancelNextDispatchAnnouncement, watchdog: queueDispatchWatchdogRuntime, } = Bindings.createTelegramQueueBindingRuntime({
|
|
646
646
|
store: telegramQueueStore,
|
|
647
647
|
queue,
|
|
648
648
|
lifecycle,
|
|
@@ -656,6 +656,9 @@ export default function (pi) {
|
|
|
656
656
|
updateStatus,
|
|
657
657
|
sendTextReply,
|
|
658
658
|
sendUserMessage,
|
|
659
|
+
reconcileNextDispatchAnnouncementReplyOwnership(item) {
|
|
660
|
+
Replies.preserveTransportReplyDedupOnNextReset(item.chatId, item.replyToMessageId, item.target);
|
|
661
|
+
},
|
|
659
662
|
recordRuntimeEvent,
|
|
660
663
|
});
|
|
661
664
|
const { finalizeMarkdownPreview, preparePreviewDelivery } = Outbound.createTelegramOutboundTextPreviewRuntime({
|
|
@@ -887,6 +890,7 @@ export default function (pi) {
|
|
|
887
890
|
isContextActive: telegramSessionContextStore.isCurrent,
|
|
888
891
|
dispatchNextQueuedTelegramTurn,
|
|
889
892
|
requestNextDispatchAnnouncement,
|
|
893
|
+
cancelNextDispatchAnnouncement,
|
|
890
894
|
requestDeferredDispatchNextQueuedTelegramTurn: deferredQueueDispatchRuntime.request,
|
|
891
895
|
hasDeferredDispatchContext: deferredQueueDispatchRuntime.isBound,
|
|
892
896
|
startTypingLoop: promptDispatchRuntime.startTypingLoop,
|
|
@@ -84,6 +84,8 @@ export interface PendingTelegramTurn extends TelegramQueueItemBase {
|
|
|
84
84
|
historyText: string;
|
|
85
85
|
priorityEmoji?: string;
|
|
86
86
|
reactionSuppressionEmoji?: string;
|
|
87
|
+
/** Emit the explicit aborted-turn notice when /next settles this active turn. */
|
|
88
|
+
announceNextAbortOnEnd?: boolean;
|
|
87
89
|
/** Turn should preferably be delivered as voice (mirror mode + user sent voice) */
|
|
88
90
|
voiceReplyPreferred?: boolean;
|
|
89
91
|
/** Turn must be delivered as voice (voice mode) */
|
|
@@ -165,6 +167,8 @@ export interface TelegramActiveTurnStore<TTurn extends PendingTelegramTurn = Pen
|
|
|
165
167
|
has: () => boolean;
|
|
166
168
|
set: (turn: TTurn) => void;
|
|
167
169
|
clear: () => void;
|
|
170
|
+
markNextAbortAnnouncement: () => boolean;
|
|
171
|
+
clearNextAbortAnnouncement: () => boolean;
|
|
168
172
|
getChatId: () => number | undefined;
|
|
169
173
|
getTarget: () => TelegramQueueTarget | undefined;
|
|
170
174
|
getReplyToMessageId: () => number | undefined;
|
|
@@ -391,6 +395,7 @@ export interface TelegramAgentEndPlan {
|
|
|
391
395
|
kind: "no-turn" | "aborted" | "error" | "text" | "attachments-only" | "empty";
|
|
392
396
|
shouldClearPreview: boolean;
|
|
393
397
|
shouldDispatchNext: boolean;
|
|
398
|
+
shouldSendAbortMessage: boolean;
|
|
394
399
|
shouldSendErrorMessage: boolean;
|
|
395
400
|
shouldSendAttachmentNotice: boolean;
|
|
396
401
|
}
|
|
@@ -517,6 +522,7 @@ export declare function buildTelegramAgentEndPlan(options: {
|
|
|
517
522
|
hasFinalText: boolean;
|
|
518
523
|
hasQueuedAttachments: boolean;
|
|
519
524
|
foldQueuedPromptsIntoHistory: boolean;
|
|
525
|
+
announceNextAbortOnEnd?: boolean;
|
|
520
526
|
}): TelegramAgentEndPlan;
|
|
521
527
|
export declare function createTelegramAgentEndHook<TTurn extends PendingTelegramTurn, TContext, TMessage, TReplyMarkup = unknown>(deps: TelegramAgentEndHookRuntimeDeps<TTurn, TContext, TMessage, TReplyMarkup>): (event: TelegramAgentEndHookEvent<TMessage>, ctx: TContext, assistantOverride?: TelegramAgentEndAssistantResult) => Promise<void>;
|
|
522
528
|
export declare function handleTelegramAgentEndRuntime<TTurn extends PendingTelegramTurn, TReplyMarkup = unknown>(deps: TelegramAgentEndRuntimeDeps<TTurn, TReplyMarkup>): Promise<void>;
|
|
@@ -759,6 +765,7 @@ export interface TelegramQueueDispatchControllerDeps<TContext = unknown> extends
|
|
|
759
765
|
}>["item"], ctx: TContext) => boolean;
|
|
760
766
|
sendUserMessage: TelegramDispatchRuntimeDeps<TContext>["sendUserMessage"];
|
|
761
767
|
onPromptDispatchFailure: (ctx: TContext, message: string) => void;
|
|
768
|
+
reconcileNextDispatchAnnouncementReplyOwnership?: (item: PendingTelegramTurn) => void;
|
|
762
769
|
isQueueItemTransportActive?: (item: TelegramQueueItem<TContext>) => boolean;
|
|
763
770
|
hasPendingInboundQueueMutationForItem?: (item: TelegramQueueItem<TContext>) => boolean;
|
|
764
771
|
isQueueItemAdmissionReady?: (item: TelegramQueueItem<TContext>) => boolean;
|
|
@@ -768,6 +775,7 @@ export interface TelegramQueueDispatchControllerDeps<TContext = unknown> extends
|
|
|
768
775
|
export interface TelegramQueueDispatchController<TContext = unknown> {
|
|
769
776
|
dispatchNext: (ctx: TContext) => void;
|
|
770
777
|
requestNextDispatchAnnouncement: () => void;
|
|
778
|
+
cancelNextDispatchAnnouncement: () => void;
|
|
771
779
|
}
|
|
772
780
|
export declare function executeTelegramQueueDispatchPlan<TContext = unknown>(plan: TelegramQueueDispatchAction<TContext>, deps: TelegramDispatchRuntimeDeps<TContext>): void;
|
|
773
781
|
export type TelegramQueueDispatchRuntimeDeps<TContext = unknown> = Omit<TelegramQueueDispatchControllerDeps<TContext>, "canDispatch"> & TelegramDispatchReadinessDeps<TContext>;
|
|
@@ -168,6 +168,18 @@ export function createTelegramActiveTurnStore() {
|
|
|
168
168
|
clear: () => {
|
|
169
169
|
activeTurn = undefined;
|
|
170
170
|
},
|
|
171
|
+
markNextAbortAnnouncement: () => {
|
|
172
|
+
if (!activeTurn)
|
|
173
|
+
return false;
|
|
174
|
+
activeTurn.announceNextAbortOnEnd = true;
|
|
175
|
+
return true;
|
|
176
|
+
},
|
|
177
|
+
clearNextAbortAnnouncement: () => {
|
|
178
|
+
if (!activeTurn?.announceNextAbortOnEnd)
|
|
179
|
+
return false;
|
|
180
|
+
delete activeTurn.announceNextAbortOnEnd;
|
|
181
|
+
return true;
|
|
182
|
+
},
|
|
171
183
|
getChatId: () => activeTurn?.chatId,
|
|
172
184
|
getTarget: () => activeTurn?.target ? { ...activeTurn.target } : undefined,
|
|
173
185
|
getReplyToMessageId: () => activeTurn?.replyToMessageId,
|
|
@@ -815,6 +827,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
815
827
|
kind: "no-turn",
|
|
816
828
|
shouldClearPreview: false,
|
|
817
829
|
shouldDispatchNext,
|
|
830
|
+
shouldSendAbortMessage: false,
|
|
818
831
|
shouldSendErrorMessage: false,
|
|
819
832
|
shouldSendAttachmentNotice: false,
|
|
820
833
|
};
|
|
@@ -824,6 +837,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
824
837
|
kind: "aborted",
|
|
825
838
|
shouldClearPreview: true,
|
|
826
839
|
shouldDispatchNext,
|
|
840
|
+
shouldSendAbortMessage: options.announceNextAbortOnEnd === true,
|
|
827
841
|
shouldSendErrorMessage: false,
|
|
828
842
|
shouldSendAttachmentNotice: false,
|
|
829
843
|
};
|
|
@@ -833,6 +847,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
833
847
|
kind: "error",
|
|
834
848
|
shouldClearPreview: true,
|
|
835
849
|
shouldDispatchNext,
|
|
850
|
+
shouldSendAbortMessage: false,
|
|
836
851
|
shouldSendErrorMessage: true,
|
|
837
852
|
shouldSendAttachmentNotice: false,
|
|
838
853
|
};
|
|
@@ -842,6 +857,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
842
857
|
kind: "text",
|
|
843
858
|
shouldClearPreview: false,
|
|
844
859
|
shouldDispatchNext,
|
|
860
|
+
shouldSendAbortMessage: false,
|
|
845
861
|
shouldSendErrorMessage: false,
|
|
846
862
|
shouldSendAttachmentNotice: false,
|
|
847
863
|
};
|
|
@@ -851,6 +867,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
851
867
|
kind: "attachments-only",
|
|
852
868
|
shouldClearPreview: true,
|
|
853
869
|
shouldDispatchNext,
|
|
870
|
+
shouldSendAbortMessage: false,
|
|
854
871
|
shouldSendErrorMessage: false,
|
|
855
872
|
shouldSendAttachmentNotice: true,
|
|
856
873
|
};
|
|
@@ -859,6 +876,7 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
859
876
|
kind: "empty",
|
|
860
877
|
shouldClearPreview: true,
|
|
861
878
|
shouldDispatchNext,
|
|
879
|
+
shouldSendAbortMessage: false,
|
|
862
880
|
shouldSendErrorMessage: false,
|
|
863
881
|
shouldSendAttachmentNotice: false,
|
|
864
882
|
};
|
|
@@ -991,6 +1009,7 @@ export async function handleTelegramAgentEndRuntime(deps) {
|
|
|
991
1009
|
hasFinalText: !!finalText || hasOutboundArtifacts,
|
|
992
1010
|
hasQueuedAttachments: (turn?.queuedAttachments.length ?? 0) > 0,
|
|
993
1011
|
foldQueuedPromptsIntoHistory: deps.foldQueuedPromptsIntoHistory,
|
|
1012
|
+
announceNextAbortOnEnd: turn?.announceNextAbortOnEnd === true,
|
|
994
1013
|
});
|
|
995
1014
|
if (!turn) {
|
|
996
1015
|
if (endPlan.shouldDispatchNext)
|
|
@@ -1116,17 +1135,26 @@ export async function handleTelegramAgentEndRuntime(deps) {
|
|
|
1116
1135
|
if (!isDeliveryActive())
|
|
1117
1136
|
return;
|
|
1118
1137
|
}
|
|
1119
|
-
if (endPlan.shouldSendErrorMessage) {
|
|
1138
|
+
if (endPlan.shouldSendAbortMessage || endPlan.shouldSendErrorMessage) {
|
|
1120
1139
|
const errorMessage = assistant.errorMessage ||
|
|
1121
1140
|
"Telegram bridge: Pi failed while processing the request.";
|
|
1122
|
-
const isOperationAborted =
|
|
1123
|
-
"This operation was aborted";
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1141
|
+
const isOperationAborted = endPlan.shouldSendAbortMessage ||
|
|
1142
|
+
errorMessage.trim().replace(/\.$/, "") === "This operation was aborted";
|
|
1143
|
+
try {
|
|
1144
|
+
await deps.sendTextReply(turn.chatId, turn.replyToMessageId, isOperationAborted
|
|
1145
|
+
? "<b>⏹️ This operation was aborted.</b>"
|
|
1146
|
+
: errorMessage, {
|
|
1147
|
+
target: turn.target,
|
|
1148
|
+
...(isOperationAborted ? { parseMode: "HTML" } : {}),
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
catch (error) {
|
|
1152
|
+
if (!isOperationAborted)
|
|
1153
|
+
throw error;
|
|
1154
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
1155
|
+
phase: "next-abort-announcement",
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1130
1158
|
if (!isDeliveryActive())
|
|
1131
1159
|
return;
|
|
1132
1160
|
if (endPlan.shouldDispatchNext)
|
|
@@ -1267,7 +1295,7 @@ export async function handleTelegramAgentEndRuntime(deps) {
|
|
|
1267
1295
|
deps.dispatchNextQueuedTelegramTurn();
|
|
1268
1296
|
};
|
|
1269
1297
|
if (deps.scheduleActiveTurnDelivery &&
|
|
1270
|
-
(endPlan.kind === "text" || endPlan.kind === "attachments-only" || endPlan.shouldSendErrorMessage || endPlan.shouldClearPreview)) {
|
|
1298
|
+
(endPlan.kind === "text" || endPlan.kind === "attachments-only" || endPlan.shouldSendAbortMessage || endPlan.shouldSendErrorMessage || endPlan.shouldClearPreview)) {
|
|
1271
1299
|
deps.scheduleActiveTurnDelivery(deliverActiveTurn);
|
|
1272
1300
|
return;
|
|
1273
1301
|
}
|
|
@@ -1765,6 +1793,7 @@ export function createTelegramQueueDispatchRuntime(deps) {
|
|
|
1765
1793
|
commitPromptDispatch: deps.commitPromptDispatch,
|
|
1766
1794
|
sendUserMessage: deps.sendUserMessage,
|
|
1767
1795
|
onPromptDispatchFailure: deps.onPromptDispatchFailure,
|
|
1796
|
+
reconcileNextDispatchAnnouncementReplyOwnership: deps.reconcileNextDispatchAnnouncementReplyOwnership,
|
|
1768
1797
|
isQueueItemTransportActive: deps.isQueueItemTransportActive,
|
|
1769
1798
|
hasPendingInboundQueueMutationForItem: deps.hasPendingInboundQueueMutationForItem,
|
|
1770
1799
|
isQueueItemAdmissionReady: deps.isQueueItemAdmissionReady,
|
|
@@ -1776,12 +1805,19 @@ export function createTelegramQueueDispatchRuntime(deps) {
|
|
|
1776
1805
|
export function createTelegramQueueDispatchController(deps) {
|
|
1777
1806
|
let controlDispatchPending = false;
|
|
1778
1807
|
let nextDispatchAnnouncementRequested = false;
|
|
1808
|
+
let nextDispatchAnnouncementGeneration = 0;
|
|
1779
1809
|
let nextDispatchAnnouncementAnchor;
|
|
1780
1810
|
const controller = {
|
|
1781
1811
|
requestNextDispatchAnnouncement: () => {
|
|
1812
|
+
nextDispatchAnnouncementGeneration += 1;
|
|
1782
1813
|
nextDispatchAnnouncementRequested = true;
|
|
1783
1814
|
nextDispatchAnnouncementAnchor = undefined;
|
|
1784
1815
|
},
|
|
1816
|
+
cancelNextDispatchAnnouncement: () => {
|
|
1817
|
+
nextDispatchAnnouncementGeneration += 1;
|
|
1818
|
+
nextDispatchAnnouncementRequested = false;
|
|
1819
|
+
nextDispatchAnnouncementAnchor = undefined;
|
|
1820
|
+
},
|
|
1785
1821
|
dispatchNext: (ctx) => {
|
|
1786
1822
|
if (deps.hasDispatchContext && !deps.hasDispatchContext())
|
|
1787
1823
|
return;
|
|
@@ -1972,11 +2008,22 @@ export function createTelegramQueueDispatchController(deps) {
|
|
|
1972
2008
|
if (dispatchPlan.kind === "prompt" && nextDispatchAnnouncementRequested) {
|
|
1973
2009
|
nextDispatchAnnouncementRequested = false;
|
|
1974
2010
|
controlDispatchPending = true;
|
|
2011
|
+
const announcementGeneration = nextDispatchAnnouncementGeneration;
|
|
1975
2012
|
const dispatchGeneration = deps.getDispatchGeneration?.();
|
|
1976
2013
|
deps.updateStatus(ctx);
|
|
1977
2014
|
void deps.sendTextReply(dispatchPlan.item.chatId, dispatchPlan.item.replyToMessageId, "<b>⏩ Dispatching next queued turn.</b>", { target: dispatchPlan.item.target }).catch((error) => {
|
|
1978
|
-
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
2015
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
2016
|
+
phase: "next-announcement",
|
|
2017
|
+
});
|
|
1979
2018
|
}).finally(() => {
|
|
2019
|
+
try {
|
|
2020
|
+
deps.reconcileNextDispatchAnnouncementReplyOwnership?.(dispatchPlan.item);
|
|
2021
|
+
}
|
|
2022
|
+
catch (error) {
|
|
2023
|
+
deps.recordRuntimeEvent?.("dispatch", error, {
|
|
2024
|
+
phase: "next-announcement-reply-ownership",
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
1980
2027
|
controlDispatchPending = false;
|
|
1981
2028
|
if (deps.hasDispatchContext && !deps.hasDispatchContext())
|
|
1982
2029
|
return;
|
|
@@ -1984,6 +2031,11 @@ export function createTelegramQueueDispatchController(deps) {
|
|
|
1984
2031
|
deps.isDispatchGenerationActive &&
|
|
1985
2032
|
!deps.isDispatchGenerationActive(dispatchGeneration))
|
|
1986
2033
|
return;
|
|
2034
|
+
if (announcementGeneration !== nextDispatchAnnouncementGeneration) {
|
|
2035
|
+
if (nextDispatchAnnouncementRequested)
|
|
2036
|
+
controller.dispatchNext(ctx);
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
1987
2039
|
executePlan();
|
|
1988
2040
|
});
|
|
1989
2041
|
return;
|
|
@@ -23,6 +23,10 @@ export interface ReplyDedupRuntime {
|
|
|
23
23
|
}
|
|
24
24
|
export declare function createReplyDedupRuntime(): ReplyDedupRuntime;
|
|
25
25
|
export declare function resetTransportReplyDedup(): void;
|
|
26
|
+
/** Keeps a successfully published transition notice as the first reply of the
|
|
27
|
+
* next agent turn. The following agent-start reset consumes this one-shot
|
|
28
|
+
* preservation, so later messages in that turn do not repeat the reply header. */
|
|
29
|
+
export declare function preserveTransportReplyDedupOnNextReset(chatId: number, messageId: number, target?: TelegramTarget): void;
|
|
26
30
|
export declare function buildTelegramReplyParameters(chatId: number, messageId: number | undefined, target?: TelegramTarget): TelegramReplyParameters | undefined;
|
|
27
31
|
export declare function withTelegramReplyParameters<T>(chatId: number, messageId: number | undefined, target: TelegramTarget | undefined, send: (parameters: TelegramReplyParameters | undefined) => Promise<T>): Promise<T>;
|
|
28
32
|
export declare function isAssistantAgentMessage(message: unknown): boolean;
|
|
@@ -31,6 +31,7 @@ export function createReplyDedupRuntime() {
|
|
|
31
31
|
}
|
|
32
32
|
// --- Transport-level dedup ---
|
|
33
33
|
const lastRepliedToMessageIdByTarget = new Map();
|
|
34
|
+
const replyDedupPreservedOnNextReset = new Map();
|
|
34
35
|
let replyDedupGeneration = 0;
|
|
35
36
|
function getReplyDedupTargetKey(chatId, target) {
|
|
36
37
|
const threadId = target?.threadId;
|
|
@@ -41,6 +42,19 @@ function getReplyDedupTargetKey(chatId, target) {
|
|
|
41
42
|
export function resetTransportReplyDedup() {
|
|
42
43
|
replyDedupGeneration += 1;
|
|
43
44
|
lastRepliedToMessageIdByTarget.clear();
|
|
45
|
+
for (const [key, messageId] of replyDedupPreservedOnNextReset) {
|
|
46
|
+
lastRepliedToMessageIdByTarget.set(key, messageId);
|
|
47
|
+
}
|
|
48
|
+
replyDedupPreservedOnNextReset.clear();
|
|
49
|
+
}
|
|
50
|
+
/** Keeps a successfully published transition notice as the first reply of the
|
|
51
|
+
* next agent turn. The following agent-start reset consumes this one-shot
|
|
52
|
+
* preservation, so later messages in that turn do not repeat the reply header. */
|
|
53
|
+
export function preserveTransportReplyDedupOnNextReset(chatId, messageId, target) {
|
|
54
|
+
const key = getReplyDedupTargetKey(chatId, target);
|
|
55
|
+
if (lastRepliedToMessageIdByTarget.get(key) !== messageId)
|
|
56
|
+
return;
|
|
57
|
+
replyDedupPreservedOnNextReset.set(key, messageId);
|
|
44
58
|
}
|
|
45
59
|
export function buildTelegramReplyParameters(chatId, messageId, target) {
|
|
46
60
|
if (messageId === undefined || messageId <= 0)
|
|
@@ -122,6 +122,7 @@ export interface TelegramInboundRouteRuntimeDeps<TMessage extends TelegramRouted
|
|
|
122
122
|
isContextActive?: (ctx: TContext) => boolean;
|
|
123
123
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
124
124
|
requestNextDispatchAnnouncement?: () => void;
|
|
125
|
+
cancelNextDispatchAnnouncement?: () => void;
|
|
125
126
|
requestDeferredDispatchNextQueuedTelegramTurn?: (dispatch: (ctx: TContext) => void) => void;
|
|
126
127
|
hasDeferredDispatchContext?: () => boolean;
|
|
127
128
|
startTypingLoop?: (ctx: TContext, chatId?: number, options?: {
|
|
@@ -1418,6 +1418,10 @@ export function createTelegramInboundRouteRuntime(deps) {
|
|
|
1418
1418
|
isContextActive: deps.isContextActive,
|
|
1419
1419
|
dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
|
|
1420
1420
|
requestNextDispatchAnnouncement: deps.requestNextDispatchAnnouncement,
|
|
1421
|
+
cancelNextTransitionAnnouncements: () => {
|
|
1422
|
+
deps.activeTurnRuntime.clearNextAbortAnnouncement();
|
|
1423
|
+
deps.cancelNextDispatchAnnouncement?.();
|
|
1424
|
+
},
|
|
1421
1425
|
requestDeferredDispatchNextQueuedTelegramTurn: deps.requestDeferredDispatchNextQueuedTelegramTurn,
|
|
1422
1426
|
startTypingLoop: deps.startTypingLoop,
|
|
1423
1427
|
stopTypingLoop: deps.stopTypingLoop,
|
|
@@ -1476,6 +1480,7 @@ export function createTelegramInboundRouteRuntime(deps) {
|
|
|
1476
1480
|
},
|
|
1477
1481
|
getPromptTemplateCommands,
|
|
1478
1482
|
sendTextReply: deps.sendTextReply,
|
|
1483
|
+
markActiveTurnNextAbortAnnouncement: deps.activeTurnRuntime.markNextAbortAnnouncement,
|
|
1479
1484
|
getActiveTurnReply: () => {
|
|
1480
1485
|
const activeTurn = deps.activeTurnRuntime.get();
|
|
1481
1486
|
if (!activeTurn)
|
|
@@ -518,7 +518,7 @@ Immediate controls:
|
|
|
518
518
|
- `/start` opens the main inline application menu.
|
|
519
519
|
- `/model`, `/thinking`, `/queue`, and `/settings` are hidden shortcuts to menu sections.
|
|
520
520
|
- `/compact` opens an inline confirmation dialog and then runs compaction when the bridge is idle.
|
|
521
|
-
- `/next` dispatches the next queued turn, aborting Pi first when needed. Active-turn settlement
|
|
521
|
+
- `/next` dispatches the next queued turn, aborting Pi first when needed. Target-bound command composition must forward the announcement request, exact-turn marker, superseding cancellation, and deferred-dispatch ports rather than silently dropping them. Active-turn settlement attempts its abort notice against the interrupted prompt before queue dispatch continues; definite or ambiguous notice failure is recorded but cannot block the selected turn. The queue owner then emits `Dispatching next queued turn.` against the exact selected prompt's chat/thread/reply id before its one model dispatch; skipped, inactive, pending-mutation, and admission-blocked candidates never receive it. A successful dispatch notice carries its reply-dedup anchor through the following agent-start reset, so later messages in that turn do not repeat the queued-prompt reply header. A later `/abort` or `/stop` cancels both pending `/next` notices before taking ownership, preventing duplicate abort results and stale future dispatch announcements. The `/next` command itself is never a lifecycle-notice reply target. Aborted pending assistant text is not projected as a second reply, while already completed intermediate output remains visible.
|
|
522
522
|
- `/abort` aborts active work while preserving queued items. Abort-history preservation is enabled only for Telegram-owned active turns; later local/non-Telegram agent starts clear stale abort-history mode so the next Telegram prompt appends instead of absorbing old queued turns as history.
|
|
523
523
|
- `/stop` aborts and clears waiting Telegram queue items.
|
|
524
524
|
|
|
@@ -485,7 +485,7 @@ All files containing routing, chat ids, thread ids, or process details use priva
|
|
|
485
485
|
|
|
486
486
|
### Follower heartbeat is missed
|
|
487
487
|
|
|
488
|
-
- Leader tolerates up to 15 seconds without a follower heartbeat before pruning it from the live registry, so short local event-loop or IPC stalls do not create false routing gaps.
|
|
488
|
+
- Leader tolerates up to 15 seconds without a follower heartbeat before pruning it from the live registry, so short local event-loop or IPC stalls do not create false routing gaps. Follower heartbeat requests use a dedicated eight-second response deadline aligned with leader stale-liveness policy rather than the generic one-second local-RPC default. The deadline also defers its final timeout through one socket poll phase: an acknowledgement already buffered while the Pi/TUI event loop was blocked wins, while a genuinely silent peer still fails in the same event-loop turn. Heartbeat pruning remains liveness bookkeeping. One leader generation owns at most one prune operation; stop makes late endpoint, policy, and cleanup settlement inert, while durable-profile mutation serialization prevents replacement registration from crossing confirmed-dead cleanup.
|
|
489
489
|
- A missed heartbeat does not delete, close, mark offline, or send a disconnected notice for the follower's Telegram thread binding because the common cause may be leader reload, IPC handoff, or transient reconnect rather than a dead follower.
|
|
490
490
|
- After removal from live routing, the current leader retains at most 26 volatile preservation observations with exact registered PID, generation, target, profile and leader epoch. The existing prune loop rechecks alive/unverifiable PIDs and retries non-destructive publication only after fresh explicit absence and a known disabled-cleanup policy. Missing identity or overflow stays protected. Observations never answer heartbeat/API/forwarding requests, launch processes, or authorize deletion. Enabling cleanup cancels deferred preservation rather than promoting it into destructive work; the existing first-prune cleanup path is not retried by this mechanism.
|
|
491
491
|
- Registry registration invalidates overlapping instance/profile-owner/target observations, even if that replacement is removed before the next prune. Admitted same-instance provisioning cancels the old observation before its store writes can precede live registration. Profile/epoch drift, registry clear and leader stop invalidate it; late old completion cannot erase a replacement runtime's observation. No observation survives leader restart, reconstructs a PID from an opaque instance ID, or invents legacy inactivity.
|
|
@@ -53,7 +53,7 @@ Stable commands inside the paired Telegram DM:
|
|
|
53
53
|
- `/start` — pair when needed and open the main application menu.
|
|
54
54
|
- `/new` — after idle and empty-queue checks, request a new Pi session in the current classic chat or Thread. The bridge acknowledges the callback, deletes its confirmation, completes and removes the exact durable update, then dispatches one runtime-armed typed action through the internal `/telegram-internal` Pi gateway using `pi.sendUserMessage(..., { expandPromptTemplates: true })`. Manual invocation reports that the gateway cannot be run manually; the armed handler receives a real `ExtensionCommandContext` and calls `ctx.newSession()`; one discriminated durable intent preserves either exact classic Profile/CWD/session/chat continuity or the Thread binding with slot/name re-key. The successor CAS-claims that intent before sending one terminal result. Busy, identity-mismatch, and unavailable-host paths fail closed.
|
|
55
55
|
- `/compact` — open confirmation and compact when idle.
|
|
56
|
-
- `/next` — abort active work first when needed,
|
|
56
|
+
- `/next` — abort active work first when needed, attempt the interrupted prompt's abort notice, then reply `Dispatching next queued turn.` to the exact queued prompt selected for the next model turn. That dispatch notice owns the turn's one reply header; later answer messages do not reply to the same prompt again. Abort-notice failure is diagnostic and cannot block dispatch. A later `/abort` or `/stop` cancels both pending transition notices before taking ownership. The `/next` command itself is never the lifecycle-notice reply target, and aborted pending assistant text is suppressed.
|
|
57
57
|
- `/continue` — enqueue a priority `continue` prompt.
|
|
58
58
|
- `/abort` — abort active work and keep the queue; abort-history is scoped to Telegram-owned active turns.
|
|
59
59
|
- `/stop` — abort active Telegram-owned work and clear waiting Telegram queue items.
|
|
@@ -52,6 +52,7 @@ export interface TelegramQueueBindingRuntime<TContext> {
|
|
|
52
52
|
mutation: Queue.TelegramQueueMutationController<TContext>;
|
|
53
53
|
dispatchNext: (ctx: TContext) => void;
|
|
54
54
|
requestNextDispatchAnnouncement: () => void;
|
|
55
|
+
cancelNextDispatchAnnouncement: () => void;
|
|
55
56
|
watchdog: Queue.TelegramQueueDispatchWatchdogRuntime<TContext>;
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -96,6 +97,9 @@ export function createTelegramQueueBindingRuntime<TContext>(deps: {
|
|
|
96
97
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
97
98
|
sendTextReply: Queue.TelegramQueueDispatchRuntimeDeps<TContext>["sendTextReply"];
|
|
98
99
|
sendUserMessage: Queue.TelegramQueueDispatchRuntimeDeps<TContext>["sendUserMessage"];
|
|
100
|
+
reconcileNextDispatchAnnouncementReplyOwnership?: (
|
|
101
|
+
item: Queue.PendingTelegramTurn,
|
|
102
|
+
) => void;
|
|
99
103
|
recordRuntimeEvent?: TelegramRuntimeEventRecorder;
|
|
100
104
|
}): TelegramQueueBindingRuntime<TContext> {
|
|
101
105
|
const settleDiscardedItems = (
|
|
@@ -159,6 +163,8 @@ export function createTelegramQueueBindingRuntime<TContext>(deps: {
|
|
|
159
163
|
},
|
|
160
164
|
updateStatus: deps.updateStatus,
|
|
161
165
|
sendTextReply: deps.sendTextReply,
|
|
166
|
+
reconcileNextDispatchAnnouncementReplyOwnership:
|
|
167
|
+
deps.reconcileNextDispatchAnnouncementReplyOwnership,
|
|
162
168
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
163
169
|
...deps.promptDispatch,
|
|
164
170
|
sendUserMessage: deps.sendUserMessage,
|
|
@@ -167,6 +173,7 @@ export function createTelegramQueueBindingRuntime<TContext>(deps: {
|
|
|
167
173
|
mutation,
|
|
168
174
|
dispatchNext: dispatch.dispatchNext,
|
|
169
175
|
requestNextDispatchAnnouncement: dispatch.requestNextDispatchAnnouncement,
|
|
176
|
+
cancelNextDispatchAnnouncement: dispatch.cancelNextDispatchAnnouncement,
|
|
170
177
|
watchdog: Queue.createTelegramQueueDispatchWatchdogRuntime({
|
|
171
178
|
hasQueuedItems: deps.store.hasQueuedItems,
|
|
172
179
|
dispatchNextQueuedTelegramTurn: dispatch.dispatchNext,
|
|
@@ -11,7 +11,11 @@ import { basename } from "node:path";
|
|
|
11
11
|
import * as Sync from "./sync.ts";
|
|
12
12
|
import * as Threads from "./threads.ts";
|
|
13
13
|
import { parseTelegramUpdateJournalQueueOwner, type TelegramUpdateJournalStoreOptions } from "./journal.ts";
|
|
14
|
-
import
|
|
14
|
+
import {
|
|
15
|
+
TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS,
|
|
16
|
+
type TelegramLockEntry,
|
|
17
|
+
type TelegramLockState,
|
|
18
|
+
} from "./locks.ts";
|
|
15
19
|
import type {
|
|
16
20
|
TelegramQueueHandoffPayload,
|
|
17
21
|
TelegramQueueHandoffStageResult,
|
|
@@ -60,6 +64,8 @@ export const TELEGRAM_BUS_FOLLOWER_PROMOTION_GRACE_MS = 2_500;
|
|
|
60
64
|
export const TELEGRAM_FOLLOWER_SESSION_HANDOFF_TTL_MS = 30_000;
|
|
61
65
|
export const TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS = 30_000;
|
|
62
66
|
export const TELEGRAM_BUS_FOLLOWER_REGISTRATION_WAIT_MS = 30_000;
|
|
67
|
+
export const TELEGRAM_BUS_FOLLOWER_HEARTBEAT_TIMEOUT_MS =
|
|
68
|
+
TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS;
|
|
63
69
|
export const TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_ATTEMPTS =
|
|
64
70
|
TELEGRAM_BUS_REGISTRATION_RETRY.attempts;
|
|
65
71
|
export const TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_DELAY_MS =
|
|
@@ -301,6 +307,7 @@ export interface TelegramBusFollowerRegistrationRuntimeDeps<
|
|
|
301
307
|
registrationRetryAttempts?: number;
|
|
302
308
|
registrationRetryDelayMs?: number;
|
|
303
309
|
heartbeatMs?: number;
|
|
310
|
+
heartbeatTimeoutMs?: number;
|
|
304
311
|
recordRuntimeEvent?: (
|
|
305
312
|
category: string,
|
|
306
313
|
error: unknown,
|
|
@@ -1619,6 +1626,10 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1619
1626
|
const getNowMs = deps.getNowMs ?? Date.now;
|
|
1620
1627
|
const getPid = deps.getPid ?? (() => process.pid);
|
|
1621
1628
|
const heartbeatMs = deps.heartbeatMs ?? 1000;
|
|
1629
|
+
const heartbeatTimeoutMs =
|
|
1630
|
+
deps.heartbeatTimeoutMs ??
|
|
1631
|
+
deps.timeoutMs ??
|
|
1632
|
+
TELEGRAM_BUS_FOLLOWER_HEARTBEAT_TIMEOUT_MS;
|
|
1622
1633
|
const registrationTimeoutMs =
|
|
1623
1634
|
deps.registrationTimeoutMs ?? deps.timeoutMs ?? 30000;
|
|
1624
1635
|
const registrationRetryAttempts =
|
|
@@ -1679,7 +1690,7 @@ export function createTelegramBusFollowerRegistrationRuntime<
|
|
|
1679
1690
|
try {
|
|
1680
1691
|
const response = await sendTelegramBusLocalEnvelope({
|
|
1681
1692
|
socketPath: leaderSocketPath,
|
|
1682
|
-
timeoutMs:
|
|
1693
|
+
timeoutMs: heartbeatTimeoutMs,
|
|
1683
1694
|
retry: getTelegramBusTransportRetryPolicy({
|
|
1684
1695
|
endpoint: leaderSocketPath,
|
|
1685
1696
|
operation: "operation",
|