@llblab/pi-kit 0.33.0 → 0.34.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/BACKLOG.md +2 -1
- package/CHANGELOG.md +9 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-telegram/AGENTS.md +5 -4
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +6 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +20 -0
- package/node_modules/@llblab/pi-telegram/README.md +29 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +8 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +41 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +10 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +34 -20
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +10 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +32 -13
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +33 -28
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +18 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +28 -20
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +102 -99
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +3 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +57 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +313 -25
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +323 -30
- package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +1 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/status.js +6 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +11 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +11 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +57 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +4 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +14 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +44 -9
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +27 -14
- package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +4 -0
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +8 -4
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +14 -10
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +52 -3
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +13 -5
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +37 -23
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +36 -10
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +52 -47
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +18 -2
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +34 -28
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +116 -162
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +8 -0
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +7 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +328 -33
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +350 -30
- package/node_modules/@llblab/pi-telegram/lib/status.ts +7 -7
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +11 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +57 -5
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +46 -15
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
|
@@ -916,6 +916,16 @@ export function createTelegramLiveRebindCoordinator(input: {
|
|
|
916
916
|
}
|
|
917
917
|
: undefined;
|
|
918
918
|
},
|
|
919
|
+
/** Observe only an already-issued leader command; never prepares, relocates or executes input. */
|
|
920
|
+
observeCommandCompletion(): "unknown" | "released" | undefined {
|
|
921
|
+
if (!continuationIssued || !leader?.complete) return undefined;
|
|
922
|
+
if (!current()) return "unknown";
|
|
923
|
+
try { return observeCommandCompletion(); }
|
|
924
|
+
catch (error) {
|
|
925
|
+
input.recordRuntimeEvent?.("telegram", error, { phase: "live-rebind-command-observation" });
|
|
926
|
+
return "unknown";
|
|
927
|
+
}
|
|
928
|
+
},
|
|
919
929
|
async advance(): Promise<"protected" | "unknown" | "released"> {
|
|
920
930
|
if (advancing || !current()) return "protected";
|
|
921
931
|
if (released) return "released";
|
|
@@ -1558,9 +1568,11 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
1558
1568
|
requiredJournalBindingKeys: readonly string[],
|
|
1559
1569
|
ownInputs?: readonly Threads.TelegramTemporaryThreadInput[],
|
|
1560
1570
|
) => readonly number[] | undefined;
|
|
1561
|
-
/** Quiet period after the last cancelled input before one cleanup attempt; defaults to
|
|
1571
|
+
/** Quiet period after the last cancelled input before one cleanup attempt; defaults to 0 ms. */
|
|
1562
1572
|
temporaryThreadCleanupDelayMs?: number;
|
|
1563
1573
|
/** Live-rebind cleanup attempt pacing; the bounded window ends in `not-issued`, never a time-based deletion. */
|
|
1574
|
+
/** Bounded ACK observation inside the original chooser admission, independent from menu/reply delivery. */
|
|
1575
|
+
liveRebindCommandCompletionTimeoutMs?: number;
|
|
1564
1576
|
liveRebindCleanupSchedule?: {
|
|
1565
1577
|
delaysMs: readonly number[];
|
|
1566
1578
|
intervalMs: number;
|
|
@@ -1937,16 +1949,28 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1937
1949
|
extensionCommand?: Commands.ParsedTelegramCommand;
|
|
1938
1950
|
extensionKind?: Commands.PreparedSelectedCommand["kind"];
|
|
1939
1951
|
coordinator?: ReturnType<typeof createTelegramLiveRebindCoordinator>;
|
|
1952
|
+
commandCompletionWaited?: true;
|
|
1940
1953
|
isCurrent?: () => boolean;
|
|
1941
1954
|
};
|
|
1942
1955
|
/** The acknowledged tab presenting an All source; `sourceTarget` is this tab, not the original's location. */
|
|
1943
1956
|
temporaryThread?: Threads.TelegramTemporaryThreadEntry;
|
|
1944
1957
|
/** Source copies left in All (a menu-picked command); deleted once, when the input is consumed. */
|
|
1945
1958
|
allTabCopies?: number[];
|
|
1946
|
-
/**
|
|
1947
|
-
|
|
1959
|
+
/** This live opening attempt did not remove its All originals; replay/revival cannot reconstruct that fact. */
|
|
1960
|
+
allTabSourceRetained?: true;
|
|
1961
|
+
/** Complete warm copy identity; journal originals stay unchanged even after their All messages leave. */
|
|
1962
|
+
allTabCopiesForwarded?: {
|
|
1963
|
+
target: { chatId: number; threadId: number };
|
|
1964
|
+
sourceMessageIds: readonly number[];
|
|
1965
|
+
messageIds: readonly number[];
|
|
1966
|
+
};
|
|
1948
1967
|
/** Acknowledged same-tab membership stays protective when its reader disappears; never a dispatch/cleanup grant. */
|
|
1949
1968
|
temporaryMembership?: true;
|
|
1969
|
+
/** Confirmed display copies only; the durable Forward fact remains the native one-shot authority. */
|
|
1970
|
+
rerouteCopies?: {
|
|
1971
|
+
target: { chatId: number; threadId: number };
|
|
1972
|
+
bySourceUpdateId: ReadonlyMap<number, number>;
|
|
1973
|
+
};
|
|
1950
1974
|
/** Re-entrancy lock for one command route in flight; orthogonal to `phase`. */
|
|
1951
1975
|
dispatching?: boolean;
|
|
1952
1976
|
abandonment?: {
|
|
@@ -2415,15 +2439,22 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2415
2439
|
const cloneTelegramMessagesForThread = (
|
|
2416
2440
|
messages: TMessage[],
|
|
2417
2441
|
threadId: number,
|
|
2442
|
+
copies?: ReadonlyMap<number, number>,
|
|
2418
2443
|
): TMessage[] => {
|
|
2419
|
-
return messages.map((message) =>
|
|
2420
|
-
Updates.
|
|
2444
|
+
return messages.map((message) => {
|
|
2445
|
+
const ids = Updates.collectTelegramAdmissionSourceUpdateIds([message]);
|
|
2446
|
+
const copyId = copies && ids.length === 1 ? copies.get(ids[0]!) : undefined;
|
|
2447
|
+
if (copies && (!Number.isSafeInteger(copyId) || copyId! <= 0))
|
|
2448
|
+
throw new Error("Reroute destination copy identity is unavailable.");
|
|
2449
|
+
const routed = {
|
|
2421
2450
|
...message,
|
|
2422
|
-
message_id: 0,
|
|
2451
|
+
message_id: copyId ?? 0,
|
|
2423
2452
|
message_thread_id: threadId,
|
|
2424
2453
|
reply_to_message: undefined,
|
|
2425
|
-
} as TMessage
|
|
2426
|
-
|
|
2454
|
+
} as TMessage;
|
|
2455
|
+
return Updates.carryTelegramUpdateExecutionFence(message,
|
|
2456
|
+
copies ? Turns.withTelegramTurnReplyAnchor(routed) : routed);
|
|
2457
|
+
});
|
|
2427
2458
|
};
|
|
2428
2459
|
/** Whether a chooser is still pending in this Thread, ignoring the ones the caller itself owns. */
|
|
2429
2460
|
const hasPendingRerouteForTarget = (
|
|
@@ -2819,9 +2850,11 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2819
2850
|
const deleteAllTabMessages = async (
|
|
2820
2851
|
chatId: number,
|
|
2821
2852
|
messageIds: readonly number[],
|
|
2853
|
+
isCurrent?: () => boolean,
|
|
2822
2854
|
): Promise<void> => {
|
|
2823
2855
|
if (!deps.deleteMessage) return;
|
|
2824
2856
|
for (const messageId of messageIds) {
|
|
2857
|
+
if (isCurrent?.() === false) return;
|
|
2825
2858
|
try {
|
|
2826
2859
|
await deps.deleteMessage(chatId, messageId);
|
|
2827
2860
|
} catch (error) {
|
|
@@ -2834,10 +2867,14 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2834
2867
|
}
|
|
2835
2868
|
};
|
|
2836
2869
|
/** One-shot per chooser: the copies are taken before deletion, so a later consuming path finds none. */
|
|
2837
|
-
const deleteAllTabCopies = (
|
|
2870
|
+
const deleteAllTabCopies = (
|
|
2871
|
+
pending: PendingUnboundReroute,
|
|
2872
|
+
isCurrent?: () => boolean,
|
|
2873
|
+
): Promise<void> =>
|
|
2838
2874
|
deleteAllTabMessages(
|
|
2839
2875
|
pending.sourceTarget.chatId,
|
|
2840
2876
|
pending.allTabCopies?.splice(0) ?? [],
|
|
2877
|
+
isCurrent,
|
|
2841
2878
|
);
|
|
2842
2879
|
/**
|
|
2843
2880
|
* Restore keeps the tab as the Thread, so the input must stay visible there: the All original goes only when the tab
|
|
@@ -2846,7 +2883,11 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2846
2883
|
const releaseAllTabCopiesIntoSurvivingTab = (
|
|
2847
2884
|
pending: PendingUnboundReroute,
|
|
2848
2885
|
): Promise<void> => {
|
|
2849
|
-
if (
|
|
2886
|
+
if (
|
|
2887
|
+
pending.allTabCopiesForwarded &&
|
|
2888
|
+
isDeepStrictEqual(pending.allTabCopiesForwarded.target, pending.sourceTarget)
|
|
2889
|
+
)
|
|
2890
|
+
return deleteAllTabCopies(pending);
|
|
2850
2891
|
pending.allTabCopies?.splice(0);
|
|
2851
2892
|
return Promise.resolve();
|
|
2852
2893
|
};
|
|
@@ -2862,26 +2903,38 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2862
2903
|
): Promise<number | undefined> => {
|
|
2863
2904
|
const messageIds = [...(pending.allTabCopies ?? [])].sort((a, b) => a - b);
|
|
2864
2905
|
if (messageIds.length === 0 || !deps.callApi || !isCurrent()) return;
|
|
2906
|
+
const copyTarget = { ...target };
|
|
2865
2907
|
const route = {
|
|
2866
|
-
chat_id:
|
|
2867
|
-
message_thread_id:
|
|
2908
|
+
chat_id: copyTarget.chatId,
|
|
2909
|
+
message_thread_id: copyTarget.threadId,
|
|
2868
2910
|
from_chat_id: pending.sourceTarget.chatId,
|
|
2869
2911
|
disable_notification: true,
|
|
2870
2912
|
};
|
|
2913
|
+
const options = {
|
|
2914
|
+
maxAttempts: 1,
|
|
2915
|
+
retrySafety: "non-idempotent" as const,
|
|
2916
|
+
assertAuthority() {
|
|
2917
|
+
if (!isCurrent())
|
|
2918
|
+
throw new Error("Temporary Thread source forwarding authority changed.");
|
|
2919
|
+
},
|
|
2920
|
+
};
|
|
2871
2921
|
try {
|
|
2872
2922
|
// An album moves as one album; forwardMessages skips what it cannot forward, so a short reply is not a move.
|
|
2873
2923
|
const copies =
|
|
2874
2924
|
messageIds.length === 1
|
|
2875
2925
|
? [
|
|
2876
|
-
await deps.callApi<{ message_id?: unknown }>(
|
|
2877
|
-
|
|
2878
|
-
message_id: messageIds[0],
|
|
2879
|
-
|
|
2926
|
+
await deps.callApi<{ message_id?: unknown }>(
|
|
2927
|
+
"forwardMessage",
|
|
2928
|
+
{ ...route, message_id: messageIds[0] },
|
|
2929
|
+
options,
|
|
2930
|
+
),
|
|
2880
2931
|
]
|
|
2881
2932
|
: await deps.callApi<Array<{ message_id?: unknown }>>(
|
|
2882
2933
|
"forwardMessages",
|
|
2883
2934
|
{ ...route, message_ids: messageIds },
|
|
2935
|
+
options,
|
|
2884
2936
|
);
|
|
2937
|
+
if (!isCurrent()) return;
|
|
2885
2938
|
const copyIds = Array.isArray(copies)
|
|
2886
2939
|
? copies.map((copy) => copy?.message_id)
|
|
2887
2940
|
: [];
|
|
@@ -2889,20 +2942,88 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2889
2942
|
copyIds.length !== messageIds.length ||
|
|
2890
2943
|
!copyIds.every(
|
|
2891
2944
|
(id): id is number =>
|
|
2892
|
-
typeof id === "number" && Number.isSafeInteger(id),
|
|
2893
|
-
)
|
|
2945
|
+
typeof id === "number" && Number.isSafeInteger(id) && id > 0,
|
|
2946
|
+
) ||
|
|
2947
|
+
new Set(copyIds).size !== copyIds.length
|
|
2894
2948
|
)
|
|
2895
2949
|
return;
|
|
2896
|
-
pending.allTabCopiesForwarded =
|
|
2897
|
-
|
|
2898
|
-
|
|
2950
|
+
pending.allTabCopiesForwarded = {
|
|
2951
|
+
target: copyTarget,
|
|
2952
|
+
sourceMessageIds: messageIds,
|
|
2953
|
+
messageIds: copyIds,
|
|
2954
|
+
};
|
|
2955
|
+
await deleteAllTabCopies(pending, isCurrent);
|
|
2956
|
+
if (!isCurrent()) return;
|
|
2957
|
+
return pending.allTabCopiesForwarded.messageIds[0];
|
|
2899
2958
|
} catch (error) {
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2959
|
+
if (isCurrent())
|
|
2960
|
+
deps.recordRuntimeEvent?.("routing", error, {
|
|
2961
|
+
phase: "temporary-thread-source-forward",
|
|
2962
|
+
});
|
|
2903
2963
|
return;
|
|
2904
2964
|
}
|
|
2905
2965
|
};
|
|
2966
|
+
/** Read original source addresses, not warm target rewrites; unknown copy provenance never grants a Reroute. */
|
|
2967
|
+
const resolvePendingRerouteForwardSource = (
|
|
2968
|
+
pending: PendingUnboundReroute,
|
|
2969
|
+
): { chatId: number; sourceUpdateIds: number[]; sourceMessageIds: number[]; messageIds: number[] } | undefined => {
|
|
2970
|
+
try {
|
|
2971
|
+
const originals = pending.messages.map((message) => {
|
|
2972
|
+
const snapshot = Updates.inspectTelegramDeferredSourceSnapshot(message);
|
|
2973
|
+
const ids = Updates.collectTelegramAdmissionSourceUpdateIds([message]);
|
|
2974
|
+
const original = snapshot?.update.message as TMessage | undefined;
|
|
2975
|
+
if (
|
|
2976
|
+
!snapshot ||
|
|
2977
|
+
ids.length !== 1 ||
|
|
2978
|
+
snapshot.source.updateId !== ids[0] ||
|
|
2979
|
+
snapshot.update.update_id !== ids[0] ||
|
|
2980
|
+
Object.keys(snapshot.update).some((key) => key !== "update_id" && key !== "message") ||
|
|
2981
|
+
snapshot.source.journalBindingKey !== deps.getAdmissionJournalBinding?.() ||
|
|
2982
|
+
!original ||
|
|
2983
|
+
original.chat?.type !== "private" ||
|
|
2984
|
+
original.chat.id !== pending.sourceTarget.chatId ||
|
|
2985
|
+
original.from?.id !== deps.configStore.getAllowedUserId() ||
|
|
2986
|
+
original.from?.is_bot !== false ||
|
|
2987
|
+
!Number.isSafeInteger(original.message_id) ||
|
|
2988
|
+
original.message_id <= 0
|
|
2989
|
+
)
|
|
2990
|
+
return undefined;
|
|
2991
|
+
return { message: original, updateId: ids[0]! };
|
|
2992
|
+
});
|
|
2993
|
+
if (originals.length === 0 || originals.some((source) => !source)) return;
|
|
2994
|
+
originals.sort((a, b) => a!.message.message_id - b!.message.message_id);
|
|
2995
|
+
const sourceMessageIds = originals.map((source) => source!.message.message_id);
|
|
2996
|
+
const sourceUpdateIds = originals.map((source) => source!.updateId);
|
|
2997
|
+
if (
|
|
2998
|
+
new Set(sourceMessageIds).size !== sourceMessageIds.length ||
|
|
2999
|
+
new Set(sourceUpdateIds).size !== sourceUpdateIds.length
|
|
3000
|
+
)
|
|
3001
|
+
return;
|
|
3002
|
+
const copied = pending.allTabCopiesForwarded;
|
|
3003
|
+
if (copied) {
|
|
3004
|
+
if (
|
|
3005
|
+
!isDeepStrictEqual(copied.target, pending.sourceTarget) ||
|
|
3006
|
+
!isDeepStrictEqual(copied.sourceMessageIds, sourceMessageIds) ||
|
|
3007
|
+
copied.messageIds.length !== sourceMessageIds.length ||
|
|
3008
|
+
!copied.messageIds.every((id) => Number.isSafeInteger(id) && id > 0) ||
|
|
3009
|
+
new Set(copied.messageIds).size !== copied.messageIds.length
|
|
3010
|
+
)
|
|
3011
|
+
return;
|
|
3012
|
+
return { chatId: copied.target.chatId, sourceUpdateIds, sourceMessageIds, messageIds: [...copied.messageIds] };
|
|
3013
|
+
}
|
|
3014
|
+
const nativeOriginals = originals.every((source) =>
|
|
3015
|
+
typeof pending.sourceTarget.threadId === "number" &&
|
|
3016
|
+
source!.message.message_thread_id === pending.sourceTarget.threadId);
|
|
3017
|
+
const retainedAllOriginals = pending.allTabSourceRetained === true &&
|
|
3018
|
+
originals.every((source) => source!.message.message_thread_id === undefined) &&
|
|
3019
|
+
isDeepStrictEqual([...(pending.allTabCopies ?? [])].sort((a, b) => a - b), sourceMessageIds);
|
|
3020
|
+
if (nativeOriginals || retainedAllOriginals)
|
|
3021
|
+
return { chatId: pending.sourceTarget.chatId, sourceUpdateIds, sourceMessageIds, messageIds: [...sourceMessageIds] };
|
|
3022
|
+
} catch (error) {
|
|
3023
|
+
deps.recordRuntimeEvent?.("routing", error, { phase: "reroute-source-proof" });
|
|
3024
|
+
}
|
|
3025
|
+
return undefined;
|
|
3026
|
+
};
|
|
2906
3027
|
const dismissRerouteChooserMessage = async (
|
|
2907
3028
|
query: TCallbackQuery,
|
|
2908
3029
|
assertExecutionCurrent?: () => void,
|
|
@@ -4762,7 +4883,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
4762
4883
|
Bus.isSameTelegramBusFollowerRegistration(live, follower!) &&
|
|
4763
4884
|
[
|
|
4764
4885
|
...Bus.TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES,
|
|
4765
|
-
...(heldSelected ? Bus.
|
|
4886
|
+
...(heldSelected ? Bus.getTelegramBusHeldCommandCapabilities(heldSelected.name) : []),
|
|
4766
4887
|
].every((cap) => Bus.hasTelegramBusCapability(live.protocol, cap)) &&
|
|
4767
4888
|
!!live.target &&
|
|
4768
4889
|
live.target.chatId === binding.target.chatId &&
|
|
@@ -5534,6 +5655,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
5534
5655
|
return {
|
|
5535
5656
|
operationId: request.operationId,
|
|
5536
5657
|
selectedCommandReference: () => coordinator?.selectedCommandReference(),
|
|
5658
|
+
observeCommandCompletion: () => coordinator?.observeCommandCompletion(),
|
|
5537
5659
|
async advance() {
|
|
5538
5660
|
if (!coordinator) {
|
|
5539
5661
|
if (!current() || !getRecipient()) return "protected";
|
|
@@ -6172,7 +6294,21 @@ export function createTelegramInboundRouteRuntime<
|
|
|
6172
6294
|
for (const wake of [...liveRebindCleanupWakers.values()]) wake(reason);
|
|
6173
6295
|
};
|
|
6174
6296
|
const onSessionSettled = (ctx: TContext): void => wakeLiveRebindCleanup(ctx, "session-settled");
|
|
6175
|
-
const onDeliverySettled = (ctx: TContext): void =>
|
|
6297
|
+
const onDeliverySettled = (ctx: TContext): void => {
|
|
6298
|
+
wakeLiveRebindCleanup(ctx, "delivery-drained");
|
|
6299
|
+
const cap = captureTemporaryThreadAuthority(ctx);
|
|
6300
|
+
if (!cap?.isCurrent()) return;
|
|
6301
|
+
try {
|
|
6302
|
+
for (const entry of cap.store.listTemporaryThreads()) {
|
|
6303
|
+
if (!cap.isCurrent()) return;
|
|
6304
|
+
if (entry.phase === "created" && entry.target && !entry.cleanupIssued &&
|
|
6305
|
+
Threads.isTelegramTemporaryThreadFullyResolved(entry))
|
|
6306
|
+
scheduleTemporaryThreadCleanup(entry.target, ctx);
|
|
6307
|
+
}
|
|
6308
|
+
} catch (error) {
|
|
6309
|
+
deps.recordRuntimeEvent?.("routing", error, { phase: "temporary-thread-cleanup-drain" });
|
|
6310
|
+
}
|
|
6311
|
+
};
|
|
6176
6312
|
const restoreWorkspace = deps.workspaceRestoreRecipient
|
|
6177
6313
|
? async (input: {
|
|
6178
6314
|
operationId: string;
|
|
@@ -6802,6 +6938,97 @@ export function createTelegramInboundRouteRuntime<
|
|
|
6802
6938
|
return false;
|
|
6803
6939
|
}
|
|
6804
6940
|
};
|
|
6941
|
+
/** Publish display copies under the native one-shot grant; positive output is not input/source settlement. */
|
|
6942
|
+
const publishPendingRerouteCopies = async (
|
|
6943
|
+
pending: PendingUnboundReroute,
|
|
6944
|
+
record: Threads.TelegramTopicTargetRecord,
|
|
6945
|
+
ctx: TContext,
|
|
6946
|
+
assertExecutionCurrent: () => void,
|
|
6947
|
+
foreign: boolean,
|
|
6948
|
+
): Promise<(() => void) | undefined> => {
|
|
6949
|
+
const source = resolvePendingRerouteForwardSource(pending);
|
|
6950
|
+
const target = { ...record.target };
|
|
6951
|
+
const recordIdentity = (value: Threads.TelegramTopicTargetRecord | undefined) => value && ({
|
|
6952
|
+
target: value.target, profileKey: value.profileKey, owner: value.owner,
|
|
6953
|
+
instanceId: value.instanceId, slot: value.slot, createdAtMs: value.createdAtMs,
|
|
6954
|
+
});
|
|
6955
|
+
const bindingIdentity = () => deps.threadStore!.listWorkspaceBindings()
|
|
6956
|
+
.filter((value) => isDeepStrictEqual(value.target, target))
|
|
6957
|
+
.map((value) => ({ bindingKey: value.bindingKey, sessionId: value.sessionId, sessionKey: value.sessionKey,
|
|
6958
|
+
cwd: value.cwd, workspaceKey: value.workspaceKey, instanceSlot: value.instanceSlot, slot: value.slot,
|
|
6959
|
+
target: value.target }));
|
|
6960
|
+
const expectedRecord = structuredClone(recordIdentity(record));
|
|
6961
|
+
const expectedBindings = structuredClone(bindingIdentity());
|
|
6962
|
+
const ownership = deps.getTargetOwnership?.(target);
|
|
6963
|
+
if (
|
|
6964
|
+
!source || !deps.callApi || expectedBindings.length > 1 ||
|
|
6965
|
+
(foreign && (!ownership || ownership.instanceId !== record.instanceId ||
|
|
6966
|
+
!ownership.ownerGeneration || !ownership.recipientBindingKey))
|
|
6967
|
+
)
|
|
6968
|
+
return;
|
|
6969
|
+
const expectedOwnership = ownership && structuredClone(ownership);
|
|
6970
|
+
const cap = captureTemporaryThreadAuthority(ctx, () => {
|
|
6971
|
+
try {
|
|
6972
|
+
assertExecutionCurrent();
|
|
6973
|
+
const current = getTelegramRoutableThreadRecords(deps.threadStore!.list(), deps.getLiveThreadTargets?.())
|
|
6974
|
+
.find((value) => isDeepStrictEqual(value.target, target));
|
|
6975
|
+
return isDeepStrictEqual(recordIdentity(current), expectedRecord) &&
|
|
6976
|
+
isDeepStrictEqual(bindingIdentity(), expectedBindings) &&
|
|
6977
|
+
(!foreign || Bus.isTelegramBusForwardOwnershipCurrent(expectedOwnership!, deps.getTargetOwnership?.(target)));
|
|
6978
|
+
} catch { return false; }
|
|
6979
|
+
});
|
|
6980
|
+
if (!cap?.isCurrent()) return;
|
|
6981
|
+
const temporary = pending.temporaryThread ?? findCreatedTemporaryThread(pending.sourceTarget);
|
|
6982
|
+
if (!temporary || !issueTemporaryThreadForward(pending, ctx)) return;
|
|
6983
|
+
pending.phase = { kind: "forward-unknown" };
|
|
6984
|
+
const assertRecipientCurrent = (): void => {
|
|
6985
|
+
assertExecutionCurrent();
|
|
6986
|
+
if (!cap.isCurrent()) throw new Error("Reroute publication recipient authority changed.");
|
|
6987
|
+
};
|
|
6988
|
+
const assertPublicationCurrent = (): void => {
|
|
6989
|
+
assertRecipientCurrent();
|
|
6990
|
+
const entry = cap.store.listTemporaryThreads().find((value) => value.token === temporary.token);
|
|
6991
|
+
const ids = [...source.sourceUpdateIds].sort((a, b) => a - b);
|
|
6992
|
+
if (
|
|
6993
|
+
!entry || entry.phase !== "created" || entry.cleanupIssued ||
|
|
6994
|
+
entry.operatorUserId !== cap.operatorUserId ||
|
|
6995
|
+
!isDeepStrictEqual(entry.executor, cap.authority.executor) ||
|
|
6996
|
+
!isDeepStrictEqual(entry.target, pending.sourceTarget) ||
|
|
6997
|
+
!entry.forwardedInputs?.some((input) => input.journalBindingKey === cap.journalBindingKey &&
|
|
6998
|
+
isDeepStrictEqual(input.updateIds, ids)) ||
|
|
6999
|
+
!isDeepStrictEqual(resolvePendingRerouteForwardSource(pending), source)
|
|
7000
|
+
)
|
|
7001
|
+
throw new Error("Reroute publication source authority changed.");
|
|
7002
|
+
};
|
|
7003
|
+
try {
|
|
7004
|
+
assertPublicationCurrent();
|
|
7005
|
+
// Bot API requires ascending source IDs. Preserve the source-UID association even if copy IDs reorder them.
|
|
7006
|
+
const order = source.messageIds.map((_, index) => index)
|
|
7007
|
+
.sort((a, b) => source.messageIds[a]! - source.messageIds[b]!);
|
|
7008
|
+
const messageIds = order.map((index) => source.messageIds[index]!);
|
|
7009
|
+
const body = { chat_id: target.chatId, message_thread_id: target.threadId,
|
|
7010
|
+
from_chat_id: source.chatId, disable_notification: true };
|
|
7011
|
+
const options = { maxAttempts: 1, retrySafety: "non-idempotent" as const, assertAuthority: assertPublicationCurrent };
|
|
7012
|
+
const copies = messageIds.length === 1
|
|
7013
|
+
? [await deps.callApi<{ message_id?: unknown }>("forwardMessage", { ...body, message_id: messageIds[0] }, options)]
|
|
7014
|
+
: await deps.callApi<Array<{ message_id?: unknown }>>("forwardMessages", { ...body, message_ids: messageIds }, options);
|
|
7015
|
+
assertPublicationCurrent();
|
|
7016
|
+
const copyIds = Array.isArray(copies) ? copies.map((copy) => copy?.message_id) : [];
|
|
7017
|
+
if (
|
|
7018
|
+
copyIds.length !== messageIds.length ||
|
|
7019
|
+
!copyIds.every((id): id is number => typeof id === "number" && Number.isSafeInteger(id) && id > 0) ||
|
|
7020
|
+
new Set(copyIds).size !== copyIds.length
|
|
7021
|
+
)
|
|
7022
|
+
return;
|
|
7023
|
+
pending.rerouteCopies = { target, bySourceUpdateId: new Map(order.map((index, position) =>
|
|
7024
|
+
[source.sourceUpdateIds[index]!, copyIds[position]!] as const)) };
|
|
7025
|
+
pending.phase = { kind: "selected" };
|
|
7026
|
+
return assertRecipientCurrent;
|
|
7027
|
+
} catch (error) {
|
|
7028
|
+
if (cap.isCurrent()) deps.recordRuntimeEvent?.("routing", error, { phase: "reroute-destination-forward" });
|
|
7029
|
+
return;
|
|
7030
|
+
}
|
|
7031
|
+
};
|
|
6805
7032
|
const forwardPendingRerouteMessages = async (
|
|
6806
7033
|
pending: PendingUnboundReroute,
|
|
6807
7034
|
instanceId: string,
|
|
@@ -6827,12 +7054,19 @@ export function createTelegramInboundRouteRuntime<
|
|
|
6827
7054
|
)
|
|
6828
7055
|
return false;
|
|
6829
7056
|
const ownership = structuredClone(liveOwnership);
|
|
6830
|
-
const
|
|
7057
|
+
const copies = pending.rerouteCopies;
|
|
7058
|
+
const published = !!copies && isDeepStrictEqual(copies.target, target);
|
|
7059
|
+
const messages = cloneTelegramMessagesForThread(
|
|
7060
|
+
pending.messages,
|
|
7061
|
+
threadId,
|
|
7062
|
+
published ? copies.bySourceUpdateId : undefined,
|
|
7063
|
+
);
|
|
6831
7064
|
// Issuance is durable before the RPC: an unacknowledged temporary-tab Forward never becomes another dispatch grant,
|
|
6832
7065
|
// even after restart. Any refusal or publication failure sends nothing and keeps the input held.
|
|
6833
7066
|
if (
|
|
6834
7067
|
!recordForwardAcceptance &&
|
|
6835
7068
|
isTemporaryReroute(pending) &&
|
|
7069
|
+
!published &&
|
|
6836
7070
|
!issueTemporaryThreadForward(pending, ctx)
|
|
6837
7071
|
) {
|
|
6838
7072
|
if (isTemporaryThreadForwardIssued(pending))
|
|
@@ -6847,6 +7081,10 @@ export function createTelegramInboundRouteRuntime<
|
|
|
6847
7081
|
message,
|
|
6848
7082
|
ownership,
|
|
6849
7083
|
ctx,
|
|
7084
|
+
assertAuthority() {
|
|
7085
|
+
assertExecutionCurrent?.();
|
|
7086
|
+
Updates.assertTelegramUpdateExecutionCurrent(message);
|
|
7087
|
+
},
|
|
6850
7088
|
}),
|
|
6851
7089
|
),
|
|
6852
7090
|
);
|
|
@@ -7073,6 +7311,16 @@ export function createTelegramInboundRouteRuntime<
|
|
|
7073
7311
|
temporaryCleanupTimers.delete(token);
|
|
7074
7312
|
if (retry) restoreSettlementTasks.add(task);
|
|
7075
7313
|
runTemporaryThreadCleanup(token, cap, reconcileExpiry)
|
|
7314
|
+
.then(() => {
|
|
7315
|
+
if (!cap.isCurrent()) return;
|
|
7316
|
+
const retained = cap.store.listTemporaryThreads().find(value => value.token === token);
|
|
7317
|
+
// A follower ACK can precede recipient receipt disposal. Reobserve a protected, unissued tab;
|
|
7318
|
+
// elapsed time never grants deletion, and an issued/unknown delete can never enter this path.
|
|
7319
|
+
if (retained?.phase === "created" && !retained.cleanupIssued &&
|
|
7320
|
+
Threads.isTelegramTemporaryThreadFullyResolved(retained) &&
|
|
7321
|
+
cap.store.inspectTemporaryThreadTarget(retained, cap.authority)?.kind === "temporary" && cap.isCurrent())
|
|
7322
|
+
queueTemporaryThreadCleanup(token, cap, 1000, reconcileExpiry, true);
|
|
7323
|
+
})
|
|
7076
7324
|
.catch((error) => {
|
|
7077
7325
|
deps.recordRuntimeEvent?.("routing", error, {
|
|
7078
7326
|
phase: "temporary-thread-cleanup",
|
|
@@ -7799,7 +8047,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
7799
8047
|
).includes(source.original.updateId)
|
|
7800
8048
|
)
|
|
7801
8049
|
continue;
|
|
7802
|
-
movedIntoTab ||= pending.allTabCopiesForwarded
|
|
8050
|
+
movedIntoTab ||= pending.allTabCopiesForwarded !== undefined;
|
|
7803
8051
|
removePendingReroute(id);
|
|
7804
8052
|
if (
|
|
7805
8053
|
signal.aborted ||
|
|
@@ -8824,6 +9072,42 @@ export function createTelegramInboundRouteRuntime<
|
|
|
8824
9072
|
if (!(await selectPendingDestination())) return true;
|
|
8825
9073
|
const operationId = selection.coordinator.operationId;
|
|
8826
9074
|
if (current()) outcome = await selection.coordinator.advance();
|
|
9075
|
+
const completionTimeoutMs = deps.liveRebindCommandCompletionTimeoutMs ?? 0;
|
|
9076
|
+
if (outcome === "unknown" && !selection.commandCompletionWaited && selection.record.owner?.kind === "leader" &&
|
|
9077
|
+
(selection.helpCommand || selection.menuCommand || selection.abortCommand || selection.nextCommand || selection.stopCommand) &&
|
|
9078
|
+
Number.isFinite(completionTimeoutMs) && completionTimeoutMs > 0 &&
|
|
9079
|
+
selection.coordinator.observeCommandCompletion() !== undefined) {
|
|
9080
|
+
selection.commandCompletionWaited = true;
|
|
9081
|
+
const coordinator = selection.coordinator;
|
|
9082
|
+
const signal = Updates.getTelegramUpdateExecutionFence(query)?.signal;
|
|
9083
|
+
outcome = await new Promise<"unknown" | "released">((resolve) => {
|
|
9084
|
+
let settled = false;
|
|
9085
|
+
const subscriptions: (() => void)[] = [];
|
|
9086
|
+
const finish = (result: "unknown" | "released") => {
|
|
9087
|
+
if (settled) return;
|
|
9088
|
+
settled = true;
|
|
9089
|
+
clearTimeout(timer);
|
|
9090
|
+
for (const unsubscribe of subscriptions) unsubscribe();
|
|
9091
|
+
signal?.removeEventListener("abort", abort);
|
|
9092
|
+
resolve(result);
|
|
9093
|
+
};
|
|
9094
|
+
const abort = () => finish("unknown");
|
|
9095
|
+
const observe = () => {
|
|
9096
|
+
if (settled) return;
|
|
9097
|
+
if (!current()) { finish("unknown"); return; }
|
|
9098
|
+
if (coordinator.observeCommandCompletion() === "released") finish("released");
|
|
9099
|
+
};
|
|
9100
|
+
const timer = setTimeout(abort, completionTimeoutMs);
|
|
9101
|
+
timer.unref?.();
|
|
9102
|
+
for (const message of selection.messages) {
|
|
9103
|
+
const unsubscribe = Updates.subscribeTelegramDeferredSourceCompletion(message, observe);
|
|
9104
|
+
if (unsubscribe) subscriptions.push(unsubscribe);
|
|
9105
|
+
}
|
|
9106
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
9107
|
+
if (signal?.aborted || !current()) abort();
|
|
9108
|
+
else if (coordinator.observeCommandCompletion() === "released") finish("released");
|
|
9109
|
+
});
|
|
9110
|
+
}
|
|
8827
9111
|
// Cleanup readiness follows the canonical released row, not source disposal: a detached command ACK may
|
|
8828
9112
|
// still be pending while every attempt samples current work afresh.
|
|
8829
9113
|
const row =
|
|
@@ -8901,6 +9185,14 @@ export function createTelegramInboundRouteRuntime<
|
|
|
8901
9185
|
);
|
|
8902
9186
|
return true;
|
|
8903
9187
|
}
|
|
9188
|
+
if (
|
|
9189
|
+
!parsed.useNewSlot &&
|
|
9190
|
+
isTemporaryReroute(pending) &&
|
|
9191
|
+
!resolvePendingRerouteForwardSource(pending)
|
|
9192
|
+
) {
|
|
9193
|
+
await deps.answerCallbackQuery(query.id, "Source copy unavailable; not routing");
|
|
9194
|
+
return true;
|
|
9195
|
+
}
|
|
8904
9196
|
if (!(await selectPendingDestination())) return true;
|
|
8905
9197
|
if (parsed.useNewSlot && sourceTarget && restoreWorkspace) {
|
|
8906
9198
|
const selection = (pending.workspaceRestore ??= {
|
|
@@ -9006,6 +9298,19 @@ export function createTelegramInboundRouteRuntime<
|
|
|
9006
9298
|
);
|
|
9007
9299
|
return true;
|
|
9008
9300
|
}
|
|
9301
|
+
let assertRerouteCurrent = assertExecutionCurrent;
|
|
9302
|
+
if (isTemporaryReroute(pending)) {
|
|
9303
|
+
const confirmed = await publishPendingRerouteCopies(pending, record, ctx, assertExecutionCurrent,
|
|
9304
|
+
!!record.instanceId && record.instanceId !== currentInstanceId && !isCurrentLeaderRecord);
|
|
9305
|
+
if (!confirmed) {
|
|
9306
|
+
await deps.answerCallbackQuery(query.id, pending.phase.kind === "forward-unknown"
|
|
9307
|
+
? "Publication unconfirmed; not resending"
|
|
9308
|
+
: "Publication unavailable; input kept");
|
|
9309
|
+
return true;
|
|
9310
|
+
}
|
|
9311
|
+
assertRerouteCurrent = confirmed;
|
|
9312
|
+
}
|
|
9313
|
+
assertRerouteCurrent();
|
|
9009
9314
|
if (
|
|
9010
9315
|
record.instanceId &&
|
|
9011
9316
|
record.instanceId !== currentInstanceId &&
|
|
@@ -9016,7 +9321,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
9016
9321
|
record.instanceId,
|
|
9017
9322
|
parsed.threadId,
|
|
9018
9323
|
ctx,
|
|
9019
|
-
|
|
9324
|
+
assertRerouteCurrent,
|
|
9020
9325
|
);
|
|
9021
9326
|
if (!allForwarded) {
|
|
9022
9327
|
await deps.answerCallbackQuery(
|
|
@@ -9032,6 +9337,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
9032
9337
|
// Local handlers and queue admission consume the same one-time group grant as follower forwarding.
|
|
9033
9338
|
if (
|
|
9034
9339
|
isTemporaryReroute(pending) &&
|
|
9340
|
+
!pending.rerouteCopies &&
|
|
9035
9341
|
!issueTemporaryThreadForward(pending, ctx)
|
|
9036
9342
|
) {
|
|
9037
9343
|
await deps.answerCallbackQuery(
|
|
@@ -9040,7 +9346,11 @@ export function createTelegramInboundRouteRuntime<
|
|
|
9040
9346
|
);
|
|
9041
9347
|
return true;
|
|
9042
9348
|
}
|
|
9043
|
-
|
|
9349
|
+
const localMessages = pending.rerouteCopies
|
|
9350
|
+
? cloneTelegramMessagesForThread(pending.messages, parsed.threadId, pending.rerouteCopies.bySourceUpdateId)
|
|
9351
|
+
: reroutedMessages;
|
|
9352
|
+
await dispatchPendingRerouteMessages(pending, localMessages, ctx);
|
|
9353
|
+
assertRerouteCurrent();
|
|
9044
9354
|
pending.messages = [];
|
|
9045
9355
|
return completeRoutedReroute();
|
|
9046
9356
|
};
|
|
@@ -10632,6 +10942,16 @@ export function createTelegramInboundRouteRuntime<
|
|
|
10632
10942
|
const tabCopyId = openedNow
|
|
10633
10943
|
? await moveAllTabSourceIntoTab(pending, target, current)
|
|
10634
10944
|
: undefined;
|
|
10945
|
+
if (!current()) {
|
|
10946
|
+
removePendingReroute(rerouteId);
|
|
10947
|
+
throw new Error("Temporary Thread source presentation authority changed.");
|
|
10948
|
+
}
|
|
10949
|
+
if (
|
|
10950
|
+
openedNow &&
|
|
10951
|
+
!pending.allTabCopiesForwarded &&
|
|
10952
|
+
pending.allTabCopies.length === held.length
|
|
10953
|
+
)
|
|
10954
|
+
pending.allTabSourceRetained = true;
|
|
10635
10955
|
try {
|
|
10636
10956
|
if (!deps.sendInteractiveMessage)
|
|
10637
10957
|
throw new Error("Temporary Thread chooser publication is unavailable.");
|
|
@@ -66,7 +66,7 @@ export function formatTelegramConnectionFailure(error: unknown): string {
|
|
|
66
66
|
|
|
67
67
|
const TELEGRAM_STATUS_DEFAULT_PROFILE_NAME = "default";
|
|
68
68
|
|
|
69
|
-
export type TelegramStatusQueueLane = "control" | "priority" | "default";
|
|
69
|
+
export type TelegramStatusQueueLane = "control" | "steer" | "priority" | "default";
|
|
70
70
|
|
|
71
71
|
export interface TelegramUsageStats {
|
|
72
72
|
totalInput: number;
|
|
@@ -441,7 +441,6 @@ export interface TelegramBridgeStatusRuntimeDeps<
|
|
|
441
441
|
hasPendingModelSwitch: () => boolean;
|
|
442
442
|
getQueuedItems: () => TQueueItem[];
|
|
443
443
|
getQueuedItemCount?: (items: TQueueItem[]) => number;
|
|
444
|
-
formatQueuedStatus: (items: TQueueItem[]) => string;
|
|
445
444
|
getRecentRuntimeEvents: () => TelegramRuntimeEvent[];
|
|
446
445
|
getRuntimeLockState?: () => string;
|
|
447
446
|
getBusRole?: () => TelegramBridgeBusRole | undefined;
|
|
@@ -1200,9 +1199,7 @@ export function buildTelegramStatusBarText(
|
|
|
1200
1199
|
state: TelegramStatusBarState,
|
|
1201
1200
|
): string {
|
|
1202
1201
|
const label = theme.fg("accent", getTelegramStatusBarLabel(state));
|
|
1203
|
-
const queued = state.queuedStatus
|
|
1204
|
-
? theme.fg("warning", state.queuedStatus)
|
|
1205
|
-
: "";
|
|
1202
|
+
const queued = state.queuedStatus ? theme.fg("warning", state.queuedStatus) : "";
|
|
1206
1203
|
if (!state.hasBotToken)
|
|
1207
1204
|
return `${label} ${theme.fg("muted", "not configured")}${queued}`;
|
|
1208
1205
|
if (
|
|
@@ -1612,6 +1609,7 @@ function countTelegramQueueLanes(
|
|
|
1612
1609
|
): Record<TelegramStatusQueueLane, number> {
|
|
1613
1610
|
const counts: Record<TelegramStatusQueueLane, number> = {
|
|
1614
1611
|
control: 0,
|
|
1612
|
+
steer: 0,
|
|
1615
1613
|
priority: 0,
|
|
1616
1614
|
default: 0,
|
|
1617
1615
|
};
|
|
@@ -1626,10 +1624,11 @@ function buildTelegramBridgeCompactStatusLines(
|
|
|
1626
1624
|
control: controlQueueCount,
|
|
1627
1625
|
priority: priorityQueueCount,
|
|
1628
1626
|
default: defaultQueueCount,
|
|
1627
|
+
steer: steerQueueCount,
|
|
1629
1628
|
} = countTelegramQueueLanes(state.queuedItems);
|
|
1630
1629
|
const queueLine = `- queued turns: ${state.queuedItems.length}${
|
|
1631
1630
|
state.queuedItems.length > 0
|
|
1632
|
-
? ` (control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount})`
|
|
1631
|
+
? ` (control=${controlQueueCount}${steerQueueCount > 0 ? `, steer=${steerQueueCount}` : ""}, priority=${priorityQueueCount}, default=${defaultQueueCount})`
|
|
1633
1632
|
: ""
|
|
1634
1633
|
}`;
|
|
1635
1634
|
const executionState = state.pendingDispatch
|
|
@@ -1698,6 +1697,7 @@ function buildTelegramBridgeDiagnosticStatusLines(
|
|
|
1698
1697
|
control: controlQueueCount,
|
|
1699
1698
|
priority: priorityQueueCount,
|
|
1700
1699
|
default: defaultQueueCount,
|
|
1700
|
+
steer: steerQueueCount,
|
|
1701
1701
|
} = countTelegramQueueLanes(state.queuedItems);
|
|
1702
1702
|
return [
|
|
1703
1703
|
"connection:",
|
|
@@ -1744,7 +1744,7 @@ function buildTelegramBridgeDiagnosticStatusLines(
|
|
|
1744
1744
|
"",
|
|
1745
1745
|
"queue:",
|
|
1746
1746
|
`- queued turns: ${state.queuedItems.length}`,
|
|
1747
|
-
`- lanes: control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount}`,
|
|
1747
|
+
`- lanes: control=${controlQueueCount}${steerQueueCount > 0 ? `, steer=${steerQueueCount}` : ""}, priority=${priorityQueueCount}, default=${defaultQueueCount}`,
|
|
1748
1748
|
...buildTelegramBusFollowerLines(state),
|
|
1749
1749
|
...buildTelegramLocalBusLines(state, { verbose: true }),
|
|
1750
1750
|
...buildTelegramTopicTargetDiagnosticLines(state),
|