Files
oh-my-pi-telegram/tests/status.test.ts
evandrodevbr 6f7e266a6e
Validate / validate (push) Successful in 9m52s
feat: keep error status until polling recovers, classify unconfirmed deliveries, document the UI
- the poll loop now clears its error status after the next successful getUpdates, not before the retry, so a slow retry never shows as healthy
- recovery is reported only when getUpdates itself failed
- a send that may have reached Telegram is reported as unconfirmed instead of failed
- README and CHANGELOG describe the states, toasts, queue preview and spinner text
2026-10-01 17:48:15 -03:00

1294 lines
41 KiB
TypeScript

/**
* Regression tests for Telegram status helpers
* Covers runtime diagnostics lines and recent-event redaction/ring-buffer behavior
*/
import assert from "node:assert/strict";
import test from "node:test";
import {
buildTelegramBridgeStatusLines,
buildTelegramRuntimeEventLines,
buildTelegramStatusBarText,
buildTelegramStatusBarLines,
clearTelegramStatusLineProviders,
createTelegramBridgeStatusRuntime,
createTelegramRuntimeDiagnosticsSnapshotScheduler,
createTelegramRuntimeEventRecorder,
createTelegramRuntimeLogScope,
createTelegramStatusHtmlBuilder,
createTelegramStatusSnapshot,
createTelegramStatusRuntime,
getTelegramStatusBarProcessingStatus,
formatTelegramWorkingMessage,
sanitizeTelegramStatusText,
summarizeTelegramStatusError,
recordStructuredTelegramRuntimeEvent,
recordTelegramRuntimeEvent,
registerTelegramStatusLineProvider,
type TelegramRuntimeEvent,
} from "../lib/status.ts";
test("Status helpers build runtime log scope and persisted snapshot projections", () => {
const state = {
busRole: "leader" as const,
botThreadMode: "enabled" as const,
botThreadModeUpdatedAtMs: 10,
botThreadModeAction: "probe",
instanceSlot: "A",
instanceThreadName: "Axial",
pollingActive: true,
lockState: "active-here",
pendingDispatch: true,
compactionInProgress: false,
activeToolExecutions: 1,
pendingModelSwitch: false,
queuedItems: [],
busFollowers: [{ instanceId: "follower", lastHeartbeatMs: 5 }],
topicTargets: [{ instanceId: "leader", status: "active" }],
threadReservations: [{ slot: "B", reason: "startup" }],
topicSyncObservations: [{ syncStatus: "open", observedAtMs: 9 }],
syncState: { pairing: { status: "fresh" } },
recentRuntimeEvents: [],
};
assert.deepEqual(
createTelegramRuntimeLogScope({ state, instanceId: "instance-1" }),
{
instanceId: "instance-1",
role: "leader",
slot: "A",
threadName: "Axial",
lockState: "active-here",
},
);
assert.deepEqual(createTelegramStatusSnapshot(state), {
runtime: {
busRole: "leader",
botThreadMode: "enabled",
botThreadModeUpdatedAtMs: 10,
botThreadModeAction: "probe",
instanceSlot: "A",
instanceThreadName: "Axial",
pollingActive: true,
lockState: "active-here",
},
liveRoster: {
busFollowers: [{ instanceId: "follower", lastHeartbeatMs: 5 }],
topicTargets: [{ instanceId: "leader", status: "active" }],
reservations: [{ slot: "B", reason: "startup" }],
syncObservations: [{ syncStatus: "open", observedAtMs: 9 }],
},
diagnostics: {
pendingDispatch: true,
compactionInProgress: false,
activeToolExecutions: 1,
pendingModelSwitch: false,
syncState: { pairing: { status: "fresh" } },
threadReconciliation: undefined,
recentRuntimeEvents: [],
},
});
});
test("Status runtime diagnostics scheduler coalesces snapshot persists", async () => {
let scheduled: (() => void) | undefined;
let persistCount = 0;
const errors: unknown[] = [];
const schedule = createTelegramRuntimeDiagnosticsSnapshotScheduler({
persistSnapshot: async () => {
persistCount += 1;
},
recordError: (error) => errors.push(error),
setTimer(callback) {
scheduled = callback as () => void;
return { unref() {} } as ReturnType<typeof setTimeout>;
},
});
schedule();
schedule();
scheduled?.();
await Promise.resolve();
assert.equal(persistCount, 1);
assert.deepEqual(errors, []);
});
const statusBarTheme = {
fg: (token: string, text: string) => `<${token}>${text}</${token}>`,
};
function plainStatusText(text: string): string {
return text.replace(/<\/?[a-z]+>/g, "");
}
function escapeRegExp(text: string): string {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
test("Status bar text keeps the label, state color and queue for every connection state", () => {
const base = {
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: false,
queuedStatus: "",
};
const cases: Array<{
state: Partial<Parameters<typeof buildTelegramStatusBarText>[1]>;
label: string;
token: string;
word: string;
queued?: string;
}> = [
{ state: { hasBotToken: false, pollingActive: false, paired: false }, label: "telegram", token: "muted", word: "not configured" },
{ state: { processing: true, queuedStatus: " +1" }, label: "telegram", token: "warning", word: "active", queued: " +1" },
{ state: { processing: true, processingStatus: "dispatching", queuedStatus: " +1" }, label: "telegram", token: "warning", word: "active", queued: " +1" },
{ state: { processing: true, processingStatus: "active" }, label: "telegram", token: "warning", word: "active" },
{ state: { compactionInProgress: true }, label: "telegram", token: "success", word: "connected" },
{ state: { compactionInProgress: true, processing: true, processingStatus: "active" }, label: "telegram", token: "warning", word: "active" },
{ state: { pollingActive: false, instanceThreadName: "Aurora", processing: true, processingStatus: "queued", queuedStatus: " +2" }, label: "telegram", token: "muted", word: "disconnected", queued: " +2" },
{ state: { pollingActive: false, busRole: "follower" }, label: "telegram", token: "success", word: "follower" },
{ state: { pollingActive: false, busRole: "follower", instanceThreadName: "Amber", processing: true, processingStatus: "active" }, label: "Amber", token: "warning", word: "active" },
{ state: { pollingActive: false, busRole: "follower", busLifecyclePhase: "electing" }, label: "telegram", token: "warning", word: "electing" },
{ state: { pollingActive: false, busLifecyclePhase: "electing" }, label: "telegram", token: "warning", word: "electing" },
{ state: { pollingActive: false, busRole: "follower", instanceThreadName: "Follower" }, label: "telegram", token: "success", word: "follower" },
{ state: { pollingActive: false, busRole: "follower", instanceThreadName: "Lname" }, label: "Lname", token: "success", word: "follower" },
{ state: { pollingActive: false, busRole: "follower", instanceSlot: "O", instanceThreadName: "extensions Follower" }, label: "extensions Follower", token: "success", word: "follower" },
{ state: { pollingActive: false, busRole: "follower", instanceSlot: "O", instanceThreadName: "Oname" }, label: "Oname", token: "success", word: "follower" },
{ state: { busRole: "leader" }, label: "telegram", token: "success", word: "leader" },
{ state: { busRole: "leader", instanceThreadName: "🌙 A-identity" }, label: "🌙 A-identity", token: "success", word: "leader" },
{ state: { paired: false, processing: true, processingStatus: "queued", queuedStatus: " +1" }, label: "telegram", token: "warning", word: "awaiting pairing", queued: " +1" },
];
for (const { state, label, token, word, queued } of cases) {
const text = buildTelegramStatusBarText(statusBarTheme, { ...base, ...state });
const expected = new RegExp(
`^<accent>${escapeRegExp(label)}</accent> <${token}>\\S+ ${word}</${token}>${queued ? `<success>${escapeRegExp(queued)}</success>` : ""}`,
);
assert.match(text, expected, JSON.stringify(state));
}
assert.match(
buildTelegramStatusBarText(statusBarTheme, {
...base,
error: "typing failed",
}),
/^<accent>telegram<\/accent> <error>\S+ error<\/error> <muted>typing failed<\/muted>$/,
);
});
test("Status bar shows bot, message counters and last activity only while the bridge is up", () => {
const lastAtMs = new Date(2026, 9, 1, 14, 32).getTime();
const connected = {
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: false,
queuedStatus: "",
botUsername: "demo_bot",
traffic: { received: 12, sent: 11, lastAtMs },
};
const up = plainStatusText(buildTelegramStatusBarText(statusBarTheme, connected));
assert.match(up, /connected/);
assert.match(up, /@demo_bot/);
assert.match(up, /12 \S*11/);
assert.match(up, /14:32/);
const withoutActivity = plainStatusText(
buildTelegramStatusBarText(statusBarTheme, {
...connected,
traffic: { received: 0, sent: 0 },
}),
);
assert.match(withoutActivity, /0 \S*0/);
assert.doesNotMatch(withoutActivity, /\d\d:\d\d/);
const working = plainStatusText(
buildTelegramStatusBarText(statusBarTheme, {
...connected,
processing: true,
processingStatus: "active",
}),
);
assert.match(working, /active/);
assert.match(working, /@demo_bot/);
assert.match(working, /12 \S*11/);
const offline = plainStatusText(
buildTelegramStatusBarText(statusBarTheme, { ...connected, pollingActive: false }),
);
assert.doesNotMatch(offline, /@demo_bot|14:32/);
assert.match(offline, /disconnected/);
});
test("Status bar tells the operator how to leave a non-working state", () => {
const base = {
hasBotToken: true,
pollingActive: false,
paired: true,
compactionInProgress: false,
processing: false,
queuedStatus: "",
};
assert.match(
plainStatusText(buildTelegramStatusBarText(statusBarTheme, { ...base, hasBotToken: false, paired: false })),
/\/telegram-setup/,
);
const pairing = plainStatusText(
buildTelegramStatusBarText(statusBarTheme, { ...base, paired: false, botUsername: "demo_bot" }),
);
assert.match(pairing, /\/start/);
assert.match(pairing, /@demo_bot/);
assert.match(
plainStatusText(buildTelegramStatusBarText(statusBarTheme, base)),
/\/telegram-connect/,
);
});
test("Status bar follows the host symbol preset and falls back without one", () => {
const state = {
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: false,
queuedStatus: "",
botUsername: "demo_bot",
traffic: { received: 3, sent: 2, lastAtMs: new Date(2026, 9, 1, 9, 5).getTime() },
};
const ascii = plainStatusText(
buildTelegramStatusBarText(
{
...statusBarTheme,
status: { enabled: "[x]" },
sep: { dot: " - " },
icon: { input: "in:", output: "out:", time: "t:" },
},
state,
),
);
assert.equal(ascii, "telegram [x] connected - @demo_bot - in:3 out:2 - t: 09:05");
const fallback = plainStatusText(buildTelegramStatusBarText(statusBarTheme, state));
assert.match(fallback, /^telegram \S+ connected · @demo_bot · \S*3 \S*2 · \S+ 09:05$/u);
});
test("Status runtime updates the status bar and exposes bridge lines", () => {
const events: string[] = [];
const ctx = {
ui: {
theme: {
fg: (token: string, text: string) => `<${token}>${text}</${token}>`,
},
setStatus: (key: string, text: string) => {
events.push(`${key}:${text}`);
},
},
};
const runtime = createTelegramStatusRuntime({
getStatusBarState: (_ctx, error) => ({
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: false,
queuedStatus: "",
error,
}),
getBridgeStatusLineState: () => ({
botUsername: "demo_bot",
allowedUserId: 7,
lockState: "active here",
pollingActive: true,
lastUpdateId: 10,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
recentRuntimeEvents: [],
}),
});
runtime.updateStatus(ctx, "demo error");
assert.match(
events[0],
/^telegram:<accent>telegram<\/accent> <error>\S+ error<\/error> <muted>demo error<\/muted>$/,
);
assert.deepEqual(runtime.getStatusLines().slice(0, 3), [
"connection:",
"- bot: @demo_bot",
"- user: 7",
]);
});
test("Status runtime keeps theme colors in the TUI through a widget and stays plain elsewhere", () => {
const createContext = (mode: string | undefined) => {
const events: string[] = [];
return {
events,
ctx: {
mode,
ui: {
theme: statusBarTheme,
setStatus: (key: string, text: string | undefined) => {
events.push(`status:${key}:${text}`);
},
setWidget: (
key: string,
content: string[] | undefined,
options?: { placement?: string },
) => {
events.push(`widget:${key}:${options?.placement}:${content?.join("|")}`);
},
},
},
};
};
const runtime = createTelegramStatusRuntime({
getStatusBarState: () => ({
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: false,
queuedStatus: "",
}),
getBridgeStatusLineState: () => {
throw new Error("not used");
},
});
const tui = createContext("tui");
runtime.updateStatus(tui.ctx);
assert.equal(tui.events.length, 2);
assert.match(tui.events[0], /^widget:telegram:belowEditor:<accent>telegram<\/accent> <success>\S+ connected<\/success>$/);
assert.equal(tui.events[1], "status:telegram:undefined");
for (const mode of ["rpc", "json", "print", undefined]) {
const other = createContext(mode);
runtime.updateStatus(other.ctx);
assert.equal(other.events.length, 1, String(mode));
assert.match(other.events[0], /^status:telegram:<accent>telegram<\/accent> <success>\S+ connected<\/success>$/);
}
const noWidgetApi = createContext("tui");
delete (noWidgetApi.ctx.ui as { setWidget?: unknown }).setWidget;
runtime.updateStatus(noWidgetApi.ctx);
assert.equal(noWidgetApi.events.length, 1);
assert.match(noWidgetApi.events[0], /^status:telegram:/);
});
test("Error summaries turn transport failures into a short state and next step", () => {
const cases: Array<[string, string | undefined, string | undefined, string | undefined]> = [
["Telegram API getUpdates failed: HTTP 401: Unauthorized", "fatal", "invalid token", "/telegram-setup"],
["Telegram API sendMessage failed: HTTP 429: Too Many Requests: retry after 12", "retrying", "rate limited 12s", "retrying"],
["Telegram API sendMessage failed: HTTP 429", "retrying", "rate limited", "retrying"],
["Conflict: terminated by other getUpdates request; make sure that only one bot instance is running", "attention", "another poller is active", "/telegram-status"],
["Telegram API getUpdates failed: HTTP 502: Bad Gateway", "retrying", "Telegram unavailable", "retrying"],
["Unable to connect. Is the computer able to access the url?", "retrying", "offline", "retrying"],
["fetch failed", "retrying", "offline", "retrying"],
["Telegram API sendRichMessage may have committed before transport failed.", "attention", "delivery unconfirmed", undefined],
["typing failed", undefined, undefined, undefined],
["settingsManager.reload is not a function", undefined, undefined, undefined],
];
for (const [message, kind, label, hint] of cases) {
const summary = summarizeTelegramStatusError(message);
assert.equal(summary?.kind, kind, message);
assert.equal(summary?.label, label, message);
assert.equal(summary?.hint, hint, message);
}
});
test("Status bar shows classified errors with their next step and keeps unknown ones verbatim", () => {
const base = {
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: false,
queuedStatus: "",
};
const fatal = buildTelegramStatusBarText(statusBarTheme, {
...base,
error: "Telegram API getUpdates failed: HTTP 401: Unauthorized",
});
assert.match(fatal, /<error>\S+ invalid token<\/error>/);
assert.match(plainStatusText(fatal), /\/telegram-setup/);
const retrying = buildTelegramStatusBarText(statusBarTheme, {
...base,
error: "Unable to connect. Is the computer able to access the url?",
});
assert.match(retrying, /<warning>\S+ offline<\/warning>/);
assert.match(plainStatusText(retrying), /retrying/);
const attention = buildTelegramStatusBarText(statusBarTheme, {
...base,
error: "HTTP 409 Conflict",
});
assert.match(attention, /<warning>\S+ another poller is active<\/warning>/);
assert.match(plainStatusText(attention), /\/telegram-status/);
assert.match(
buildTelegramStatusBarText(statusBarTheme, { ...base, error: "typing failed" }),
/<error>\S+ error<\/error> <muted>typing failed<\/muted>/,
);
const long = plainStatusText(
buildTelegramStatusBarText(statusBarTheme, { ...base, error: "x".repeat(200) }),
);
assert.ok(long.includes("…"));
assert.equal(long.includes("x".repeat(101)), false);
});
test("Status bar lines carry no terminal control sequences from Telegram-controlled text", () => {
const hostile = "\u001b]0;pwned\u0007\u001b[31mred\u001b[0m\u202e";
const base = {
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: true,
queuedStatus: " +1",
instanceThreadName: `thread${hostile}`,
botUsername: `bot${hostile}`,
queuedPreview: [`queued${hostile}`],
};
const unsafe = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e]/;
for (const state of [
base,
{ ...base, error: `boom${hostile}` },
{ ...base, paired: false },
]) {
for (const line of buildTelegramStatusBarLines(statusBarTheme, state)) {
assert.doesNotMatch(line, unsafe, JSON.stringify(line));
}
}
assert.equal(sanitizeTelegramStatusText(" a\u001b[0m b\u0000 "), "a [0m b");
});
test("Status bar previews the next queued turns only while the bridge is up", () => {
const base = {
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: true,
queuedStatus: " +3",
};
const three = buildTelegramStatusBarLines(statusBarTheme, {
...base,
queuedPreview: ["one", "two", "three"],
});
assert.equal(three.length, 2);
assert.match(plainStatusText(three[1]), /next: "one" · "two" · \+1 more/);
const two = buildTelegramStatusBarLines(statusBarTheme, {
...base,
queuedPreview: ["one", "two"],
});
assert.match(plainStatusText(two[1]), /next: "one" · "two"$/);
for (const state of [
{ ...base, queuedPreview: [] },
{ ...base, queuedPreview: ["one"], pollingActive: false },
{ ...base, queuedPreview: ["one"], paired: false },
{ ...base, queuedPreview: ["one"], error: "typing failed" },
]) {
assert.equal(buildTelegramStatusBarLines(statusBarTheme, state).length, 1, JSON.stringify(state));
}
});
test("Status bar surfaces failed deliveries only when there are some", () => {
const state = {
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: false,
queuedStatus: "",
traffic: { received: 12, sent: 11, failed: 2 },
};
assert.match(plainStatusText(buildTelegramStatusBarText(statusBarTheme, state)), /12 \S*11 \S*2/);
const none = plainStatusText(
buildTelegramStatusBarText(statusBarTheme, {
...state,
traffic: { received: 12, sent: 11, failed: 0 },
}),
);
assert.equal(none.includes("✘"), false);
});
test("Status runtime puts the queue preview in the widget and keeps the plain status to one line", () => {
const runtime = createTelegramStatusRuntime({
getStatusBarState: () => ({
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: true,
queuedStatus: " +1",
queuedPreview: ["one"],
}),
getBridgeStatusLineState: () => {
throw new Error("not used");
},
});
const widgets: string[][] = [];
const statuses: Array<string | undefined> = [];
const ui = {
theme: statusBarTheme,
setStatus: (_key: string, text: string | undefined) => {
statuses.push(text);
},
setWidget: (_key: string, content: string[] | undefined) => {
widgets.push(content ?? []);
},
};
runtime.updateStatus({ mode: "tui", ui });
assert.equal(widgets[0].length, 2);
assert.match(plainStatusText(widgets[0][1]), /next: "one"/);
runtime.updateStatus({ mode: "rpc", ui });
assert.equal(statuses.at(-1)?.includes("next:"), false);
});
test("Working message names the Telegram prompt and ignores control characters", () => {
assert.equal(formatTelegramWorkingMessage("say pong"), 'Answering Telegram · "say pong"');
assert.equal(formatTelegramWorkingMessage(" "), "Answering Telegram");
assert.equal(formatTelegramWorkingMessage("a\u001b[0mb"), 'Answering Telegram · "a [0mb"');
});
test("Status lines expose thread reconciliation state", () => {
const lines = buildTelegramBridgeStatusLines({
botUsername: "demo_bot",
allowedUserId: 7,
pollingActive: true,
lastUpdateId: 10,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
threadReconciliation: {
phase: "cleanup-required",
event: "cleanup-required",
atMs: 1000,
leaderEpoch: 3,
pendingProvisionCount: 1,
syncActionCount: 2,
cleanupActionCount: 1,
},
recentRuntimeEvents: [],
});
assert.ok(lines.includes("reconciliation:"));
assert.ok(
lines.includes("- phase: cleanup-required event=cleanup-required epoch=3"),
);
assert.ok(lines.includes("- counts: pending=1, sync=2, cleanup=1"));
});
test("Status runtime propagates status update failures to safety wrappers", () => {
const runtime = createTelegramStatusRuntime({
getStatusBarState: () => ({
hasBotToken: true,
pollingActive: true,
paired: true,
compactionInProgress: false,
processing: false,
queuedStatus: "",
}),
getBridgeStatusLineState: () => ({
botUsername: undefined,
allowedUserId: undefined,
pollingActive: false,
lastUpdateId: undefined,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
recentRuntimeEvents: [],
}),
});
assert.throws(
() =>
runtime.updateStatus({
ui: {
theme: { fg: (_token: string, text: string) => text },
setStatus: () => {
throw new Error("ctx is stale after session reload");
},
},
}),
/stale after session/,
);
});
test("Status bar processing labels prefer the most specific live state", () => {
assert.equal(
getTelegramStatusBarProcessingStatus({
hasActiveTurn: true,
hasPendingDispatch: true,
hasPendingModelSwitch: true,
activeToolExecutions: 1,
queuedItems: 1,
}),
"model",
);
assert.equal(
getTelegramStatusBarProcessingStatus({
hasActiveTurn: true,
hasPendingDispatch: false,
hasPendingModelSwitch: false,
activeToolExecutions: 1,
queuedItems: 1,
}),
"active",
);
assert.equal(
getTelegramStatusBarProcessingStatus({
hasActiveTurn: false,
hasPendingDispatch: false,
hasPendingModelSwitch: false,
activeToolExecutions: 1,
queuedItems: 1,
}),
"active",
);
assert.equal(
getTelegramStatusBarProcessingStatus({
hasActiveTurn: false,
hasPendingDispatch: true,
hasPendingModelSwitch: false,
activeToolExecutions: 0,
queuedItems: 1,
}),
"dispatching",
);
assert.equal(
getTelegramStatusBarProcessingStatus({
hasActiveTurn: false,
hasPendingDispatch: false,
hasPendingModelSwitch: false,
activeToolExecutions: 0,
queuedItems: 1,
}),
"queued",
);
});
test("Bridge status runtime stays active while tools run after queue changes", () => {
const events: string[] = [];
const runtime = createTelegramBridgeStatusRuntime({
getConfig: () => ({
botToken: "token",
botUsername: "demo_bot",
allowedUserId: 7,
}),
isPollingActive: () => true,
getActiveSourceMessageIds: () => undefined,
hasActiveTurn: () => false,
hasDispatchPending: () => false,
isCompactionInProgress: () => false,
getActiveToolExecutions: () => 1,
hasPendingModelSwitch: () => false,
getQueuedItems: () => [],
formatQueuedStatus: () => "",
getRecentRuntimeEvents: () => [],
});
runtime.updateStatus({
ui: {
theme: {
fg: (token: string, text: string) => `<${token}>${text}</${token}>`,
},
setStatus: (key: string, text: string) => {
events.push(`${key}:${text}`);
},
},
});
assert.match(
events[0],
/^telegram:<accent>telegram<\/accent> <warning>\S+ active<\/warning>/,
);
});
test("Bridge status runtime builds status state from live ports", () => {
const events: string[] = [];
const runtime = createTelegramBridgeStatusRuntime({
getConfig: () => ({
botToken: "token",
botUsername: "demo_bot",
allowedUserId: 7,
lastUpdateId: 99,
}),
isPollingActive: () => true,
getActiveSourceMessageIds: () => [1, 2],
hasActiveTurn: () => false,
hasDispatchPending: () => true,
isCompactionInProgress: () => false,
getActiveToolExecutions: () => 3,
hasPendingModelSwitch: () => true,
getQueuedItems: () => [{ queueLane: "control" as const }],
formatQueuedStatus: () => " +1",
getRecentRuntimeEvents: () => [
{ at: 1000, category: "api", message: "ok" },
],
getRuntimeLockState: () => "active here",
});
runtime.updateStatus({
ui: {
theme: {
fg: (token: string, text: string) => `<${token}>${text}</${token}>`,
},
setStatus: (key: string, text: string) => {
events.push(`${key}:${text}`);
},
},
});
assert.match(
events[0],
/^telegram:<accent>telegram<\/accent> <warning>\S+ active<\/warning><success> \+1<\/success>/,
);
assert.deepEqual(runtime.getStatusLines(), [
"connection:",
"- bot: @demo_bot",
"- user: 7",
"- owner: active here",
"",
"health:",
"- polling: running",
"- state: pending dispatch",
"- queued turns: 1 (control=1, priority=0, default=0)",
"- active tools: 3",
"- pending model switch: yes",
"",
"diagnostics:",
"- state: ~/.pi/agent/tmp/telegram/state.json",
"- logs: ~/.pi/agent/tmp/telegram/logs.jsonl",
"- full dump: /telegram-status --debug",
]);
});
test("Bridge status lines render named-profile diagnostic paths", () => {
const lines = buildTelegramBridgeStatusLines({
activeProfileName: "work",
botUsername: "work_bot",
pollingActive: false,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
recentRuntimeEvents: [],
});
assert.ok(
lines.includes("- state: ~/.pi/agent/tmp/telegram/state.work.json"),
);
assert.ok(
lines.includes("- logs: ~/.pi/agent/tmp/telegram/logs.work.jsonl"),
);
});
test("Bridge status lines distinguish unknown bot identity from missing config", () => {
const base = {
allowedUserId: 42,
pollingActive: true,
lastUpdateId: 100,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
recentRuntimeEvents: [],
};
assert.equal(
buildTelegramBridgeStatusLines({ ...base, hasBotToken: true })[1],
"- bot: unknown",
);
assert.equal(
buildTelegramBridgeStatusLines({ ...base, hasBotToken: false })[1],
"- bot: not configured",
);
});
test("Bridge status lines include role and instance thread name", () => {
const lines = buildTelegramBridgeStatusLines({
botUsername: "demo_bot",
allowedUserId: 42,
busRole: "leader",
instanceSlot: "A",
instanceThreadName: "A-identity",
pollingActive: true,
lastUpdateId: 100,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
recentRuntimeEvents: [],
});
assert.deepEqual(lines.slice(0, 5), [
"connection:",
"- bot: @demo_bot",
"- user: 42",
"- role: leader",
"- instance: A-identity",
]);
});
test("Bridge status lines include sync slice diagnostics", () => {
const lines = buildTelegramBridgeStatusLines(
{
botUsername: "demo_bot",
allowedUserId: 42,
pollingActive: true,
lastUpdateId: 100,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
syncState: {
"topic-state": {
status: "fresh",
updatedAtMs: 2000,
lastReconcileAction: "topic-lifecycle",
},
"transport-health": {
status: "suspect",
suspectAtMs: 3000,
reason: "rate limited",
},
},
recentRuntimeEvents: [],
},
{ verbose: true },
);
assert.ok(lines.includes("sync:"));
assert.ok(lines.includes("- topic-state: fresh reconcile=topic-lifecycle"));
assert.ok(lines.includes("- transport-health: suspect reason=rate limited"));
});
test("Bridge status lines include bot thread capability diagnostics", () => {
const lines = buildTelegramBridgeStatusLines(
{
botUsername: "demo_bot",
allowedUserId: 42,
botThreadMode: "disabled",
botThreadModeAction: "thread-mode-unavailable",
pollingActive: true,
lastUpdateId: 100,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
recentRuntimeEvents: [],
},
{ verbose: true },
);
assert.ok(
lines.includes("- thread mode: disabled reconcile=thread-mode-unavailable"),
);
});
test("Bridge status lines include topic binding diagnostics", () => {
const lines = buildTelegramBridgeStatusLines(
{
botUsername: "demo_bot",
allowedUserId: 42,
pollingActive: true,
lastUpdateId: 100,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
topicTargets: [
{
instanceId: "inst-a",
status: "active",
target: { chatId: 42, threadId: 10 },
slot: "B",
threadName: "Beacon",
syncStatus: "open",
lastSyncObservedAtMs: 1000,
lastSyncProbeAtMs: 2000,
lastReconcileAction: "leader-startup-probe",
},
{
instanceId: "inst-a",
status: "starting",
target: { chatId: 42, threadId: 11 },
slot: "C",
threadName: "Cedar",
},
{ instanceId: "inst-b", status: "offline", slot: "D" },
],
threadReservations: [
{
instanceId: "old-leader",
target: { chatId: 42, threadId: 9 },
slot: "A",
reason: "previous-process-still-probes-alive",
lastReconcileAction: "leader-topic-previous-instance-still-live",
},
],
topicSyncObservations: [
{
instanceId: "closed-inst",
target: { chatId: 42, threadId: 8 },
slot: "D",
syncStatus: "closed",
observedAtMs: 3000,
lastReconcileAction: "mark-stale",
},
],
recentRuntimeEvents: [],
},
{ verbose: true },
);
assert.deepEqual(lines.slice(18, 21), [
"topics:",
"- active bindings: instances=1, targets=2",
"- duplicate inst-a: 2 active threads Beacon target 42:10, Cedar target 42:11",
]);
assert.ok(
lines.includes(
"- Beacon target 42:10 sync=open observed=1970-01-01T00:00:01.000Z probed=1970-01-01T00:00:02.000Z reconcile=leader-startup-probe",
),
);
assert.ok(lines.includes("- Cedar target 42:11"));
assert.ok(
lines.includes(
"- reservation [A] target 42:9 reason=previous-process-still-probes-alive instance=old-leader reconcile=leader-topic-previous-instance-still-live",
),
);
assert.ok(
lines.includes(
"- sync [D] target 42:8 sync=closed observed=1970-01-01T00:00:03.000Z instance=closed-inst reconcile=mark-stale",
),
);
});
test("Bridge status lines include bus follower diagnostics when present", () => {
const lines = buildTelegramBridgeStatusLines(
{
botUsername: "demo_bot",
allowedUserId: 42,
pollingActive: true,
lastUpdateId: 100,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
busNowMs: 20_000,
busFollowers: [
{
instanceId: "inst-a",
cwd: "/repo/a",
lastHeartbeatMs: 18_600,
target: { chatId: -1007, threadId: 42 },
threadName: "Ember",
},
{ instanceId: "inst-b", lastHeartbeatMs: 12_000 },
],
recentRuntimeEvents: [],
},
{ verbose: true },
);
assert.deepEqual(lines.slice(19, 24), [
"bus:",
"- followers: 2",
"- inst-a: Ember heartbeat 1s ago target -1007:42 /repo/a",
"- inst-b: heartbeat 8s ago",
"",
]);
});
test("Bridge status lines include local bus diagnostics", () => {
const lines = buildTelegramBridgeStatusLines(
{
botUsername: "demo_bot",
allowedUserId: 42,
pollingActive: false,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
localBus: {
leaderSocketPath: "\\\\.\\pipe\\pi-telegram-demo-bus",
leaderTransport: "pipe",
followerSocketPath: "\\\\.\\pipe\\pi-telegram-demo-follower",
followerTransport: "pipe",
followerRegistered: true,
followerTarget: { chatId: 42, threadId: 9 },
followerThreadName: "Boreal",
},
recentRuntimeEvents: [],
},
{ verbose: true },
);
assert.ok(lines.includes("local bus:"));
assert.ok(lines.includes("- follower registered: yes Boreal target 42:9"));
assert.ok(
lines.includes(
"- leader endpoint [pipe]: \\\\.\\pipe\\pi-telegram-demo-bus",
),
);
assert.ok(
lines.includes(
"- follower endpoint [pipe]: \\\\.\\pipe\\pi-telegram-demo-follower",
),
);
});
test("Bridge status lines include queue lanes and recent runtime events", () => {
const lines = buildTelegramBridgeStatusLines(
{
botUsername: "demo_bot",
allowedUserId: 42,
pollingActive: true,
lastUpdateId: 100,
activeSourceMessageIds: [7, 8],
pendingDispatch: true,
compactionInProgress: false,
activeToolExecutions: 2,
pendingModelSwitch: true,
queuedItems: [
{ queueLane: "control" },
{ queueLane: "priority" },
{ queueLane: "default" },
{ queueLane: "default" },
],
recentRuntimeEvents: [
{ at: 1, category: "api:sendMessage", message: "rate limited" },
],
},
{ verbose: true },
);
assert.deepEqual(lines, [
"connection:",
"- bot: @demo_bot",
"- allowed user: 42",
"",
"polling:",
"- state: running",
"- last update id: 100",
"",
"execution:",
"- active turn: 7,8",
"- pending dispatch: yes",
"- compaction: idle",
"- active tools: 2",
"- pending model switch: yes",
"",
"queue:",
"- queued turns: 4",
"- lanes: control=1, priority=1, default=2",
"",
"recent runtime events:",
"- summary: api:sendMessage=1",
"- 1970-01-01T00:00:00.001Z api:sendMessage: rate limited",
]);
});
test("Status HTML builder binds active model lookup", () => {
const model = { provider: "openai", id: "gpt-5", contextWindow: 1000 };
const buildStatusHtml = createTelegramStatusHtmlBuilder({
getActiveModel: () => model,
});
const html = buildStatusHtml({
sessionManager: { getEntries: () => [] },
getContextUsage: () => ({ percent: 0, contextWindow: undefined }),
isIdle: () => true,
modelRegistry: { isUsingOAuth: () => false },
});
assert.match(html, /Status.*idle/s);
assert.match(html, /Context.*0\.0%\/1\.0k/s);
});
test("Status HTML builder appends Threaded Mode bus role to status row", () => {
const buildStatusHtml = createTelegramStatusHtmlBuilder({
getActiveModel: () => undefined,
getBridgeStatusLineState: () => ({
hasBotToken: true,
botThreadMode: "enabled",
busRole: "leader",
instanceThreadName: "Dune",
pollingActive: true,
pendingDispatch: false,
compactionInProgress: false,
activeToolExecutions: 0,
pendingModelSwitch: false,
queuedItems: [],
recentRuntimeEvents: [],
}),
});
const html = buildStatusHtml({
sessionManager: { getEntries: () => [] },
getContextUsage: () => ({ percent: 0, contextWindow: 1000 }),
isIdle: () => true,
modelRegistry: { isUsingOAuth: () => false },
});
assert.match(html, /Status.*idle @leader/s);
assert.doesNotMatch(html, /Telegram/s);
});
test("Status HTML builder includes extension-provided status lines", () => {
clearTelegramStatusLineProviders();
const unregisterCodex = registerTelegramStatusLineProvider(
({ activeModel }) =>
activeModel?.contextWindow === 1000
? { label: "codex", value: "████ 23.7h" }
: undefined,
{ id: "@scope/codex" },
);
const unregisterBroken = registerTelegramStatusLineProvider(
() => {
throw new Error("optional provider failed");
},
{ id: "@scope/broken" },
);
try {
const buildStatusHtml = createTelegramStatusHtmlBuilder({
getActiveModel: () => ({ contextWindow: 1000 }),
});
const html = buildStatusHtml({
sessionManager: { getEntries: () => [] },
getContextUsage: () => ({ percent: 0, contextWindow: undefined }),
isIdle: () => true,
modelRegistry: { isUsingOAuth: () => false },
});
assert.match(html, /Context.*0\.0%\/1\.0k/s);
assert.match(html, /Codex.*████ 23\.7h/s);
} finally {
unregisterCodex();
unregisterBroken();
clearTelegramStatusLineProviders();
}
});
test("Status HTML builder leaves compaction lifecycle to Pi", () => {
const buildStatusHtml = createTelegramStatusHtmlBuilder({
getActiveModel: () => undefined,
isCompactionInProgress: () => true,
});
const html = buildStatusHtml({
sessionManager: { getEntries: () => [] },
getContextUsage: () => ({ percent: 0, contextWindow: 1000 }),
isIdle: () => true,
modelRegistry: { isUsingOAuth: () => false },
});
assert.match(html, /Status.*idle/s);
assert.doesNotMatch(html, /compacting/);
});
test("Runtime event lines render the recent-event ring newest first", () => {
assert.deepEqual(buildTelegramRuntimeEventLines([]), [
"recent runtime events: none",
]);
assert.deepEqual(
buildTelegramRuntimeEventLines([
{ at: 0, category: "poll", message: "started" },
{ at: 1000, category: "api:sendMessage", message: "rate limited" },
]),
[
"recent runtime events:",
"- summary: api:sendMessage=1, poll=1",
"- 1970-01-01T00:00:01.000Z api:sendMessage: rate limited",
"- 1970-01-01T00:00:00.000Z poll: started",
],
);
});
test("Structured runtime event recording redacts messages and details", () => {
const events: TelegramRuntimeEvent[] = [];
recordStructuredTelegramRuntimeEvent(
events,
{
category: "api",
error: new Error("token 123:abc failed"),
details: { method: "sendMessage", token: "123:abc", retryable: true },
},
{ botToken: "123:abc", maxEvents: 3, now: 1000 },
);
assert.deepEqual(events, [
{
at: 1000,
category: "api",
message: "token <redacted-token> failed",
details: {
method: "sendMessage",
token: "<redacted-token>",
retryable: true,
},
},
]);
assert.deepEqual(buildTelegramRuntimeEventLines(events), [
"recent runtime events:",
"- summary: api:sendMessage=1",
'- 1970-01-01T00:00:01.000Z api:sendMessage: token <redacted-token> failed (token="<redacted-token>", retryable=true)',
]);
});
test("Runtime event recording bounds messages and string details", () => {
const events: TelegramRuntimeEvent[] = [];
recordStructuredTelegramRuntimeEvent(
events,
{
category: "handler",
error: new Error("x".repeat(1200)),
details: { output: "y".repeat(1200) },
},
{ maxEvents: 3, now: 1000 },
);
assert.equal(events[0]?.message.length, 1023);
assert.match(events[0]?.message ?? "", /truncated 200 chars/);
assert.equal(String(events[0]?.details?.output).length, 1023);
assert.match(String(events[0]?.details?.output), /truncated 200 chars/);
});
test("Runtime event recorder owns redacted bounded event state", () => {
const recorder = createTelegramRuntimeEventRecorder({
getBotToken: () => "123:abc",
maxEvents: 1,
now: () => 1000,
});
recorder.record("api", new Error("token 123:abc failed"), {
method: "sendMessage",
});
recorder.record("poll", "ok");
assert.deepEqual(recorder.getEvents(), [
{ at: 1000, category: "poll", message: "ok" },
]);
recorder.clear();
assert.deepEqual(recorder.getEvents(), []);
});
test("Runtime event recording redacts bot tokens and keeps a bounded ring", () => {
const events: TelegramRuntimeEvent[] = [];
recordTelegramRuntimeEvent(events, "one", new Error("token 123:abc failed"), {
botToken: "123:abc",
maxEvents: 3,
now: 1,
});
assert.deepEqual(events, [
{ at: 1, category: "one", message: "token <redacted-token> failed" },
]);
recordTelegramRuntimeEvent(events, "two", "plain", {
botToken: "123:abc",
maxEvents: 3,
now: 2,
});
recordTelegramRuntimeEvent(events, "three", "last", {
botToken: "123:abc",
maxEvents: 2,
now: 3,
});
assert.deepEqual(events, [
{ at: 2, category: "two", message: "plain" },
{ at: 3, category: "three", message: "last" },
]);
});