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)
166 lines
7.8 KiB
TypeScript
166 lines
7.8 KiB
TypeScript
/**
|
|
* Telegram prompt injection helpers
|
|
* Zones: pi agent prompts, telegram guidance
|
|
* Owns Telegram-specific system prompt suffixes injected into pi agent turns
|
|
*/
|
|
|
|
import { Type } from "@sinclair/typebox";
|
|
|
|
import { getTelegramDiagnosticsDisplayPaths } from "./paths.ts";
|
|
import type { BeforeAgentStartEvent, ExtensionAPI } from "./pi.ts";
|
|
import { TELEGRAM_PREFIX } from "./turns.ts";
|
|
|
|
const LOCAL_SYSTEM_PROMPT_SUFFIX = `
|
|
|
|
Telegram bridge available. Do not use it from local/TUI prompts unless explicitly asked.`;
|
|
|
|
const TELEGRAM_TURN_SYSTEM_PROMPT_SUFFIX = `
|
|
|
|
Telegram turn note: If context was compacted or you need the pi-telegram bridge contract, call tool \`telegram_help\`; hidden comments are valid only for explicit \`telegram_voice\` or \`telegram_button\` actions with payload. For voice use a top-level HTML action: \`<!-- telegram_voice: Speak this. -->\`, multiline \`<!-- telegram_voice lang=ru\nSpeak this.\n-->\`, or paired \`<!-- telegram_voice lang=ru -->\nSpeak this.\n<!-- /telegram_voice -->\`.`;
|
|
|
|
function buildTelegramHelpText(profileName?: string): string {
|
|
const diagnosticsPaths = getTelegramDiagnosticsDisplayPaths(profileName);
|
|
return `--- TELEGRAM BRIDGE HELP ---
|
|
|
|
How to understand Telegram turns:
|
|
- \`[telegram|thread:name|from:user|guest:group]\` marks Telegram origin and attributes.
|
|
- \`thread\` is the visible Thread identity in Threaded Mode; it is not a bus role.
|
|
- \`[reply]\` is quoted context only; act on the user's current instruction.
|
|
- \`[attachments]\` are local files; \`[outputs]\` are handler results/transcripts; \`[time]\` is wall-clock context; \`[voice]\` gives reply-mode policy.
|
|
|
|
How to answer Telegram turns:
|
|
- Reply in concise, scannable mobile Telegram Rich Markdown.
|
|
- Use \`$...$\` for inline math and \`$$...$$\` for block math.
|
|
- Real code blocks must stay literal.
|
|
- For generated/requested files, call \`telegram_attach(local_path)\`; do not only mention the path.
|
|
|
|
Assistant-authored Telegram actions:
|
|
- \`telegram_voice\` and \`telegram_button\` are hidden top-level HTML comments, not Pi tools.
|
|
- Put action comments at column zero, outside code, quotes, lists, and indented examples.
|
|
- Voice forms: \`<!-- telegram_voice text="Short summary" -->\`, \`<!-- telegram_voice: Short summary -->\`, multiline \`<!-- telegram_voice lang=ru\nShort summary.\n-->\`, or paired \`<!-- telegram_voice lang=ru -->\nShort summary.\n<!-- /telegram_voice -->\`.
|
|
- Keep the complete action at top level and include a non-empty voice payload.
|
|
- Keep voice text TTS-friendly; avoid raw Markdown, code, and tables in voice text.
|
|
- Voice delivery generates and attaches OGG automatically; do not also call \`telegram_attach\` for the same audio.
|
|
- Button forms: \`<!-- telegram_button: OK -->\`, \`<!-- telegram_button label=Continue prompt="Continue with the current plan." -->\`, or multiline \`<!-- telegram_button label="Show risks"\nList the main risks first.\n-->\`.
|
|
- If hidden comments would be the whole reply, add visible text such as \`Choose one:\`.
|
|
|
|
Local/TUI direct delivery:
|
|
- Do not send Telegram actions from local/TUI prompts unless explicitly asked.
|
|
- Use \`telegram_attach\` for files and \`telegram_message\` for direct Markdown text.
|
|
- Direct delivery requires this Pi instance to own \`/telegram-connect\` or be registered with the Threaded Mode bus.
|
|
- For explicit targets, pass \`chat_id\` plus optional \`thread_id\`; registered followers default to their assigned Thread target.
|
|
- Do not use \`telegram_message\` for ordinary Telegram-originated replies; answer normally and let the bridge deliver the active turn reply.
|
|
|
|
Threaded Mode:
|
|
- pi-telegram supports private-chat Threaded Mode when Telegram exposes thread support for the bot.
|
|
- Product/user language is Thread; Bot API primitive names may still say topic.
|
|
- Threaded Mode has one leader transport and visible follower Pi processes joined manually through \`/telegram-connect\`.
|
|
- Thread names are bridge-assigned or preserved identities; do not invent rename prompts or use a rename tool.
|
|
- The \`All\` surface is for routing/control, not hidden Pi process creation.
|
|
|
|
Configurable handlers:
|
|
- \`telegram.json\` can add no-code \`inboundHandlers\`/\`outboundHandlers\` using command templates before writing an extension.
|
|
- For speech-to-text, configure an \`inboundHandlers\` entry matching \`type: "voice"\` or \`mime: "audio/*"\`; stdout becomes \`[outputs]\` prompt context.
|
|
- If command-template config is not enough, build a companion extension through the public pi-telegram APIs; do not import package-private \`lib/*\` paths.
|
|
|
|
Debugging pi-telegram:
|
|
- Inspect \`${diagnosticsPaths.state}\` for runtime state, roster, bindings, slots, reservations, and diagnostics.
|
|
- Inspect \`${diagnosticsPaths.logs}\` for redacted runtime event evidence.
|
|
- Use terminal \`telegram-status\` for compact human health; use \`telegram-status --debug\` for the full human-readable diagnostic dump.`;
|
|
}
|
|
|
|
export function getTelegramHelpText(profileName?: string): string {
|
|
return buildTelegramHelpText(profileName);
|
|
}
|
|
|
|
export function registerTelegramHelpTool(
|
|
pi: ExtensionAPI,
|
|
options: { getActiveProfileName?: () => string | undefined } = {},
|
|
): void {
|
|
pi.registerTool({
|
|
name: "telegram_help",
|
|
label: "Telegram Help",
|
|
description:
|
|
"Read pi-telegram usage guidance for delivery actions, Threaded Mode, handlers, formatting, and debugging.",
|
|
parameters: Type.Object({}),
|
|
async execute() {
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: getTelegramHelpText(options.getActiveProfileName?.()),
|
|
},
|
|
],
|
|
details: {},
|
|
};
|
|
},
|
|
});
|
|
}
|
|
|
|
export function buildTelegramBridgeSystemPrompt(options: {
|
|
prompt: string;
|
|
systemPrompt: string;
|
|
telegramPrefix?: string;
|
|
localSystemPromptSuffix: string;
|
|
telegramTurnSystemPromptSuffix: string;
|
|
}): { systemPrompt: string } {
|
|
const telegramPrefix = options.telegramPrefix ?? TELEGRAM_PREFIX;
|
|
const telegramHead = telegramPrefix.endsWith("]")
|
|
? telegramPrefix.slice(0, -1)
|
|
: telegramPrefix;
|
|
const trimmedPrompt = options.prompt.trimStart();
|
|
const telegramTurn =
|
|
trimmedPrompt.startsWith(`${telegramHead}]`) ||
|
|
trimmedPrompt.startsWith(`${telegramHead}|`);
|
|
const telegramSuffix = telegramTurn
|
|
? `${options.telegramTurnSystemPromptSuffix}\n- The current user message came from Telegram.`
|
|
: "";
|
|
return {
|
|
systemPrompt:
|
|
options.systemPrompt + options.localSystemPromptSuffix + telegramSuffix,
|
|
};
|
|
}
|
|
|
|
export function createTelegramBeforeAgentStartHook(
|
|
options: {
|
|
telegramPrefix?: string;
|
|
localSystemPromptSuffix?: string;
|
|
telegramTurnSystemPromptSuffix?: string;
|
|
} = {},
|
|
): (event: BeforeAgentStartEvent) => { systemPrompt: string } {
|
|
return (event) =>
|
|
buildTelegramBridgeSystemPrompt({
|
|
prompt: event.prompt,
|
|
systemPrompt: event.systemPrompt,
|
|
telegramPrefix: options.telegramPrefix,
|
|
localSystemPromptSuffix:
|
|
options.localSystemPromptSuffix ?? LOCAL_SYSTEM_PROMPT_SUFFIX,
|
|
telegramTurnSystemPromptSuffix:
|
|
options.telegramTurnSystemPromptSuffix ??
|
|
TELEGRAM_TURN_SYSTEM_PROMPT_SUFFIX,
|
|
});
|
|
}
|
|
|
|
export interface TelegramProactivePromptHookDeps<TContext> {
|
|
baseHook?: (event: BeforeAgentStartEvent) => { systemPrompt: string };
|
|
isConfigured: () => boolean;
|
|
isProactivePushEnabled: () => boolean;
|
|
isCurrentOwner: (ctx: TContext) => boolean;
|
|
}
|
|
|
|
export function createTelegramProactiveBeforeAgentStartHook<TContext>(
|
|
deps: TelegramProactivePromptHookDeps<TContext>,
|
|
): (
|
|
event: BeforeAgentStartEvent,
|
|
ctx: TContext,
|
|
) => Promise<{ systemPrompt: string }> {
|
|
const baseHook = deps.baseHook ?? createTelegramBeforeAgentStartHook();
|
|
return async (event, ctx) => {
|
|
if (!deps.isConfigured()) return { systemPrompt: event.systemPrompt };
|
|
const result = baseHook(event);
|
|
if (!deps.isProactivePushEnabled()) return result;
|
|
if (!deps.isCurrentOwner(ctx)) return result;
|
|
return result;
|
|
};
|
|
}
|