@llblab/pi-telegram 0.52.2 → 0.53.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/AGENTS.md +9 -7
- package/BACKLOG.md +1 -18
- package/CHANGELOG.md +11 -0
- package/README.md +7 -11
- package/api/commands.ts +3 -0
- package/dist/api/commands.d.ts +1 -1
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +15 -42
- package/dist/lib/activity.d.ts +9 -0
- package/dist/lib/activity.js +39 -10
- package/dist/lib/agent-messages.js +7 -3
- package/dist/lib/bindings.d.ts +43 -5
- package/dist/lib/bindings.js +163 -20
- package/dist/lib/bus-api.d.ts +12 -0
- package/dist/lib/bus-api.js +182 -79
- package/dist/lib/bus-follower.d.ts +111 -31
- package/dist/lib/bus-follower.js +1324 -359
- package/dist/lib/bus-leader.d.ts +41 -91
- package/dist/lib/bus-leader.js +969 -637
- package/dist/lib/bus-transport.js +16 -9
- package/dist/lib/bus.d.ts +215 -18
- package/dist/lib/bus.js +1048 -327
- package/dist/lib/channel-posts.d.ts +5 -0
- package/dist/lib/channel-posts.js +434 -261
- package/dist/lib/command-templates.d.ts +26 -5
- package/dist/lib/command-templates.js +92 -176
- package/dist/lib/commands.d.ts +168 -82
- package/dist/lib/commands.js +1409 -291
- package/dist/lib/config.d.ts +1 -8
- package/dist/lib/config.js +34 -76
- package/dist/lib/delivery.d.ts +2 -2
- package/dist/lib/delivery.js +16 -16
- package/dist/lib/extension.js +639 -171
- package/dist/lib/generative-app-worker.mjs +19 -7
- package/dist/lib/generative-apps.d.ts +0 -5
- package/dist/lib/generative-apps.js +61 -49
- package/dist/lib/inbound.d.ts +5 -0
- package/dist/lib/inbound.js +35 -59
- package/dist/lib/journal.d.ts +33 -3
- package/dist/lib/journal.js +2063 -782
- package/dist/lib/lifecycle.d.ts +2 -6
- package/dist/lib/lifecycle.js +75 -58
- package/dist/lib/locks.d.ts +29 -3
- package/dist/lib/locks.js +289 -108
- package/dist/lib/logging.js +50 -14
- package/dist/lib/media.d.ts +22 -1
- package/dist/lib/media.js +46 -28
- package/dist/lib/menu-model.d.ts +19 -5
- package/dist/lib/menu-model.js +53 -11
- package/dist/lib/menu-queue.d.ts +5 -4
- package/dist/lib/menu-queue.js +45 -63
- package/dist/lib/menu-settings.d.ts +5 -4
- package/dist/lib/menu-settings.js +114 -78
- package/dist/lib/menu-status.d.ts +1 -1
- package/dist/lib/menu-status.js +8 -25
- package/dist/lib/menu-thinking.d.ts +6 -1
- package/dist/lib/menu-thinking.js +14 -21
- package/dist/lib/menu.d.ts +8 -61
- package/dist/lib/menu.js +92 -164
- package/dist/lib/model.d.ts +1 -1
- package/dist/lib/model.js +2 -16
- package/dist/lib/outbound-attachments.d.ts +6 -27
- package/dist/lib/outbound-attachments.js +45 -38
- package/dist/lib/outbound-buttons.js +22 -16
- package/dist/lib/outbound-markup.d.ts +0 -2
- package/dist/lib/outbound-markup.js +51 -64
- package/dist/lib/outbound-voice.js +6 -3
- package/dist/lib/outbound.d.ts +6 -6
- package/dist/lib/outbound.js +61 -51
- package/dist/lib/ownership.js +6 -2
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +37 -21
- package/dist/lib/pi.d.ts +2 -0
- package/dist/lib/pi.js +8 -1
- package/dist/lib/polling.d.ts +21 -37
- package/dist/lib/polling.js +59 -50
- package/dist/lib/preview.js +39 -19
- package/dist/lib/process-identity.d.ts +24 -0
- package/dist/lib/process-identity.js +133 -0
- package/dist/lib/prompts.js +2 -7
- package/dist/lib/queue.d.ts +14 -16
- package/dist/lib/queue.js +249 -126
- package/dist/lib/recovery.d.ts +2 -83
- package/dist/lib/recovery.js +20 -274
- package/dist/lib/replies.d.ts +16 -24
- package/dist/lib/replies.js +99 -116
- package/dist/lib/routing.d.ts +120 -7
- package/dist/lib/routing.js +4667 -1152
- package/dist/lib/runtime.d.ts +1 -11
- package/dist/lib/sections.d.ts +4 -29
- package/dist/lib/sections.js +7 -20
- package/dist/lib/skills.js +4 -3
- package/dist/lib/status.js +86 -57
- package/dist/lib/sync.d.ts +1 -3
- package/dist/lib/sync.js +67 -43
- package/dist/lib/target.d.ts +4 -0
- package/dist/lib/target.js +18 -0
- package/dist/lib/telegram-api.d.ts +18 -13
- package/dist/lib/telegram-api.js +148 -53
- package/dist/lib/text-groups.js +4 -39
- package/dist/lib/thread-cleanup-manager.d.ts +6 -27
- package/dist/lib/thread-cleanup-manager.js +369 -186
- package/dist/lib/thread-display.d.ts +1 -2
- package/dist/lib/thread-display.js +58 -38
- package/dist/lib/thread-naming.d.ts +28 -8
- package/dist/lib/thread-naming.js +175 -66
- package/dist/lib/thread-reconciler.d.ts +42 -16
- package/dist/lib/thread-reconciler.js +90 -50
- package/dist/lib/threads.d.ts +82 -20
- package/dist/lib/threads.js +2247 -855
- package/dist/lib/time-injection.js +1 -2
- package/dist/lib/turns.js +1 -19
- package/dist/lib/updates.d.ts +169 -80
- package/dist/lib/updates.js +2418 -641
- package/dist/lib/voice.d.ts +7 -22
- package/dist/lib/voice.js +7 -22
- package/dist/lib/wire.js +1 -1
- package/dist/lib/workspace-admission.d.ts +3 -1
- package/dist/lib/workspace-admission.js +425 -211
- package/dist/lib/workspace-identity.js +8 -13
- package/dist/lib/workspace-retirement.d.ts +1 -1
- package/dist/lib/workspace-retirement.js +328 -140
- package/dist/lib/workspace-slots.js +8 -4
- package/dist/package.json +1 -1
- package/docs/architecture.md +31 -29
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +15 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +29 -1
- package/docs/ui-style.md +25 -8
- package/lib/activity-verbosity.ts +59 -82
- package/lib/activity.ts +146 -102
- package/lib/agent-messages.ts +12 -5
- package/lib/bindings.ts +389 -82
- package/lib/bus-api.ts +263 -102
- package/lib/bus-follower.ts +2440 -601
- package/lib/bus-leader.ts +2209 -1290
- package/lib/bus-transport.ts +46 -12
- package/lib/bus.ts +1673 -444
- package/lib/channel-posts.ts +848 -327
- package/lib/command-templates.ts +152 -265
- package/lib/commands.ts +2398 -555
- package/lib/config.ts +177 -170
- package/lib/delivery.ts +41 -29
- package/lib/extension.ts +1206 -433
- package/lib/generative-app-worker.mjs +19 -7
- package/lib/generative-apps.ts +324 -211
- package/lib/inbound.ts +95 -97
- package/lib/journal.ts +5311 -1763
- package/lib/keyboard.ts +1 -3
- package/lib/lifecycle.ts +128 -78
- package/lib/locks.ts +677 -203
- package/lib/logging.ts +147 -58
- package/lib/media.ts +82 -43
- package/lib/menu-model.ts +85 -18
- package/lib/menu-queue.ts +88 -129
- package/lib/menu-settings.ts +260 -155
- package/lib/menu-status.ts +20 -60
- package/lib/menu-thinking.ts +41 -56
- package/lib/menu.ts +112 -285
- package/lib/model.ts +8 -22
- package/lib/outbound-attachments.ts +156 -125
- package/lib/outbound-buttons.ts +76 -49
- package/lib/outbound-markup.ts +96 -94
- package/lib/outbound-voice.ts +33 -27
- package/lib/outbound.ts +125 -125
- package/lib/ownership.ts +19 -10
- package/lib/paths.ts +148 -49
- package/lib/pi.ts +35 -15
- package/lib/polling.ts +144 -147
- package/lib/preview.ts +93 -40
- package/lib/process-identity.ts +164 -0
- package/lib/prompts.ts +4 -11
- package/lib/queue.ts +511 -300
- package/lib/recovery.ts +50 -454
- package/lib/rendering.ts +1 -3
- package/lib/replies.ts +248 -209
- package/lib/routing.ts +8509 -2186
- package/lib/runtime.ts +1 -12
- package/lib/sections.ts +24 -89
- package/lib/setup.ts +1 -3
- package/lib/skills.ts +9 -4
- package/lib/status.ts +215 -110
- package/lib/sync.ts +448 -361
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +302 -127
- package/lib/text-groups.ts +5 -39
- package/lib/thread-cleanup-manager.ts +833 -331
- package/lib/thread-display.ts +213 -97
- package/lib/thread-naming.ts +232 -87
- package/lib/thread-reconciler.ts +155 -72
- package/lib/threads.ts +5318 -1845
- package/lib/time-injection.ts +1 -4
- package/lib/turns.ts +15 -28
- package/lib/updates.ts +5184 -1612
- package/lib/voice.ts +7 -35
- package/lib/wire.ts +1 -1
- package/lib/workspace-admission.ts +996 -416
- package/lib/workspace-identity.ts +14 -17
- package/lib/workspace-retirement.ts +878 -376
- package/lib/workspace-slots.ts +22 -13
- package/package.json +1 -1
- package/scripts/check-downgrade.mjs +0 -472
- package/scripts/measure-workspace.mjs +0 -108
package/dist/lib/logging.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Zones: telegram diagnostics, filesystem, session observability
|
|
4
4
|
* Owns bounded JSONL runtime evidence files, previous-log preservation, and profile-aware log paths without becoming routing state
|
|
5
5
|
*/
|
|
6
|
-
import { copyFileSync, existsSync, mkdirSync, statSync, writeFileSync,
|
|
6
|
+
import { appendFileSync, copyFileSync, existsSync, mkdirSync, statSync, writeFileSync, } from "node:fs";
|
|
7
7
|
import { basename, dirname, join } from "node:path";
|
|
8
|
-
import { resolveAgentDir, resolveTelegramProfileTempFilePath, resolveTelegramRuntimeLogPath, resolveTelegramPreviousSharedRuntimeLogPath, } from "./paths.js";
|
|
9
8
|
import { withTelegramFileTransaction } from "./locks.js";
|
|
9
|
+
import { resolveAgentDir, resolveTelegramPreviousSharedRuntimeLogPath, resolveTelegramProfileTempFilePath, resolveTelegramRuntimeLogPath, } from "./paths.js";
|
|
10
10
|
import * as Status from "./status.js";
|
|
11
11
|
const DEFAULT_MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
12
12
|
export function getTelegramRuntimeLogPath(agentDir = resolveAgentDir(), profileName) {
|
|
@@ -27,7 +27,9 @@ function safeJsonLine(value) {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
export function createTelegramRuntimeJsonlLog(options = {}) {
|
|
30
|
-
const shared = options.sharedProfiles
|
|
30
|
+
const shared = options.sharedProfiles
|
|
31
|
+
? { ...options.sharedProfiles }
|
|
32
|
+
: undefined;
|
|
31
33
|
const pathSource = options.path, previousSource = options.previousPath;
|
|
32
34
|
const canReset = options.canReset, commitReset = options.commitReset;
|
|
33
35
|
const resolvePath = () => typeof pathSource === "function"
|
|
@@ -38,7 +40,8 @@ export function createTelegramRuntimeJsonlLog(options = {}) {
|
|
|
38
40
|
return previousSource();
|
|
39
41
|
if (previousSource)
|
|
40
42
|
return previousSource;
|
|
41
|
-
return shared
|
|
43
|
+
return shared
|
|
44
|
+
? join(dirname(resolvePath()), "logs", `${basename(resolvePath(), ".jsonl")}._prev.jsonl`)
|
|
42
45
|
: resolvePath().replace(/\.jsonl$/u, "._prev.jsonl");
|
|
43
46
|
};
|
|
44
47
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_LOG_BYTES;
|
|
@@ -48,12 +51,17 @@ export function createTelegramRuntimeJsonlLog(options = {}) {
|
|
|
48
51
|
let appendScheduled = false;
|
|
49
52
|
let queuedAppends = [];
|
|
50
53
|
const getProfile = () => shared?.getProfileName() ?? "default";
|
|
51
|
-
const transactionPath = (path) => shared
|
|
54
|
+
const transactionPath = (path) => shared
|
|
55
|
+
? join(dirname(path), "runtime", `${basename(path)}.transaction`)
|
|
56
|
+
: `${path}.transaction`;
|
|
52
57
|
const captureRotation = (path, previousPath, profile) => {
|
|
53
58
|
if (!shared)
|
|
54
59
|
return undefined;
|
|
55
60
|
const authority = shared.captureAuthority();
|
|
56
|
-
return () => authority?.() === true &&
|
|
61
|
+
return () => authority?.() === true &&
|
|
62
|
+
resolvePath() === path &&
|
|
63
|
+
resolvePreviousPath() === previousPath &&
|
|
64
|
+
getProfile() === profile;
|
|
57
65
|
};
|
|
58
66
|
const scopeStorageKey = (path, profile) => shared ? JSON.stringify([path, profile]) : path;
|
|
59
67
|
const ensureParent = (path) => {
|
|
@@ -94,11 +102,18 @@ export function createTelegramRuntimeJsonlLog(options = {}) {
|
|
|
94
102
|
if (shared && !isCurrent?.())
|
|
95
103
|
return;
|
|
96
104
|
if (shared) {
|
|
97
|
-
const line = safeJsonLine({
|
|
105
|
+
const line = safeJsonLine({
|
|
106
|
+
at: getNowMs(),
|
|
107
|
+
kind: "reset",
|
|
108
|
+
reason,
|
|
109
|
+
scope,
|
|
110
|
+
profile,
|
|
111
|
+
}) + "\n";
|
|
98
112
|
if (!isCurrent?.())
|
|
99
113
|
return;
|
|
100
114
|
ensureParent(path);
|
|
101
|
-
if (existsSync(path) &&
|
|
115
|
+
if (existsSync(path) &&
|
|
116
|
+
statSync(path).size + Buffer.byteLength(line) > maxBytes &&
|
|
102
117
|
!writeResetLocked(path, previousPath, "max-bytes", { maxBytes }, profile, isCurrent))
|
|
103
118
|
return;
|
|
104
119
|
if (!isCurrent?.())
|
|
@@ -121,8 +136,17 @@ export function createTelegramRuntimeJsonlLog(options = {}) {
|
|
|
121
136
|
const appendEvent = (event) => {
|
|
122
137
|
const path = resolvePath(), previousPath = resolvePreviousPath(), profile = getProfile();
|
|
123
138
|
const rotationCurrent = captureRotation(path, previousPath, profile);
|
|
124
|
-
const line = safeJsonLine({
|
|
125
|
-
|
|
139
|
+
const line = safeJsonLine({
|
|
140
|
+
kind: "event",
|
|
141
|
+
...event,
|
|
142
|
+
...(shared ? { profile } : {}),
|
|
143
|
+
}) + "\n";
|
|
144
|
+
queuedAppends.push({
|
|
145
|
+
path,
|
|
146
|
+
previousPath,
|
|
147
|
+
line,
|
|
148
|
+
...(shared ? { profile, rotationCurrent } : {}),
|
|
149
|
+
});
|
|
126
150
|
if (appendScheduled)
|
|
127
151
|
return;
|
|
128
152
|
appendScheduled = true;
|
|
@@ -244,10 +268,20 @@ export function createTelegramRuntimeDiagnosticsRuntime(options = {}) {
|
|
|
244
268
|
getBotToken: () => getBotToken(),
|
|
245
269
|
});
|
|
246
270
|
const jsonl = createTelegramRuntimeJsonlLog({
|
|
247
|
-
path: () => sharedFile
|
|
248
|
-
|
|
271
|
+
path: () => sharedFile
|
|
272
|
+
? resolveTelegramRuntimeLogPath()
|
|
273
|
+
: getTelegramRuntimeLogPath(undefined, getProfileName()),
|
|
274
|
+
previousPath: () => sharedFile
|
|
275
|
+
? resolveTelegramPreviousSharedRuntimeLogPath()
|
|
249
276
|
: getTelegramPreviousRuntimeLogPath(undefined, getProfileName()),
|
|
250
|
-
...(sharedFile
|
|
277
|
+
...(sharedFile
|
|
278
|
+
? {
|
|
279
|
+
sharedProfiles: {
|
|
280
|
+
getProfileName: () => getProfileName(),
|
|
281
|
+
captureAuthority: () => captureAuthority(),
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
: {}),
|
|
251
285
|
canReset: () => canReset(),
|
|
252
286
|
commitReset: (commit) => commitReset(commit),
|
|
253
287
|
});
|
|
@@ -283,7 +317,9 @@ export function createTelegramRuntimeDiagnosticsRuntime(options = {}) {
|
|
|
283
317
|
await ports.persistSnapshot(snapshot);
|
|
284
318
|
},
|
|
285
319
|
recordError(error) {
|
|
286
|
-
events.record("telegram", error, {
|
|
320
|
+
events.record("telegram", error, {
|
|
321
|
+
phase: "runtime-diagnostics-snapshot-persist",
|
|
322
|
+
});
|
|
287
323
|
},
|
|
288
324
|
});
|
|
289
325
|
const updateRuntimeLogScope = function (reason) {
|
package/dist/lib/media.d.ts
CHANGED
|
@@ -180,7 +180,6 @@ export declare function guessMediaType(path: string): string | undefined;
|
|
|
180
180
|
export declare function extractTelegramMessageText(message: TelegramMediaMessage): string;
|
|
181
181
|
export declare function extractTelegramForwardContextText(message: TelegramMediaMessage, allowedUserId?: number): string;
|
|
182
182
|
export declare function buildTelegramReplyContextBlock(message: TelegramMediaMessage, replyFiles?: Pick<DownloadedTelegramFile, "path">[], replyOutputs?: readonly string[]): string;
|
|
183
|
-
export declare function extractTelegramMessagePromptText(message: TelegramMediaMessage): string;
|
|
184
183
|
export declare function extractTelegramMessagesText(messages: TelegramMediaMessage[]): string;
|
|
185
184
|
export declare function extractTelegramMessagesPromptText(messages: TelegramMediaMessage[]): string;
|
|
186
185
|
export declare function extractFirstTelegramMessageText(messages: TelegramMediaMessage[]): string;
|
|
@@ -197,8 +196,30 @@ export declare function queueTelegramMediaGroupMessage<TMessage extends Telegram
|
|
|
197
196
|
clearTimer: (timer: ReturnType<typeof setTimeout>) => void;
|
|
198
197
|
dispatchMessages: (messages: TMessage[], ctx?: TContext) => unknown | Promise<unknown>;
|
|
199
198
|
}): boolean;
|
|
199
|
+
/** Timer and dispatch lifecycle shared by delayed media-group and text-group coalescing. */
|
|
200
|
+
export interface TelegramPendingGroupState<TMessage, TContext> {
|
|
201
|
+
messages: TMessage[];
|
|
202
|
+
context?: TContext;
|
|
203
|
+
flushTimer?: ReturnType<typeof setTimeout>;
|
|
204
|
+
dispatching?: boolean;
|
|
205
|
+
suspended?: boolean;
|
|
206
|
+
reschedule?: () => void;
|
|
207
|
+
dispatchNow?: () => Promise<void>;
|
|
208
|
+
}
|
|
209
|
+
export interface TelegramPendingGroupLifecycle<TContext> {
|
|
210
|
+
flushMessage: (messageId: number) => Promise<boolean>;
|
|
211
|
+
suspend: () => void;
|
|
212
|
+
resume: (context: TContext) => void;
|
|
213
|
+
clear: () => void;
|
|
214
|
+
}
|
|
215
|
+
export declare function createTelegramPendingGroupLifecycle<TMessage extends {
|
|
216
|
+
message_id: number;
|
|
217
|
+
}, TContext>(groups: Map<string, TelegramPendingGroupState<TMessage, TContext>>, clearTimer: (timer: ReturnType<typeof setTimeout>) => void): TelegramPendingGroupLifecycle<TContext>;
|
|
200
218
|
export declare function createTelegramMediaGroupController<TMessage extends TelegramMediaGroupMessage, TContext = unknown>(options?: TelegramMediaGroupControllerOptions): TelegramMediaGroupController<TMessage, TContext>;
|
|
201
219
|
export declare function createTelegramMediaGroupDispatchRuntime<TMessage extends TelegramMediaGroupMessage, TContext>(deps: TelegramMediaGroupDispatchRuntimeDeps<TMessage, TContext>): TelegramMediaGroupDispatchRuntime<TMessage, TContext>;
|
|
220
|
+
/** Pure `[title]` bullet section appended after a blank line; empty items leave text unchanged. */
|
|
221
|
+
export declare function appendTelegramListSection(text: string, title: string, items: string[]): string;
|
|
222
|
+
export declare function appendTelegramAttachmentSection(text: string, files: Pick<DownloadedTelegramFile, "path">[]): string;
|
|
202
223
|
export declare function formatTelegramHistoryText(rawText: string, files: DownloadedTelegramFile[], handlerOutputs?: string[]): string;
|
|
203
224
|
export declare function downloadTelegramMessageFiles(messages: TelegramMediaMessage[], deps: DownloadTelegramMessageFilesDeps): Promise<DownloadedTelegramMessageFile[]>;
|
|
204
225
|
export declare function collectTelegramFileInfos(messages: TelegramMediaMessage[]): TelegramFileInfo[];
|
package/dist/lib/media.js
CHANGED
|
@@ -200,9 +200,6 @@ function appendTelegramReplyContext(text, replyContext) {
|
|
|
200
200
|
return text;
|
|
201
201
|
return text ? `${text}\n\n${replyContext}` : `_\n\n${replyContext}`;
|
|
202
202
|
}
|
|
203
|
-
export function extractTelegramMessagePromptText(message) {
|
|
204
|
-
return appendTelegramReplyContext(extractTelegramMessageText(message), buildTelegramReplyContextBlock(message));
|
|
205
|
-
}
|
|
206
203
|
export function extractTelegramMessagesText(messages) {
|
|
207
204
|
return messages.map(extractTelegramMessageText).filter(Boolean).join("\n\n");
|
|
208
205
|
}
|
|
@@ -317,23 +314,8 @@ export function queueTelegramMediaGroupMessage(options) {
|
|
|
317
314
|
options.groups.set(key, existing);
|
|
318
315
|
return true;
|
|
319
316
|
}
|
|
320
|
-
export function
|
|
321
|
-
const groups = new Map();
|
|
322
|
-
const debounceMs = options.debounceMs ?? TELEGRAM_MEDIA_GROUP_DEBOUNCE_MS;
|
|
323
|
-
const setTimer = options.setTimer ??
|
|
324
|
-
((callback, ms) => setTimeout(callback, ms));
|
|
325
|
-
const clearTimer = options.clearTimer ?? clearTimeout;
|
|
317
|
+
export function createTelegramPendingGroupLifecycle(groups, clearTimer) {
|
|
326
318
|
return {
|
|
327
|
-
queueMessage: ({ message, context, dispatchMessages }) => queueTelegramMediaGroupMessage({
|
|
328
|
-
message,
|
|
329
|
-
context,
|
|
330
|
-
groups,
|
|
331
|
-
debounceMs,
|
|
332
|
-
setTimer,
|
|
333
|
-
clearTimer,
|
|
334
|
-
dispatchMessages,
|
|
335
|
-
}),
|
|
336
|
-
removeMessages: (messageIds) => removePendingTelegramMediaGroupMessages(groups, messageIds, clearTimer),
|
|
337
319
|
async flushMessage(messageId) {
|
|
338
320
|
for (const state of groups.values()) {
|
|
339
321
|
if (!state.messages.some((message) => message.message_id === messageId)) {
|
|
@@ -376,6 +358,26 @@ export function createTelegramMediaGroupController(options = {}) {
|
|
|
376
358
|
},
|
|
377
359
|
};
|
|
378
360
|
}
|
|
361
|
+
export function createTelegramMediaGroupController(options = {}) {
|
|
362
|
+
const groups = new Map();
|
|
363
|
+
const debounceMs = options.debounceMs ?? TELEGRAM_MEDIA_GROUP_DEBOUNCE_MS;
|
|
364
|
+
const setTimer = options.setTimer ??
|
|
365
|
+
((callback, ms) => setTimeout(callback, ms));
|
|
366
|
+
const clearTimer = options.clearTimer ?? clearTimeout;
|
|
367
|
+
return {
|
|
368
|
+
queueMessage: ({ message, context, dispatchMessages }) => queueTelegramMediaGroupMessage({
|
|
369
|
+
message,
|
|
370
|
+
context,
|
|
371
|
+
groups,
|
|
372
|
+
debounceMs,
|
|
373
|
+
setTimer,
|
|
374
|
+
clearTimer,
|
|
375
|
+
dispatchMessages,
|
|
376
|
+
}),
|
|
377
|
+
removeMessages: (messageIds) => removePendingTelegramMediaGroupMessages(groups, messageIds, clearTimer),
|
|
378
|
+
...createTelegramPendingGroupLifecycle(groups, clearTimer),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
379
381
|
export function createTelegramMediaGroupDispatchRuntime(deps) {
|
|
380
382
|
return {
|
|
381
383
|
handleMessage: async (message, ctx) => {
|
|
@@ -394,13 +396,14 @@ export function createTelegramMediaGroupDispatchRuntime(deps) {
|
|
|
394
396
|
},
|
|
395
397
|
};
|
|
396
398
|
}
|
|
397
|
-
|
|
399
|
+
/** Pure `[title]` bullet section appended after a blank line; empty items leave text unchanged. */
|
|
400
|
+
export function appendTelegramListSection(text, title, items) {
|
|
398
401
|
if (items.length === 0)
|
|
399
402
|
return text;
|
|
400
403
|
const prefix = text.length > 0 ? `${text}\n\n` : "";
|
|
401
404
|
return `${prefix}[${title}]\n${items.map((item) => `- ${item}`).join("\n")}`;
|
|
402
405
|
}
|
|
403
|
-
function appendTelegramAttachmentSection(text, files) {
|
|
406
|
+
export function appendTelegramAttachmentSection(text, files) {
|
|
404
407
|
if (files.length === 0)
|
|
405
408
|
return text;
|
|
406
409
|
const dirs = [...new Set(files.map((file) => dirname(file.path)))];
|
|
@@ -537,7 +540,9 @@ export function collectTelegramFileInfos(messages) {
|
|
|
537
540
|
files.push({
|
|
538
541
|
file_id: message.document.file_id,
|
|
539
542
|
fileName,
|
|
540
|
-
...(message.document.file_name
|
|
543
|
+
...(message.document.file_name
|
|
544
|
+
? { userFileName: message.document.file_name }
|
|
545
|
+
: {}),
|
|
541
546
|
mimeType: message.document.mime_type,
|
|
542
547
|
kind: "document",
|
|
543
548
|
isImage: isImageMimeType(message.document.mime_type),
|
|
@@ -549,7 +554,9 @@ export function collectTelegramFileInfos(messages) {
|
|
|
549
554
|
files.push({
|
|
550
555
|
file_id: message.video.file_id,
|
|
551
556
|
fileName,
|
|
552
|
-
...(message.video.file_name
|
|
557
|
+
...(message.video.file_name
|
|
558
|
+
? { userFileName: message.video.file_name }
|
|
559
|
+
: {}),
|
|
553
560
|
mimeType: message.video.mime_type,
|
|
554
561
|
kind: "video",
|
|
555
562
|
isImage: false,
|
|
@@ -562,7 +569,9 @@ export function collectTelegramFileInfos(messages) {
|
|
|
562
569
|
files.push({
|
|
563
570
|
file_id: message.audio.file_id,
|
|
564
571
|
fileName,
|
|
565
|
-
...(message.audio.file_name
|
|
572
|
+
...(message.audio.file_name
|
|
573
|
+
? { userFileName: message.audio.file_name }
|
|
574
|
+
: {}),
|
|
566
575
|
mimeType: message.audio.mime_type,
|
|
567
576
|
kind: "audio",
|
|
568
577
|
isImage: false,
|
|
@@ -584,7 +593,9 @@ export function collectTelegramFileInfos(messages) {
|
|
|
584
593
|
files.push({
|
|
585
594
|
file_id: message.animation.file_id,
|
|
586
595
|
fileName,
|
|
587
|
-
...(message.animation.file_name
|
|
596
|
+
...(message.animation.file_name
|
|
597
|
+
? { userFileName: message.animation.file_name }
|
|
598
|
+
: {}),
|
|
588
599
|
mimeType: message.animation.mime_type,
|
|
589
600
|
kind: "animation",
|
|
590
601
|
isImage: false,
|
|
@@ -598,16 +609,23 @@ export function collectTelegramFileInfos(messages) {
|
|
|
598
609
|
files.push({
|
|
599
610
|
file_id: message.sticker.file_id,
|
|
600
611
|
fileName: `sticker-${message.message_id}${extension}`,
|
|
601
|
-
mimeType: video
|
|
612
|
+
mimeType: video
|
|
613
|
+
? "video/webm"
|
|
614
|
+
: animated
|
|
615
|
+
? "application/x-tgsticker"
|
|
616
|
+
: "image/webp",
|
|
602
617
|
kind: "sticker",
|
|
603
618
|
isImage: !video && !animated,
|
|
604
619
|
});
|
|
605
620
|
}
|
|
606
621
|
for (const file of files.slice(firstOfMessage)) {
|
|
607
|
-
file.source = {
|
|
622
|
+
file.source = {
|
|
623
|
+
kind: file.kind,
|
|
624
|
+
messageId: message.message_id,
|
|
608
625
|
...(file.index !== undefined ? { index: file.index } : {}),
|
|
609
626
|
...(file.userFileName ? { userFileName: file.userFileName } : {}),
|
|
610
|
-
...(message.chat ? { chat: message.chat } : {})
|
|
627
|
+
...(message.chat ? { chat: message.chat } : {}),
|
|
628
|
+
};
|
|
611
629
|
}
|
|
612
630
|
}
|
|
613
631
|
const seenFileIds = new Set();
|
package/dist/lib/menu-model.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
7
7
|
import { type MenuModel, type ScopedTelegramModel, type TelegramModelSwitchContinuationSource, type ThinkingLevel } from "./model.ts";
|
|
8
|
+
import type { TelegramTarget } from "./target.ts";
|
|
9
|
+
import { type TelegramApiCallOptions } from "./telegram-api.ts";
|
|
8
10
|
export type TelegramModelScope = "all" | "scoped";
|
|
9
11
|
export interface TelegramModelMenuState<TModel extends MenuModel = MenuModel> {
|
|
10
12
|
chatId: number;
|
|
@@ -79,12 +81,20 @@ export type TelegramReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
|
79
81
|
export interface TelegramMenuMessageRuntimeDeps {
|
|
80
82
|
editInteractiveMessage: (chatId: number, messageId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramReplyMarkup) => Promise<void>;
|
|
81
83
|
sendInteractiveMessage: (chatId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramReplyMarkup, options?: {
|
|
82
|
-
target?:
|
|
83
|
-
|
|
84
|
-
threadId?: number;
|
|
85
|
-
};
|
|
86
|
-
}) => Promise<number | undefined>;
|
|
84
|
+
target?: TelegramTarget;
|
|
85
|
+
} & Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<number | undefined>;
|
|
87
86
|
}
|
|
87
|
+
/** Per-open delivery lifetime; never retain this guard in stored callback state. */
|
|
88
|
+
export declare function createTelegramMenuDelivery<TModel extends MenuModel>(target: TelegramTarget, options: Pick<TelegramApiCallOptions, "assertAuthority"> | undefined, deps: Pick<TelegramMenuMessageRuntimeDeps, "sendInteractiveMessage"> & {
|
|
89
|
+
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
|
|
90
|
+
}): {
|
|
91
|
+
assertCurrent: () => void;
|
|
92
|
+
assertAuthority: (() => void) | undefined;
|
|
93
|
+
sendInteractiveMessage: (chatId: number, text: string, mode: "html" | "markdown" | "plain", replyMarkup: TelegramInlineKeyboardMarkup, options?: ({
|
|
94
|
+
target?: TelegramTarget;
|
|
95
|
+
} & Pick<TelegramApiCallOptions, "assertAuthority">) | undefined) => Promise<number | undefined>;
|
|
96
|
+
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
|
|
97
|
+
};
|
|
88
98
|
export type TelegramModelMenuCallbackDeps<TModel extends MenuModel = MenuModel> = {
|
|
89
99
|
answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
|
|
90
100
|
updateModelMenuMessage: () => Promise<void>;
|
|
@@ -177,6 +187,10 @@ export interface TelegramModelMenuRuntime<TModel extends MenuModel = MenuModel>
|
|
|
177
187
|
export declare const TELEGRAM_MODEL_PAGE_SIZE = 6;
|
|
178
188
|
export declare const MODEL_MENU_TITLE = "<b>\uD83E\uDD16 Choose a model:</b>";
|
|
179
189
|
export declare const MODEL_PAGE_MENU_TITLE = "<b>Choose a page:</b>";
|
|
190
|
+
/** Apply the payload mode to state, then edit the existing menu message in place. */
|
|
191
|
+
export declare function editTelegramMenuMessage(state: TelegramModelMenuState, payload: TelegramMenuRenderPayload, deps: TelegramMenuMessageRuntimeDeps): Promise<void>;
|
|
192
|
+
/** Apply the payload mode to state, then send a fresh menu message to its exact chat/Thread. */
|
|
193
|
+
export declare function sendTelegramMenuMessage(state: TelegramModelMenuState, payload: TelegramMenuRenderPayload, deps: TelegramMenuMessageRuntimeDeps): Promise<number | undefined>;
|
|
180
194
|
export declare function formatScopedModelButtonText<TModel extends MenuModel = MenuModel>(entry: ScopedTelegramModel<TModel>, currentModel: TModel | undefined): string;
|
|
181
195
|
export declare function formatStatusButtonLabel(label: string, value: string): string;
|
|
182
196
|
export declare function getModelMenuItems<TModel extends MenuModel = MenuModel>(state: TelegramModelMenuState<TModel>): ScopedTelegramModel<TModel>[];
|
package/dist/lib/menu-model.js
CHANGED
|
@@ -4,9 +4,49 @@
|
|
|
4
4
|
* Owns model-menu state, scoped model pages, model callback planning, and model-menu message rendering
|
|
5
5
|
*/
|
|
6
6
|
import { getCanonicalModelId, modelsMatch, parseTelegramCliScopedModelPatterns, resolveScopedModelPatterns, sortScopedModels, } from "./model.js";
|
|
7
|
+
import { TelegramApiAuthorityError, } from "./telegram-api.js";
|
|
7
8
|
const TELEGRAM_MODEL_MENU_CACHE_TTL_MS = 5000;
|
|
8
9
|
const TELEGRAM_MODEL_MENU_STATE_TTL_MS = 10 * 60 * 1000;
|
|
9
10
|
const MAX_STORED_TELEGRAM_MODEL_MENUS = 50;
|
|
11
|
+
/** Per-open delivery lifetime; never retain this guard in stored callback state. */
|
|
12
|
+
export function createTelegramMenuDelivery(target, options, deps) {
|
|
13
|
+
const assertAuthority = options?.assertAuthority, capturedTarget = { ...target };
|
|
14
|
+
const sendInteractiveMessage = deps.sendInteractiveMessage, storeModelMenuState = deps.storeModelMenuState;
|
|
15
|
+
let requestIssued = false;
|
|
16
|
+
const assertCurrent = () => {
|
|
17
|
+
try {
|
|
18
|
+
assertAuthority?.();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new TelegramApiAuthorityError(requestIssued);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
assertCurrent();
|
|
25
|
+
return {
|
|
26
|
+
assertCurrent,
|
|
27
|
+
assertAuthority,
|
|
28
|
+
sendInteractiveMessage: async (...args) => {
|
|
29
|
+
assertCurrent();
|
|
30
|
+
const [chatId, text, mode, replyMarkup, sendOptions] = args;
|
|
31
|
+
const messageId = await sendInteractiveMessage(assertAuthority ? capturedTarget.chatId : chatId, text, mode, replyMarkup, assertAuthority
|
|
32
|
+
? { ...sendOptions, target: { ...capturedTarget }, assertAuthority }
|
|
33
|
+
: sendOptions);
|
|
34
|
+
requestIssued = true;
|
|
35
|
+
assertCurrent();
|
|
36
|
+
return messageId;
|
|
37
|
+
},
|
|
38
|
+
storeModelMenuState: (state) => {
|
|
39
|
+
assertCurrent();
|
|
40
|
+
if (assertAuthority &&
|
|
41
|
+
(state.chatId !== capturedTarget.chatId ||
|
|
42
|
+
state.threadId !== capturedTarget.threadId)) {
|
|
43
|
+
throw new TelegramApiAuthorityError(requestIssued);
|
|
44
|
+
}
|
|
45
|
+
storeModelMenuState(state);
|
|
46
|
+
assertCurrent();
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
10
50
|
export const TELEGRAM_MODEL_PAGE_SIZE = 6;
|
|
11
51
|
const TELEGRAM_MODEL_PAGE_PICKER_ROW_SIZE = 4;
|
|
12
52
|
export const MODEL_MENU_TITLE = "<b>🤖 Choose a model:</b>";
|
|
@@ -42,11 +82,13 @@ function applyTelegramMenuRenderPayload(state, payload) {
|
|
|
42
82
|
state.mode = payload.nextMode;
|
|
43
83
|
return payload;
|
|
44
84
|
}
|
|
45
|
-
|
|
85
|
+
/** Apply the payload mode to state, then edit the existing menu message in place. */
|
|
86
|
+
export async function editTelegramMenuMessage(state, payload, deps) {
|
|
46
87
|
const appliedPayload = applyTelegramMenuRenderPayload(state, payload);
|
|
47
88
|
await deps.editInteractiveMessage(state.chatId, state.messageId, appliedPayload.text, appliedPayload.mode, appliedPayload.replyMarkup);
|
|
48
89
|
}
|
|
49
|
-
|
|
90
|
+
/** Apply the payload mode to state, then send a fresh menu message to its exact chat/Thread. */
|
|
91
|
+
export function sendTelegramMenuMessage(state, payload, deps) {
|
|
50
92
|
const appliedPayload = applyTelegramMenuRenderPayload(state, payload);
|
|
51
93
|
return deps.sendInteractiveMessage(state.chatId, appliedPayload.text, appliedPayload.mode, appliedPayload.replyMarkup, state.threadId !== undefined
|
|
52
94
|
? { target: { chatId: state.chatId, threadId: state.threadId } }
|
|
@@ -366,7 +408,7 @@ export function buildTelegramModelCallbackPlan(params) {
|
|
|
366
408
|
if (action.action === "open") {
|
|
367
409
|
const detailResult = applyTelegramModelDetailSelection(params.state, action.value);
|
|
368
410
|
if (detailResult === "invalid") {
|
|
369
|
-
return { kind: "answer", text: "Selected model is no longer available
|
|
411
|
+
return { kind: "answer", text: "Selected model is no longer available" };
|
|
370
412
|
}
|
|
371
413
|
return { kind: "update-menu" };
|
|
372
414
|
}
|
|
@@ -376,12 +418,12 @@ export function buildTelegramModelCallbackPlan(params) {
|
|
|
376
418
|
if (params.state.canMutateScope === false) {
|
|
377
419
|
return {
|
|
378
420
|
kind: "answer",
|
|
379
|
-
text: "Model scope is controlled by CLI --models
|
|
421
|
+
text: "Model scope is controlled by CLI --models",
|
|
380
422
|
};
|
|
381
423
|
}
|
|
382
424
|
const selectionResult = getTelegramSelectedDetailModel(params.state);
|
|
383
425
|
if (selectionResult.kind !== "selected") {
|
|
384
|
-
return { kind: "answer", text: "Selected model is no longer available
|
|
426
|
+
return { kind: "answer", text: "Selected model is no longer available" };
|
|
385
427
|
}
|
|
386
428
|
const model = selectionResult.selection.model;
|
|
387
429
|
const enabled = action.action === "scope-toggle"
|
|
@@ -406,10 +448,10 @@ export function buildTelegramModelCallbackPlan(params) {
|
|
|
406
448
|
? getTelegramSelectedDetailModel(params.state)
|
|
407
449
|
: getTelegramModelSelection(params.state, action.value);
|
|
408
450
|
if (selectionResult.kind === "invalid") {
|
|
409
|
-
return { kind: "answer", text: "Invalid model selection
|
|
451
|
+
return { kind: "answer", text: "Invalid model selection" };
|
|
410
452
|
}
|
|
411
453
|
if (selectionResult.kind === "missing") {
|
|
412
|
-
return { kind: "answer", text: "Selected model is no longer available
|
|
454
|
+
return { kind: "answer", text: "Selected model is no longer available" };
|
|
413
455
|
}
|
|
414
456
|
const selection = selectionResult.selection;
|
|
415
457
|
if (modelsMatch(selection.model, params.activeModel)) {
|
|
@@ -428,7 +470,7 @@ export function buildTelegramModelCallbackPlan(params) {
|
|
|
428
470
|
if (!params.canRestartBusyRun) {
|
|
429
471
|
return {
|
|
430
472
|
kind: "answer",
|
|
431
|
-
text: "Pi is busy. Send /abort, /next, or /stop
|
|
473
|
+
text: "Pi is busy. Send /abort, /next, or /stop",
|
|
432
474
|
};
|
|
433
475
|
}
|
|
434
476
|
return {
|
|
@@ -481,7 +523,7 @@ export async function handleTelegramModelMenuCallbackAction(callbackQueryId, par
|
|
|
481
523
|
}
|
|
482
524
|
if (plan.kind === "persist-scope") {
|
|
483
525
|
if (!deps.persistScopedModelPatterns) {
|
|
484
|
-
await deps.answerCallbackQuery(callbackQueryId, "Scoped model persistence is unavailable
|
|
526
|
+
await deps.answerCallbackQuery(callbackQueryId, "Scoped model persistence is unavailable");
|
|
485
527
|
return true;
|
|
486
528
|
}
|
|
487
529
|
await deps.persistScopedModelPatterns(plan.patterns);
|
|
@@ -499,7 +541,7 @@ export async function handleTelegramModelMenuCallbackAction(callbackQueryId, par
|
|
|
499
541
|
}
|
|
500
542
|
const changed = await deps.setModel(plan.selection.model);
|
|
501
543
|
if (changed === false) {
|
|
502
|
-
await deps.answerCallbackQuery(callbackQueryId, "Model is not available
|
|
544
|
+
await deps.answerCallbackQuery(callbackQueryId, "Model is not available");
|
|
503
545
|
return true;
|
|
504
546
|
}
|
|
505
547
|
deps.setCurrentModel(plan.selection.model);
|
|
@@ -526,7 +568,7 @@ export async function handleTelegramModelMenuCallbackAction(callbackQueryId, par
|
|
|
526
568
|
const restarted = await deps.restartInterruptedTelegramTurn(plan.selection, continuationTurn);
|
|
527
569
|
await deps.updateModelMenuMessage();
|
|
528
570
|
if (!restarted) {
|
|
529
|
-
await deps.answerCallbackQuery(callbackQueryId, "Pi is busy. Send /abort, /next, or /stop
|
|
571
|
+
await deps.answerCallbackQuery(callbackQueryId, "Pi is busy. Send /abort, /next, or /stop");
|
|
530
572
|
return true;
|
|
531
573
|
}
|
|
532
574
|
}
|
package/dist/lib/menu-queue.d.ts
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Owns queue-menu rendering, queue item callbacks, and queue-menu runtime adapters while core queue mechanics stay in queue
|
|
5
5
|
*/
|
|
6
6
|
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
7
|
-
import type
|
|
7
|
+
import { type TelegramMenuMessageRuntimeDeps, type TelegramModelMenuState } from "./menu-model.ts";
|
|
8
8
|
import type { MenuModel } from "./model.ts";
|
|
9
9
|
import * as Queue from "./queue.ts";
|
|
10
|
+
import type { TelegramApiCallOptions } from "./telegram-api.ts";
|
|
10
11
|
type TelegramQueueMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
11
12
|
interface TelegramQueueMenuCallbackQuery {
|
|
12
13
|
id: string;
|
|
@@ -19,16 +20,16 @@ interface TelegramQueueMenuCallbackQuery {
|
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
interface TelegramQueueMenuRuntime<Context> {
|
|
22
|
-
openQueueMenu: (chatId: number, replyToMessageId: number, ctx: Context) => Promise<void>;
|
|
23
|
+
openQueueMenu: (chatId: number, replyToMessageId: number, ctx: Context, threadId?: number, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
|
|
23
24
|
handleCallbackQuery: (query: TelegramQueueMenuCallbackQuery, ctx: Context) => Promise<boolean>;
|
|
24
25
|
}
|
|
25
26
|
export declare function createTelegramQueueMenuRuntime<Context, TModel extends MenuModel = MenuModel>(deps: {
|
|
26
27
|
telegramQueueStore: Queue.TelegramQueueStateStore<Context>;
|
|
27
28
|
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
|
28
|
-
sendInteractiveMessage:
|
|
29
|
+
sendInteractiveMessage: TelegramMenuMessageRuntimeDeps["sendInteractiveMessage"];
|
|
29
30
|
editInteractiveMessage: (chatId: number, messageId: number, text: string, mode: "html", replyMarkup: TelegramQueueMenuReplyMarkup) => Promise<void>;
|
|
30
31
|
answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
|
|
31
|
-
getModelMenuState: (chatId: number, ctx: Context) => Promise<TelegramModelMenuState<TModel>>;
|
|
32
|
+
getModelMenuState: (chatId: number, ctx: Context, threadId?: number) => Promise<TelegramModelMenuState<TModel>>;
|
|
32
33
|
getStoredModelMenuState: (messageId: number | undefined, chatId?: number) => TelegramModelMenuState<TModel> | undefined;
|
|
33
34
|
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
|
|
34
35
|
updateStatusMessage: (state: TelegramModelMenuState<TModel>, ctx: Context) => Promise<void>;
|