@llblab/pi-telegram 0.51.0 → 0.51.2
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 +4 -3
- package/BACKLOG.md +3 -8
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/dist/lib/bindings.d.ts +2 -0
- package/dist/lib/bindings.js +3 -4
- package/dist/lib/bus-api.d.ts +0 -1
- package/dist/lib/bus-api.js +0 -3
- package/dist/lib/bus-follower.d.ts +3 -1
- package/dist/lib/bus-follower.js +6 -1
- package/dist/lib/commands.d.ts +5 -0
- package/dist/lib/commands.js +9 -0
- package/dist/lib/extension.js +6 -3
- package/dist/lib/menu-model.d.ts +3 -3
- package/dist/lib/menu-model.js +17 -3
- package/dist/lib/menu.d.ts +5 -7
- package/dist/lib/menu.js +4 -5
- package/dist/lib/model.d.ts +12 -12
- package/dist/lib/model.js +49 -35
- package/dist/lib/queue.d.ts +8 -2
- package/dist/lib/queue.js +64 -19
- package/dist/lib/replies.d.ts +4 -0
- package/dist/lib/replies.js +14 -0
- package/dist/lib/routing.d.ts +1 -0
- package/dist/lib/routing.js +5 -1
- package/dist/lib/runtime.d.ts +3 -6
- package/dist/lib/runtime.js +4 -55
- package/dist/lib/telegram-api.js +36 -10
- package/dist/package.json +1 -1
- package/docs/architecture.md +9 -9
- package/docs/multi-instance-bus.md +3 -3
- package/docs/public-api.md +1 -1
- package/lib/bindings.ts +14 -4
- package/lib/bus-api.ts +0 -7
- package/lib/bus-follower.ts +13 -2
- package/lib/commands.ts +19 -0
- package/lib/extension.ts +10 -3
- package/lib/menu-model.ts +25 -4
- package/lib/menu.ts +14 -9
- package/lib/model.ts +77 -44
- package/lib/queue.ts +76 -24
- package/lib/replies.ts +18 -0
- package/lib/routing.ts +7 -1
- package/lib/runtime.ts +7 -58
- package/lib/telegram-api.ts +43 -17
- package/package.json +1 -1
package/lib/runtime.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns small session-local runtime primitives that are shared by orchestration but are not specific to queueing, rendering, polling, or Telegram transport
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const TELEGRAM_TYPING_ACTION_INTERVAL_MS =
|
|
7
|
+
const TELEGRAM_TYPING_ACTION_INTERVAL_MS = 3_000;
|
|
8
8
|
const TELEGRAM_TYPING_IDLE_DRAIN_MAX_MS = 250;
|
|
9
9
|
|
|
10
10
|
export interface TelegramRuntimeQueueCounters {
|
|
@@ -319,7 +319,6 @@ export interface TelegramTypingLoopDeps {
|
|
|
319
319
|
chatId: number,
|
|
320
320
|
options?: { message_thread_id?: number },
|
|
321
321
|
) => Promise<unknown>;
|
|
322
|
-
sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
|
|
323
322
|
shouldContinue?: () => boolean;
|
|
324
323
|
onStopped?: () => void;
|
|
325
324
|
}
|
|
@@ -364,7 +363,6 @@ export interface TelegramTypingLoopStarterDeps<
|
|
|
364
363
|
chatId: number,
|
|
365
364
|
options?: { message_thread_id?: number },
|
|
366
365
|
) => Promise<unknown>;
|
|
367
|
-
sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
|
|
368
366
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
369
367
|
isContextActive?: (ctx: TContext) => boolean;
|
|
370
368
|
isTransportAvailable?: () => boolean;
|
|
@@ -378,7 +376,7 @@ export function createTelegramTypingLoopStarter<TContext>(
|
|
|
378
376
|
ctx: TContext,
|
|
379
377
|
chatId?: number,
|
|
380
378
|
options?: { target?: TelegramTypingLoopTarget },
|
|
381
|
-
) =>
|
|
379
|
+
) => boolean {
|
|
382
380
|
return (ctx, chatId, options) => {
|
|
383
381
|
const transportAuthority = deps.getTransportAuthority?.();
|
|
384
382
|
const hasTransport = (): boolean =>
|
|
@@ -386,9 +384,9 @@ export function createTelegramTypingLoopStarter<TContext>(
|
|
|
386
384
|
? transportAuthority !== undefined &&
|
|
387
385
|
Object.is(deps.getTransportAuthority(), transportAuthority)
|
|
388
386
|
: deps.isTransportAvailable?.() !== false;
|
|
389
|
-
if (!hasTransport()) return;
|
|
387
|
+
if (!hasTransport()) return false;
|
|
390
388
|
let active = true;
|
|
391
|
-
deps.typing.start({
|
|
389
|
+
return deps.typing.start({
|
|
392
390
|
chatId: chatId ?? deps.getDefaultChatId(),
|
|
393
391
|
target: options?.target,
|
|
394
392
|
intervalMs: deps.intervalMs ?? TELEGRAM_TYPING_ACTION_INTERVAL_MS,
|
|
@@ -407,14 +405,6 @@ export function createTelegramTypingLoopStarter<TContext>(
|
|
|
407
405
|
deps.typing.stop();
|
|
408
406
|
return;
|
|
409
407
|
}
|
|
410
|
-
const message =
|
|
411
|
-
error instanceof Error ? error.message : String(error);
|
|
412
|
-
updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
|
|
413
|
-
error: message,
|
|
414
|
-
category: "typing",
|
|
415
|
-
phase: "status-update",
|
|
416
|
-
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
417
|
-
});
|
|
418
408
|
try {
|
|
419
409
|
deps.recordRuntimeEvent?.("typing", error, {
|
|
420
410
|
chatId: targetChatId,
|
|
@@ -424,41 +414,6 @@ export function createTelegramTypingLoopStarter<TContext>(
|
|
|
424
414
|
}
|
|
425
415
|
}
|
|
426
416
|
},
|
|
427
|
-
sendAggregateTypingAction: deps.sendAggregateTypingAction
|
|
428
|
-
? async (targetChatId) => {
|
|
429
|
-
if (!active) return;
|
|
430
|
-
if (!hasTransport()) {
|
|
431
|
-
deps.typing.stop();
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
try {
|
|
435
|
-
await deps.sendAggregateTypingAction?.(targetChatId);
|
|
436
|
-
} catch (error) {
|
|
437
|
-
if (deps.isContextActive?.(ctx) === false) return;
|
|
438
|
-
if (!active) return;
|
|
439
|
-
if (!hasTransport()) {
|
|
440
|
-
deps.typing.stop();
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
const message =
|
|
444
|
-
error instanceof Error ? error.message : String(error);
|
|
445
|
-
updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
|
|
446
|
-
error: message,
|
|
447
|
-
category: "typing",
|
|
448
|
-
phase: "status-update",
|
|
449
|
-
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
450
|
-
});
|
|
451
|
-
try {
|
|
452
|
-
deps.recordRuntimeEvent?.("typing", error, {
|
|
453
|
-
chatId: targetChatId,
|
|
454
|
-
aggregate: true,
|
|
455
|
-
});
|
|
456
|
-
} catch {
|
|
457
|
-
// Typing diagnostics cannot escape the in-flight action owner.
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
: undefined,
|
|
462
417
|
shouldContinue: hasTransport,
|
|
463
418
|
onStopped: () => {
|
|
464
419
|
active = false;
|
|
@@ -502,12 +457,7 @@ export function startTelegramTypingLoop(
|
|
|
502
457
|
const targetChatId = activeDeps.chatId;
|
|
503
458
|
const threadParams = getTelegramTypingLoopThreadParams(activeDeps.target);
|
|
504
459
|
const typing = Promise.resolve()
|
|
505
|
-
.then(
|
|
506
|
-
await activeDeps.sendTypingAction(targetChatId, threadParams);
|
|
507
|
-
if (threadParams?.message_thread_id !== undefined) {
|
|
508
|
-
await activeDeps.sendAggregateTypingAction?.(targetChatId);
|
|
509
|
-
}
|
|
510
|
-
})
|
|
460
|
+
.then(() => activeDeps.sendTypingAction(targetChatId, threadParams))
|
|
511
461
|
.then(() => undefined)
|
|
512
462
|
.catch(() => undefined);
|
|
513
463
|
state.typingInFlight = typing;
|
|
@@ -602,7 +552,7 @@ export interface TelegramPromptDispatchLifecycleDeps<
|
|
|
602
552
|
ctx: TContext,
|
|
603
553
|
chatId?: number,
|
|
604
554
|
options?: { target?: TelegramTypingLoopTarget },
|
|
605
|
-
) => void;
|
|
555
|
+
) => boolean | void;
|
|
606
556
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
607
557
|
}
|
|
608
558
|
|
|
@@ -616,7 +566,6 @@ export interface TelegramPromptDispatchRuntimeDeps<
|
|
|
616
566
|
chatId: number,
|
|
617
567
|
options?: { message_thread_id?: number },
|
|
618
568
|
) => Promise<unknown>;
|
|
619
|
-
sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
|
|
620
569
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
621
570
|
isContextActive?: (ctx: TContext) => boolean;
|
|
622
571
|
isTransportAvailable?: () => boolean;
|
|
@@ -629,7 +578,7 @@ export interface TelegramPromptDispatchRuntime<TContext> {
|
|
|
629
578
|
ctx: TContext,
|
|
630
579
|
chatId?: number,
|
|
631
580
|
options?: { target?: TelegramTypingLoopTarget },
|
|
632
|
-
) => void;
|
|
581
|
+
) => boolean | void;
|
|
633
582
|
onPromptDispatchStart: (ctx: TContext, chatId?: number) => void;
|
|
634
583
|
onPromptDispatchFailure: (ctx: TContext, message: string) => void;
|
|
635
584
|
}
|
package/lib/telegram-api.ts
CHANGED
|
@@ -1866,14 +1866,16 @@ export function createTelegramBridgeApiRuntime(
|
|
|
1866
1866
|
deps.chatActionMinIntervalMs ?? 2_000,
|
|
1867
1867
|
);
|
|
1868
1868
|
const chatActionMaxGates = Math.max(1, deps.chatActionMaxGates ?? 256);
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
const
|
|
1869
|
+
type ChatActionGate = {
|
|
1870
|
+
inFlight?: Promise<unknown>;
|
|
1871
|
+
notBeforeMs: number;
|
|
1872
|
+
};
|
|
1873
|
+
const chatActionGates = new Map<string, ChatActionGate>();
|
|
1874
|
+
const chatActionChatGates = new Map<string, ChatActionGate>();
|
|
1875
|
+
const getChatActionKeys = (
|
|
1874
1876
|
method: string,
|
|
1875
1877
|
body: Record<string, unknown>,
|
|
1876
|
-
): string | undefined => {
|
|
1878
|
+
): { action: string; chat: string } | undefined => {
|
|
1877
1879
|
if (method !== "sendChatAction") return undefined;
|
|
1878
1880
|
const chatId = body.chat_id;
|
|
1879
1881
|
const action = body.action;
|
|
@@ -1883,12 +1885,16 @@ export function createTelegramBridgeApiRuntime(
|
|
|
1883
1885
|
) {
|
|
1884
1886
|
return undefined;
|
|
1885
1887
|
}
|
|
1888
|
+
const chat = String(chatId);
|
|
1886
1889
|
const threadId = body.message_thread_id;
|
|
1887
|
-
return
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1890
|
+
return {
|
|
1891
|
+
action: `${chat}:${
|
|
1892
|
+
typeof threadId === "number" || typeof threadId === "string"
|
|
1893
|
+
? String(threadId)
|
|
1894
|
+
: "all"
|
|
1895
|
+
}:${action}`,
|
|
1896
|
+
chat,
|
|
1897
|
+
};
|
|
1892
1898
|
};
|
|
1893
1899
|
const callRecorded = async <TResponse>(
|
|
1894
1900
|
method: string,
|
|
@@ -1896,22 +1902,38 @@ export function createTelegramBridgeApiRuntime(
|
|
|
1896
1902
|
options?: TelegramApiCallOptions,
|
|
1897
1903
|
): Promise<TResponse> => {
|
|
1898
1904
|
const recoverError = deps.captureRequestErrorHandler?.(body);
|
|
1899
|
-
const
|
|
1900
|
-
if (
|
|
1905
|
+
const chatActionKeys = getChatActionKeys(method, body);
|
|
1906
|
+
if (chatActionKeys) {
|
|
1901
1907
|
const nowMs = now();
|
|
1902
1908
|
for (const [key, candidate] of chatActionGates) {
|
|
1903
1909
|
if (!candidate.inFlight && nowMs >= candidate.notBeforeMs) {
|
|
1904
1910
|
chatActionGates.delete(key);
|
|
1905
1911
|
}
|
|
1906
1912
|
}
|
|
1907
|
-
|
|
1913
|
+
for (const [key, candidate] of chatActionChatGates) {
|
|
1914
|
+
if (!candidate.inFlight && nowMs >= candidate.notBeforeMs) {
|
|
1915
|
+
chatActionChatGates.delete(key);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
let gate = chatActionGates.get(chatActionKeys.action);
|
|
1908
1919
|
if (!gate) {
|
|
1909
1920
|
if (chatActionGates.size >= chatActionMaxGates) return true as TResponse;
|
|
1910
1921
|
gate = { notBeforeMs: 0 };
|
|
1911
|
-
chatActionGates.set(
|
|
1922
|
+
chatActionGates.set(chatActionKeys.action, gate);
|
|
1923
|
+
}
|
|
1924
|
+
let chatGate = chatActionChatGates.get(chatActionKeys.chat);
|
|
1925
|
+
if (!chatGate) {
|
|
1926
|
+
if (chatActionChatGates.size >= chatActionMaxGates) {
|
|
1927
|
+
return true as TResponse;
|
|
1928
|
+
}
|
|
1929
|
+
chatGate = { notBeforeMs: 0 };
|
|
1930
|
+
chatActionChatGates.set(chatActionKeys.chat, chatGate);
|
|
1912
1931
|
}
|
|
1913
1932
|
if (gate.inFlight) return (await gate.inFlight) as TResponse;
|
|
1914
|
-
if (
|
|
1933
|
+
if (chatGate.inFlight) return true as TResponse;
|
|
1934
|
+
if (nowMs < gate.notBeforeMs || nowMs < chatGate.notBeforeMs) {
|
|
1935
|
+
return true as TResponse;
|
|
1936
|
+
}
|
|
1915
1937
|
let request: Promise<TResponse>;
|
|
1916
1938
|
request = Promise.resolve()
|
|
1917
1939
|
.then(() =>
|
|
@@ -1931,7 +1953,9 @@ export function createTelegramBridgeApiRuntime(
|
|
|
1931
1953
|
chatActionMinIntervalMs,
|
|
1932
1954
|
(error.retryAfterSeconds ?? 0) * 1_000,
|
|
1933
1955
|
);
|
|
1934
|
-
|
|
1956
|
+
const notBeforeMs = now() + retryAfterMs;
|
|
1957
|
+
gate.notBeforeMs = notBeforeMs;
|
|
1958
|
+
chatGate.notBeforeMs = Math.max(chatGate.notBeforeMs, notBeforeMs);
|
|
1935
1959
|
deps.recordRuntimeEvent(
|
|
1936
1960
|
"api",
|
|
1937
1961
|
error,
|
|
@@ -1952,8 +1976,10 @@ export function createTelegramBridgeApiRuntime(
|
|
|
1952
1976
|
})
|
|
1953
1977
|
.finally(() => {
|
|
1954
1978
|
if (gate.inFlight === request) gate.inFlight = undefined;
|
|
1979
|
+
if (chatGate.inFlight === request) chatGate.inFlight = undefined;
|
|
1955
1980
|
});
|
|
1956
1981
|
gate.inFlight = request;
|
|
1982
|
+
chatGate.inFlight = request;
|
|
1957
1983
|
return request;
|
|
1958
1984
|
}
|
|
1959
1985
|
try {
|