Files
Evandro Fonseca Junior d1d04001d0
Release / release (push) Failing after 5s
Validate / validate (push) Failing after 5m21s
chore: fork @llblab/[email protected] as @evandrodevbr/oh-my-pi-telegram
Fork of upstream llblab/[email protected].0 (SHA afe09c5) repackaged as a
drop-in extension for can1357/oh-my-pi (omp >= 16.5.2).

Changes vs upstream:
- Rename package: @llblab/pi-telegram -> @evandrodevbr/oh-my-pi-telegram
- Bump version: 0.22.0 -> 0.22.0-evandro.1
- Update repository/homepage/bugs URLs to evandrodevbr
- Add omp field in package.json (displayName + compatibleWith)
- README: prepend 'Oh My Pi (omp) Compatibility' section with install
  commands, verified-components table, upstream lineage, and rationale
- CHANGELOG: add '0.22.0-evandro.1' entry under Unreleased
- docs/: rename @llblab/pi-telegram imports to @evandrodevbr/oh-my-pi-telegram
- tests/public-api.test.ts: rename package import paths

Gates re-run on the fork:
- npm run typecheck  -> tsc --noEmit (success)
- npm test           -> 1229 pass / 0 fail / 1 platform-only skip
- npm run audit      -> found 0 vulnerabilities
- npm run pack:check -> evandrodevbr-oh-my-pi-telegram-0.22.0-evandro.1.tgz

Verified:
- omp install --link ./pi-telegram -> Linked OK
- omp plugin list -> @evandrodevbr/[email protected].0-evandro.1

Upstream lineage: badlogic/pi-telegram -> llblab/pi-telegram (0.22.0) -> evandrodevbr/oh-my-pi-telegram (this fork)
2026-07-15 14:18:16 -03:00

252 lines
7.1 KiB
TypeScript

/**
* Telegram bus local transport boundary
* Zones: multi-instance bus, IPC transport, Windows named pipes, Unix sockets
* Owns endpoint derivation, transport-kind detection, retry/error classification, and small timing policy.
*/
import { createHash } from "node:crypto";
import { createConnection } from "node:net";
import { join, resolve } from "node:path";
export type TelegramBusTransportKind = "pipe" | "socket";
export type TelegramBusTransportEventRecorder = (
phase: string,
details: Record<string, unknown>,
) => void;
export type TelegramBusTransportEndpointDiagnostics = Record<
string,
unknown
> & {
endpoint: string;
transport: TelegramBusTransportKind;
};
export interface TelegramBusTransportRetryPolicy {
attempts: number;
delayMs: number;
}
export interface TelegramBusTransportRetryPolicyOverrides {
attempts?: number;
delayMs?: number;
}
export type TelegramBusTransportOperation = "registration" | "operation";
export const TELEGRAM_BUS_REGISTRATION_RETRY: TelegramBusTransportRetryPolicy = {
attempts: 10,
delayMs: 150,
};
export const TELEGRAM_BUS_OPERATION_RETRY: TelegramBusTransportRetryPolicy = {
attempts: 3,
delayMs: 100,
};
export const TELEGRAM_BUS_PIPE_REGISTRATION_RETRY =
TELEGRAM_BUS_REGISTRATION_RETRY;
export const TELEGRAM_BUS_PIPE_OPERATION_RETRY = TELEGRAM_BUS_OPERATION_RETRY;
export function getTelegramBusPipePath(input: {
agentDir: string;
scope: string;
}): string {
const digest = createHash("sha256")
.update(resolve(input.agentDir))
.digest("base64url")
.slice(0, 16);
const scope = input.scope.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 80);
return `\\\\.\\pipe\\pi-telegram-${digest}-${scope}`;
}
export function isTelegramBusPipePath(endpoint: string): boolean {
return /^\\\\[.?]\\pipe\\/i.test(endpoint);
}
export function getTelegramBusTransportKind(
endpoint: string,
): TelegramBusTransportKind {
return isTelegramBusPipePath(endpoint) ? "pipe" : "socket";
}
export function getTelegramBusEndpointDiagnostics(
endpoint: string,
): TelegramBusTransportEndpointDiagnostics {
return {
endpoint,
transport: getTelegramBusTransportKind(endpoint),
};
}
export function getTelegramBusTransportRetryPolicy(input: {
endpoint: string;
operation: TelegramBusTransportOperation;
overrides?: TelegramBusTransportRetryPolicyOverrides;
}): TelegramBusTransportRetryPolicy | undefined {
const base =
input.operation === "registration"
? TELEGRAM_BUS_REGISTRATION_RETRY
: isTelegramBusPipePath(input.endpoint)
? TELEGRAM_BUS_OPERATION_RETRY
: undefined;
if (!base && !input.overrides) return undefined;
return {
attempts: Math.max(1, input.overrides?.attempts ?? base?.attempts ?? 1),
delayMs: Math.max(0, input.overrides?.delayMs ?? base?.delayMs ?? 0),
};
}
function normalizeTelegramBusEndpointScope(value: string): string {
return value.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 80);
}
export function getTelegramBusLeaderEndpoint(input: {
agentDir: string;
platform: NodeJS.Platform | string;
profileName?: string;
}): string {
const profileScope = input.profileName
? normalizeTelegramBusEndpointScope(input.profileName)
: undefined;
return input.platform === "win32"
? getTelegramBusPipePath({
agentDir: input.agentDir,
scope: profileScope ? `bus-${profileScope}` : "bus",
})
: join(
input.agentDir,
"tmp",
"telegram",
profileScope ? `bus.${profileScope}.sock` : "bus.sock",
);
}
export function getTelegramBusFollowerEndpoint(input: {
agentDir: string;
platform: NodeJS.Platform | string;
instanceId: string;
profileName?: string;
}): string {
const instanceScope = normalizeTelegramBusEndpointScope(input.instanceId);
const profileScope = input.profileName
? normalizeTelegramBusEndpointScope(input.profileName)
: undefined;
return input.platform === "win32"
? getTelegramBusPipePath({
agentDir: input.agentDir,
scope: profileScope
? `follower-${profileScope}-${instanceScope}`
: `follower-${instanceScope}`,
})
: join(
input.agentDir,
"tmp",
"telegram",
"followers",
...(profileScope ? [profileScope] : []),
`${instanceScope}.sock`,
);
}
export interface TelegramBusTransportErrorInfo {
message: string;
code?: string;
syscall?: string;
kind: "connect" | "timeout" | "auth" | "protocol" | "unknown";
retryable: boolean;
}
export function classifyTelegramBusTransportError(
error: unknown,
): TelegramBusTransportErrorInfo {
const message = error instanceof Error ? error.message : String(error);
const maybeNodeError = error as NodeJS.ErrnoException;
const code =
typeof maybeNodeError?.code === "string" ? maybeNodeError.code : undefined;
const syscall =
typeof maybeNodeError?.syscall === "string"
? maybeNodeError.syscall
: undefined;
const timeout = code === "ETIMEDOUT" || /timed out/i.test(message);
const retryable =
code === "ENOENT" ||
code === "ECONNREFUSED" ||
code === "EPIPE" ||
code === "ECONNRESET" ||
code === "EBUSY" ||
timeout;
const kind = timeout
? "timeout"
: code === "ENOENT" || code === "ECONNREFUSED"
? "connect"
: "unknown";
return { message, code, syscall, kind, retryable };
}
export function isRetryableTelegramBusTransportError(error: unknown): boolean {
return classifyTelegramBusTransportError(error).retryable;
}
export function createTelegramBusTransportTimeoutError(
message: string,
): NodeJS.ErrnoException {
const error = new Error(message) as NodeJS.ErrnoException;
error.code = "ETIMEDOUT";
return error;
}
export function delayTelegramBusTransportRetry(ms: number): Promise<void> {
return new Promise((resolve) => {
const timer = setTimeout(resolve, ms);
timer.unref?.();
});
}
export interface TelegramBusTransportProbeResult
extends TelegramBusTransportEndpointDiagnostics {
reachable: boolean;
error?: TelegramBusTransportErrorInfo;
}
export function probeTelegramBusEndpoint(input: {
endpoint: string;
timeoutMs?: number;
}): Promise<TelegramBusTransportProbeResult> {
const timeoutMs = input.timeoutMs ?? 250;
const diagnostics = getTelegramBusEndpointDiagnostics(input.endpoint);
return new Promise((resolve) => {
const socket = createConnection(input.endpoint);
let settled = false;
const settle = (result: TelegramBusTransportProbeResult): void => {
if (settled) return;
settled = true;
clearTimeout(timeout);
socket.destroy();
resolve(result);
};
const timeout = setTimeout(() => {
settle({
...diagnostics,
reachable: false,
error: classifyTelegramBusTransportError(
createTelegramBusTransportTimeoutError(
"Timed out probing Telegram bus endpoint",
),
),
});
}, timeoutMs);
timeout.unref?.();
socket.once("connect", () => settle({ ...diagnostics, reachable: true }));
socket.once("error", (error) =>
settle({
...diagnostics,
reachable: false,
error: classifyTelegramBusTransportError(error),
}),
);
});
}