@llblab/pi-kit 0.27.5 → 0.28.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 +3 -1
- package/CHANGELOG.md +9 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +6 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +26 -13
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -1
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +2 -1
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +25 -12
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +10 -8
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -9
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-telegram/LICENSE +21 -0
- package/node_modules/@llblab/pi-telegram/README.md +15 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +5 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +6 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +54 -56
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +206 -139
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +181 -25
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +4 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +28 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +52 -56
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +78 -246
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +1 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +23 -26
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +0 -23
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +15 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +0 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/config.js +12 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +0 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +2 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +255 -61
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -19
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +177 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +1115 -249
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +100 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +380 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +30 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +129 -72
- package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +28 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/media.js +26 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +0 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +8 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +0 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +18 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +4 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +12 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +1 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +1 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +4 -21
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +0 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +50 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +155 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +0 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +3 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +69 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +29 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +104 -33
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +0 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +73 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2206 -307
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +44 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/status.js +149 -27
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +5 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +34 -23
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +18 -21
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +32 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +191 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +291 -59
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +1647 -369
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +184 -28
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +1032 -114
- package/node_modules/@llblab/pi-telegram/dist/lib/wire.d.ts +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +21 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +25 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +95 -20
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.d.ts +20 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +103 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +43 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +148 -52
- package/node_modules/@llblab/pi-telegram/dist/package.json +6 -6
- package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +3 -3
- package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +152 -34
- package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/delivery.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +125 -19
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -1
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
- package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +5 -9
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +6 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +243 -224
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +178 -31
- package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +30 -8
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +110 -334
- package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +29 -29
- package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +15 -15
- package/node_modules/@llblab/pi-telegram/lib/config.ts +12 -17
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +2 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +257 -72
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +0 -22
- package/node_modules/@llblab/pi-telegram/lib/inbound.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +1182 -326
- package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +402 -23
- package/node_modules/@llblab/pi-telegram/lib/logging.ts +151 -101
- package/node_modules/@llblab/pi-telegram/lib/media.ts +51 -9
- package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +8 -8
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +18 -18
- package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +11 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +5 -1
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +1 -10
- package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +5 -24
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +177 -17
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +3 -3
- package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +67 -9
- package/node_modules/@llblab/pi-telegram/lib/recovery.ts +104 -44
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +1955 -375
- package/node_modules/@llblab/pi-telegram/lib/sections.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/status.ts +156 -36
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +48 -33
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +24 -21
- package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +267 -9
- package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +3 -1
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +1697 -488
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1072 -151
- package/node_modules/@llblab/pi-telegram/lib/wire.ts +28 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +97 -47
- package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +147 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +188 -89
- package/node_modules/@llblab/pi-telegram/package.json +6 -6
- package/node_modules/@llblab/pi-telegram/scripts/audit-exports.mjs +100 -0
- package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +80 -43
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +3 -3
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/package.json +3 -3
|
@@ -12,33 +12,40 @@ import {
|
|
|
12
12
|
writeFileSync,
|
|
13
13
|
appendFileSync,
|
|
14
14
|
} from "node:fs";
|
|
15
|
-
import { dirname } from "node:path";
|
|
15
|
+
import { basename, dirname, join } from "node:path";
|
|
16
16
|
import {
|
|
17
17
|
resolveAgentDir,
|
|
18
18
|
resolveTelegramProfileTempFilePath,
|
|
19
|
+
resolveTelegramRuntimeLogPath,
|
|
20
|
+
resolveTelegramPreviousSharedRuntimeLogPath,
|
|
19
21
|
} from "./paths.ts";
|
|
20
22
|
import { withTelegramFileTransaction } from "./locks.ts";
|
|
21
23
|
import * as Status from "./status.ts";
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
type TelegramLogPathInput = string | (() => string);
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
interface TelegramRuntimeJsonlEvent {
|
|
26
28
|
at: number;
|
|
27
29
|
category: string;
|
|
28
30
|
message: string;
|
|
29
31
|
details?: Record<string, unknown>;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
interface TelegramRuntimeJsonlLogOptions {
|
|
33
35
|
path?: TelegramLogPathInput;
|
|
34
36
|
previousPath?: TelegramLogPathInput;
|
|
35
37
|
maxBytes?: number;
|
|
36
38
|
getNowMs?: () => number;
|
|
37
39
|
canReset?: () => boolean;
|
|
38
40
|
commitReset?: (commit: () => void) => boolean;
|
|
41
|
+
/** Explicit shared-file protocol; profile-labelled scope reset is an append, never truncation. */
|
|
42
|
+
sharedProfiles?: {
|
|
43
|
+
getProfileName: () => string | undefined;
|
|
44
|
+
captureAuthority: () => (() => boolean) | undefined;
|
|
45
|
+
};
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
interface TelegramRuntimeJsonlLog {
|
|
42
49
|
getPath: () => string;
|
|
43
50
|
reset: (reason: string, scope?: Record<string, unknown>) => void;
|
|
44
51
|
resetIfScopeChanged: (
|
|
@@ -88,15 +95,18 @@ function safeJsonLine(value: unknown): string {
|
|
|
88
95
|
export function createTelegramRuntimeJsonlLog(
|
|
89
96
|
options: TelegramRuntimeJsonlLogOptions = {},
|
|
90
97
|
): TelegramRuntimeJsonlLog {
|
|
98
|
+
const shared = options.sharedProfiles ? { ...options.sharedProfiles } : undefined;
|
|
99
|
+
const pathSource = options.path, previousSource = options.previousPath;
|
|
100
|
+
const canReset = options.canReset, commitReset = options.commitReset;
|
|
91
101
|
const resolvePath = () =>
|
|
92
|
-
typeof
|
|
93
|
-
?
|
|
94
|
-
: (
|
|
102
|
+
typeof pathSource === "function"
|
|
103
|
+
? pathSource()
|
|
104
|
+
: (pathSource ?? getTelegramRuntimeLogPath());
|
|
95
105
|
const resolvePreviousPath = () => {
|
|
96
|
-
if (typeof
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
if (typeof previousSource === "function") return previousSource();
|
|
107
|
+
if (previousSource) return previousSource;
|
|
108
|
+
return shared ? join(dirname(resolvePath()), "logs", `${basename(resolvePath(), ".jsonl")}._prev.jsonl`)
|
|
109
|
+
: resolvePath().replace(/\.jsonl$/u, "._prev.jsonl");
|
|
100
110
|
};
|
|
101
111
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_LOG_BYTES;
|
|
102
112
|
const getNowMs = options.getNowMs ?? Date.now;
|
|
@@ -107,7 +117,17 @@ export function createTelegramRuntimeJsonlLog(
|
|
|
107
117
|
path: string;
|
|
108
118
|
previousPath: string;
|
|
109
119
|
line: string;
|
|
120
|
+
profile?: string;
|
|
121
|
+
rotationCurrent?: () => boolean;
|
|
110
122
|
}[] = [];
|
|
123
|
+
const getProfile = () => shared?.getProfileName() ?? "default";
|
|
124
|
+
const transactionPath = (path: string) => shared ? join(dirname(path), "runtime", `${basename(path)}.transaction`) : `${path}.transaction`;
|
|
125
|
+
const captureRotation = (path: string, previousPath: string, profile: string): (() => boolean) | undefined => {
|
|
126
|
+
if (!shared) return undefined;
|
|
127
|
+
const authority = shared.captureAuthority();
|
|
128
|
+
return () => authority?.() === true && resolvePath() === path && resolvePreviousPath() === previousPath && getProfile() === profile;
|
|
129
|
+
};
|
|
130
|
+
const scopeStorageKey = (path: string, profile: string) => shared ? JSON.stringify([path, profile]) : path;
|
|
111
131
|
|
|
112
132
|
const ensureParent = (path: string) => {
|
|
113
133
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
@@ -124,49 +144,63 @@ export function createTelegramRuntimeJsonlLog(
|
|
|
124
144
|
previousPath: string,
|
|
125
145
|
reason: string,
|
|
126
146
|
scope?: Record<string, unknown>,
|
|
127
|
-
|
|
147
|
+
profile?: string,
|
|
148
|
+
isCurrent?: () => boolean,
|
|
149
|
+
): boolean => {
|
|
150
|
+
const line = safeJsonLine({
|
|
151
|
+
at: getNowMs(),
|
|
152
|
+
kind: "reset",
|
|
153
|
+
reason,
|
|
154
|
+
scope,
|
|
155
|
+
previousPath,
|
|
156
|
+
...(shared ? { profile } : {}),
|
|
157
|
+
}) + "\n";
|
|
158
|
+
if (shared && !isCurrent?.()) return false;
|
|
128
159
|
ensureParent(path);
|
|
129
160
|
preserveCurrentLog(path, previousPath);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
at: getNowMs(),
|
|
134
|
-
kind: "reset",
|
|
135
|
-
reason,
|
|
136
|
-
scope,
|
|
137
|
-
previousPath,
|
|
138
|
-
}) + "\n",
|
|
139
|
-
{ mode: 0o600 },
|
|
140
|
-
);
|
|
161
|
+
if (shared && !isCurrent?.()) return false;
|
|
162
|
+
writeFileSync(path, line, { mode: 0o600 });
|
|
163
|
+
return true;
|
|
141
164
|
};
|
|
142
165
|
|
|
143
166
|
const writeReset = (
|
|
144
167
|
reason: string,
|
|
145
|
-
scope
|
|
168
|
+
scope: Record<string, unknown> | undefined,
|
|
169
|
+
path: string,
|
|
170
|
+
profile: string,
|
|
146
171
|
): boolean => {
|
|
147
|
-
|
|
148
|
-
const
|
|
172
|
+
const previousPath = resolvePreviousPath();
|
|
173
|
+
const isCurrent = captureRotation(path, previousPath, profile);
|
|
174
|
+
if (canReset && !canReset()) return false;
|
|
149
175
|
let didReset = false;
|
|
150
|
-
withTelegramFileTransaction(
|
|
176
|
+
withTelegramFileTransaction(transactionPath(path), () => {
|
|
151
177
|
const commit = () => {
|
|
152
|
-
|
|
153
|
-
|
|
178
|
+
if (shared && !isCurrent?.()) return;
|
|
179
|
+
if (shared) {
|
|
180
|
+
const line = safeJsonLine({ at: getNowMs(), kind: "reset", reason, scope, profile }) + "\n";
|
|
181
|
+
if (!isCurrent?.()) return;
|
|
182
|
+
ensureParent(path);
|
|
183
|
+
if (existsSync(path) && statSync(path).size + Buffer.byteLength(line) > maxBytes &&
|
|
184
|
+
!writeResetLocked(path, previousPath, "max-bytes", { maxBytes }, profile, isCurrent)) return;
|
|
185
|
+
if (!isCurrent?.()) return;
|
|
186
|
+
appendFileSync(path, line, { mode: 0o600 });
|
|
187
|
+
didReset = true;
|
|
188
|
+
} else didReset = writeResetLocked(path, previousPath, reason, scope);
|
|
154
189
|
};
|
|
155
|
-
if (
|
|
156
|
-
|
|
190
|
+
if (commitReset) {
|
|
191
|
+
commitReset(commit);
|
|
157
192
|
} else {
|
|
158
193
|
commit();
|
|
159
194
|
}
|
|
160
195
|
});
|
|
161
|
-
return didReset;
|
|
196
|
+
return didReset && (!shared || isCurrent?.() === true);
|
|
162
197
|
};
|
|
163
198
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});
|
|
199
|
+
const appendEvent = (event: TelegramRuntimeJsonlEvent) => {
|
|
200
|
+
const path = resolvePath(), previousPath = resolvePreviousPath(), profile = getProfile();
|
|
201
|
+
const rotationCurrent = captureRotation(path, previousPath, profile);
|
|
202
|
+
const line = safeJsonLine({ kind: "event", ...event, ...(shared ? { profile } : {}) }) + "\n";
|
|
203
|
+
queuedAppends.push({ path, previousPath, line, ...(shared ? { profile, rotationCurrent } : {}) });
|
|
170
204
|
if (appendScheduled) return;
|
|
171
205
|
appendScheduled = true;
|
|
172
206
|
pending = pending
|
|
@@ -176,18 +210,18 @@ export function createTelegramRuntimeJsonlLog(
|
|
|
176
210
|
queuedAppends = [];
|
|
177
211
|
const groups = new Map<
|
|
178
212
|
string,
|
|
179
|
-
{ path: string; previousPath: string;
|
|
213
|
+
{ path: string; previousPath: string; entries: typeof queuedAppends }
|
|
180
214
|
>();
|
|
181
215
|
for (const entry of batch) {
|
|
182
216
|
const key = `${entry.path}\u0000${entry.previousPath}`;
|
|
183
217
|
const group = groups.get(key);
|
|
184
|
-
if (group) group.
|
|
185
|
-
else groups.set(key, { ...entry,
|
|
218
|
+
if (group) group.entries.push(entry);
|
|
219
|
+
else groups.set(key, { ...entry, entries: [entry] });
|
|
186
220
|
}
|
|
187
221
|
for (const group of groups.values()) {
|
|
188
222
|
try {
|
|
189
223
|
ensureParent(group.path);
|
|
190
|
-
withTelegramFileTransaction(
|
|
224
|
+
withTelegramFileTransaction(transactionPath(group.path), () => {
|
|
191
225
|
let currentSize = existsSync(group.path)
|
|
192
226
|
? statSync(group.path).size
|
|
193
227
|
: 0;
|
|
@@ -200,31 +234,35 @@ export function createTelegramRuntimeJsonlLog(
|
|
|
200
234
|
chunk = "";
|
|
201
235
|
chunkBytes = 0;
|
|
202
236
|
};
|
|
203
|
-
const rotate = (): boolean => {
|
|
204
|
-
if (
|
|
237
|
+
const rotate = (entry: typeof queuedAppends[number]): boolean => {
|
|
238
|
+
if (canReset && !canReset()) return false;
|
|
239
|
+
if (shared && !entry.rotationCurrent?.()) return false;
|
|
205
240
|
let rotated = false;
|
|
206
241
|
const commit = () => {
|
|
207
|
-
|
|
242
|
+
if (shared && !entry.rotationCurrent?.()) return;
|
|
243
|
+
rotated = writeResetLocked(
|
|
208
244
|
group.path,
|
|
209
245
|
group.previousPath,
|
|
210
246
|
"max-bytes",
|
|
211
247
|
{ maxBytes },
|
|
248
|
+
entry.profile,
|
|
249
|
+
entry.rotationCurrent,
|
|
212
250
|
);
|
|
213
|
-
rotated = true;
|
|
214
251
|
};
|
|
215
|
-
if (
|
|
252
|
+
if (commitReset) commitReset(commit);
|
|
216
253
|
else commit();
|
|
217
254
|
if (rotated) currentSize = statSync(group.path).size;
|
|
218
255
|
return rotated;
|
|
219
256
|
};
|
|
220
|
-
for (const
|
|
257
|
+
for (const entry of group.entries) {
|
|
258
|
+
const line = entry.line;
|
|
221
259
|
const lineBytes = Buffer.byteLength(line);
|
|
222
260
|
if (
|
|
223
261
|
currentSize + chunkBytes > 0 &&
|
|
224
262
|
currentSize + chunkBytes + lineBytes > maxBytes
|
|
225
263
|
) {
|
|
226
264
|
flushChunk();
|
|
227
|
-
rotate();
|
|
265
|
+
rotate(entry);
|
|
228
266
|
}
|
|
229
267
|
chunk += line;
|
|
230
268
|
chunkBytes += lineBytes;
|
|
@@ -242,27 +280,27 @@ export function createTelegramRuntimeJsonlLog(
|
|
|
242
280
|
return {
|
|
243
281
|
getPath: resolvePath,
|
|
244
282
|
reset(reason, scope) {
|
|
245
|
-
const path = resolvePath();
|
|
283
|
+
const path = resolvePath(), profile = getProfile();
|
|
246
284
|
try {
|
|
247
|
-
if (writeReset(reason, scope)) {
|
|
248
|
-
scopeKeys.set(path, scope ? safeJsonLine(scope) : undefined);
|
|
285
|
+
if (writeReset(reason, scope, path, profile)) {
|
|
286
|
+
scopeKeys.set(scopeStorageKey(path, profile), scope ? safeJsonLine(scope) : undefined);
|
|
249
287
|
}
|
|
250
288
|
} catch {
|
|
251
289
|
// Diagnostics must never break Telegram runtime behavior.
|
|
252
290
|
}
|
|
253
291
|
},
|
|
254
292
|
resetIfScopeChanged(nextScopeKey, reason, scope) {
|
|
255
|
-
const path = resolvePath();
|
|
256
|
-
if (scopeKeys.get(
|
|
293
|
+
const path = resolvePath(), profile = getProfile(), key = scopeStorageKey(path, profile);
|
|
294
|
+
if (scopeKeys.get(key) === nextScopeKey) return;
|
|
257
295
|
try {
|
|
258
|
-
if (writeReset(reason, scope)) scopeKeys.set(
|
|
296
|
+
if (writeReset(reason, scope, path, profile)) scopeKeys.set(key, nextScopeKey);
|
|
259
297
|
} catch {
|
|
260
298
|
// Diagnostics must never break Telegram runtime behavior.
|
|
261
299
|
}
|
|
262
300
|
},
|
|
263
301
|
record(event) {
|
|
264
302
|
try {
|
|
265
|
-
|
|
303
|
+
appendEvent(event);
|
|
266
304
|
} catch {
|
|
267
305
|
// Diagnostics must never break Telegram runtime behavior.
|
|
268
306
|
}
|
|
@@ -270,7 +308,19 @@ export function createTelegramRuntimeJsonlLog(
|
|
|
270
308
|
};
|
|
271
309
|
}
|
|
272
310
|
|
|
273
|
-
|
|
311
|
+
interface TelegramRuntimeDiagnosticsStatusPorts<TContext> {
|
|
312
|
+
instanceId: string;
|
|
313
|
+
updateStatus(ctx: TContext, error?: string): void;
|
|
314
|
+
getStatusState(): Status.TelegramBridgeStatusLineState;
|
|
315
|
+
persistSnapshot(snapshot: ReturnType<typeof Status.createTelegramStatusSnapshot>): Promise<void>;
|
|
316
|
+
session?: {
|
|
317
|
+
get(): TContext | undefined;
|
|
318
|
+
getGeneration(): number;
|
|
319
|
+
isCurrent(ctx: TContext, generation?: number): boolean;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
interface TelegramRuntimeDiagnosticsRuntime<TContext> {
|
|
274
324
|
events: Status.TelegramRuntimeEventRecorder;
|
|
275
325
|
recordRuntimeEvent(
|
|
276
326
|
category: string,
|
|
@@ -282,15 +332,11 @@ export interface TelegramRuntimeDiagnosticsRuntime<TContext> {
|
|
|
282
332
|
getProfileName(): string | undefined;
|
|
283
333
|
canReset(): boolean;
|
|
284
334
|
commitReset(commit: () => void): boolean;
|
|
335
|
+
captureAuthority?: () => (() => boolean) | undefined;
|
|
285
336
|
}): void;
|
|
286
|
-
bindStatus(ports:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
getStatusState(): Status.TelegramBridgeStatusLineState;
|
|
290
|
-
persistSnapshot(
|
|
291
|
-
snapshot: ReturnType<typeof Status.createTelegramStatusSnapshot>,
|
|
292
|
-
): Promise<void>;
|
|
293
|
-
}): void;
|
|
337
|
+
bindStatus(ports: TelegramRuntimeDiagnosticsStatusPorts<TContext>): void;
|
|
338
|
+
onSessionStart(): void;
|
|
339
|
+
onSessionShutdown(): Promise<void>;
|
|
294
340
|
updateStatus(ctx: TContext, error?: string): void;
|
|
295
341
|
getStatusLines(options?: Status.TelegramBridgeStatusLineOptions): string[];
|
|
296
342
|
scheduleSnapshotPersist(): void;
|
|
@@ -298,29 +344,25 @@ export interface TelegramRuntimeDiagnosticsRuntime<TContext> {
|
|
|
298
344
|
|
|
299
345
|
export function createTelegramRuntimeDiagnosticsRuntime<
|
|
300
346
|
TContext,
|
|
301
|
-
>(
|
|
347
|
+
>(options: {
|
|
348
|
+
sharedFile?: boolean;
|
|
349
|
+
snapshotTimer?: Pick<Parameters<typeof Status.createTelegramRuntimeDiagnosticsSnapshotScheduler>[0], "setTimer" | "clearTimer">;
|
|
350
|
+
} = {}): TelegramRuntimeDiagnosticsRuntime<TContext> {
|
|
351
|
+
const sharedFile = options.sharedFile === true;
|
|
302
352
|
let getBotToken = (): string | undefined => undefined;
|
|
303
353
|
let getProfileName = (): string | undefined => undefined;
|
|
304
354
|
let canReset = (): boolean => false;
|
|
305
355
|
let commitReset = (_commit: () => void): boolean => false;
|
|
306
|
-
let
|
|
307
|
-
|
|
308
|
-
instanceId: string;
|
|
309
|
-
updateStatus(ctx: TContext, error?: string): void;
|
|
310
|
-
getStatusState(): Status.TelegramBridgeStatusLineState;
|
|
311
|
-
persistSnapshot(
|
|
312
|
-
snapshot: ReturnType<typeof Status.createTelegramStatusSnapshot>,
|
|
313
|
-
): Promise<void>;
|
|
314
|
-
}
|
|
315
|
-
| undefined;
|
|
316
|
-
let requestSnapshotPersist = (): void => {};
|
|
356
|
+
let captureAuthority = (): (() => boolean) | undefined => undefined;
|
|
357
|
+
let statusPorts: TelegramRuntimeDiagnosticsStatusPorts<TContext> | undefined;
|
|
317
358
|
const events = Status.createTelegramRuntimeEventRecorder({
|
|
318
359
|
getBotToken: () => getBotToken(),
|
|
319
360
|
});
|
|
320
361
|
const jsonl = createTelegramRuntimeJsonlLog({
|
|
321
|
-
path: () => getTelegramRuntimeLogPath(undefined, getProfileName()),
|
|
322
|
-
previousPath: () =>
|
|
323
|
-
getTelegramPreviousRuntimeLogPath(undefined, getProfileName()),
|
|
362
|
+
path: () => sharedFile ? resolveTelegramRuntimeLogPath() : getTelegramRuntimeLogPath(undefined, getProfileName()),
|
|
363
|
+
previousPath: () => sharedFile ? resolveTelegramPreviousSharedRuntimeLogPath()
|
|
364
|
+
: getTelegramPreviousRuntimeLogPath(undefined, getProfileName()),
|
|
365
|
+
...(sharedFile ? { sharedProfiles: { getProfileName: () => getProfileName(), captureAuthority: () => captureAuthority() } } : {}),
|
|
324
366
|
canReset: () => canReset(),
|
|
325
367
|
commitReset: (commit) => commitReset(commit),
|
|
326
368
|
});
|
|
@@ -334,12 +376,29 @@ export function createTelegramRuntimeDiagnosticsRuntime<
|
|
|
334
376
|
if (latestEvent) jsonl.record(latestEvent);
|
|
335
377
|
requestSnapshotPersist();
|
|
336
378
|
};
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
);
|
|
379
|
+
const captureSnapshotScope = (): (() => boolean) | undefined => {
|
|
380
|
+
const ports = statusPorts;
|
|
381
|
+
if (!ports) return undefined;
|
|
382
|
+
if (!ports.session) return () => statusPorts === ports;
|
|
383
|
+
const session = ports.session, ctx = session.get(), generation = session.getGeneration();
|
|
384
|
+
if (ctx === undefined || !session.isCurrent(ctx, generation)) return undefined;
|
|
385
|
+
return () => statusPorts === ports && session.isCurrent(ctx, generation);
|
|
342
386
|
};
|
|
387
|
+
const requestSnapshotPersist = Status.createTelegramRuntimeDiagnosticsSnapshotScheduler({
|
|
388
|
+
...options.snapshotTimer,
|
|
389
|
+
captureScope: captureSnapshotScope,
|
|
390
|
+
async persistSnapshot(isCurrent) {
|
|
391
|
+
const ports = statusPorts;
|
|
392
|
+
// Projection may acquire journal guards or recover a snapshot: fence before reading it.
|
|
393
|
+
if (!ports || !isCurrent()) return;
|
|
394
|
+
const snapshot = Status.createTelegramStatusSnapshot(ports.getStatusState());
|
|
395
|
+
if (!isCurrent()) return;
|
|
396
|
+
await ports.persistSnapshot(snapshot);
|
|
397
|
+
},
|
|
398
|
+
recordError(error) {
|
|
399
|
+
events.record("telegram", error, { phase: "runtime-diagnostics-snapshot-persist" });
|
|
400
|
+
},
|
|
401
|
+
});
|
|
343
402
|
const updateRuntimeLogScope = function (reason: string): void {
|
|
344
403
|
if (!statusPorts) return;
|
|
345
404
|
const scope = Status.createTelegramRuntimeLogScope({
|
|
@@ -356,31 +415,22 @@ export function createTelegramRuntimeDiagnosticsRuntime<
|
|
|
356
415
|
getProfileName = ports.getProfileName;
|
|
357
416
|
canReset = ports.canReset;
|
|
358
417
|
commitReset = ports.commitReset;
|
|
418
|
+
captureAuthority = ports.captureAuthority ?? (() => undefined);
|
|
359
419
|
},
|
|
360
420
|
bindStatus(ports) {
|
|
361
421
|
statusPorts = ports;
|
|
362
|
-
requestSnapshotPersist
|
|
363
|
-
Status.createTelegramRuntimeDiagnosticsSnapshotScheduler({
|
|
364
|
-
persistSnapshot: persistCurrentSnapshot,
|
|
365
|
-
recordError(error) {
|
|
366
|
-
events.record("telegram", error, {
|
|
367
|
-
phase: "runtime-diagnostics-snapshot-persist",
|
|
368
|
-
});
|
|
369
|
-
},
|
|
370
|
-
});
|
|
422
|
+
if (ports.session) void requestSnapshotPersist.suspend();
|
|
371
423
|
},
|
|
424
|
+
onSessionStart: requestSnapshotPersist.resume,
|
|
425
|
+
onSessionShutdown: requestSnapshotPersist.suspend,
|
|
372
426
|
updateStatus(ctx, error) {
|
|
373
|
-
if (!statusPorts) return;
|
|
427
|
+
if (!statusPorts || statusPorts.session?.isCurrent(ctx) === false) return;
|
|
374
428
|
statusPorts.updateStatus(ctx, error);
|
|
375
429
|
updateRuntimeLogScope("status-scope-change");
|
|
376
430
|
},
|
|
377
431
|
getStatusLines(options) {
|
|
378
432
|
if (!statusPorts) return [];
|
|
379
|
-
|
|
380
|
-
recordRuntimeEvent("telegram", error, {
|
|
381
|
-
phase: "status-snapshot-persist",
|
|
382
|
-
});
|
|
383
|
-
});
|
|
433
|
+
requestSnapshotPersist();
|
|
384
434
|
return Status.buildTelegramBridgeStatusLines(
|
|
385
435
|
statusPorts.getStatusState(),
|
|
386
436
|
options,
|
|
@@ -67,10 +67,13 @@ export interface TelegramReplyToMessage {
|
|
|
67
67
|
|
|
68
68
|
export interface TelegramSticker {
|
|
69
69
|
file_id: string;
|
|
70
|
+
is_video?: boolean;
|
|
71
|
+
is_animated?: boolean;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
export interface TelegramMediaMessage {
|
|
73
75
|
message_id: number;
|
|
76
|
+
chat?: { id: number; type?: string; username?: string };
|
|
74
77
|
from?: TelegramMessageUser;
|
|
75
78
|
forward_origin?: TelegramMessageForwardOrigin;
|
|
76
79
|
forward_from?: TelegramMessageUser;
|
|
@@ -154,9 +157,26 @@ export interface TelegramMediaGroupControllerOptions {
|
|
|
154
157
|
export type TelegramAttachmentKind =
|
|
155
158
|
"photo" | "document" | "video" | "audio" | "voice" | "animation" | "sticker";
|
|
156
159
|
|
|
160
|
+
/** Where an inbound attachment came from; the download layer turns it into a scoped file name. */
|
|
161
|
+
export interface TelegramAttachmentSource {
|
|
162
|
+
kind: string;
|
|
163
|
+
messageId: number;
|
|
164
|
+
index?: number;
|
|
165
|
+
/** Sender-chosen file name, kept as a trailing readable segment. */
|
|
166
|
+
userFileName?: string;
|
|
167
|
+
chat?: { id: number; type?: string; username?: string };
|
|
168
|
+
/** Explicit source scope, e.g. a Guest Mode peer whose message IDs are not the bot chat's. */
|
|
169
|
+
scope?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
157
172
|
export interface TelegramFileInfo {
|
|
158
173
|
file_id: string;
|
|
159
174
|
fileName: string;
|
|
175
|
+
/** Present only when the sender chose the name. */
|
|
176
|
+
userFileName?: string;
|
|
177
|
+
/** Position inside a rich message. */
|
|
178
|
+
index?: number;
|
|
179
|
+
source?: TelegramAttachmentSource;
|
|
160
180
|
mimeType?: string;
|
|
161
181
|
kind: TelegramAttachmentKind;
|
|
162
182
|
isImage: boolean;
|
|
@@ -179,10 +199,14 @@ export interface DownloadedTelegramMessageFile {
|
|
|
179
199
|
}
|
|
180
200
|
|
|
181
201
|
export interface DownloadTelegramMessageFilesDeps {
|
|
182
|
-
downloadFile: (
|
|
202
|
+
downloadFile: (
|
|
203
|
+
fileId: string,
|
|
204
|
+
fileName: string,
|
|
205
|
+
source?: TelegramAttachmentSource,
|
|
206
|
+
) => Promise<string>;
|
|
183
207
|
}
|
|
184
208
|
|
|
185
|
-
|
|
209
|
+
function guessExtensionFromMime(
|
|
186
210
|
mimeType: string | undefined,
|
|
187
211
|
fallback: string,
|
|
188
212
|
): string {
|
|
@@ -351,7 +375,7 @@ export function extractTelegramForwardContextText(
|
|
|
351
375
|
return `from: ${origin}`;
|
|
352
376
|
}
|
|
353
377
|
|
|
354
|
-
|
|
378
|
+
function extractTelegramReplyContextText(
|
|
355
379
|
message: TelegramMediaMessage,
|
|
356
380
|
): string {
|
|
357
381
|
const quoted = (
|
|
@@ -396,7 +420,7 @@ export function buildTelegramReplyContextBlock(
|
|
|
396
420
|
return "";
|
|
397
421
|
}
|
|
398
422
|
|
|
399
|
-
|
|
423
|
+
function appendTelegramReplyContext(
|
|
400
424
|
text: string,
|
|
401
425
|
replyContext: string,
|
|
402
426
|
): string {
|
|
@@ -437,7 +461,7 @@ export function extractFirstTelegramMessageText(
|
|
|
437
461
|
return messages.map(extractTelegramMessageText).find(Boolean) ?? "";
|
|
438
462
|
}
|
|
439
463
|
|
|
440
|
-
|
|
464
|
+
function hasTelegramMessagePromptContent(
|
|
441
465
|
message: TelegramMediaMessage,
|
|
442
466
|
): boolean {
|
|
443
467
|
return (
|
|
@@ -712,7 +736,7 @@ export async function downloadTelegramMessageFiles(
|
|
|
712
736
|
const downloaded: DownloadedTelegramMessageFile[] = [];
|
|
713
737
|
for (const file of collectTelegramFileInfos(messages)) {
|
|
714
738
|
downloaded.push({
|
|
715
|
-
path: await deps.downloadFile(file.file_id, file.fileName),
|
|
739
|
+
path: await deps.downloadFile(file.file_id, file.fileName, file.source),
|
|
716
740
|
fileName: file.fileName,
|
|
717
741
|
isImage: file.isImage,
|
|
718
742
|
mimeType: file.mimeType,
|
|
@@ -759,6 +783,7 @@ function collectTelegramRichBlockFileInfos(
|
|
|
759
783
|
files.push({
|
|
760
784
|
file_id: photo.file_id,
|
|
761
785
|
fileName: `photo-${messageId}-${mediaIndex}.jpg`,
|
|
786
|
+
index: mediaIndex,
|
|
762
787
|
mimeType: "image/jpeg",
|
|
763
788
|
kind: "photo",
|
|
764
789
|
isImage: true,
|
|
@@ -788,6 +813,8 @@ function collectTelegramRichBlockFileInfos(
|
|
|
788
813
|
kind === "voice" ? ".ogg" : kind === "audio" ? ".mp3" : ".mp4";
|
|
789
814
|
files.push({
|
|
790
815
|
file_id: fileId,
|
|
816
|
+
index: mediaIndex,
|
|
817
|
+
...(typeof fileName === "string" ? { userFileName: fileName } : {}),
|
|
791
818
|
fileName:
|
|
792
819
|
typeof fileName === "string"
|
|
793
820
|
? fileName
|
|
@@ -817,6 +844,7 @@ export function collectTelegramFileInfos(
|
|
|
817
844
|
): TelegramFileInfo[] {
|
|
818
845
|
const files: TelegramFileInfo[] = [];
|
|
819
846
|
for (const message of messages) {
|
|
847
|
+
const firstOfMessage = files.length;
|
|
820
848
|
files.push(
|
|
821
849
|
...collectTelegramRichBlockFileInfos(
|
|
822
850
|
message.rich_message?.blocks,
|
|
@@ -847,6 +875,7 @@ export function collectTelegramFileInfos(
|
|
|
847
875
|
files.push({
|
|
848
876
|
file_id: message.document.file_id,
|
|
849
877
|
fileName,
|
|
878
|
+
...(message.document.file_name ? { userFileName: message.document.file_name } : {}),
|
|
850
879
|
mimeType: message.document.mime_type,
|
|
851
880
|
kind: "document",
|
|
852
881
|
isImage: isImageMimeType(message.document.mime_type),
|
|
@@ -862,6 +891,7 @@ export function collectTelegramFileInfos(
|
|
|
862
891
|
files.push({
|
|
863
892
|
file_id: message.video.file_id,
|
|
864
893
|
fileName,
|
|
894
|
+
...(message.video.file_name ? { userFileName: message.video.file_name } : {}),
|
|
865
895
|
mimeType: message.video.mime_type,
|
|
866
896
|
kind: "video",
|
|
867
897
|
isImage: false,
|
|
@@ -878,6 +908,7 @@ export function collectTelegramFileInfos(
|
|
|
878
908
|
files.push({
|
|
879
909
|
file_id: message.audio.file_id,
|
|
880
910
|
fileName,
|
|
911
|
+
...(message.audio.file_name ? { userFileName: message.audio.file_name } : {}),
|
|
881
912
|
mimeType: message.audio.mime_type,
|
|
882
913
|
kind: "audio",
|
|
883
914
|
isImage: false,
|
|
@@ -907,20 +938,31 @@ export function collectTelegramFileInfos(
|
|
|
907
938
|
files.push({
|
|
908
939
|
file_id: message.animation.file_id,
|
|
909
940
|
fileName,
|
|
941
|
+
...(message.animation.file_name ? { userFileName: message.animation.file_name } : {}),
|
|
910
942
|
mimeType: message.animation.mime_type,
|
|
911
943
|
kind: "animation",
|
|
912
944
|
isImage: false,
|
|
913
945
|
});
|
|
914
946
|
}
|
|
915
947
|
if (message.sticker) {
|
|
948
|
+
// Sticker format is defined by Bot API flags, not a documented MIME field.
|
|
949
|
+
const video = message.sticker.is_video === true;
|
|
950
|
+
const animated = message.sticker.is_animated === true;
|
|
951
|
+
const extension = video ? ".webm" : animated ? ".tgs" : ".webp";
|
|
916
952
|
files.push({
|
|
917
953
|
file_id: message.sticker.file_id,
|
|
918
|
-
fileName: `sticker-${message.message_id}
|
|
919
|
-
mimeType: "image/webp",
|
|
954
|
+
fileName: `sticker-${message.message_id}${extension}`,
|
|
955
|
+
mimeType: video ? "video/webm" : animated ? "application/x-tgsticker" : "image/webp",
|
|
920
956
|
kind: "sticker",
|
|
921
|
-
isImage:
|
|
957
|
+
isImage: !video && !animated,
|
|
922
958
|
});
|
|
923
959
|
}
|
|
960
|
+
for (const file of files.slice(firstOfMessage)) {
|
|
961
|
+
file.source = { kind: file.kind, messageId: message.message_id,
|
|
962
|
+
...(file.index !== undefined ? { index: file.index } : {}),
|
|
963
|
+
...(file.userFileName ? { userFileName: file.userFileName } : {}),
|
|
964
|
+
...(message.chat ? { chat: message.chat } : {}) };
|
|
965
|
+
}
|
|
924
966
|
}
|
|
925
967
|
const seenFileIds = new Set<string>();
|
|
926
968
|
return files.filter((file) => {
|
|
@@ -267,7 +267,7 @@ export const TELEGRAM_MODEL_PAGE_SIZE = 6;
|
|
|
267
267
|
const TELEGRAM_MODEL_PAGE_PICKER_ROW_SIZE = 4;
|
|
268
268
|
export const MODEL_MENU_TITLE = "<b>🤖 Choose a model:</b>";
|
|
269
269
|
export const MODEL_PAGE_MENU_TITLE = "<b>Choose a page:</b>";
|
|
270
|
-
|
|
270
|
+
const MODEL_DETAIL_MENU_TITLE = "<b>🤖 Model:</b>";
|
|
271
271
|
|
|
272
272
|
function truncateTelegramButtonLabel(label: string, maxLength = 56): string {
|
|
273
273
|
return label.length <= maxLength
|
|
@@ -647,7 +647,7 @@ export function getTelegramModelSelection<TModel extends MenuModel = MenuModel>(
|
|
|
647
647
|
return { kind: "selected", selection };
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
-
|
|
650
|
+
function applyTelegramModelDetailSelection(
|
|
651
651
|
state: TelegramModelMenuState,
|
|
652
652
|
value: string | undefined,
|
|
653
653
|
): TelegramMenuMutationResult {
|
|
@@ -661,7 +661,7 @@ export function applyTelegramModelDetailSelection(
|
|
|
661
661
|
return "changed";
|
|
662
662
|
}
|
|
663
663
|
|
|
664
|
-
|
|
664
|
+
function getTelegramSelectedDetailModel<
|
|
665
665
|
TModel extends MenuModel = MenuModel,
|
|
666
666
|
>(state: TelegramModelMenuState<TModel>): TelegramMenuSelectionResult<TModel> {
|
|
667
667
|
const indexedSelection = getTelegramModelSelection(
|
|
@@ -687,7 +687,7 @@ export function getTelegramSelectedDetailModel<
|
|
|
687
687
|
return indexedSelection;
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
-
|
|
690
|
+
function isTelegramModelScoped(
|
|
691
691
|
state: TelegramModelMenuState,
|
|
692
692
|
model: MenuModel,
|
|
693
693
|
): boolean {
|
|
@@ -697,7 +697,7 @@ export function isTelegramModelScoped(
|
|
|
697
697
|
);
|
|
698
698
|
}
|
|
699
699
|
|
|
700
|
-
|
|
700
|
+
function focusTelegramModelListPage(
|
|
701
701
|
state: TelegramModelMenuState,
|
|
702
702
|
model: MenuModel,
|
|
703
703
|
pageSize = TELEGRAM_MODEL_PAGE_SIZE,
|
|
@@ -715,7 +715,7 @@ function formatScopedModelPattern(entry: ScopedTelegramModel): string {
|
|
|
715
715
|
return entry.thinkingLevel ? `${key}:${entry.thinkingLevel}` : key;
|
|
716
716
|
}
|
|
717
717
|
|
|
718
|
-
|
|
718
|
+
function setTelegramModelScope(
|
|
719
719
|
state: TelegramModelMenuState,
|
|
720
720
|
model: MenuModel,
|
|
721
721
|
enabled: boolean,
|
|
@@ -1062,7 +1062,7 @@ export function buildModelMenuReplyMarkup(
|
|
|
1062
1062
|
return { inline_keyboard: rows };
|
|
1063
1063
|
}
|
|
1064
1064
|
|
|
1065
|
-
|
|
1065
|
+
function buildModelDetailMenuReplyMarkup(
|
|
1066
1066
|
state: TelegramModelMenuState,
|
|
1067
1067
|
currentModel: MenuModel | undefined,
|
|
1068
1068
|
): TelegramReplyMarkup {
|
|
@@ -1100,7 +1100,7 @@ export function buildModelDetailMenuReplyMarkup(
|
|
|
1100
1100
|
};
|
|
1101
1101
|
}
|
|
1102
1102
|
|
|
1103
|
-
|
|
1103
|
+
function buildModelDetailMenuText(
|
|
1104
1104
|
state: TelegramModelMenuState,
|
|
1105
1105
|
): string {
|
|
1106
1106
|
const selection = getTelegramSelectedDetailModel(state);
|