@llblab/pi-telegram 0.52.2 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -7
- package/BACKLOG.md +1 -18
- package/CHANGELOG.md +11 -0
- package/README.md +7 -11
- package/api/commands.ts +3 -0
- package/dist/api/commands.d.ts +1 -1
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +15 -42
- package/dist/lib/activity.d.ts +9 -0
- package/dist/lib/activity.js +39 -10
- package/dist/lib/agent-messages.js +7 -3
- package/dist/lib/bindings.d.ts +43 -5
- package/dist/lib/bindings.js +163 -20
- package/dist/lib/bus-api.d.ts +12 -0
- package/dist/lib/bus-api.js +182 -79
- package/dist/lib/bus-follower.d.ts +111 -31
- package/dist/lib/bus-follower.js +1324 -359
- package/dist/lib/bus-leader.d.ts +41 -91
- package/dist/lib/bus-leader.js +969 -637
- package/dist/lib/bus-transport.js +16 -9
- package/dist/lib/bus.d.ts +215 -18
- package/dist/lib/bus.js +1048 -327
- package/dist/lib/channel-posts.d.ts +5 -0
- package/dist/lib/channel-posts.js +434 -261
- package/dist/lib/command-templates.d.ts +26 -5
- package/dist/lib/command-templates.js +92 -176
- package/dist/lib/commands.d.ts +168 -82
- package/dist/lib/commands.js +1409 -291
- package/dist/lib/config.d.ts +1 -8
- package/dist/lib/config.js +34 -76
- package/dist/lib/delivery.d.ts +2 -2
- package/dist/lib/delivery.js +16 -16
- package/dist/lib/extension.js +639 -171
- package/dist/lib/generative-app-worker.mjs +19 -7
- package/dist/lib/generative-apps.d.ts +0 -5
- package/dist/lib/generative-apps.js +61 -49
- package/dist/lib/inbound.d.ts +5 -0
- package/dist/lib/inbound.js +35 -59
- package/dist/lib/journal.d.ts +33 -3
- package/dist/lib/journal.js +2063 -782
- package/dist/lib/lifecycle.d.ts +2 -6
- package/dist/lib/lifecycle.js +75 -58
- package/dist/lib/locks.d.ts +29 -3
- package/dist/lib/locks.js +289 -108
- package/dist/lib/logging.js +50 -14
- package/dist/lib/media.d.ts +22 -1
- package/dist/lib/media.js +46 -28
- package/dist/lib/menu-model.d.ts +19 -5
- package/dist/lib/menu-model.js +53 -11
- package/dist/lib/menu-queue.d.ts +5 -4
- package/dist/lib/menu-queue.js +45 -63
- package/dist/lib/menu-settings.d.ts +5 -4
- package/dist/lib/menu-settings.js +114 -78
- package/dist/lib/menu-status.d.ts +1 -1
- package/dist/lib/menu-status.js +8 -25
- package/dist/lib/menu-thinking.d.ts +6 -1
- package/dist/lib/menu-thinking.js +14 -21
- package/dist/lib/menu.d.ts +8 -61
- package/dist/lib/menu.js +92 -164
- package/dist/lib/model.d.ts +1 -1
- package/dist/lib/model.js +2 -16
- package/dist/lib/outbound-attachments.d.ts +6 -27
- package/dist/lib/outbound-attachments.js +45 -38
- package/dist/lib/outbound-buttons.js +22 -16
- package/dist/lib/outbound-markup.d.ts +0 -2
- package/dist/lib/outbound-markup.js +51 -64
- package/dist/lib/outbound-voice.js +6 -3
- package/dist/lib/outbound.d.ts +6 -6
- package/dist/lib/outbound.js +61 -51
- package/dist/lib/ownership.js +6 -2
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +37 -21
- package/dist/lib/pi.d.ts +2 -0
- package/dist/lib/pi.js +8 -1
- package/dist/lib/polling.d.ts +21 -37
- package/dist/lib/polling.js +59 -50
- package/dist/lib/preview.js +39 -19
- package/dist/lib/process-identity.d.ts +24 -0
- package/dist/lib/process-identity.js +133 -0
- package/dist/lib/prompts.js +2 -7
- package/dist/lib/queue.d.ts +14 -16
- package/dist/lib/queue.js +249 -126
- package/dist/lib/recovery.d.ts +2 -83
- package/dist/lib/recovery.js +20 -274
- package/dist/lib/replies.d.ts +16 -24
- package/dist/lib/replies.js +99 -116
- package/dist/lib/routing.d.ts +120 -7
- package/dist/lib/routing.js +4667 -1152
- package/dist/lib/runtime.d.ts +1 -11
- package/dist/lib/sections.d.ts +4 -29
- package/dist/lib/sections.js +7 -20
- package/dist/lib/skills.js +4 -3
- package/dist/lib/status.js +86 -57
- package/dist/lib/sync.d.ts +1 -3
- package/dist/lib/sync.js +67 -43
- package/dist/lib/target.d.ts +4 -0
- package/dist/lib/target.js +18 -0
- package/dist/lib/telegram-api.d.ts +18 -13
- package/dist/lib/telegram-api.js +148 -53
- package/dist/lib/text-groups.js +4 -39
- package/dist/lib/thread-cleanup-manager.d.ts +6 -27
- package/dist/lib/thread-cleanup-manager.js +369 -186
- package/dist/lib/thread-display.d.ts +1 -2
- package/dist/lib/thread-display.js +58 -38
- package/dist/lib/thread-naming.d.ts +28 -8
- package/dist/lib/thread-naming.js +175 -66
- package/dist/lib/thread-reconciler.d.ts +42 -16
- package/dist/lib/thread-reconciler.js +90 -50
- package/dist/lib/threads.d.ts +82 -20
- package/dist/lib/threads.js +2247 -855
- package/dist/lib/time-injection.js +1 -2
- package/dist/lib/turns.js +1 -19
- package/dist/lib/updates.d.ts +169 -80
- package/dist/lib/updates.js +2418 -641
- package/dist/lib/voice.d.ts +7 -22
- package/dist/lib/voice.js +7 -22
- package/dist/lib/wire.js +1 -1
- package/dist/lib/workspace-admission.d.ts +3 -1
- package/dist/lib/workspace-admission.js +425 -211
- package/dist/lib/workspace-identity.js +8 -13
- package/dist/lib/workspace-retirement.d.ts +1 -1
- package/dist/lib/workspace-retirement.js +328 -140
- package/dist/lib/workspace-slots.js +8 -4
- package/dist/package.json +1 -1
- package/docs/architecture.md +31 -29
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +15 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +29 -1
- package/docs/ui-style.md +25 -8
- package/lib/activity-verbosity.ts +59 -82
- package/lib/activity.ts +146 -102
- package/lib/agent-messages.ts +12 -5
- package/lib/bindings.ts +389 -82
- package/lib/bus-api.ts +263 -102
- package/lib/bus-follower.ts +2440 -601
- package/lib/bus-leader.ts +2209 -1290
- package/lib/bus-transport.ts +46 -12
- package/lib/bus.ts +1673 -444
- package/lib/channel-posts.ts +848 -327
- package/lib/command-templates.ts +152 -265
- package/lib/commands.ts +2398 -555
- package/lib/config.ts +177 -170
- package/lib/delivery.ts +41 -29
- package/lib/extension.ts +1206 -433
- package/lib/generative-app-worker.mjs +19 -7
- package/lib/generative-apps.ts +324 -211
- package/lib/inbound.ts +95 -97
- package/lib/journal.ts +5311 -1763
- package/lib/keyboard.ts +1 -3
- package/lib/lifecycle.ts +128 -78
- package/lib/locks.ts +677 -203
- package/lib/logging.ts +147 -58
- package/lib/media.ts +82 -43
- package/lib/menu-model.ts +85 -18
- package/lib/menu-queue.ts +88 -129
- package/lib/menu-settings.ts +260 -155
- package/lib/menu-status.ts +20 -60
- package/lib/menu-thinking.ts +41 -56
- package/lib/menu.ts +112 -285
- package/lib/model.ts +8 -22
- package/lib/outbound-attachments.ts +156 -125
- package/lib/outbound-buttons.ts +76 -49
- package/lib/outbound-markup.ts +96 -94
- package/lib/outbound-voice.ts +33 -27
- package/lib/outbound.ts +125 -125
- package/lib/ownership.ts +19 -10
- package/lib/paths.ts +148 -49
- package/lib/pi.ts +35 -15
- package/lib/polling.ts +144 -147
- package/lib/preview.ts +93 -40
- package/lib/process-identity.ts +164 -0
- package/lib/prompts.ts +4 -11
- package/lib/queue.ts +511 -300
- package/lib/recovery.ts +50 -454
- package/lib/rendering.ts +1 -3
- package/lib/replies.ts +248 -209
- package/lib/routing.ts +8509 -2186
- package/lib/runtime.ts +1 -12
- package/lib/sections.ts +24 -89
- package/lib/setup.ts +1 -3
- package/lib/skills.ts +9 -4
- package/lib/status.ts +215 -110
- package/lib/sync.ts +448 -361
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +302 -127
- package/lib/text-groups.ts +5 -39
- package/lib/thread-cleanup-manager.ts +833 -331
- package/lib/thread-display.ts +213 -97
- package/lib/thread-naming.ts +232 -87
- package/lib/thread-reconciler.ts +155 -72
- package/lib/threads.ts +5318 -1845
- package/lib/time-injection.ts +1 -4
- package/lib/turns.ts +15 -28
- package/lib/updates.ts +5184 -1612
- package/lib/voice.ts +7 -35
- package/lib/wire.ts +1 -1
- package/lib/workspace-admission.ts +996 -416
- package/lib/workspace-identity.ts +14 -17
- package/lib/workspace-retirement.ts +878 -376
- package/lib/workspace-slots.ts +22 -13
- package/package.json +1 -1
- package/scripts/check-downgrade.mjs +0 -472
- package/scripts/measure-workspace.mjs +0 -108
package/lib/status.ts
CHANGED
|
@@ -9,25 +9,53 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
9
9
|
|
|
10
10
|
/** UI copy is allowlisted; raw exception text belongs only in redacted diagnostics. */
|
|
11
11
|
export function formatTelegramConnectionFailure(error: unknown): string {
|
|
12
|
-
const message =
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const message =
|
|
13
|
+
error instanceof Error
|
|
14
|
+
? error.message
|
|
15
|
+
: typeof error === "string"
|
|
16
|
+
? error
|
|
17
|
+
: "";
|
|
18
|
+
const status =
|
|
19
|
+
error && typeof error === "object" && "status" in error
|
|
20
|
+
? error.status
|
|
21
|
+
: undefined;
|
|
22
|
+
const code =
|
|
23
|
+
error && typeof error === "object" && "code" in error
|
|
24
|
+
? error.code
|
|
25
|
+
: undefined;
|
|
15
26
|
if (status === 401) return "Telegram token rejected. Run /telegram-setup.";
|
|
16
|
-
if (status === 403)
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
if (status === 403)
|
|
28
|
+
return "Telegram access denied. Check /telegram-status --debug.";
|
|
29
|
+
if (
|
|
30
|
+
(typeof code === "string" &&
|
|
31
|
+
["ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "ECONNRESET"].includes(
|
|
32
|
+
code,
|
|
33
|
+
)) ||
|
|
34
|
+
/network unavailable|fetch failed/i.test(message)
|
|
35
|
+
) {
|
|
19
36
|
return "Telegram network unavailable. Retry /telegram-connect.";
|
|
20
37
|
}
|
|
21
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
/Workspace slots?.*(?:unavailable|exhausted)|no free.*slot/i.test(message)
|
|
40
|
+
) {
|
|
22
41
|
return "No Telegram slot available. Check /telegram-status --debug.";
|
|
23
42
|
}
|
|
24
|
-
if (
|
|
43
|
+
if (
|
|
44
|
+
code === "incompatible-protocol" ||
|
|
45
|
+
/protocol.*(?:incompatible|mismatch)|incompatible.*protocol/i.test(message)
|
|
46
|
+
) {
|
|
25
47
|
return "Telegram instances are incompatible. Update them together.";
|
|
26
48
|
}
|
|
27
|
-
if (
|
|
49
|
+
if (
|
|
50
|
+
/unsupported.*(?:journal|version)|(?:journal|version).*unsupported/i.test(
|
|
51
|
+
message,
|
|
52
|
+
)
|
|
53
|
+
) {
|
|
28
54
|
return "Telegram state version unsupported. Use a compatible runtime.";
|
|
29
55
|
}
|
|
30
|
-
if (
|
|
56
|
+
if (
|
|
57
|
+
/follower registration failed|active in another Pi instance/i.test(message)
|
|
58
|
+
) {
|
|
31
59
|
return "Telegram leader is active but unavailable. Check /telegram-status --debug.";
|
|
32
60
|
}
|
|
33
61
|
if (/unfinished Thread creation does not match/i.test(message)) {
|
|
@@ -466,30 +494,15 @@ function truncateTelegramRuntimeEventText(
|
|
|
466
494
|
return `${text.slice(0, maxLength).trimEnd()}… [truncated ${text.length - maxLength} chars]`;
|
|
467
495
|
}
|
|
468
496
|
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
botToken: string | undefined,
|
|
472
|
-
): string {
|
|
473
|
-
const redacted = botToken
|
|
474
|
-
? message.split(botToken).join("<redacted-token>")
|
|
475
|
-
: message;
|
|
476
|
-
return truncateTelegramRuntimeEventText(
|
|
477
|
-
redacted,
|
|
478
|
-
MAX_TELEGRAM_RUNTIME_EVENT_MESSAGE_LENGTH,
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function redactTelegramRuntimeDetail(
|
|
483
|
-
message: string,
|
|
497
|
+
function redactTelegramRuntimeText(
|
|
498
|
+
text: string,
|
|
484
499
|
botToken: string | undefined,
|
|
500
|
+
maxLength: number,
|
|
485
501
|
): string {
|
|
486
502
|
const redacted = botToken
|
|
487
|
-
?
|
|
488
|
-
:
|
|
489
|
-
return truncateTelegramRuntimeEventText(
|
|
490
|
-
redacted,
|
|
491
|
-
MAX_TELEGRAM_RUNTIME_EVENT_DETAIL_LENGTH,
|
|
492
|
-
);
|
|
503
|
+
? text.split(botToken).join("<redacted-token>")
|
|
504
|
+
: text;
|
|
505
|
+
return truncateTelegramRuntimeEventText(redacted, maxLength);
|
|
493
506
|
}
|
|
494
507
|
|
|
495
508
|
function normalizeTelegramRuntimeEventDetails(
|
|
@@ -501,7 +514,11 @@ function normalizeTelegramRuntimeEventDetails(
|
|
|
501
514
|
for (const [key, value] of Object.entries(details)) {
|
|
502
515
|
if (value === undefined) continue;
|
|
503
516
|
if (typeof value === "string") {
|
|
504
|
-
normalized[key] =
|
|
517
|
+
normalized[key] = redactTelegramRuntimeText(
|
|
518
|
+
value,
|
|
519
|
+
botToken,
|
|
520
|
+
MAX_TELEGRAM_RUNTIME_EVENT_DETAIL_LENGTH,
|
|
521
|
+
);
|
|
505
522
|
continue;
|
|
506
523
|
}
|
|
507
524
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
@@ -512,7 +529,11 @@ function normalizeTelegramRuntimeEventDetails(
|
|
|
512
529
|
normalized[key] = null;
|
|
513
530
|
continue;
|
|
514
531
|
}
|
|
515
|
-
normalized[key] =
|
|
532
|
+
normalized[key] = redactTelegramRuntimeText(
|
|
533
|
+
String(value),
|
|
534
|
+
botToken,
|
|
535
|
+
MAX_TELEGRAM_RUNTIME_EVENT_DETAIL_LENGTH,
|
|
536
|
+
);
|
|
516
537
|
}
|
|
517
538
|
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
518
539
|
}
|
|
@@ -537,9 +558,10 @@ export function recordStructuredTelegramRuntimeEvent(
|
|
|
537
558
|
events.push({
|
|
538
559
|
at: options.now ?? Date.now(),
|
|
539
560
|
category: input.category,
|
|
540
|
-
message:
|
|
561
|
+
message: redactTelegramRuntimeText(
|
|
541
562
|
getTelegramRuntimeEventMessage(input),
|
|
542
563
|
options.botToken,
|
|
564
|
+
MAX_TELEGRAM_RUNTIME_EVENT_MESSAGE_LENGTH,
|
|
543
565
|
),
|
|
544
566
|
...(details ? { details } : {}),
|
|
545
567
|
});
|
|
@@ -738,7 +760,9 @@ export function createTelegramBridgeStatusRuntime<
|
|
|
738
760
|
const config = deps.getConfig();
|
|
739
761
|
const queuedItems = deps.getQueuedItems();
|
|
740
762
|
const hasPendingDispatch = deps.hasDispatchPending();
|
|
741
|
-
const waitingItems = hasPendingDispatch
|
|
763
|
+
const waitingItems = hasPendingDispatch
|
|
764
|
+
? queuedItems.slice(1)
|
|
765
|
+
: queuedItems;
|
|
742
766
|
const queuedItemCount =
|
|
743
767
|
deps.getQueuedItemCount?.(waitingItems) ?? waitingItems.length;
|
|
744
768
|
const hasActiveTurn = deps.hasActiveTurn();
|
|
@@ -797,9 +821,7 @@ export function createTelegramBridgeStatusRuntime<
|
|
|
797
821
|
instanceThreadName: deps.getInstanceThreadName?.(),
|
|
798
822
|
lockState: deps.getRuntimeLockState?.(),
|
|
799
823
|
pollingActive: deps.isPollingActive(),
|
|
800
|
-
...(deps.getPollingState
|
|
801
|
-
? { polling: deps.getPollingState() }
|
|
802
|
-
: {}),
|
|
824
|
+
...(deps.getPollingState ? { polling: deps.getPollingState() } : {}),
|
|
803
825
|
...(deps.getInboundWorkerState
|
|
804
826
|
? { inboundWorker: deps.getInboundWorkerState() }
|
|
805
827
|
: {}),
|
|
@@ -891,12 +913,33 @@ export function createTelegramStatusSnapshot(
|
|
|
891
913
|
};
|
|
892
914
|
}
|
|
893
915
|
|
|
894
|
-
function isRuntimeProjection(
|
|
916
|
+
function isRuntimeProjection(
|
|
917
|
+
value: unknown,
|
|
918
|
+
): value is TelegramStoredRuntimeProjection {
|
|
895
919
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
896
920
|
const record = value as Record<string, unknown>;
|
|
897
|
-
return
|
|
898
|
-
|
|
899
|
-
|
|
921
|
+
return (
|
|
922
|
+
record.version === 1 &&
|
|
923
|
+
record.source === "snapshot" &&
|
|
924
|
+
Number.isSafeInteger(record.writtenAtMs) &&
|
|
925
|
+
(record.writtenAtMs as number) >= 0 &&
|
|
926
|
+
Object.keys(record).every((key) =>
|
|
927
|
+
[
|
|
928
|
+
"version",
|
|
929
|
+
"source",
|
|
930
|
+
"writtenAtMs",
|
|
931
|
+
"runtime",
|
|
932
|
+
"liveRoster",
|
|
933
|
+
"diagnostics",
|
|
934
|
+
].includes(key),
|
|
935
|
+
) &&
|
|
936
|
+
["runtime", "liveRoster", "diagnostics"].every(
|
|
937
|
+
(key) =>
|
|
938
|
+
record[key] !== null &&
|
|
939
|
+
typeof record[key] === "object" &&
|
|
940
|
+
!Array.isArray(record[key]),
|
|
941
|
+
)
|
|
942
|
+
);
|
|
900
943
|
}
|
|
901
944
|
export interface TelegramRuntimeProjectionScope {
|
|
902
945
|
path: string;
|
|
@@ -920,7 +963,9 @@ export interface TelegramRuntimeProjectionStoreOptions {
|
|
|
920
963
|
getNowMs?: () => number;
|
|
921
964
|
}
|
|
922
965
|
/** Observational runtime storage only; Workspace, admission, transport, logs and recovery remain with their owners. */
|
|
923
|
-
export function createTelegramRuntimeProjectionStore(
|
|
966
|
+
export function createTelegramRuntimeProjectionStore(
|
|
967
|
+
options: TelegramRuntimeProjectionStoreOptions,
|
|
968
|
+
): {
|
|
924
969
|
read: () => TelegramStoredRuntimeProjection | undefined;
|
|
925
970
|
persist: (snapshot: TelegramStatusSnapshot) => Promise<boolean>;
|
|
926
971
|
} {
|
|
@@ -928,36 +973,69 @@ export function createTelegramRuntimeProjectionStore(options: TelegramRuntimePro
|
|
|
928
973
|
const { getPath, getProfile: readProfile, captureAuthority } = options;
|
|
929
974
|
const { read, publish } = options.storage;
|
|
930
975
|
const getNowMs = options.getNowMs ?? Date.now;
|
|
931
|
-
const getProfile = (): string =>
|
|
976
|
+
const getProfile = (): string =>
|
|
977
|
+
readProfile() || TELEGRAM_STATUS_DEFAULT_PROFILE_NAME;
|
|
932
978
|
return {
|
|
933
979
|
read() {
|
|
934
980
|
try {
|
|
935
|
-
const path = getPath(),
|
|
936
|
-
|
|
937
|
-
|
|
981
|
+
const path = getPath(),
|
|
982
|
+
profile = getProfile(),
|
|
983
|
+
value = read({ path, profile });
|
|
984
|
+
return getPath() === path &&
|
|
985
|
+
getProfile() === profile &&
|
|
986
|
+
isRuntimeProjection(value)
|
|
987
|
+
? value
|
|
988
|
+
: undefined;
|
|
989
|
+
} catch {
|
|
990
|
+
return undefined;
|
|
991
|
+
}
|
|
938
992
|
},
|
|
939
993
|
async persist(snapshot) {
|
|
940
|
-
const path = getPath(),
|
|
994
|
+
const path = getPath(),
|
|
995
|
+
profile = getProfile(),
|
|
996
|
+
authority = captureAuthority();
|
|
941
997
|
if (!authority) return false;
|
|
942
998
|
// Freeze wire content at submission, but do not duplicate the event log in persistent runtime state.
|
|
943
|
-
const semantic = JSON.parse(
|
|
944
|
-
|
|
945
|
-
|
|
999
|
+
const semantic = JSON.parse(
|
|
1000
|
+
JSON.stringify({
|
|
1001
|
+
version: 1,
|
|
1002
|
+
source: "snapshot",
|
|
1003
|
+
runtime: snapshot.runtime,
|
|
1004
|
+
liveRoster: snapshot.liveRoster,
|
|
1005
|
+
diagnostics: snapshot.diagnostics,
|
|
1006
|
+
}),
|
|
1007
|
+
) as Omit<TelegramStoredRuntimeProjection, "writtenAtMs">;
|
|
1008
|
+
if (!isRuntimeProjection({ ...semantic, writtenAtMs: 0 }))
|
|
1009
|
+
throw new Error("Telegram runtime projection is malformed.");
|
|
946
1010
|
delete semantic.diagnostics.recentRuntimeEvents;
|
|
947
|
-
const isCurrent = (): boolean =>
|
|
1011
|
+
const isCurrent = (): boolean =>
|
|
1012
|
+
getPath() === path && getProfile() === profile && authority() === true;
|
|
948
1013
|
const publication = publicationQueue.then(() => {
|
|
949
1014
|
if (!isCurrent()) return false;
|
|
950
|
-
return
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1015
|
+
return (
|
|
1016
|
+
publish(
|
|
1017
|
+
{ path, profile },
|
|
1018
|
+
(current) => {
|
|
1019
|
+
if (isRuntimeProjection(current)) {
|
|
1020
|
+
const { writtenAtMs: _writtenAtMs, ...existing } = current;
|
|
1021
|
+
if (isDeepStrictEqual(existing, semantic))
|
|
1022
|
+
return { value: current, changed: false };
|
|
1023
|
+
}
|
|
1024
|
+
const writtenAtMs = getNowMs();
|
|
1025
|
+
if (!Number.isSafeInteger(writtenAtMs) || writtenAtMs < 0)
|
|
1026
|
+
throw new Error(
|
|
1027
|
+
"Telegram runtime projection timestamp is invalid.",
|
|
1028
|
+
);
|
|
1029
|
+
return { value: { ...semantic, writtenAtMs }, changed: true };
|
|
1030
|
+
},
|
|
1031
|
+
isCurrent,
|
|
1032
|
+
) === true
|
|
1033
|
+
);
|
|
959
1034
|
});
|
|
960
|
-
publicationQueue = publication.then(
|
|
1035
|
+
publicationQueue = publication.then(
|
|
1036
|
+
() => undefined,
|
|
1037
|
+
() => undefined,
|
|
1038
|
+
);
|
|
961
1039
|
return publication;
|
|
962
1040
|
},
|
|
963
1041
|
};
|
|
@@ -969,14 +1047,20 @@ export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps: {
|
|
|
969
1047
|
persistSnapshot: (isCurrent: () => boolean) => Promise<void>;
|
|
970
1048
|
recordError: (error: unknown) => void;
|
|
971
1049
|
captureScope?: () => (() => boolean) | undefined;
|
|
972
|
-
setTimer?: (
|
|
1050
|
+
setTimer?: (
|
|
1051
|
+
callback: () => void,
|
|
1052
|
+
ms: number,
|
|
1053
|
+
) => { unref?: () => void } | number;
|
|
973
1054
|
clearTimer?: (timer: { unref?: () => void } | number) => void;
|
|
974
1055
|
}): (() => void) & { resume(): void; suspend(): Promise<void> } {
|
|
975
1056
|
const setTimer = deps.setTimer ?? setTimeout;
|
|
976
|
-
const clearTimer =
|
|
1057
|
+
const clearTimer =
|
|
1058
|
+
deps.clearTimer ??
|
|
1059
|
+
((handle) => clearTimeout(handle as ReturnType<typeof setTimeout>));
|
|
977
1060
|
let generation = 0;
|
|
978
1061
|
let enabled = true;
|
|
979
|
-
let timer:
|
|
1062
|
+
let timer:
|
|
1063
|
+
{ token: object; handle: { unref?: () => void } | number } | undefined;
|
|
980
1064
|
let persistPromise: Promise<void> | undefined;
|
|
981
1065
|
let pending: (() => boolean) | undefined;
|
|
982
1066
|
const recordError = (error: unknown): void => {
|
|
@@ -990,25 +1074,41 @@ export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps: {
|
|
|
990
1074
|
const previous = timer;
|
|
991
1075
|
timer = undefined;
|
|
992
1076
|
if (previous) {
|
|
993
|
-
try {
|
|
1077
|
+
try {
|
|
1078
|
+
clearTimer(previous.handle);
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
recordError(error);
|
|
1081
|
+
}
|
|
994
1082
|
}
|
|
995
1083
|
};
|
|
996
1084
|
const capture = (): (() => boolean) | undefined => {
|
|
997
1085
|
if (!enabled) return undefined;
|
|
998
1086
|
const expectedGeneration = generation;
|
|
999
1087
|
let scope: (() => boolean) | undefined;
|
|
1000
|
-
try {
|
|
1001
|
-
|
|
1088
|
+
try {
|
|
1089
|
+
scope = deps.captureScope ? deps.captureScope() : () => true;
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
recordError(error);
|
|
1092
|
+
return undefined;
|
|
1093
|
+
}
|
|
1002
1094
|
if (!scope) return undefined;
|
|
1003
1095
|
return () => {
|
|
1004
1096
|
if (!enabled || generation !== expectedGeneration) return false;
|
|
1005
|
-
try {
|
|
1097
|
+
try {
|
|
1098
|
+
return scope!();
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
recordError(error);
|
|
1101
|
+
return false;
|
|
1102
|
+
}
|
|
1006
1103
|
};
|
|
1007
1104
|
};
|
|
1008
1105
|
const arm = (): void => {
|
|
1009
1106
|
if (timer || persistPromise || !pending) return;
|
|
1010
1107
|
const authority = pending;
|
|
1011
|
-
if (!authority()) {
|
|
1108
|
+
if (!authority()) {
|
|
1109
|
+
pending = undefined;
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1012
1112
|
const token = {};
|
|
1013
1113
|
const handle = setTimer(() => {
|
|
1014
1114
|
if (timer?.token !== token) return;
|
|
@@ -1018,7 +1118,11 @@ export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps: {
|
|
|
1018
1118
|
if (!authority() || !publicationAuthority?.()) return;
|
|
1019
1119
|
let tracked: Promise<void>;
|
|
1020
1120
|
tracked = Promise.resolve()
|
|
1021
|
-
.then(() =>
|
|
1121
|
+
.then(() =>
|
|
1122
|
+
publicationAuthority()
|
|
1123
|
+
? deps.persistSnapshot(publicationAuthority)
|
|
1124
|
+
: undefined,
|
|
1125
|
+
)
|
|
1022
1126
|
.catch(recordError)
|
|
1023
1127
|
.finally(() => {
|
|
1024
1128
|
if (persistPromise !== tracked) return;
|
|
@@ -1069,11 +1173,7 @@ export function getTelegramStatusBarProcessingStatus(state: {
|
|
|
1069
1173
|
}
|
|
1070
1174
|
|
|
1071
1175
|
function getTelegramStatusBarLabel(state: TelegramStatusBarState): string {
|
|
1072
|
-
|
|
1073
|
-
if (!threadName) return "telegram";
|
|
1074
|
-
const genericLabels = new Set(["telegram", "leader", "follower"]);
|
|
1075
|
-
if (genericLabels.has(threadName.toLowerCase())) return "telegram";
|
|
1076
|
-
return threadName;
|
|
1176
|
+
return state.instanceThreadName?.trim() || "telegram";
|
|
1077
1177
|
}
|
|
1078
1178
|
|
|
1079
1179
|
export function buildTelegramStatusBarText(
|
|
@@ -1086,14 +1186,17 @@ export function buildTelegramStatusBarText(
|
|
|
1086
1186
|
: "";
|
|
1087
1187
|
if (!state.hasBotToken)
|
|
1088
1188
|
return `${label} ${theme.fg("muted", "not configured")}${queued}`;
|
|
1089
|
-
if (
|
|
1189
|
+
if (
|
|
1190
|
+
state.pollingStopReason === "persistent-conflict" &&
|
|
1191
|
+
state.busRole !== "follower"
|
|
1192
|
+
)
|
|
1090
1193
|
return `${label} ${theme.fg("error", "error")}`;
|
|
1091
1194
|
if (!state.paired)
|
|
1092
1195
|
return `${label} ${theme.fg("warning", "awaiting pairing")}${queued}`;
|
|
1093
1196
|
if (state.busLifecyclePhase === "electing")
|
|
1094
1197
|
return `${label} ${theme.fg("warning", "electing")}${queued}`;
|
|
1095
1198
|
if (!state.pollingActive && state.busRole !== "follower")
|
|
1096
|
-
return `${
|
|
1199
|
+
return `${label} ${theme.fg("dim", "disconnected")}${queued}`;
|
|
1097
1200
|
if (state.error) {
|
|
1098
1201
|
return `${label} ${theme.fg("error", "error")}${queued}`;
|
|
1099
1202
|
}
|
|
@@ -1190,9 +1293,7 @@ function buildTelegramLocalBusLines(
|
|
|
1190
1293
|
slot: localBus.followerSlot,
|
|
1191
1294
|
threadName: localBus.followerThreadName,
|
|
1192
1295
|
});
|
|
1193
|
-
const protocol = formatTelegramBusProtocolIdentity(
|
|
1194
|
-
localBus.leaderProtocol,
|
|
1195
|
-
);
|
|
1296
|
+
const protocol = formatTelegramBusProtocolIdentity(localBus.leaderProtocol);
|
|
1196
1297
|
const followerLine = `- follower registered: ${localBus.followerRegistered ? "yes" : "no"}${label ? ` ${label}` : ""}${target}${protocol}`;
|
|
1197
1298
|
const lines = ["", "local bus:", followerLine];
|
|
1198
1299
|
if (options.verbose) {
|
|
@@ -1399,9 +1500,7 @@ function buildTelegramPollingDiagnosticLines(
|
|
|
1399
1500
|
return [
|
|
1400
1501
|
`- phase: ${polling.phase}`,
|
|
1401
1502
|
...(polling.phaseStartedAtMs !== undefined
|
|
1402
|
-
? [
|
|
1403
|
-
`- phase started: ${new Date(polling.phaseStartedAtMs).toISOString()}`,
|
|
1404
|
-
]
|
|
1503
|
+
? [`- phase started: ${new Date(polling.phaseStartedAtMs).toISOString()}`]
|
|
1405
1504
|
: []),
|
|
1406
1505
|
...(polling.currentUpdateId !== undefined
|
|
1407
1506
|
? [`- current update id: ${polling.currentUpdateId}`]
|
|
@@ -1472,7 +1571,9 @@ function buildTelegramInboundWorkerDiagnosticLines(
|
|
|
1472
1571
|
? [`- blocked reason: ${worker.blockedReason}`]
|
|
1473
1572
|
: []),
|
|
1474
1573
|
...(worker.blockedInputCustody
|
|
1475
|
-
? [
|
|
1574
|
+
? [
|
|
1575
|
+
`- blocked input custody: update=${worker.blockedInputCustody.updateId}, kind=${worker.blockedInputCustody.kind}`,
|
|
1576
|
+
]
|
|
1476
1577
|
: []),
|
|
1477
1578
|
...(worker.lastCompletedUpdateId !== undefined
|
|
1478
1579
|
? [
|
|
@@ -1487,18 +1588,26 @@ function buildTelegramInboundWorkerDiagnosticLines(
|
|
|
1487
1588
|
];
|
|
1488
1589
|
}
|
|
1489
1590
|
|
|
1591
|
+
function countTelegramQueueLanes(
|
|
1592
|
+
items: TelegramBridgeStatusLineState["queuedItems"],
|
|
1593
|
+
): Record<TelegramStatusQueueLane, number> {
|
|
1594
|
+
const counts: Record<TelegramStatusQueueLane, number> = {
|
|
1595
|
+
control: 0,
|
|
1596
|
+
priority: 0,
|
|
1597
|
+
default: 0,
|
|
1598
|
+
};
|
|
1599
|
+
for (const item of items) counts[item.queueLane]++;
|
|
1600
|
+
return counts;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1490
1603
|
function buildTelegramBridgeCompactStatusLines(
|
|
1491
1604
|
state: TelegramBridgeStatusLineState,
|
|
1492
1605
|
): string[] {
|
|
1493
|
-
const
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
).length;
|
|
1499
|
-
const defaultQueueCount = state.queuedItems.filter(
|
|
1500
|
-
(item) => item.queueLane === "default",
|
|
1501
|
-
).length;
|
|
1606
|
+
const {
|
|
1607
|
+
control: controlQueueCount,
|
|
1608
|
+
priority: priorityQueueCount,
|
|
1609
|
+
default: defaultQueueCount,
|
|
1610
|
+
} = countTelegramQueueLanes(state.queuedItems);
|
|
1502
1611
|
const queueLine = `- queued turns: ${state.queuedItems.length}${
|
|
1503
1612
|
state.queuedItems.length > 0
|
|
1504
1613
|
? ` (control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount})`
|
|
@@ -1566,15 +1675,11 @@ export function buildTelegramBridgeStatusLines(
|
|
|
1566
1675
|
function buildTelegramBridgeDiagnosticStatusLines(
|
|
1567
1676
|
state: TelegramBridgeStatusLineState,
|
|
1568
1677
|
): string[] {
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
).length;
|
|
1575
|
-
const defaultQueueCount = state.queuedItems.filter(
|
|
1576
|
-
(item) => item.queueLane === "default",
|
|
1577
|
-
).length;
|
|
1678
|
+
const {
|
|
1679
|
+
control: controlQueueCount,
|
|
1680
|
+
priority: priorityQueueCount,
|
|
1681
|
+
default: defaultQueueCount,
|
|
1682
|
+
} = countTelegramQueueLanes(state.queuedItems);
|
|
1578
1683
|
return [
|
|
1579
1684
|
"connection:",
|
|
1580
1685
|
`- bot: ${formatTelegramBridgeBotStatus(state)}`,
|
|
@@ -1631,13 +1736,6 @@ function buildTelegramBridgeDiagnosticStatusLines(
|
|
|
1631
1736
|
];
|
|
1632
1737
|
}
|
|
1633
1738
|
|
|
1634
|
-
function escapeHtml(text: string): string {
|
|
1635
|
-
return text
|
|
1636
|
-
.replace(/&/g, "&")
|
|
1637
|
-
.replace(/</g, "<")
|
|
1638
|
-
.replace(/>/g, ">");
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
1739
|
function formatTokens(count: number): string {
|
|
1642
1740
|
if (count < 1000) return count.toString();
|
|
1643
1741
|
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
@@ -1773,3 +1871,10 @@ function buildStatusHtml(
|
|
|
1773
1871
|
}
|
|
1774
1872
|
return lines.join("\n");
|
|
1775
1873
|
}
|
|
1874
|
+
|
|
1875
|
+
function escapeHtml(text: string): string {
|
|
1876
|
+
return text
|
|
1877
|
+
.replace(/&/g, "&")
|
|
1878
|
+
.replace(/</g, "<")
|
|
1879
|
+
.replace(/>/g, ">");
|
|
1880
|
+
}
|