@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,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram target-aware operational delivery and logical message lifecycle
|
|
3
|
+
* Zones: telegram delivery, extension API, runtime binding
|
|
4
|
+
* Owns the public extension delivery contract, authorized scope resolution, operational rendering adapter, per-target serialization, chunk reconciliation, generation-fenced logical handles, and process-local runtime membrane; composes the established reply renderer with bus-aware Telegram API ports and excludes bot clients, Pi contexts, and consumer-extension policy
|
|
5
|
+
*/
|
|
6
|
+
import { markTelegramBusAggregateDelivery } from "./bus.js";
|
|
7
|
+
import { assertTelegramInlineKeyboardCallbackData, } from "./keyboard.js";
|
|
8
|
+
import { withTelegramReplyParameters, renderTelegramMessage, } from "./replies.js";
|
|
9
|
+
import { getTelegramTargetThreadParams, } from "./target.js";
|
|
10
|
+
import { getTelegramApiRetryAfterMs, isRetryableTelegramApiError, isTelegramApiCommitUnknownError, isTelegramMessageUnavailableError, } from "./telegram-api.js";
|
|
11
|
+
const TELEGRAM_DELIVERY_RUNTIME_KEY = "__piTelegramDeliveryRuntime__";
|
|
12
|
+
class TelegramDeliveryTransportGenerationError extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super("Telegram Delivery transport generation is no longer active.");
|
|
15
|
+
this.name = "TelegramDeliveryTransportGenerationError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** @internal */
|
|
19
|
+
export function createTelegramDeliveryLifecycleHooks(createRuntime) {
|
|
20
|
+
let runtime;
|
|
21
|
+
let unbind;
|
|
22
|
+
const stopCurrentRuntime = () => {
|
|
23
|
+
unbind?.();
|
|
24
|
+
unbind = undefined;
|
|
25
|
+
runtime?.shutdown();
|
|
26
|
+
runtime = undefined;
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
onSessionStart: async () => {
|
|
30
|
+
stopCurrentRuntime();
|
|
31
|
+
runtime = createRuntime();
|
|
32
|
+
unbind = bindTelegramDeliveryRuntime(runtime);
|
|
33
|
+
},
|
|
34
|
+
onSessionShutdown: async () => {
|
|
35
|
+
stopCurrentRuntime();
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function createTelegramDeliveryGenerationSeed(instanceId) {
|
|
40
|
+
return `${instanceId}:${Date.now()}`;
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
export function createTelegramBridgeDeliveryLifecycleHooks(deps) {
|
|
44
|
+
let generationSequence = 0;
|
|
45
|
+
return createTelegramDeliveryLifecycleHooks(() => {
|
|
46
|
+
const transportStamp = deps.getTransportStamp?.();
|
|
47
|
+
return createTelegramBridgeDeliveryRuntime({
|
|
48
|
+
...deps,
|
|
49
|
+
generation: `${deps.generationSeed}:${++generationSequence}`,
|
|
50
|
+
isTransportActive: transportStamp !== undefined && deps.isTransportStampActive
|
|
51
|
+
? () => deps.isTransportStampActive?.(transportStamp) ?? false
|
|
52
|
+
: undefined,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function getTelegramDeliveryRuntimeRegistry() {
|
|
57
|
+
const globals = globalThis;
|
|
58
|
+
const existing = globals[TELEGRAM_DELIVERY_RUNTIME_KEY];
|
|
59
|
+
if (existing && typeof existing === "object" && "runtime" in existing) {
|
|
60
|
+
return existing;
|
|
61
|
+
}
|
|
62
|
+
const registry = {};
|
|
63
|
+
globals[TELEGRAM_DELIVERY_RUNTIME_KEY] = registry;
|
|
64
|
+
return registry;
|
|
65
|
+
}
|
|
66
|
+
function failure(reason, message, partial, retryAfterMs) {
|
|
67
|
+
const retry = retryAfterMs === undefined ? {} : { retryAfterMs };
|
|
68
|
+
return partial === undefined
|
|
69
|
+
? { ok: false, reason, message, ...retry }
|
|
70
|
+
: { ok: false, reason, message, partial, ...retry };
|
|
71
|
+
}
|
|
72
|
+
export function classifyTelegramDeliveryTransportError(error) {
|
|
73
|
+
if (isTelegramApiCommitUnknownError(error))
|
|
74
|
+
return { reason: "commit-unknown" };
|
|
75
|
+
if (isTelegramMessageUnavailableError(error))
|
|
76
|
+
return { reason: "message-unavailable" };
|
|
77
|
+
const retryAfterMs = getTelegramApiRetryAfterMs(error);
|
|
78
|
+
if (retryAfterMs !== undefined)
|
|
79
|
+
return { reason: "rate-limited", retryAfterMs };
|
|
80
|
+
if (isRetryableTelegramApiError(error))
|
|
81
|
+
return { reason: "transport-retryable" };
|
|
82
|
+
return { reason: "transport-failed" };
|
|
83
|
+
}
|
|
84
|
+
/** @internal */
|
|
85
|
+
export function createTelegramDeliveryTargetPolicyRuntime(deps) {
|
|
86
|
+
return {
|
|
87
|
+
getTargetPolicyView() {
|
|
88
|
+
const ownsDirect = deps.ownsDirect();
|
|
89
|
+
return {
|
|
90
|
+
canDeliver: ownsDirect || deps.isFollowerRegistered(),
|
|
91
|
+
ownsDirect,
|
|
92
|
+
allowedChatId: deps.getAllowedChatId(),
|
|
93
|
+
followerTarget: deps.getFollowerTarget(),
|
|
94
|
+
leaderTarget: deps.getLeaderTarget(),
|
|
95
|
+
liveTargets: deps.listThreadRecords().map((record) => record.target),
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
getActiveTurnTarget() {
|
|
99
|
+
return deps.getActiveGuestQueryId()
|
|
100
|
+
? undefined
|
|
101
|
+
: deps.getActiveTurnTarget();
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** @internal */
|
|
106
|
+
export function resolveTelegramDeliveryInstanceTarget(view) {
|
|
107
|
+
if (!view.canDeliver)
|
|
108
|
+
return undefined;
|
|
109
|
+
return (view.followerTarget ??
|
|
110
|
+
view.leaderTarget ??
|
|
111
|
+
(view.allowedChatId === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: { chatId: view.allowedChatId }));
|
|
114
|
+
}
|
|
115
|
+
/** @internal */
|
|
116
|
+
export function resolveTelegramDeliveryAggregateTarget(view) {
|
|
117
|
+
return !view.canDeliver || view.allowedChatId === undefined
|
|
118
|
+
? undefined
|
|
119
|
+
: { chatId: view.allowedChatId };
|
|
120
|
+
}
|
|
121
|
+
/** @internal */
|
|
122
|
+
export function isTelegramDeliveryExplicitTargetAuthorized(candidate, view) {
|
|
123
|
+
if (!view.canDeliver ||
|
|
124
|
+
view.allowedChatId === undefined ||
|
|
125
|
+
candidate.chatId !== view.allowedChatId) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (candidate.threadId === undefined)
|
|
129
|
+
return true;
|
|
130
|
+
if (view.followerTarget) {
|
|
131
|
+
return areDeliveryTargetsEqual(candidate, view.followerTarget);
|
|
132
|
+
}
|
|
133
|
+
if (!view.ownsDirect)
|
|
134
|
+
return false;
|
|
135
|
+
if (view.leaderTarget &&
|
|
136
|
+
areDeliveryTargetsEqual(candidate, view.leaderTarget)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
return (view.liveTargets ?? []).some(function (target) {
|
|
140
|
+
return areDeliveryTargetsEqual(candidate, target);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function areDeliveryTargetsEqual(left, right) {
|
|
144
|
+
return left.chatId === right.chatId && left.threadId === right.threadId;
|
|
145
|
+
}
|
|
146
|
+
function cloneTarget(target) {
|
|
147
|
+
return target.threadId === undefined
|
|
148
|
+
? { chatId: target.chatId }
|
|
149
|
+
: { chatId: target.chatId, threadId: target.threadId };
|
|
150
|
+
}
|
|
151
|
+
function targetKey(target) {
|
|
152
|
+
return `${target.chatId}:${target.threadId ?? "root"}`;
|
|
153
|
+
}
|
|
154
|
+
function resolveTelegramDeliveryTarget(scope, deps) {
|
|
155
|
+
if (scope.kind === "target") {
|
|
156
|
+
if (!deps.isExplicitTargetAuthorized(scope.target)) {
|
|
157
|
+
return failure("target-unauthorized", "Telegram delivery target is not authorized for this runtime.");
|
|
158
|
+
}
|
|
159
|
+
return { ok: true, value: cloneTarget(scope.target) };
|
|
160
|
+
}
|
|
161
|
+
const target = scope.kind === "active-turn"
|
|
162
|
+
? deps.getActiveTurnTarget()
|
|
163
|
+
: scope.kind === "instance"
|
|
164
|
+
? deps.getInstanceTarget()
|
|
165
|
+
: deps.getAggregateTarget();
|
|
166
|
+
if (!target) {
|
|
167
|
+
return failure("target-unavailable", `Telegram delivery ${scope.kind} target is unavailable.`);
|
|
168
|
+
}
|
|
169
|
+
return { ok: true, value: cloneTarget(target) };
|
|
170
|
+
}
|
|
171
|
+
function createTelegramDeliveryTargetQueue() {
|
|
172
|
+
const queues = new Map();
|
|
173
|
+
return async function run(target, operation) {
|
|
174
|
+
const key = targetKey(target);
|
|
175
|
+
const previous = queues.get(key) ?? Promise.resolve();
|
|
176
|
+
const current = previous.then(operation, operation);
|
|
177
|
+
const settled = current.then(() => { }, () => { });
|
|
178
|
+
queues.set(key, settled);
|
|
179
|
+
try {
|
|
180
|
+
return await current;
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
if (queues.get(key) === settled)
|
|
184
|
+
queues.delete(key);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function getChunkTransportOptions(view, index, chunkCount, replyToMessageId, editing = false) {
|
|
189
|
+
const isFirst = index === 0;
|
|
190
|
+
const isLast = index === chunkCount - 1;
|
|
191
|
+
return {
|
|
192
|
+
...(isFirst && replyToMessageId !== undefined ? { replyToMessageId } : {}),
|
|
193
|
+
...(isLast
|
|
194
|
+
? { replyMarkup: view.replyMarkup ?? (editing ? null : undefined) }
|
|
195
|
+
: editing
|
|
196
|
+
? { replyMarkup: null }
|
|
197
|
+
: {}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/** @internal */
|
|
201
|
+
export function createTelegramDeliveryRuntime(deps) {
|
|
202
|
+
let active = true;
|
|
203
|
+
const handleBindings = new WeakMap();
|
|
204
|
+
const runForTarget = createTelegramDeliveryTargetQueue();
|
|
205
|
+
const render = (view) => {
|
|
206
|
+
const chunks = deps.renderView(view);
|
|
207
|
+
if (chunks.length === 0 ||
|
|
208
|
+
chunks.some((chunk) => typeof chunk.text !== "string" || chunk.text.trim().length === 0)) {
|
|
209
|
+
return failure("invalid-view", "Telegram delivery view rendered no content.");
|
|
210
|
+
}
|
|
211
|
+
return { ok: true, value: chunks };
|
|
212
|
+
};
|
|
213
|
+
const inactive = () => failure("runtime-unavailable", "Telegram delivery runtime generation is inactive.");
|
|
214
|
+
const transportFailure = (operation, error, target, partial) => {
|
|
215
|
+
deps.recordFailure?.(operation, error, target);
|
|
216
|
+
if (error instanceof TelegramDeliveryTransportGenerationError) {
|
|
217
|
+
return inactive();
|
|
218
|
+
}
|
|
219
|
+
const classified = classifyTelegramDeliveryTransportError(error);
|
|
220
|
+
return failure(classified.reason, classified.reason === "commit-unknown"
|
|
221
|
+
? `Telegram delivery ${operation} may have committed before transport failed.`
|
|
222
|
+
: `Telegram delivery ${operation} failed.`, partial, classified.retryAfterMs);
|
|
223
|
+
};
|
|
224
|
+
const createHandle = (target, messageIds) => {
|
|
225
|
+
const canonicalTarget = Object.freeze(cloneTarget(target));
|
|
226
|
+
const canonicalMessageIds = Object.freeze([...messageIds]);
|
|
227
|
+
const handle = Object.freeze({
|
|
228
|
+
target: canonicalTarget,
|
|
229
|
+
messageIds: canonicalMessageIds,
|
|
230
|
+
generation: deps.generation,
|
|
231
|
+
});
|
|
232
|
+
handleBindings.set(handle, {
|
|
233
|
+
target: canonicalTarget,
|
|
234
|
+
messageIds: canonicalMessageIds,
|
|
235
|
+
});
|
|
236
|
+
return handle;
|
|
237
|
+
};
|
|
238
|
+
const resolveHandle = (handle) => {
|
|
239
|
+
if (!active)
|
|
240
|
+
return inactive();
|
|
241
|
+
const binding = handleBindings.get(handle);
|
|
242
|
+
if (!binding || handle.generation !== deps.generation) {
|
|
243
|
+
return failure("stale-handle", "Telegram delivery handle belongs to an inactive runtime generation.");
|
|
244
|
+
}
|
|
245
|
+
const authorized = resolveTelegramDeliveryTarget({ kind: "target", target: binding.target }, deps);
|
|
246
|
+
if (!authorized.ok) {
|
|
247
|
+
return failure(authorized.reason, authorized.message);
|
|
248
|
+
}
|
|
249
|
+
return { ok: true, value: binding };
|
|
250
|
+
};
|
|
251
|
+
return {
|
|
252
|
+
generation: deps.generation,
|
|
253
|
+
shutdown() {
|
|
254
|
+
active = false;
|
|
255
|
+
},
|
|
256
|
+
async sendView(view, options) {
|
|
257
|
+
if (!active)
|
|
258
|
+
return inactive();
|
|
259
|
+
const resolved = resolveTelegramDeliveryTarget(options.scope, deps);
|
|
260
|
+
if (!resolved.ok)
|
|
261
|
+
return failure(resolved.reason, resolved.message);
|
|
262
|
+
const rendered = render(view);
|
|
263
|
+
if (!rendered.ok)
|
|
264
|
+
return failure(rendered.reason, rendered.message);
|
|
265
|
+
const target = resolved.value;
|
|
266
|
+
return runForTarget(target, async () => {
|
|
267
|
+
if (!active)
|
|
268
|
+
return inactive();
|
|
269
|
+
const messageIds = [];
|
|
270
|
+
try {
|
|
271
|
+
for (const [index, chunk] of rendered.value.entries()) {
|
|
272
|
+
if (!active)
|
|
273
|
+
return inactive();
|
|
274
|
+
const messageId = await deps.sendChunk(target, chunk, getChunkTransportOptions(view, index, rendered.value.length, options.replyToMessageId));
|
|
275
|
+
messageIds.push(messageId);
|
|
276
|
+
if (!active)
|
|
277
|
+
return inactive();
|
|
278
|
+
}
|
|
279
|
+
return { ok: true, value: createHandle(target, messageIds) };
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
return active
|
|
283
|
+
? transportFailure("send", error, target, messageIds.length > 0
|
|
284
|
+
? createHandle(target, messageIds)
|
|
285
|
+
: undefined)
|
|
286
|
+
: inactive();
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
},
|
|
290
|
+
async editView(handle, view) {
|
|
291
|
+
const resolved = resolveHandle(handle);
|
|
292
|
+
if (!resolved.ok)
|
|
293
|
+
return failure(resolved.reason, resolved.message);
|
|
294
|
+
const rendered = render(view);
|
|
295
|
+
if (!rendered.ok)
|
|
296
|
+
return failure(rendered.reason, rendered.message);
|
|
297
|
+
const target = resolved.value.target;
|
|
298
|
+
return runForTarget(target, async () => {
|
|
299
|
+
if (!active)
|
|
300
|
+
return inactive();
|
|
301
|
+
const visibleMessageIds = [...resolved.value.messageIds];
|
|
302
|
+
try {
|
|
303
|
+
const sharedCount = Math.min(visibleMessageIds.length, rendered.value.length);
|
|
304
|
+
for (let index = 0; index < sharedCount; index += 1) {
|
|
305
|
+
if (!active)
|
|
306
|
+
return inactive();
|
|
307
|
+
await deps.editChunk(target, visibleMessageIds[index], rendered.value[index], getChunkTransportOptions(view, index, rendered.value.length, undefined, true));
|
|
308
|
+
if (!active)
|
|
309
|
+
return inactive();
|
|
310
|
+
}
|
|
311
|
+
for (let index = sharedCount; index < rendered.value.length; index += 1) {
|
|
312
|
+
if (!active)
|
|
313
|
+
return inactive();
|
|
314
|
+
visibleMessageIds.push(await deps.sendChunk(target, rendered.value[index], getChunkTransportOptions(view, index, rendered.value.length)));
|
|
315
|
+
if (!active)
|
|
316
|
+
return inactive();
|
|
317
|
+
}
|
|
318
|
+
const removedMessageIds = visibleMessageIds.slice(rendered.value.length);
|
|
319
|
+
for (const messageId of removedMessageIds) {
|
|
320
|
+
if (!active)
|
|
321
|
+
return inactive();
|
|
322
|
+
await deps.deleteMessage(target, messageId);
|
|
323
|
+
visibleMessageIds.splice(visibleMessageIds.indexOf(messageId), 1);
|
|
324
|
+
if (!active)
|
|
325
|
+
return inactive();
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
ok: true,
|
|
329
|
+
value: createHandle(target, visibleMessageIds),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
return active
|
|
334
|
+
? transportFailure("edit", error, target, createHandle(target, visibleMessageIds))
|
|
335
|
+
: inactive();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
},
|
|
339
|
+
async deleteView(handle) {
|
|
340
|
+
const resolved = resolveHandle(handle);
|
|
341
|
+
if (!resolved.ok)
|
|
342
|
+
return failure(resolved.reason, resolved.message);
|
|
343
|
+
const target = resolved.value.target;
|
|
344
|
+
return runForTarget(target, async () => {
|
|
345
|
+
if (!active)
|
|
346
|
+
return inactive();
|
|
347
|
+
try {
|
|
348
|
+
for (const messageId of resolved.value.messageIds) {
|
|
349
|
+
if (!active)
|
|
350
|
+
return inactive();
|
|
351
|
+
await deps.deleteMessage(target, messageId);
|
|
352
|
+
if (!active)
|
|
353
|
+
return inactive();
|
|
354
|
+
}
|
|
355
|
+
return { ok: true, value: undefined };
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
return active
|
|
359
|
+
? transportFailure("delete", error, target)
|
|
360
|
+
: inactive();
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
},
|
|
364
|
+
async sendChatAction(action, scope) {
|
|
365
|
+
if (!active)
|
|
366
|
+
return inactive();
|
|
367
|
+
const resolved = resolveTelegramDeliveryTarget(scope, deps);
|
|
368
|
+
if (!resolved.ok)
|
|
369
|
+
return failure(resolved.reason, resolved.message);
|
|
370
|
+
const target = resolved.value;
|
|
371
|
+
return runForTarget(target, async () => {
|
|
372
|
+
if (!active)
|
|
373
|
+
return inactive();
|
|
374
|
+
try {
|
|
375
|
+
await deps.sendChatAction(target, action);
|
|
376
|
+
if (!active)
|
|
377
|
+
return inactive();
|
|
378
|
+
return { ok: true, value: undefined };
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
return active
|
|
382
|
+
? transportFailure("chat-action", error, target)
|
|
383
|
+
: inactive();
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/** @internal */
|
|
390
|
+
export function createTelegramBridgeDeliveryRuntime(deps) {
|
|
391
|
+
const getPolicyView = deps.getTargetPolicyView;
|
|
392
|
+
const assertTransportActive = () => {
|
|
393
|
+
if (deps.isTransportActive?.() === false) {
|
|
394
|
+
throw new TelegramDeliveryTransportGenerationError();
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
return createTelegramDeliveryRuntime({
|
|
398
|
+
generation: deps.generation,
|
|
399
|
+
getActiveTurnTarget: deps.getActiveTurnTarget,
|
|
400
|
+
getInstanceTarget() {
|
|
401
|
+
return resolveTelegramDeliveryInstanceTarget(getPolicyView());
|
|
402
|
+
},
|
|
403
|
+
getAggregateTarget() {
|
|
404
|
+
return resolveTelegramDeliveryAggregateTarget(getPolicyView());
|
|
405
|
+
},
|
|
406
|
+
isExplicitTargetAuthorized(target) {
|
|
407
|
+
return isTelegramDeliveryExplicitTargetAuthorized(target, getPolicyView());
|
|
408
|
+
},
|
|
409
|
+
renderView(view) {
|
|
410
|
+
assertTelegramInlineKeyboardCallbackData(view.replyMarkup);
|
|
411
|
+
return renderTelegramMessage(view.text, {
|
|
412
|
+
mode: view.parseMode ?? "plain",
|
|
413
|
+
}).map(function (chunk) {
|
|
414
|
+
return {
|
|
415
|
+
text: chunk.text,
|
|
416
|
+
parseMode: chunk.parseMode === "HTML" ? "html" : "plain",
|
|
417
|
+
};
|
|
418
|
+
});
|
|
419
|
+
},
|
|
420
|
+
async sendChunk(target, chunk, options) {
|
|
421
|
+
assertTransportActive();
|
|
422
|
+
const sent = await withTelegramReplyParameters(target.chatId, options.replyToMessageId, target, (replyParameters) => {
|
|
423
|
+
const body = {
|
|
424
|
+
chat_id: target.chatId,
|
|
425
|
+
text: chunk.text,
|
|
426
|
+
...(chunk.parseMode === "html" ? { parse_mode: "HTML" } : {}),
|
|
427
|
+
...getTelegramTargetThreadParams(target),
|
|
428
|
+
...(replyParameters ? { reply_parameters: replyParameters } : {}),
|
|
429
|
+
...(options.replyMarkup ? { reply_markup: options.replyMarkup } : {}),
|
|
430
|
+
};
|
|
431
|
+
return deps.api.sendMessage(target.threadId === undefined
|
|
432
|
+
? markTelegramBusAggregateDelivery(body)
|
|
433
|
+
: body);
|
|
434
|
+
});
|
|
435
|
+
assertTransportActive();
|
|
436
|
+
deps.recordOwnership({
|
|
437
|
+
chatId: target.chatId,
|
|
438
|
+
messageId: sent.message_id,
|
|
439
|
+
target,
|
|
440
|
+
});
|
|
441
|
+
return sent.message_id;
|
|
442
|
+
},
|
|
443
|
+
async editChunk(target, messageId, chunk, options) {
|
|
444
|
+
assertTransportActive();
|
|
445
|
+
await deps.api.editMessageText({
|
|
446
|
+
chat_id: target.chatId,
|
|
447
|
+
message_id: messageId,
|
|
448
|
+
text: chunk.text,
|
|
449
|
+
...(chunk.parseMode === "html" ? { parse_mode: "HTML" } : {}),
|
|
450
|
+
reply_markup: options.replyMarkup === null
|
|
451
|
+
? { inline_keyboard: [] }
|
|
452
|
+
: options.replyMarkup,
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
deleteMessage(target, messageId) {
|
|
456
|
+
assertTransportActive();
|
|
457
|
+
return deps.api.deleteMessage(target.chatId, messageId);
|
|
458
|
+
},
|
|
459
|
+
async sendChatAction(target, action) {
|
|
460
|
+
assertTransportActive();
|
|
461
|
+
await deps.api.sendChatAction(target.chatId, action, {
|
|
462
|
+
message_thread_id: target.threadId,
|
|
463
|
+
});
|
|
464
|
+
},
|
|
465
|
+
recordFailure: deps.recordFailure,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
function getBoundTelegramDeliveryRuntime() {
|
|
469
|
+
const runtime = getTelegramDeliveryRuntimeRegistry().runtime;
|
|
470
|
+
return (runtime ??
|
|
471
|
+
failure("runtime-unavailable", "Telegram delivery runtime is unavailable in this Pi session."));
|
|
472
|
+
}
|
|
473
|
+
function isFailure(value) {
|
|
474
|
+
return "ok" in value;
|
|
475
|
+
}
|
|
476
|
+
function validateView(view) {
|
|
477
|
+
if (typeof view.text !== "string" || view.text.trim().length === 0) {
|
|
478
|
+
return failure("invalid-view", "Telegram delivery view text is empty.");
|
|
479
|
+
}
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
async function runDeliveryOperation(operation) {
|
|
483
|
+
const runtime = getBoundTelegramDeliveryRuntime();
|
|
484
|
+
if (isFailure(runtime))
|
|
485
|
+
return runtime;
|
|
486
|
+
try {
|
|
487
|
+
return await operation(runtime);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return failure("transport-failed", "Telegram delivery failed.");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/** @internal */
|
|
494
|
+
export function bindTelegramDeliveryRuntime(runtime) {
|
|
495
|
+
const registry = getTelegramDeliveryRuntimeRegistry();
|
|
496
|
+
if (registry.runtime !== runtime)
|
|
497
|
+
registry.runtime?.shutdown();
|
|
498
|
+
registry.runtime = runtime;
|
|
499
|
+
return () => {
|
|
500
|
+
if (registry.runtime === runtime)
|
|
501
|
+
registry.runtime = undefined;
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
/** @internal */
|
|
505
|
+
export function clearTelegramDeliveryRuntime() {
|
|
506
|
+
const registry = getTelegramDeliveryRuntimeRegistry();
|
|
507
|
+
registry.runtime?.shutdown();
|
|
508
|
+
registry.runtime = undefined;
|
|
509
|
+
}
|
|
510
|
+
/** @internal */
|
|
511
|
+
export function isTelegramDeliveryHandleCurrent(handle) {
|
|
512
|
+
const runtime = getTelegramDeliveryRuntimeRegistry().runtime;
|
|
513
|
+
return runtime !== undefined && runtime.generation === handle.generation;
|
|
514
|
+
}
|
|
515
|
+
export async function sendTelegramView(view, options) {
|
|
516
|
+
const invalid = validateView(view);
|
|
517
|
+
if (invalid)
|
|
518
|
+
return invalid;
|
|
519
|
+
return runDeliveryOperation((runtime) => runtime.sendView(view, options));
|
|
520
|
+
}
|
|
521
|
+
export async function editTelegramView(handle, view) {
|
|
522
|
+
const invalid = validateView(view);
|
|
523
|
+
if (invalid)
|
|
524
|
+
return invalid;
|
|
525
|
+
return runDeliveryOperation((runtime) => {
|
|
526
|
+
if (runtime.generation !== handle.generation) {
|
|
527
|
+
return Promise.resolve(failure("stale-handle", "Telegram delivery handle belongs to an inactive runtime generation."));
|
|
528
|
+
}
|
|
529
|
+
return runtime.editView(handle, view);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
export async function deleteTelegramView(handle) {
|
|
533
|
+
return runDeliveryOperation((runtime) => {
|
|
534
|
+
if (runtime.generation !== handle.generation) {
|
|
535
|
+
return Promise.resolve(failure("stale-handle", "Telegram delivery handle belongs to an inactive runtime generation."));
|
|
536
|
+
}
|
|
537
|
+
return runtime.deleteView(handle);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
export async function sendTelegramChatAction(action, options) {
|
|
541
|
+
return runDeliveryOperation((runtime) => runtime.sendChatAction(action, options.scope));
|
|
542
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram bridge extension composition and orchestration layer
|
|
3
|
+
* Zones: telegram, pi agent, orchestration
|
|
4
|
+
* Keeps runtime wiring in one place while the package entrypoint remains a thin re-export
|
|
5
|
+
*/
|
|
6
|
+
import * as Pi from "./pi.ts";
|
|
7
|
+
export default function (pi: Pi.ExtensionAPI): void;
|