@llblab/pi-telegram 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKLOG.md +0 -1
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/dist/api/activity.d.ts +6 -0
- package/dist/api/activity.js +6 -0
- package/dist/api/commands.d.ts +6 -0
- package/dist/api/commands.js +6 -0
- package/dist/api/delivery.d.ts +6 -0
- package/dist/api/delivery.js +6 -0
- package/dist/api/inbound.d.ts +6 -0
- package/dist/api/inbound.js +6 -0
- package/dist/api/keyboard.d.ts +6 -0
- package/dist/api/keyboard.js +6 -0
- package/dist/api/outbound.d.ts +6 -0
- package/dist/api/outbound.js +6 -0
- package/dist/api/sections.d.ts +7 -0
- package/dist/api/sections.js +6 -0
- package/dist/api/status.d.ts +6 -0
- package/dist/api/status.js +6 -0
- package/dist/api/updates.d.ts +6 -0
- package/dist/api/updates.js +6 -0
- package/dist/api/voice.d.ts +6 -0
- package/dist/api/voice.js +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/activity-verbosity.d.ts +52 -0
- package/dist/lib/activity-verbosity.js +596 -0
- package/dist/lib/activity.d.ts +220 -0
- package/dist/lib/activity.js +574 -0
- package/dist/lib/agent-messages.d.ts +28 -0
- package/dist/lib/agent-messages.js +86 -0
- package/dist/lib/bindings.d.ts +250 -0
- package/dist/lib/bindings.js +936 -0
- package/dist/lib/bus-api.d.ts +18 -0
- package/dist/lib/bus-api.js +254 -0
- package/dist/lib/bus-follower.d.ts +464 -0
- package/dist/lib/bus-follower.js +1686 -0
- package/dist/lib/bus-leader.d.ts +292 -0
- package/dist/lib/bus-leader.js +2242 -0
- package/dist/lib/bus-transport.d.ts +64 -0
- package/dist/lib/bus-transport.js +140 -0
- package/dist/lib/bus.d.ts +518 -0
- package/dist/lib/bus.js +2055 -0
- package/dist/lib/channel-posts.d.ts +170 -0
- package/dist/lib/channel-posts.js +611 -0
- package/dist/lib/command-templates.d.ts +70 -0
- package/dist/lib/command-templates.js +744 -0
- package/dist/lib/commands.d.ts +541 -0
- package/dist/lib/commands.js +1155 -0
- package/dist/lib/config.d.ts +252 -0
- package/dist/lib/config.js +889 -0
- package/dist/lib/delivery.d.ts +163 -0
- package/dist/lib/delivery.js +542 -0
- package/dist/lib/extension.d.ts +7 -0
- package/dist/lib/extension.js +1608 -0
- package/dist/lib/generative-app-worker.mjs +104 -0
- package/dist/lib/generative-apps.d.ts +212 -0
- package/dist/lib/generative-apps.js +1006 -0
- package/dist/lib/inbound.d.ts +91 -0
- package/dist/lib/inbound.js +502 -0
- package/dist/lib/journal.d.ts +665 -0
- package/dist/lib/journal.js +3701 -0
- package/dist/lib/keyboard.d.ts +23 -0
- package/dist/lib/keyboard.js +37 -0
- package/dist/lib/lifecycle.d.ts +157 -0
- package/dist/lib/lifecycle.js +395 -0
- package/dist/lib/locks.d.ts +164 -0
- package/dist/lib/locks.js +1208 -0
- package/dist/lib/logging.d.ts +50 -0
- package/dist/lib/logging.js +274 -0
- package/dist/lib/media.d.ts +181 -0
- package/dist/lib/media.js +602 -0
- package/dist/lib/menu-model.d.ts +217 -0
- package/dist/lib/menu-model.js +663 -0
- package/dist/lib/menu-queue.d.ts +37 -0
- package/dist/lib/menu-queue.js +408 -0
- package/dist/lib/menu-settings.d.ts +116 -0
- package/dist/lib/menu-settings.js +595 -0
- package/dist/lib/menu-status.d.ts +32 -0
- package/dist/lib/menu-status.js +112 -0
- package/dist/lib/menu-thinking.d.ts +29 -0
- package/dist/lib/menu-thinking.js +82 -0
- package/dist/lib/menu.d.ts +171 -0
- package/dist/lib/menu.js +323 -0
- package/dist/lib/model.d.ts +127 -0
- package/dist/lib/model.js +409 -0
- package/dist/lib/outbound-attachments.d.ts +241 -0
- package/dist/lib/outbound-attachments.js +639 -0
- package/dist/lib/outbound-buttons.d.ts +69 -0
- package/dist/lib/outbound-buttons.js +248 -0
- package/dist/lib/outbound-markup.d.ts +42 -0
- package/dist/lib/outbound-markup.js +678 -0
- package/dist/lib/outbound-voice.d.ts +55 -0
- package/dist/lib/outbound-voice.js +152 -0
- package/dist/lib/outbound.d.ts +185 -0
- package/dist/lib/outbound.js +516 -0
- package/dist/lib/ownership.d.ts +77 -0
- package/dist/lib/ownership.js +174 -0
- package/dist/lib/paths.d.ts +40 -0
- package/dist/lib/paths.js +94 -0
- package/dist/lib/pi.d.ts +72 -0
- package/dist/lib/pi.js +121 -0
- package/dist/lib/polling.d.ts +351 -0
- package/dist/lib/polling.js +1196 -0
- package/dist/lib/preview.d.ts +192 -0
- package/dist/lib/preview.js +614 -0
- package/dist/lib/prompt-templates.d.ts +24 -0
- package/dist/lib/prompt-templates.js +118 -0
- package/dist/lib/prompts.d.ts +57 -0
- package/dist/lib/prompts.js +167 -0
- package/dist/lib/queue.d.ts +766 -0
- package/dist/lib/queue.js +1965 -0
- package/dist/lib/recovery.d.ts +86 -0
- package/dist/lib/recovery.js +285 -0
- package/dist/lib/rendering.d.ts +20 -0
- package/dist/lib/rendering.js +983 -0
- package/dist/lib/replies.d.ts +214 -0
- package/dist/lib/replies.js +737 -0
- package/dist/lib/routing.d.ts +207 -0
- package/dist/lib/routing.js +2282 -0
- package/dist/lib/runtime.d.ts +172 -0
- package/dist/lib/runtime.js +402 -0
- package/dist/lib/sections.d.ts +165 -0
- package/dist/lib/sections.js +327 -0
- package/dist/lib/setup.d.ts +82 -0
- package/dist/lib/setup.js +150 -0
- package/dist/lib/skills.d.ts +8 -0
- package/dist/lib/skills.js +12 -0
- package/dist/lib/status.d.ts +442 -0
- package/dist/lib/status.js +1006 -0
- package/dist/lib/sync.d.ts +179 -0
- package/dist/lib/sync.js +782 -0
- package/dist/lib/target.d.ts +20 -0
- package/dist/lib/target.js +27 -0
- package/dist/lib/telegram-api.d.ts +541 -0
- package/dist/lib/telegram-api.js +1159 -0
- package/dist/lib/text-groups.d.ts +89 -0
- package/dist/lib/text-groups.js +317 -0
- package/dist/lib/thread-cleanup-manager.d.ts +288 -0
- package/dist/lib/thread-cleanup-manager.js +560 -0
- package/dist/lib/thread-display.d.ts +46 -0
- package/dist/lib/thread-display.js +257 -0
- package/dist/lib/thread-naming.d.ts +46 -0
- package/dist/lib/thread-naming.js +78 -0
- package/dist/lib/thread-reconciler.d.ts +239 -0
- package/dist/lib/thread-reconciler.js +644 -0
- package/dist/lib/threads.d.ts +621 -0
- package/dist/lib/threads.js +3679 -0
- package/dist/lib/time-injection.d.ts +15 -0
- package/dist/lib/time-injection.js +56 -0
- package/dist/lib/turns.d.ts +109 -0
- package/dist/lib/turns.js +500 -0
- package/dist/lib/updates.d.ts +1290 -0
- package/dist/lib/updates.js +3634 -0
- package/dist/lib/voice.d.ts +117 -0
- package/dist/lib/voice.js +174 -0
- package/dist/lib/workspace-admission.d.ts +264 -0
- package/dist/lib/workspace-admission.js +1136 -0
- package/dist/lib/workspace-retirement.d.ts +219 -0
- package/dist/lib/workspace-retirement.js +587 -0
- package/dist/lib/workspace-slots.d.ts +30 -0
- package/dist/lib/workspace-slots.js +54 -0
- package/dist/package.json +126 -0
- package/dist/pi-telegram/index.js +1 -0
- package/dist/skills/generated-control-surface/SKILL.md +107 -0
- package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
- package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
- package/dist/skills/generative-apps/SKILL.md +115 -0
- package/dist/skills/show-me/SKILL.md +166 -0
- package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
- package/dist/skills/telegram-bridge/SKILL.md +129 -0
- package/dist/skills/telegram-bridge/references/configuration.md +15 -0
- package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
- package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
- package/docs/architecture.md +1 -1
- package/docs/compact-matrix-literal.md +10 -3
- package/docs/generative-apps.md +15 -13
- package/docs/multi-instance-bus.md +4 -2
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +2 -2
- package/lib/bindings.ts +89 -6
- package/lib/bus-follower.ts +7 -3
- package/lib/bus-leader.ts +44 -19
- package/lib/bus.ts +4 -1
- package/lib/commands.ts +2 -2
- package/lib/config.ts +14 -7
- package/lib/delivery.ts +38 -6
- package/lib/extension.ts +19 -0
- package/lib/generative-apps.ts +379 -13
- package/lib/menu-settings.ts +21 -5
- package/lib/outbound-markup.ts +16 -7
- package/lib/telegram-api.ts +12 -1
- package/lib/thread-display.ts +96 -18
- package/package.json +56 -13
- package/scripts/build-dist.mjs +44 -0
- package/scripts/measure-bus.mjs +8 -1
- package/scripts/measure-workspace.mjs +8 -1
- package/skills/generative-apps/SKILL.md +1 -1
- package/skills/telegram-bridge/SKILL.md +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram inbound handler pipeline
|
|
3
|
+
* Zones: telegram inbound, command templates, prompt preparation
|
|
4
|
+
* Owns MIME/type matching, command-template execution, fallback handling, and prompt injection before prompt enqueueing
|
|
5
|
+
*/
|
|
6
|
+
import { type CommandTemplateConfig, type CommandTemplateObjectConfig } from "./command-templates.ts";
|
|
7
|
+
type TelegramInboundCommandTemplateConfig = string | CommandTemplateObjectConfig;
|
|
8
|
+
export interface TelegramInboundHandlerConfig {
|
|
9
|
+
match?: string | string[];
|
|
10
|
+
mime?: string | string[];
|
|
11
|
+
type?: string | string[];
|
|
12
|
+
template?: string | TelegramInboundCommandTemplateConfig[];
|
|
13
|
+
args?: string[];
|
|
14
|
+
defaults?: Record<string, unknown>;
|
|
15
|
+
timeout?: number | string;
|
|
16
|
+
}
|
|
17
|
+
export interface TelegramInboundHandlerFile {
|
|
18
|
+
path: string;
|
|
19
|
+
fileName?: string;
|
|
20
|
+
mimeType?: string;
|
|
21
|
+
kind?: string;
|
|
22
|
+
isImage?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface TelegramInboundHandlerOutput {
|
|
25
|
+
file: TelegramInboundHandlerFile;
|
|
26
|
+
output: string;
|
|
27
|
+
handler: TelegramInboundHandlerConfig;
|
|
28
|
+
}
|
|
29
|
+
export interface TelegramInboundHandlerProcessResult<TFile extends TelegramInboundHandlerFile = TelegramInboundHandlerFile> {
|
|
30
|
+
rawText: string;
|
|
31
|
+
promptFiles: TFile[];
|
|
32
|
+
handlerOutputs: string[];
|
|
33
|
+
handledFiles: TelegramInboundHandlerOutput[];
|
|
34
|
+
}
|
|
35
|
+
export interface TelegramInboundHandlerExecOptions {
|
|
36
|
+
cwd?: string;
|
|
37
|
+
timeout?: number;
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
stdin?: string;
|
|
40
|
+
retry?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface TelegramInboundHandlerExecResult {
|
|
43
|
+
stdout: string;
|
|
44
|
+
stderr: string;
|
|
45
|
+
code: number;
|
|
46
|
+
killed: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface TelegramInboundHandlerRuntimeDeps<TContext> {
|
|
49
|
+
getHandlers: () => TelegramInboundHandlerConfig[] | undefined;
|
|
50
|
+
execCommand: (command: string, args: string[], options?: TelegramInboundHandlerExecOptions) => Promise<TelegramInboundHandlerExecResult>;
|
|
51
|
+
getCwd: (ctx: TContext) => string;
|
|
52
|
+
recordRuntimeEvent?: (category: string, error: unknown, details?: Record<string, unknown>) => void;
|
|
53
|
+
}
|
|
54
|
+
export interface TelegramInboundHandlerRuntime<TContext> {
|
|
55
|
+
process: <TFile extends TelegramInboundHandlerFile>(files: TFile[], rawText: string, ctx: TContext) => Promise<TelegramInboundHandlerProcessResult<TFile>>;
|
|
56
|
+
}
|
|
57
|
+
export type TelegramInboundProgrammaticHandlerResult = string | {
|
|
58
|
+
text: string;
|
|
59
|
+
} | undefined;
|
|
60
|
+
export interface TelegramInboundProgrammaticHandlerInput {
|
|
61
|
+
kind: string;
|
|
62
|
+
text?: string;
|
|
63
|
+
file?: TelegramInboundHandlerFile;
|
|
64
|
+
mimeType?: string;
|
|
65
|
+
}
|
|
66
|
+
export type TelegramInboundProgrammaticHandler = (input: TelegramInboundProgrammaticHandlerInput, options?: {
|
|
67
|
+
cwd?: string;
|
|
68
|
+
}) => Promise<TelegramInboundProgrammaticHandlerResult>;
|
|
69
|
+
export interface TelegramInboundHandlerRegistry {
|
|
70
|
+
handlers: Map<string, TelegramInboundProgrammaticHandler[]>;
|
|
71
|
+
}
|
|
72
|
+
interface InboundHandlerInvocation {
|
|
73
|
+
command: string;
|
|
74
|
+
args: string[];
|
|
75
|
+
}
|
|
76
|
+
export declare function registerTelegramInboundHandler(kind: string, handler: TelegramInboundProgrammaticHandler): () => void;
|
|
77
|
+
export declare function getTelegramInboundProgrammaticHandlers(kind: string): TelegramInboundProgrammaticHandler[];
|
|
78
|
+
export declare function clearTelegramInboundHandlers(): void;
|
|
79
|
+
export declare function telegramInboundHandlerMatchesFile(handler: TelegramInboundHandlerConfig, file: TelegramInboundHandlerFile): boolean;
|
|
80
|
+
export declare function findTelegramInboundHandlers(handlers: TelegramInboundHandlerConfig[] | undefined, file: TelegramInboundHandlerFile): TelegramInboundHandlerConfig[];
|
|
81
|
+
export declare function buildTelegramInboundHandlerInvocation(handler: CommandTemplateConfig, file: TelegramInboundHandlerFile, cwd: string, appendFileIfMissing?: boolean): InboundHandlerInvocation;
|
|
82
|
+
export declare function processTelegramInboundHandlers<TFile extends TelegramInboundHandlerFile>(options: {
|
|
83
|
+
files: TFile[];
|
|
84
|
+
rawText: string;
|
|
85
|
+
handlers?: TelegramInboundHandlerConfig[];
|
|
86
|
+
cwd: string;
|
|
87
|
+
execCommand: TelegramInboundHandlerRuntimeDeps<unknown>["execCommand"];
|
|
88
|
+
recordRuntimeEvent?: TelegramInboundHandlerRuntimeDeps<unknown>["recordRuntimeEvent"];
|
|
89
|
+
}): Promise<TelegramInboundHandlerProcessResult<TFile>>;
|
|
90
|
+
export declare function createTelegramInboundHandlerRuntime<TContext>(deps: TelegramInboundHandlerRuntimeDeps<TContext>): TelegramInboundHandlerRuntime<TContext>;
|
|
91
|
+
export {};
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram inbound handler pipeline
|
|
3
|
+
* Zones: telegram inbound, command templates, prompt preparation
|
|
4
|
+
* Owns MIME/type matching, command-template execution, fallback handling, and prompt injection before prompt enqueueing
|
|
5
|
+
*/
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { basename } from "node:path";
|
|
8
|
+
import { buildCommandTemplateInvocation, expandCommandTemplateConfigs, normalizeCommandTemplateConfig, substituteCommandTemplateToken, } from "./command-templates.js";
|
|
9
|
+
import { getTelegramVoiceTranscriptionProviders } from "./voice.js";
|
|
10
|
+
const DEFAULT_INBOUND_HANDLER_TIMEOUT_MS = 120_000;
|
|
11
|
+
const INBOUND_HANDLER_REGISTRY_KEY = "__piTelegramInboundHandlers__";
|
|
12
|
+
const MAX_INBOUND_HANDLER_OUTPUT_LENGTH = 24_000;
|
|
13
|
+
const MAX_INBOUND_HANDLER_FAILURE_STREAM_LENGTH = 4_000;
|
|
14
|
+
const BUILT_IN_TEXT_ATTACHMENT_MAX_BYTES = 1_000_000;
|
|
15
|
+
function getOrCreateInboundHandlerRegistry() {
|
|
16
|
+
const existing = globalThis[INBOUND_HANDLER_REGISTRY_KEY];
|
|
17
|
+
if (existing &&
|
|
18
|
+
typeof existing === "object" &&
|
|
19
|
+
existing !== null &&
|
|
20
|
+
"handlers" in existing &&
|
|
21
|
+
existing.handlers instanceof Map) {
|
|
22
|
+
return existing;
|
|
23
|
+
}
|
|
24
|
+
const registry = {
|
|
25
|
+
handlers: new Map(),
|
|
26
|
+
};
|
|
27
|
+
globalThis[INBOUND_HANDLER_REGISTRY_KEY] =
|
|
28
|
+
registry;
|
|
29
|
+
return registry;
|
|
30
|
+
}
|
|
31
|
+
export function registerTelegramInboundHandler(kind, handler) {
|
|
32
|
+
const normalizedKind = kind.trim() || "*";
|
|
33
|
+
const registry = getOrCreateInboundHandlerRegistry();
|
|
34
|
+
const list = registry.handlers.get(normalizedKind) ?? [];
|
|
35
|
+
list.push(handler);
|
|
36
|
+
registry.handlers.set(normalizedKind, list);
|
|
37
|
+
return () => {
|
|
38
|
+
const updated = registry.handlers.get(normalizedKind) ?? [];
|
|
39
|
+
const index = updated.indexOf(handler);
|
|
40
|
+
if (index !== -1) {
|
|
41
|
+
updated.splice(index, 1);
|
|
42
|
+
registry.handlers.set(normalizedKind, updated);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function getTelegramInboundProgrammaticHandlers(kind) {
|
|
47
|
+
const registry = getOrCreateInboundHandlerRegistry();
|
|
48
|
+
return [
|
|
49
|
+
...(registry.handlers.get(kind) ?? []),
|
|
50
|
+
...(kind === "*" ? [] : (registry.handlers.get("*") ?? [])),
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
export function clearTelegramInboundHandlers() {
|
|
54
|
+
getOrCreateInboundHandlerRegistry().handlers.clear();
|
|
55
|
+
}
|
|
56
|
+
function truncateTelegramInboundText(text, maxLength) {
|
|
57
|
+
if (text.length <= maxLength)
|
|
58
|
+
return text;
|
|
59
|
+
return `${text.slice(0, maxLength).trimEnd()}… [truncated ${text.length - maxLength} chars]`;
|
|
60
|
+
}
|
|
61
|
+
function truncateTelegramInboundOutput(text) {
|
|
62
|
+
return truncateTelegramInboundText(text, MAX_INBOUND_HANDLER_OUTPUT_LENGTH);
|
|
63
|
+
}
|
|
64
|
+
function truncateTelegramInboundFailureStream(text) {
|
|
65
|
+
return truncateTelegramInboundText(text.trimEnd(), MAX_INBOUND_HANDLER_FAILURE_STREAM_LENGTH);
|
|
66
|
+
}
|
|
67
|
+
function normalizeInboundProgrammaticHandlerText(result) {
|
|
68
|
+
const text = typeof result === "string" ? result : result?.text;
|
|
69
|
+
const normalized = text?.trim();
|
|
70
|
+
return normalized ? truncateTelegramInboundOutput(normalized) : undefined;
|
|
71
|
+
}
|
|
72
|
+
function normalizeStringList(value) {
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
return value
|
|
75
|
+
.map(String)
|
|
76
|
+
.map((item) => item.trim())
|
|
77
|
+
.filter(Boolean);
|
|
78
|
+
}
|
|
79
|
+
if (typeof value === "string" && value.trim())
|
|
80
|
+
return [value.trim()];
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
function matchesWildcard(pattern, value) {
|
|
84
|
+
if (!value)
|
|
85
|
+
return false;
|
|
86
|
+
const normalizedPattern = pattern.toLowerCase();
|
|
87
|
+
const normalizedValue = value.toLowerCase();
|
|
88
|
+
if (normalizedPattern === "*")
|
|
89
|
+
return true;
|
|
90
|
+
const escaped = normalizedPattern
|
|
91
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
92
|
+
.replace(/\\\*/g, ".*");
|
|
93
|
+
return new RegExp(`^${escaped}$`).test(normalizedValue);
|
|
94
|
+
}
|
|
95
|
+
function handlerHasSelectors(handler) {
|
|
96
|
+
return (normalizeStringList(handler.match).length > 0 ||
|
|
97
|
+
normalizeStringList(handler.mime).length > 0 ||
|
|
98
|
+
normalizeStringList(handler.type).length > 0);
|
|
99
|
+
}
|
|
100
|
+
function matchesAnyPattern(patterns, value) {
|
|
101
|
+
return patterns.some((pattern) => matchesWildcard(pattern, value));
|
|
102
|
+
}
|
|
103
|
+
function isTelegramTextMimeType(mimeType) {
|
|
104
|
+
return matchesWildcard("text/*", mimeType);
|
|
105
|
+
}
|
|
106
|
+
export function telegramInboundHandlerMatchesFile(handler, file) {
|
|
107
|
+
if (!handlerHasSelectors(handler))
|
|
108
|
+
return true;
|
|
109
|
+
const matchPatterns = normalizeStringList(handler.match);
|
|
110
|
+
const mimePatterns = normalizeStringList(handler.mime);
|
|
111
|
+
const typePatterns = normalizeStringList(handler.type);
|
|
112
|
+
if (matchesAnyPattern(mimePatterns, file.mimeType))
|
|
113
|
+
return true;
|
|
114
|
+
if (matchesAnyPattern(typePatterns, file.kind))
|
|
115
|
+
return true;
|
|
116
|
+
if (matchesAnyPattern(matchPatterns, file.mimeType))
|
|
117
|
+
return true;
|
|
118
|
+
return matchesAnyPattern(matchPatterns, file.kind);
|
|
119
|
+
}
|
|
120
|
+
export function findTelegramInboundHandlers(handlers, file) {
|
|
121
|
+
if (!Array.isArray(handlers))
|
|
122
|
+
return [];
|
|
123
|
+
return handlers.filter((handler) => !!handler &&
|
|
124
|
+
typeof handler === "object" &&
|
|
125
|
+
telegramInboundHandlerMatchesFile(handler, file));
|
|
126
|
+
}
|
|
127
|
+
function hasInboundFilePlaceholder(value) {
|
|
128
|
+
return /\{file\}/.test(value);
|
|
129
|
+
}
|
|
130
|
+
function getTelegramInboundHandlerTemplateValues(file, text = "") {
|
|
131
|
+
return {
|
|
132
|
+
file: file.path,
|
|
133
|
+
mime: file.mimeType ?? "",
|
|
134
|
+
text,
|
|
135
|
+
type: file.kind ?? "",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function buildTelegramInboundTemplateInvocation(handler, file, cwd, appendFileIfMissing = true) {
|
|
139
|
+
const values = getTelegramInboundHandlerTemplateValues(file);
|
|
140
|
+
const templateConfig = normalizeCommandTemplateConfig(handler);
|
|
141
|
+
const hadFilePlaceholder = typeof templateConfig.template === "string"
|
|
142
|
+
? hasInboundFilePlaceholder(templateConfig.template)
|
|
143
|
+
: false;
|
|
144
|
+
const invocation = buildCommandTemplateInvocation(handler, values, cwd, {
|
|
145
|
+
emptyMessage: "Inbound handler template is empty",
|
|
146
|
+
missingLabel: "inbound handler template",
|
|
147
|
+
});
|
|
148
|
+
if (appendFileIfMissing && !hadFilePlaceholder)
|
|
149
|
+
invocation.args.push(file.path);
|
|
150
|
+
return invocation;
|
|
151
|
+
}
|
|
152
|
+
export function buildTelegramInboundHandlerInvocation(handler, file, cwd, appendFileIfMissing = true) {
|
|
153
|
+
const { template } = normalizeCommandTemplateConfig(handler);
|
|
154
|
+
if (!template)
|
|
155
|
+
throw new Error("Inbound handler template is required");
|
|
156
|
+
return buildTelegramInboundTemplateInvocation(handler, file, cwd, appendFileIfMissing);
|
|
157
|
+
}
|
|
158
|
+
function resolveTelegramInboundNumericControlField(value, values, label) {
|
|
159
|
+
if (value === undefined)
|
|
160
|
+
return undefined;
|
|
161
|
+
const resolved = typeof value === "string"
|
|
162
|
+
? substituteCommandTemplateToken(value, values, label)
|
|
163
|
+
: value;
|
|
164
|
+
if (resolved === "")
|
|
165
|
+
return undefined;
|
|
166
|
+
const numeric = Number(resolved);
|
|
167
|
+
if (!Number.isFinite(numeric) || numeric < 0)
|
|
168
|
+
throw new Error(`Command template ${label} must be a non-negative number.`);
|
|
169
|
+
return numeric;
|
|
170
|
+
}
|
|
171
|
+
function getTelegramInboundHandlerConfiguredTimeout(handler) {
|
|
172
|
+
const timeout = typeof handler === "string" ? undefined : handler.timeout;
|
|
173
|
+
return resolveTelegramInboundNumericControlField(timeout, {}, "timeout");
|
|
174
|
+
}
|
|
175
|
+
function getTelegramInboundHandlerTimeout(handler) {
|
|
176
|
+
return (getTelegramInboundHandlerConfiguredTimeout(handler) ??
|
|
177
|
+
DEFAULT_INBOUND_HANDLER_TIMEOUT_MS);
|
|
178
|
+
}
|
|
179
|
+
function getRemainingTelegramInboundTimeout(timeout, startedAt) {
|
|
180
|
+
return Math.max(1, timeout - (Date.now() - startedAt));
|
|
181
|
+
}
|
|
182
|
+
function getTelegramInboundInitialCompositionStepTimeout(handler, step) {
|
|
183
|
+
const timeout = getTelegramInboundHandlerTimeout(handler);
|
|
184
|
+
const stepTimeout = getTelegramInboundHandlerConfiguredTimeout(step);
|
|
185
|
+
return stepTimeout === undefined ? timeout : Math.min(stepTimeout, timeout);
|
|
186
|
+
}
|
|
187
|
+
function getTelegramInboundCompositionStepTimeout(handler, step, startedAt) {
|
|
188
|
+
const remaining = getRemainingTelegramInboundTimeout(getTelegramInboundHandlerTimeout(handler), startedAt);
|
|
189
|
+
const stepTimeout = getTelegramInboundHandlerConfiguredTimeout(step);
|
|
190
|
+
return stepTimeout === undefined
|
|
191
|
+
? remaining
|
|
192
|
+
: Math.min(stepTimeout, remaining);
|
|
193
|
+
}
|
|
194
|
+
function getTelegramInboundHandlerKind(handler) {
|
|
195
|
+
if (Array.isArray(handler.template))
|
|
196
|
+
return "composition";
|
|
197
|
+
if (handler.template)
|
|
198
|
+
return "template";
|
|
199
|
+
return "unknown";
|
|
200
|
+
}
|
|
201
|
+
function formatTelegramInboundHandlerFailure(result) {
|
|
202
|
+
const parts = [
|
|
203
|
+
`Inbound handler exited with code ${result.code}${result.killed ? " (killed)" : ""}`,
|
|
204
|
+
];
|
|
205
|
+
if (result.stderr.trim())
|
|
206
|
+
parts.push(`stderr:\n${truncateTelegramInboundFailureStream(result.stderr)}`);
|
|
207
|
+
if (result.stdout.trim())
|
|
208
|
+
parts.push(`stdout:\n${truncateTelegramInboundFailureStream(result.stdout)}`);
|
|
209
|
+
return parts.join("\n\n");
|
|
210
|
+
}
|
|
211
|
+
async function executeTelegramInboundHandlerInvocation(handler, file, cwd, deps, appendFileIfMissing = true, timeout = getTelegramInboundHandlerTimeout(handler), stdin) {
|
|
212
|
+
const invocation = buildTelegramInboundHandlerInvocation(handler, file, cwd, appendFileIfMissing);
|
|
213
|
+
const result = await deps.execCommand(invocation.command, invocation.args, {
|
|
214
|
+
cwd,
|
|
215
|
+
timeout,
|
|
216
|
+
...(typeof handler === "object" && handler.retry !== undefined
|
|
217
|
+
? {
|
|
218
|
+
retry: resolveTelegramInboundNumericControlField(handler.retry, {}, "retry"),
|
|
219
|
+
}
|
|
220
|
+
: {}),
|
|
221
|
+
...(stdin !== undefined ? { stdin } : {}),
|
|
222
|
+
});
|
|
223
|
+
if (result.code !== 0)
|
|
224
|
+
throw new Error(formatTelegramInboundHandlerFailure(result));
|
|
225
|
+
return truncateTelegramInboundOutput(result.stdout);
|
|
226
|
+
}
|
|
227
|
+
function getTelegramInboundHandlerCompositionSteps(handler) {
|
|
228
|
+
if (Array.isArray(handler.template)) {
|
|
229
|
+
return expandCommandTemplateConfigs(handler);
|
|
230
|
+
}
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
function getTelegramTextHandlerFile() {
|
|
234
|
+
return {
|
|
235
|
+
path: "",
|
|
236
|
+
fileName: "message.txt",
|
|
237
|
+
mimeType: "text/plain",
|
|
238
|
+
kind: "text",
|
|
239
|
+
isImage: false,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function findTelegramTextHandlers(handlers) {
|
|
243
|
+
if (!Array.isArray(handlers))
|
|
244
|
+
return [];
|
|
245
|
+
const textFile = getTelegramTextHandlerFile();
|
|
246
|
+
return handlers.filter((handler) => !!handler &&
|
|
247
|
+
typeof handler === "object" &&
|
|
248
|
+
handlerHasSelectors(handler) &&
|
|
249
|
+
telegramInboundHandlerMatchesFile(handler, textFile));
|
|
250
|
+
}
|
|
251
|
+
function buildTelegramTextHandlerInvocation(handler, text, cwd) {
|
|
252
|
+
const values = getTelegramInboundHandlerTemplateValues(getTelegramTextHandlerFile(), text);
|
|
253
|
+
const { template } = normalizeCommandTemplateConfig(handler);
|
|
254
|
+
if (!template)
|
|
255
|
+
throw new Error("Text handler template is required");
|
|
256
|
+
return buildCommandTemplateInvocation(handler, values, cwd, {
|
|
257
|
+
emptyMessage: "Text handler template is empty",
|
|
258
|
+
missingLabel: "text handler template",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
async function executeTelegramTextHandlerInvocation(handler, text, cwd, deps, timeout = getTelegramInboundHandlerTimeout(handler)) {
|
|
262
|
+
const invocation = buildTelegramTextHandlerInvocation(handler, text, cwd);
|
|
263
|
+
const result = await deps.execCommand(invocation.command, invocation.args, {
|
|
264
|
+
cwd,
|
|
265
|
+
timeout,
|
|
266
|
+
stdin: text,
|
|
267
|
+
...(typeof handler === "object" && handler.retry !== undefined
|
|
268
|
+
? {
|
|
269
|
+
retry: resolveTelegramInboundNumericControlField(handler.retry, {}, "retry"),
|
|
270
|
+
}
|
|
271
|
+
: {}),
|
|
272
|
+
});
|
|
273
|
+
if (result.code !== 0)
|
|
274
|
+
throw new Error(formatTelegramInboundHandlerFailure(result));
|
|
275
|
+
return truncateTelegramInboundOutput(result.stdout);
|
|
276
|
+
}
|
|
277
|
+
async function executeTelegramTextHandler(handler, text, cwd, deps) {
|
|
278
|
+
const steps = getTelegramInboundHandlerCompositionSteps(handler);
|
|
279
|
+
if (steps.length === 0) {
|
|
280
|
+
return (await executeTelegramTextHandlerInvocation(handler, text, cwd, deps)).trim();
|
|
281
|
+
}
|
|
282
|
+
const startedAt = Date.now();
|
|
283
|
+
let output = text;
|
|
284
|
+
for (const [index, step] of steps.entries()) {
|
|
285
|
+
try {
|
|
286
|
+
output = await executeTelegramTextHandlerInvocation(step, output, cwd, deps, index === 0
|
|
287
|
+
? getTelegramInboundInitialCompositionStepTimeout(handler, step)
|
|
288
|
+
: getTelegramInboundCompositionStepTimeout(handler, step, startedAt));
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
if (typeof step === "object" && step.failure === "root")
|
|
292
|
+
throw error;
|
|
293
|
+
output = "";
|
|
294
|
+
}
|
|
295
|
+
if (index > 0 && !output)
|
|
296
|
+
output = text;
|
|
297
|
+
}
|
|
298
|
+
return output.trim();
|
|
299
|
+
}
|
|
300
|
+
async function processTelegramTextHandlers(options) {
|
|
301
|
+
if (!options.rawText)
|
|
302
|
+
return options.rawText;
|
|
303
|
+
let text = options.rawText;
|
|
304
|
+
for (const handler of findTelegramTextHandlers(options.handlers)) {
|
|
305
|
+
try {
|
|
306
|
+
const output = await executeTelegramTextHandler(handler, text, options.cwd, options);
|
|
307
|
+
if (output)
|
|
308
|
+
text = output;
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
options.recordRuntimeEvent?.("inbound-text-handler", error, {
|
|
312
|
+
handler: getTelegramInboundHandlerKind(handler),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
for (const handler of getTelegramInboundProgrammaticHandlers("text")) {
|
|
317
|
+
try {
|
|
318
|
+
const output = normalizeInboundProgrammaticHandlerText(await handler({ kind: "text", text, mimeType: "text/plain" }, { cwd: options.cwd }));
|
|
319
|
+
if (output)
|
|
320
|
+
text = output;
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
options.recordRuntimeEvent?.("inbound-programmatic-handler", error, {
|
|
324
|
+
kind: "text",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return text;
|
|
329
|
+
}
|
|
330
|
+
function isTelegramVoiceLikeFile(file) {
|
|
331
|
+
return (file.kind === "voice" ||
|
|
332
|
+
file.kind === "audio" ||
|
|
333
|
+
matchesWildcard("audio/*", file.mimeType));
|
|
334
|
+
}
|
|
335
|
+
async function processTelegramFileWithProgrammaticHandlers(file, options) {
|
|
336
|
+
const kind = file.kind || "*";
|
|
337
|
+
for (const handler of getTelegramInboundProgrammaticHandlers(kind)) {
|
|
338
|
+
try {
|
|
339
|
+
const output = normalizeInboundProgrammaticHandlerText(await handler({
|
|
340
|
+
kind,
|
|
341
|
+
file,
|
|
342
|
+
mimeType: file.mimeType,
|
|
343
|
+
}, { cwd: options.cwd }));
|
|
344
|
+
if (output)
|
|
345
|
+
return output;
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
options.recordRuntimeEvent?.("inbound-programmatic-handler", error, {
|
|
349
|
+
fileName: file.fileName || basename(file.path),
|
|
350
|
+
kind,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
async function transcribeTelegramVoiceFileWithProviders(file, options) {
|
|
357
|
+
if (!isTelegramVoiceLikeFile(file))
|
|
358
|
+
return undefined;
|
|
359
|
+
for (const provider of getTelegramVoiceTranscriptionProviders()) {
|
|
360
|
+
try {
|
|
361
|
+
const result = await provider(file, {});
|
|
362
|
+
const text = typeof result === "string" ? result : result?.text;
|
|
363
|
+
if (text?.trim())
|
|
364
|
+
return truncateTelegramInboundOutput(text.trim());
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
options.recordRuntimeEvent?.("voice-transcription-provider", error, {
|
|
368
|
+
fileName: file.fileName || basename(file.path),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
async function readBuiltInTelegramTextAttachment(file) {
|
|
375
|
+
if (!isTelegramTextMimeType(file.mimeType))
|
|
376
|
+
return undefined;
|
|
377
|
+
const content = await readFile(file.path, "utf8");
|
|
378
|
+
const normalized = content.trim();
|
|
379
|
+
if (!normalized ||
|
|
380
|
+
Buffer.byteLength(normalized, "utf8") > BUILT_IN_TEXT_ATTACHMENT_MAX_BYTES) {
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
const name = file.fileName || basename(file.path);
|
|
384
|
+
return truncateTelegramInboundOutput(`[${name}]\n${normalized}`);
|
|
385
|
+
}
|
|
386
|
+
async function executeTelegramInboundHandler(handler, file, cwd, deps) {
|
|
387
|
+
const steps = getTelegramInboundHandlerCompositionSteps(handler);
|
|
388
|
+
if (steps.length === 0) {
|
|
389
|
+
const output = await executeTelegramInboundHandlerInvocation(handler, file, cwd, deps);
|
|
390
|
+
return output.trim();
|
|
391
|
+
}
|
|
392
|
+
const startedAt = Date.now();
|
|
393
|
+
let output = "";
|
|
394
|
+
for (const [index, step] of steps.entries()) {
|
|
395
|
+
try {
|
|
396
|
+
output = await executeTelegramInboundHandlerInvocation(step, file, cwd, deps, false, index === 0
|
|
397
|
+
? getTelegramInboundInitialCompositionStepTimeout(handler, step)
|
|
398
|
+
: getTelegramInboundCompositionStepTimeout(handler, step, startedAt), index === 0 ? undefined : output);
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
if (typeof step === "object" && step.failure === "root")
|
|
402
|
+
throw error;
|
|
403
|
+
output = "";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return output.trim();
|
|
407
|
+
}
|
|
408
|
+
export async function processTelegramInboundHandlers(options) {
|
|
409
|
+
const rawText = await processTelegramTextHandlers({
|
|
410
|
+
rawText: options.rawText,
|
|
411
|
+
handlers: options.handlers,
|
|
412
|
+
cwd: options.cwd,
|
|
413
|
+
execCommand: options.execCommand,
|
|
414
|
+
recordRuntimeEvent: options.recordRuntimeEvent,
|
|
415
|
+
});
|
|
416
|
+
const promptFiles = [...options.files];
|
|
417
|
+
const outputs = [];
|
|
418
|
+
for (const file of options.files) {
|
|
419
|
+
let hasOutput = false;
|
|
420
|
+
const handlers = findTelegramInboundHandlers(options.handlers, file);
|
|
421
|
+
for (const handler of handlers) {
|
|
422
|
+
try {
|
|
423
|
+
const output = await executeTelegramInboundHandler(handler, file, options.cwd, options);
|
|
424
|
+
if (output) {
|
|
425
|
+
outputs.push({ file, output, handler });
|
|
426
|
+
hasOutput = true;
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
options.recordRuntimeEvent?.("inbound-handler", error, {
|
|
432
|
+
fileName: file.fileName || basename(file.path),
|
|
433
|
+
handler: getTelegramInboundHandlerKind(handler),
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (!hasOutput) {
|
|
438
|
+
try {
|
|
439
|
+
const output = await processTelegramFileWithProgrammaticHandlers(file, {
|
|
440
|
+
cwd: options.cwd,
|
|
441
|
+
recordRuntimeEvent: options.recordRuntimeEvent,
|
|
442
|
+
});
|
|
443
|
+
if (output) {
|
|
444
|
+
outputs.push({ file, output, handler: { type: "programmatic" } });
|
|
445
|
+
hasOutput = true;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
options.recordRuntimeEvent?.("inbound-programmatic-handler", error, {
|
|
450
|
+
fileName: file.fileName || basename(file.path),
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (!hasOutput) {
|
|
455
|
+
try {
|
|
456
|
+
const output = await transcribeTelegramVoiceFileWithProviders(file, {
|
|
457
|
+
recordRuntimeEvent: options.recordRuntimeEvent,
|
|
458
|
+
});
|
|
459
|
+
if (output) {
|
|
460
|
+
outputs.push({ file, output, handler: { type: "voice-provider" } });
|
|
461
|
+
hasOutput = true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
options.recordRuntimeEvent?.("voice-transcription-provider", error, {
|
|
466
|
+
fileName: file.fileName || basename(file.path),
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (!hasOutput) {
|
|
471
|
+
try {
|
|
472
|
+
const output = await readBuiltInTelegramTextAttachment(file);
|
|
473
|
+
if (output)
|
|
474
|
+
outputs.push({ file, output, handler: { type: "text" } });
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
options.recordRuntimeEvent?.("inbound-handler", error, {
|
|
478
|
+
fileName: file.fileName || basename(file.path),
|
|
479
|
+
handler: "built-in-text",
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
rawText,
|
|
486
|
+
promptFiles,
|
|
487
|
+
handlerOutputs: outputs.map((output) => output.output),
|
|
488
|
+
handledFiles: outputs,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
export function createTelegramInboundHandlerRuntime(deps) {
|
|
492
|
+
return {
|
|
493
|
+
process: (files, rawText, ctx) => processTelegramInboundHandlers({
|
|
494
|
+
files,
|
|
495
|
+
rawText,
|
|
496
|
+
handlers: deps.getHandlers(),
|
|
497
|
+
cwd: deps.getCwd(ctx),
|
|
498
|
+
execCommand: deps.execCommand,
|
|
499
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
500
|
+
}),
|
|
501
|
+
};
|
|
502
|
+
}
|