feat: show connection state, bot, message counters and last activity in the omp status line

This commit is contained in:
evandrodevbr committed 2026-10-01 11:21:14 -03:00
1 parent 60254b288b
commit ca05b58f38
7 files changed
+554 -269

No files matched your search

+125 -17
View File
@@ -4,6 +4,13 @@
* Builds usage, cost, and context summaries for the interactive Telegram status view
*/
/** Message counters the status shows; structurally satisfied by the traffic domain. */
export interface TelegramStatusTraffic {
received: number;
sent: number;
lastAtMs?: number;
}
export type TelegramStatusQueueLane = "control" | "priority" | "default";
export interface TelegramUsageStats {
@@ -191,6 +198,7 @@ export interface TelegramBridgeStatusLineState {
instanceSlot?: string;
instanceThreadName?: string;
lockState?: string;
traffic?: TelegramStatusTraffic;
pollingActive: boolean;
lastUpdateId?: number;
activeSourceMessageIds?: number[];
@@ -215,12 +223,28 @@ export interface TelegramStatusBarTheme {
token: "accent" | "error" | "muted" | "warning" | "success",
text: string,
) => string;
/**
* omp symbol presets (unicode, nerd font, ascii) so the status follows the
* user's glyph set. Hosts that do not expose them get the unicode fallbacks.
*/
status?: {
enabled?: string;
shadowed?: string;
running?: string;
warning?: string;
error?: string;
disabled?: string;
};
sep?: { dot?: string };
icon?: { input?: string; output?: string; time?: string };
}
export interface TelegramStatusBarState {
hasBotToken: boolean;
pollingActive: boolean;
paired: boolean;
botUsername?: string;
traffic?: TelegramStatusTraffic;
busRole?: TelegramBridgeBusRole;
busLifecyclePhase?: TelegramBridgeBusLifecyclePhase;
instanceSlot?: string;
@@ -298,6 +322,7 @@ export interface TelegramBridgeStatusRuntimeDeps<
getInstanceSlot?: () => string | undefined;
getInstanceThreadName?: () => string | undefined;
getNowMs?: () => number;
getTraffic?: () => TelegramStatusTraffic;
}
export interface TelegramBridgeStatusLineOptions {
@@ -599,6 +624,8 @@ export function createTelegramBridgeStatusRuntime<
hasBotToken: !!config.botToken,
pollingActive: deps.isPollingActive(),
paired: !!config.allowedUserId,
botUsername: config.botUsername,
traffic: deps.getTraffic?.(),
busRole: deps.getBusRole?.(),
busLifecyclePhase: deps.getBusLifecyclePhase?.(),
instanceSlot: deps.getInstanceSlot?.(),
@@ -639,6 +666,7 @@ export function createTelegramBridgeStatusRuntime<
instanceSlot: deps.getInstanceSlot?.(),
instanceThreadName: deps.getInstanceThreadName?.(),
lockState: deps.getRuntimeLockState?.(),
traffic: deps.getTraffic?.(),
pollingActive: deps.isPollingActive(),
lastUpdateId: config.lastUpdateId,
activeSourceMessageIds: deps.getActiveSourceMessageIds(),
@@ -761,38 +789,116 @@ function getTelegramStatusBarLabel(state: TelegramStatusBarState): string {
return threadName;
}
interface TelegramStatusBarGlyphs {
enabled: string;
shadowed: string;
running: string;
warning: string;
error: string;
disabled: string;
dot: string;
input: string;
output: string;
time: string;
}
function resolveTelegramStatusBarGlyphs(
theme: TelegramStatusBarTheme,
): TelegramStatusBarGlyphs {
return {
enabled: theme.status?.enabled ?? "●",
shadowed: theme.status?.shadowed ?? "○",
running: theme.status?.running ?? "⟳",
warning: theme.status?.warning ?? "⚠",
error: theme.status?.error ?? "✘",
disabled: theme.status?.disabled ?? "⦸",
dot: theme.sep?.dot ?? " · ",
input: theme.icon?.input ?? "↓",
output: theme.icon?.output ?? "↑",
time: theme.icon?.time ?? "⏱",
};
}
function formatTelegramStatusBarClock(timestampMs: number): string {
const date = new Date(timestampMs);
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
return `${hours}:${minutes}`;
}
/** Bot name, message counters and last activity, each behind a muted dot. */
function buildTelegramStatusBarDetails(
theme: TelegramStatusBarTheme,
glyphs: TelegramStatusBarGlyphs,
state: TelegramStatusBarState,
): string {
const parts: string[] = [];
if (state.botUsername) parts.push(`@${state.botUsername}`);
if (state.traffic) {
parts.push(
`${glyphs.input}${state.traffic.received} ${glyphs.output}${state.traffic.sent}`,
);
if (state.traffic.lastAtMs !== undefined) {
parts.push(
`${glyphs.time} ${formatTelegramStatusBarClock(state.traffic.lastAtMs)}`,
);
}
}
return parts
.map((part) => theme.fg("muted", `${glyphs.dot}${part}`))
.join("");
}
function buildTelegramTrafficLines(
state: Pick<TelegramBridgeStatusLineState, "traffic">,
): string[] {
if (!state.traffic) return [];
const last =
state.traffic.lastAtMs === undefined
? ""
: `, last ${formatTelegramStatusBarClock(state.traffic.lastAtMs)}`;
return [
`- messages: ${state.traffic.received} received, ${state.traffic.sent} sent${last}`,
];
}
export function buildTelegramStatusBarText(
theme: TelegramStatusBarTheme,
state: TelegramStatusBarState,
): string {
const glyphs = resolveTelegramStatusBarGlyphs(theme);
const label = theme.fg("accent", getTelegramStatusBarLabel(state));
if (state.error) {
return `${label} ${theme.fg("error", "error")} ${theme.fg("muted", state.error)}`;
}
const hint = (text: string) => theme.fg("muted", `${glyphs.dot}${text}`);
const queued = state.queuedStatus
? theme.fg("success", state.queuedStatus)
: "";
if (!state.hasBotToken)
return `${label} ${theme.fg("muted", "not configured")}${queued}`;
if (!state.paired)
return `${label} ${theme.fg("warning", "awaiting pairing")}${queued}`;
if (state.busLifecyclePhase === "electing")
return `${label} ${theme.fg("warning", "electing")}${queued}`;
if (!state.pollingActive && state.busRole !== "follower")
return `${theme.fg("accent", "telegram")} ${theme.fg("muted", "disconnected")}${queued}`;
if (state.error) {
return `${label} ${theme.fg("error", `${glyphs.error} error`)} ${theme.fg("muted", state.error)}`;
}
if (!state.hasBotToken) {
return `${label} ${theme.fg("muted", `${glyphs.disabled} not configured`)}${queued}${hint("/telegram-setup")}`;
}
if (!state.paired) {
const bot = state.botUsername ? `@${state.botUsername}` : "the bot";
return `${label} ${theme.fg("warning", `${glyphs.warning} awaiting pairing`)}${queued}${hint(`send /start to ${bot}`)}`;
}
if (state.busLifecyclePhase === "electing") {
return `${label} ${theme.fg("warning", `${glyphs.running} electing`)}${queued}`;
}
if (!state.pollingActive && state.busRole !== "follower") {
return `${theme.fg("accent", "telegram")} ${theme.fg("muted", `${glyphs.shadowed} disconnected`)}${queued}${hint("/telegram-connect")}`;
}
const details = buildTelegramStatusBarDetails(theme, glyphs, state);
if (state.processing) {
const processingStatus = state.queuedStatus
? "active"
: (state.processingStatus ?? "processing");
const processingToken =
processingStatus === "active" ? "warning" : "accent";
return `${label} ${theme.fg(processingToken, processingStatus)}${queued}`;
return `${label} ${theme.fg(processingToken, `${glyphs.running} ${processingStatus}`)}${queued}${details}`;
}
if (state.busRole === "follower")
return `${label} ${theme.fg("success", "follower")}${queued}`;
if (state.busRole === "leader")
return `${label} ${theme.fg("success", "leader")}`;
return `${label} ${theme.fg("success", "connected")}`;
const role = state.busRole ?? "connected";
return `${label} ${theme.fg("success", `${glyphs.enabled} ${role}`)}${queued}${details}`;
}
function formatTelegramBridgeBotStatus(
@@ -1079,6 +1185,7 @@ function buildTelegramBridgeCompactStatusLines(
`- bot: ${formatTelegramBridgeBotStatus(state)}`,
...(state.activeProfileName ? [`- profile: ${state.activeProfileName}`] : []),
`- user: ${state.allowedUserId ?? "not paired"}`,
...buildTelegramTrafficLines(state),
...(state.botThreadMode ? [`- thread mode: ${state.botThreadMode}`] : []),
...(state.busRole ? [`- role: ${state.busRole}`] : []),
...(state.busLifecyclePhase
@@ -1134,6 +1241,7 @@ export function buildTelegramBridgeDiagnosticStatusLines(
`- bot: ${formatTelegramBridgeBotStatus(state)}`,
...(state.activeProfileName ? [`- profile: ${state.activeProfileName}`] : []),
`- allowed user: ${state.allowedUserId ?? "not paired"}`,
...buildTelegramTrafficLines(state),
...(state.botThreadMode
? [
`- thread mode: ${state.botThreadMode}${state.botThreadModeAction ? ` reconcile=${state.botThreadModeAction}` : ""}`,