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

649 lines
23 KiB
TypeScript

/**
* Telegram bridge binding composition
* Zones: telegram, pi agent, orchestration
* Owns pi-facing tool, command, and lifecycle hook registration for the entrypoint
*/
import * as Activity from "./activity.ts";
import * as CommandTemplates from "./command-templates.ts";
import * as Commands from "./commands.ts";
import * as Config from "./config.ts";
import * as Keyboard from "./keyboard.ts";
import * as Lifecycle from "./lifecycle.ts";
import * as Locks from "./locks.ts";
import * as Model from "./model.ts";
import * as OutboundAttachments from "./outbound-attachments.ts";
import * as OutboundHandlers from "./outbound.ts";
import * as Pi from "./pi.ts";
import * as Preview from "./preview.ts";
import * as Prompts from "./prompts.ts";
import * as Queue from "./queue.ts";
import * as Replies from "./replies.ts";
import * as Runtime from "./runtime.ts";
import * as Setup from "./setup.ts";
import * as Status from "./status.ts";
import * as TelegramApi from "./telegram-api.ts";
type ActivePiModel = NonNullable<Pi.ExtensionContext["model"]>;
type TelegramRuntimeEventRecorder = (
category: string,
error: unknown,
details?: Record<string, unknown>,
) => void;
type TelegramBridgeStatusUpdater =
Status.TelegramStatusRuntime<Pi.ExtensionContext>["updateStatus"];
interface TelegramCommandsAndToolsBindingDeps {
pi: Pi.ExtensionAPI;
configStore: Config.TelegramConfigStore;
persistConfig: (config?: Config.TelegramConfig) => Promise<void>;
setup: Setup.TelegramSetupGuard;
activeTurnRuntime: Queue.TelegramActiveTurnStore<Queue.PendingTelegramTurn>;
lockedPollingRuntime: Locks.TelegramLockedPollingRuntime<Pi.ExtensionContext>;
stopPolling?: () => Promise<void | string>;
onTransportChanged?: () => Promise<void> | void;
getStatusLines: (
options?: Status.TelegramBridgeStatusLineOptions,
) => string[];
buttonActionStore: OutboundHandlers.TelegramButtonActionStore;
sendMarkdownReply: (
chatId: number,
replyToMessageId: number | undefined,
markdown: string,
options?: { replyMarkup?: unknown },
) => Promise<number | undefined>;
callMultipart: OutboundHandlers.TelegramVoiceReplySenderDeps["sendMultipart"];
getDefaultChatId: () => number | undefined;
getDefaultTarget?: () => OutboundAttachments.TelegramQueuedOutboundAttachmentTurnView["target"];
canSendDirect: () => boolean;
updateStatus: TelegramBridgeStatusUpdater;
recordRuntimeEvent: TelegramRuntimeEventRecorder;
}
export function registerTelegramCommandsAndTools({
pi,
configStore,
persistConfig,
setup,
activeTurnRuntime,
lockedPollingRuntime,
stopPolling,
onTransportChanged,
getStatusLines,
buttonActionStore,
sendMarkdownReply,
callMultipart,
getDefaultChatId,
getDefaultTarget,
canSendDirect,
recordRuntimeEvent,
updateStatus,
}: TelegramCommandsAndToolsBindingDeps): void {
OutboundAttachments.registerTelegramOutboundAttachmentTool(pi, {
getActiveTurn: activeTurnRuntime.get,
getDefaultChatId,
getDefaultTarget,
canSendDirect,
sendMultipart: callMultipart,
recordRuntimeEvent,
});
OutboundAttachments.registerTelegramOutboundMessageTool(pi, {
getDefaultChatId,
getDefaultTarget,
canSendDirect,
planMessage:
OutboundHandlers.createTelegramOutboundReplyPlanner(buttonActionStore),
sendMarkdownMessage: (chatId, markdown, options) =>
sendMarkdownReply(chatId, undefined, markdown, options),
recordRuntimeEvent,
});
Prompts.registerTelegramHelpTool(pi, {
getActiveProfileName: configStore.getActiveProfileName,
});
Commands.registerTelegramBridgeCommands(pi, {
promptForConfig: async (ctx, profileName) => {
const nextProfileName = profileName ?? undefined;
if (profileName && !Config.isValidTelegramProfileName(profileName)) {
ctx.ui.notify(`Invalid Telegram profile name: ${profileName}`, "error");
return;
}
const previousProfileName = configStore.getActiveProfileName();
let setupConfigStore = configStore;
let persistSetupConfig = persistConfig;
if (!profileName) {
if (previousProfileName !== nextProfileName) {
await (stopPolling ?? lockedPollingRuntime.stop)();
}
configStore.activateProfile(undefined);
await onTransportChanged?.();
} else {
const storedConfig = configStore.getStoredConfig();
setupConfigStore = Config.createTelegramConfigStore({
initialConfig: {
...storedConfig,
profiles: {
...(storedConfig.profiles ?? {}),
[profileName]: storedConfig.profiles?.[profileName] ?? {
botToken: "",
},
},
},
});
setupConfigStore.activateProfile(profileName);
persistSetupConfig = async () => {
try {
if (previousProfileName !== profileName) {
await (stopPolling ?? lockedPollingRuntime.stop)();
}
const profile = Config.getTelegramProfileFields(
setupConfigStore.get(),
);
if (!profile) {
throw new Error(
`Telegram profile "${profileName}" has no token.`,
);
}
await configStore.load();
const latestConfig = configStore.getStoredConfig();
configStore.activateProfile(undefined);
configStore.set({
...latestConfig,
profiles: {
...(latestConfig.profiles ?? {}),
[profileName]: profile,
},
});
configStore.activateProfile(profileName);
await onTransportChanged?.();
await persistConfig(configStore.get());
} catch (error) {
await configStore.load().catch(() => undefined);
configStore.activateProfile(previousProfileName);
await onTransportChanged?.();
throw error;
}
};
}
const runSetup = Setup.createTelegramSetupPromptRuntime({
getConfig: setupConfigStore.get,
setConfig: setupConfigStore.set,
setupGuard: setup,
getMe: TelegramApi.fetchTelegramBotIdentity,
persistConfig: persistSetupConfig,
startPolling: lockedPollingRuntime.start,
updateStatus,
recordRuntimeEvent,
});
const completion = await runSetup(ctx);
if (profileName && completion.status === "success") {
ctx.ui.notify(`Profile "${profileName}" saved and connected.`, "info");
}
},
getStatusLines,
reloadConfig: configStore.load,
hasBotToken: configStore.hasBotToken,
startPolling: lockedPollingRuntime.start,
stopPolling: stopPolling ?? lockedPollingRuntime.stop,
updateStatus,
getProfileNames: () =>
Config.getTelegramProfileNames(configStore.getStoredConfig()),
activateDefaultProfileConfig: async () => {
const previousProfileName = configStore.getActiveProfileName();
await configStore.load();
if (previousProfileName) {
await (stopPolling ?? lockedPollingRuntime.stop)();
}
configStore.activateProfile(undefined);
await onTransportChanged?.();
},
activateProfileConfig: async (_ctx, profileName) => {
const previousProfileName = configStore.getActiveProfileName();
await configStore.load();
if (!Config.isValidTelegramProfileName(profileName)) return false;
const storedConfig = configStore.getStoredConfig();
if (!storedConfig.profiles?.[profileName]) return false;
if (previousProfileName !== profileName) {
await (stopPolling ?? lockedPollingRuntime.stop)();
}
if (!configStore.activateProfile(profileName)) return false;
await onTransportChanged?.();
return true;
},
});
}
interface TelegramLifecycleBindingDeps {
pi: Pi.ExtensionAPI;
activityRuntime: Activity.TelegramActivityRuntime;
sessionLifecycleRuntime: Pick<
Lifecycle.TelegramLifecycleRegistrationDeps,
"onSessionStart" | "onSessionShutdown" | "onModelSelect"
>;
configStore: Pick<
Config.TelegramConfigStore,
"get" | "getOutboundHandlers" | "hasBotToken" | "load"
>;
abort: Runtime.TelegramRuntimeAbortPort;
typing: Runtime.TelegramRuntimeTypingPort;
lifecycle: Runtime.TelegramRuntimeLifecyclePort;
activeTurnRuntime: Queue.TelegramActiveTurnStore<Queue.PendingTelegramTurn>;
telegramQueueStore: Queue.TelegramQueueStore<Pi.ExtensionContext>;
modelSwitchController: Model.TelegramModelSwitchController<
Pi.ExtensionContext,
Model.ScopedTelegramModel<ActivePiModel>
>;
previewRuntime: Preview.TelegramAssistantPreviewRuntime<
Pi.AgentEndEvent["messages"][number],
Keyboard.TelegramInlineKeyboardMarkup
>;
promptDispatchRuntime: Runtime.TelegramPromptDispatchRuntime<Pi.ExtensionContext>;
deferredQueueDispatchRuntime: Queue.TelegramDeferredQueueDispatchRuntime<Pi.ExtensionContext>;
lockOwnershipGuard: Pick<
Locks.TelegramLockOwnershipGuard<Pi.ExtensionContext>,
"ownsContext"
>;
buttonActionStore: OutboundHandlers.TelegramButtonActionStore;
callMultipart: OutboundHandlers.TelegramVoiceReplySenderDeps["sendMultipart"];
sendChatAction: NonNullable<
OutboundHandlers.TelegramVoiceReplySenderDeps["sendChatAction"]
>;
sendRecordVoiceAction: NonNullable<
OutboundHandlers.TelegramVoiceReplySenderDeps["sendRecordVoiceAction"]
>;
sendMarkdownReply: Queue.TelegramAgentEndHookRuntimeDeps<
Queue.PendingTelegramTurn,
Pi.ExtensionContext,
Pi.AgentEndEvent["messages"][number],
Keyboard.TelegramInlineKeyboardMarkup
>["sendMarkdownReply"];
sendTextReply: Queue.TelegramAgentEndHookRuntimeDeps<
Queue.PendingTelegramTurn,
Pi.ExtensionContext,
Pi.AgentEndEvent["messages"][number],
Keyboard.TelegramInlineKeyboardMarkup
>["sendTextReply"] &
NonNullable<OutboundHandlers.TelegramVoiceReplySenderDeps["sendTextReply"]>;
dispatchNextQueuedTelegramTurn: (ctx: Pi.ExtensionContext) => void;
answerGuestQuery: TelegramApi.TelegramBridgeApiRuntime["answerGuestQuery"];
deleteMessage: TelegramApi.TelegramBridgeApiRuntime["deleteMessage"];
sendGuestReply: NonNullable<
Queue.TelegramAgentEndHookRuntimeDeps<
Queue.PendingTelegramTurn,
Pi.ExtensionContext,
Pi.AgentEndEvent["messages"][number],
Keyboard.TelegramInlineKeyboardMarkup
>["sendGuestReply"]
>;
finalizeMarkdownPreview: Queue.TelegramAgentEndHookRuntimeDeps<
Queue.PendingTelegramTurn,
Pi.ExtensionContext,
Pi.AgentEndEvent["messages"][number],
Keyboard.TelegramInlineKeyboardMarkup
>["finalizeMarkdownPreview"];
proactivePushChatIdGetter: () => number | undefined;
proactivePushTargetGetter: () => Queue.TelegramQueueTarget | undefined;
isProactivePushEnabled: () => boolean;
canSendProactivePush: (ctx: Pi.ExtensionContext) => boolean;
canSendAgentActivity: (ctx: Pi.ExtensionContext) => boolean;
isSessionContextActive: (ctx: Pi.ExtensionContext) => boolean;
isTurnTransportActive?: (turn: Queue.PendingTelegramTurn) => boolean;
updateStatus: TelegramBridgeStatusUpdater;
recordRuntimeEvent: TelegramRuntimeEventRecorder;
}
export function registerTelegramLifecycleRuntimeHooks({
pi,
activityRuntime,
sessionLifecycleRuntime,
configStore,
abort,
typing,
lifecycle,
activeTurnRuntime,
telegramQueueStore,
modelSwitchController,
previewRuntime,
promptDispatchRuntime,
deferredQueueDispatchRuntime,
lockOwnershipGuard,
buttonActionStore,
callMultipart,
sendChatAction,
sendRecordVoiceAction,
sendMarkdownReply,
sendTextReply,
dispatchNextQueuedTelegramTurn,
answerGuestQuery,
deleteMessage,
sendGuestReply,
finalizeMarkdownPreview,
proactivePushChatIdGetter,
proactivePushTargetGetter,
isProactivePushEnabled,
canSendProactivePush,
canSendAgentActivity,
isSessionContextActive = () => true,
isTurnTransportActive,
updateStatus,
recordRuntimeEvent,
}: TelegramLifecycleBindingDeps): void {
const agentEndResetter = Runtime.createTelegramAgentEndResetter({
abort,
typing,
clearActiveTurn: activeTurnRuntime.clear,
resetToolExecutions: lifecycle.resetActiveToolExecutions,
clearPendingModelSwitch: modelSwitchController.clearPendingSwitch,
clearDispatchPending: lifecycle.clearDispatchPending,
});
const queuedAttachmentSender =
OutboundAttachments.createTelegramQueuedOutboundAttachmentSender({
sendMultipart: callMultipart,
sendTextReply,
recordRuntimeEvent,
});
const sendGuestAttachment = async (
turn: Queue.PendingTelegramTurn,
attachment: Queue.QueuedAttachment,
caption?: string,
): Promise<void> => {
const stagingTarget = proactivePushTargetGetter();
const stagingChatId = stagingTarget?.chatId ?? proactivePushChatIdGetter();
if (stagingChatId === undefined) {
throw new Error(
"Guest attachment staging requires a paired Telegram chat",
);
}
await OutboundAttachments.deliverTelegramGuestCachedAttachment({
guestQueryId: turn.guestQueryId!,
stagingChatId,
stagingTarget,
attachment,
caption,
sendMultipart: callMultipart,
answerGuestQuery: (guestQueryId, result) =>
answerGuestQuery(guestQueryId, undefined, { result }),
answerGuestText: (guestQueryId, text) =>
answerGuestQuery(guestQueryId, text),
fallbackText:
caption ||
"Telegram bridge could not deliver the requested attachment.",
deleteMessage,
recordRuntimeEvent,
});
};
const outboundReplyPlanner =
OutboundHandlers.createTelegramOutboundReplyPlanner(buttonActionStore);
const voiceReplySenderDeps = {
execCommand: CommandTemplates.execCommandTemplate,
sendMultipart: callMultipart,
sendTextReply,
sendChatAction,
sendRecordVoiceAction,
getHandlers: configStore.getOutboundHandlers,
recordRuntimeEvent,
};
const outboundReplyArtifactSender =
OutboundHandlers.createTelegramOutboundReplyArtifactSender(
voiceReplySenderDeps,
);
const sendGuestVoiceReply = async (
turn: Queue.PendingTelegramTurn,
plan: OutboundHandlers.TelegramOutboundReplyPlan,
caption?: string,
): Promise<void> => {
const stagingTarget = proactivePushTargetGetter();
const stagingChatId = stagingTarget?.chatId ?? proactivePushChatIdGetter();
if (stagingChatId === undefined) {
throw new Error("Guest voice staging requires a paired Telegram chat");
}
const guestVoiceSender =
OutboundHandlers.createTelegramOutboundReplyArtifactSender({
...voiceReplySenderDeps,
sendChatAction: undefined,
sendRecordVoiceAction: undefined,
sendMultipart: async (
_method,
_fields,
_fileField,
filePath,
fileName,
) => {
try {
await OutboundAttachments.deliverTelegramGuestCachedAttachment({
guestQueryId: turn.guestQueryId!,
stagingChatId,
stagingTarget,
attachment: { path: filePath, fileName },
caption,
sendMultipart: callMultipart,
answerGuestQuery: (guestQueryId, result) =>
answerGuestQuery(guestQueryId, undefined, { result }),
answerGuestText: (guestQueryId, text) =>
answerGuestQuery(guestQueryId, text),
fallbackText:
caption || "Telegram bridge could not deliver the voice reply.",
deleteMessage,
recordRuntimeEvent,
});
} catch (error) {
recordRuntimeEvent("delivery", error, {
phase: "guest-voice-answer",
guestQueryId: turn.guestQueryId,
});
}
return {};
},
});
await guestVoiceSender(
turn,
{
...plan,
...(plan.voiceReplies?.length
? { voiceReplies: [plan.voiceReplies[0]!] }
: {}),
},
{ replyToPrompt: false },
);
};
const agentLifecycleHooks = Queue.createTelegramAgentLifecycleHooks<
Queue.PendingTelegramTurn,
Pi.ExtensionContext,
unknown,
Keyboard.TelegramInlineKeyboardMarkup
>({
setAbortHandler: Runtime.createTelegramContextAbortHandlerSetter(abort),
getQueuedItems: telegramQueueStore.getQueuedItems,
hasPendingDispatch: lifecycle.hasDispatchPending,
hasActiveTurn: activeTurnRuntime.has,
resetToolExecutions: lifecycle.resetActiveToolExecutions,
resetPendingModelSwitch: modelSwitchController.clearPendingSwitch,
setQueuedItems: telegramQueueStore.setQueuedItems,
clearDispatchPending: lifecycle.clearDispatchPending,
setFoldQueuedPromptsIntoHistory: lifecycle.setFoldQueuedPromptsIntoHistory,
setActiveTurn: activeTurnRuntime.set,
createPreviewState: previewRuntime.resetState,
startTypingLoop: (ctx) => {
const turn = activeTurnRuntime.get();
promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId, {
target: turn?.target,
});
},
updateStatus,
getActiveTurn: activeTurnRuntime.get,
loadConfig: configStore.load,
extractAssistant: Replies.extractLatestAssistantMessageText,
getFoldQueuedPromptsIntoHistory:
lifecycle.shouldFoldQueuedPromptsIntoHistory,
resetRuntimeState: agentEndResetter,
isSessionActive: isSessionContextActive,
isTurnTransportActive,
waitForTypingIdle: typing.waitForIdle,
dispatchNextQueuedTelegramTurn,
requestDeferredDispatchNextQueuedTelegramTurn:
deferredQueueDispatchRuntime.request,
scheduleActiveTurnDelivery(task) {
const timer = setTimeout(() => {
void task().catch((error) => {
recordRuntimeEvent("delivery", error, {
phase: "agent-end-background-delivery",
});
});
}, 0);
timer.unref?.();
},
clearPreview: previewRuntime.clear,
setPreviewPendingText: previewRuntime.setPendingText,
finalizeMarkdownPreview,
sendMarkdownReply,
sendTextReply,
sendQueuedAttachments: queuedAttachmentSender,
answerGuestQuery,
sendGuestReply,
sendGuestAttachment,
sendGuestVoiceReply,
planOutboundReply: outboundReplyPlanner,
sendOutboundReplyArtifacts: outboundReplyArtifactSender,
getDefaultChatId: proactivePushChatIdGetter,
getDefaultTarget: proactivePushTargetGetter,
isProactivePushEnabled,
canSendProactivePush,
recordRuntimeEvent,
getActiveToolExecutions: lifecycle.getActiveToolExecutions,
setActiveToolExecutions: lifecycle.setActiveToolExecutions,
triggerPendingModelSwitchAbort: modelSwitchController.triggerPendingAbort,
});
Lifecycle.setResetTransportReplyDedup(Replies.resetTransportReplyDedup);
const agentStartWithDedupReset = Lifecycle.createAgentStartDedupHook(
agentLifecycleHooks.onAgentStart,
);
const startAgentActivityTypingLoop = (ctx: Pi.ExtensionContext): boolean => {
if (!canSendAgentActivity(ctx)) return false;
const turn = activeTurnRuntime.get();
const target = turn?.target ?? proactivePushTargetGetter();
promptDispatchRuntime.startTypingLoop(
ctx,
turn?.chatId ?? target?.chatId ?? proactivePushChatIdGetter(),
{ target },
);
return true;
};
const startActiveTurnTypingLoop = (ctx: Pi.ExtensionContext): void => {
const turn = activeTurnRuntime.get();
promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId, {
target: turn?.target,
});
};
const compactionObserver = Lifecycle.createTelegramCompactionObserverRuntime({
isContextActive: isSessionContextActive,
setCompactionInProgress: lifecycle.setCompactionInProgress,
updateStatus,
startTypingLoop: startAgentActivityTypingLoop,
stopTypingLoop: typing.stop,
requestDeferredDispatchNextQueuedTelegramTurn:
deferredQueueDispatchRuntime.request,
dispatchNextQueuedTelegramTurn,
recordRuntimeEvent,
onCompactionAbandoned: activityRuntime.onCompactionAbandoned,
});
const messageActivityTypingHooks =
Lifecycle.createTelegramMessageActivityTypingHooks({
hasActiveTurn: activeTurnRuntime.has,
startTypingLoop: startActiveTurnTypingLoop,
onMessageStart: previewRuntime.onMessageStart,
onMessageUpdate: previewRuntime.onMessageUpdate,
recordRuntimeEvent,
});
const messageActivityHooks = messageActivityTypingHooks;
Lifecycle.registerTelegramLifecycleHooks(pi, {
isSessionActive: isSessionContextActive,
...sessionLifecycleRuntime,
...agentLifecycleHooks,
onInput(event) {
activityRuntime.recordInputSource(event.source ?? "unknown");
},
async onSessionStart(event, ctx) {
previewRuntime.invalidate();
activityRuntime.onSessionStart?.();
await sessionLifecycleRuntime.onSessionStart(event, ctx);
},
async onSessionShutdown(event, ctx) {
if (!isSessionContextActive(ctx)) return;
activityRuntime.onSessionShutdown();
compactionObserver.onSessionShutdown();
await sessionLifecycleRuntime.onSessionShutdown(event, ctx);
},
onSessionBeforeCompact(event, ctx) {
if (!isSessionContextActive(ctx)) return;
activityRuntime.onCompactionStart(Pi.getSessionCompactionReason(event));
compactionObserver.onSessionBeforeCompact(event, ctx);
},
onSessionCompact(event, ctx) {
if (!isSessionContextActive(ctx)) return;
activityRuntime.onCompactionEnd(Pi.getSessionCompactionReason(event));
compactionObserver.onSessionCompact(event, ctx);
},
async onAgentStart(event, ctx) {
if (!isSessionContextActive(ctx)) return;
await agentStartWithDedupReset(event, ctx);
activityRuntime.onAgentStart(activeTurnRuntime.get()?.target);
startAgentActivityTypingLoop(ctx);
},
async onToolExecutionStart(event, ctx) {
if (!isSessionContextActive(ctx)) return;
agentLifecycleHooks.onToolExecutionStart();
activityRuntime.onToolStart({
toolCallId: event.toolCallId,
toolName: event.toolName,
args: event.args,
});
},
onToolExecutionUpdate(event, ctx) {
if (!isSessionContextActive(ctx)) return;
activityRuntime.onToolUpdate({
toolCallId: event.toolCallId,
toolName: event.toolName,
update: event.partialResult,
});
},
async onToolExecutionEnd(event, ctx) {
if (!isSessionContextActive(ctx)) return;
activityRuntime.onToolEnd({
toolCallId: event.toolCallId,
toolName: event.toolName,
result: event.result,
isError: event.isError,
});
agentLifecycleHooks.onToolExecutionEnd(event, ctx);
},
async onMessageStart(event, ctx) {
if (!isSessionContextActive(ctx)) return;
await messageActivityHooks.onMessageStart(event, ctx);
},
async onMessageUpdate(event, ctx) {
if (!isSessionContextActive(ctx)) return;
if (event.assistantMessageEvent) {
activityRuntime.onAssistantEvent(
event.assistantMessageEvent as Activity.TelegramAssistantStreamEvent,
);
}
await messageActivityHooks.onMessageUpdate(event, ctx);
},
async onAgentEnd(event, ctx) {
if (!isSessionContextActive(ctx)) return;
activityRuntime.onAgentEnd();
await agentLifecycleHooks.onAgentEnd(event, ctx);
},
onAgentSettled(_event, ctx) {
if (!isSessionContextActive(ctx)) return;
activityRuntime.onAgentSettled();
},
onBeforeAgentStart: Prompts.createTelegramProactiveBeforeAgentStartHook({
isConfigured: configStore.hasBotToken,
isProactivePushEnabled,
isCurrentOwner: lockOwnershipGuard.ownsContext,
}),
});
}