@llblab/pi-telegram 0.23.3 → 0.24.1
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 +4 -4
- package/BACKLOG.md +6 -8
- package/CHANGELOG.md +131 -274
- package/README.md +4 -5
- package/docs/README.md +0 -1
- package/docs/architecture.md +26 -15
- package/docs/multi-instance-bus.md +18 -12
- package/docs/public-api.md +10 -2
- package/docs/sections.md +1 -1
- package/docs/updates.md +1 -1
- package/lib/bindings.ts +1 -9
- package/lib/bus-transport.ts +7 -14
- package/lib/bus.ts +48 -19
- package/lib/command-templates.ts +36 -20
- package/lib/commands.ts +5 -2
- package/lib/config.ts +157 -65
- package/lib/inbound.ts +22 -26
- package/lib/lifecycle.ts +0 -13
- package/lib/locks.ts +59 -40
- package/lib/logs.ts +76 -18
- package/lib/media.ts +0 -9
- package/lib/menu-model.ts +5 -12
- package/lib/menu.ts +0 -19
- package/lib/outbound.ts +2 -12
- package/lib/paths.ts +9 -6
- package/lib/pi.ts +0 -4
- package/lib/queue.ts +0 -5
- package/lib/replies.ts +0 -23
- package/lib/runtime.ts +1 -2
- package/lib/sections.ts +1 -48
- package/lib/status.ts +26 -13
- package/lib/telegram-api.ts +0 -4
- package/lib/threads.ts +41 -8
- package/package.json +1 -1
- package/docs/locks.md +0 -156
package/lib/locks.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram singleton lock helpers
|
|
3
|
-
* Zones:
|
|
4
|
-
* Owns
|
|
3
|
+
* Zones: telegram ownership, filesystem, transport authority
|
|
4
|
+
* Owns extension-local owners.json access and Telegram bridge ownership semantics
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -19,10 +19,11 @@ import {
|
|
|
19
19
|
} from "node:fs";
|
|
20
20
|
import { randomUUID } from "node:crypto";
|
|
21
21
|
import { basename, dirname, join } from "node:path";
|
|
22
|
-
import {
|
|
22
|
+
import { resolveTelegramOwnersPath } from "./paths.ts";
|
|
23
23
|
|
|
24
|
-
export const TELEGRAM_LOCK_KEY = "
|
|
25
|
-
export const TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS =
|
|
24
|
+
export const TELEGRAM_LOCK_KEY = "default";
|
|
25
|
+
export const TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS = 8_000;
|
|
26
|
+
const TELEGRAM_OWNERSHIP_REFRESH_MS = 2_000;
|
|
26
27
|
const TELEGRAM_LOCK_WRITE_RETRY_ATTEMPTS = 5;
|
|
27
28
|
const TELEGRAM_LOCK_WRITE_RETRY_DELAY_MS = 25;
|
|
28
29
|
const TELEGRAM_LOCK_TRANSACTION_ATTEMPTS = 80;
|
|
@@ -42,18 +43,17 @@ function allocateTelegramLockRuntimeGeneration(): number {
|
|
|
42
43
|
return generation;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
function
|
|
46
|
-
return
|
|
46
|
+
function getOwnersPath(): string {
|
|
47
|
+
return resolveTelegramOwnersPath();
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
|
-
* Resolve the
|
|
51
|
-
* Default profile →
|
|
52
|
-
* Named profile →
|
|
51
|
+
* Resolve the extension-local owner slot for the active Telegram profile.
|
|
52
|
+
* Default profile → default
|
|
53
|
+
* Named profile → the validated profile name
|
|
53
54
|
*/
|
|
54
55
|
export function resolveTelegramLockKey(activeProfile?: string): string {
|
|
55
|
-
|
|
56
|
-
return TELEGRAM_LOCK_KEY;
|
|
56
|
+
return activeProfile || TELEGRAM_LOCK_KEY;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export interface TelegramActiveProfileGetter {
|
|
@@ -139,7 +139,7 @@ export interface TelegramLockRuntimeOptions {
|
|
|
139
139
|
staleHeartbeatMs?: number;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
export function readLocks(path =
|
|
142
|
+
export function readLocks(path = getOwnersPath()): Record<string, unknown> {
|
|
143
143
|
if (!existsSync(path)) return {};
|
|
144
144
|
try {
|
|
145
145
|
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -161,7 +161,7 @@ function readLocksForTransaction(path: string): Record<string, unknown> {
|
|
|
161
161
|
}
|
|
162
162
|
const value: unknown = JSON.parse(source);
|
|
163
163
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
164
|
-
throw new Error(`Invalid Telegram
|
|
164
|
+
throw new Error(`Invalid Telegram owner store: ${path}`);
|
|
165
165
|
}
|
|
166
166
|
return value as Record<string, unknown>;
|
|
167
167
|
}
|
|
@@ -182,8 +182,7 @@ interface TelegramLockTransactionOwner {
|
|
|
182
182
|
generation: string;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
const TELEGRAM_TRANSACTION_OWNER_PATTERN =
|
|
186
|
-
/^owner\.([A-Za-z0-9-]+)\.json$/u;
|
|
185
|
+
const TELEGRAM_TRANSACTION_OWNER_PATTERN = /^owner\.([A-Za-z0-9-]+)\.json$/u;
|
|
187
186
|
|
|
188
187
|
function getLockTransactionOwnerFile(generation: string): string {
|
|
189
188
|
return `owner.${generation}.json`;
|
|
@@ -351,6 +350,8 @@ type TelegramTransactionGlobal = typeof globalThis & {
|
|
|
351
350
|
|
|
352
351
|
export interface TelegramFileTransactionOptions {
|
|
353
352
|
recoveryRename?: typeof renameSync;
|
|
353
|
+
attempts?: number;
|
|
354
|
+
retryDelayMs?: number;
|
|
354
355
|
}
|
|
355
356
|
|
|
356
357
|
function getActiveTransactionReclaims(): Set<string> {
|
|
@@ -367,7 +368,13 @@ function reclaimAbandonedDirectoryGuard(
|
|
|
367
368
|
} catch {
|
|
368
369
|
return false;
|
|
369
370
|
}
|
|
370
|
-
|
|
371
|
+
let entries: string[];
|
|
372
|
+
try {
|
|
373
|
+
entries = readdirSync(path);
|
|
374
|
+
} catch {
|
|
375
|
+
// Another recovery candidate may remove the observed guard after lstat.
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
371
378
|
if (entries.length !== 1) return false;
|
|
372
379
|
const entry = entries[0];
|
|
373
380
|
let observedPid: number;
|
|
@@ -383,10 +390,7 @@ function reclaimAbandonedDirectoryGuard(
|
|
|
383
390
|
observedReclaimGeneration = match[2];
|
|
384
391
|
}
|
|
385
392
|
const activeReclaims = getActiveTransactionReclaims();
|
|
386
|
-
if (
|
|
387
|
-
observedPid === process.pid &&
|
|
388
|
-
observedReclaimGeneration !== undefined
|
|
389
|
-
) {
|
|
393
|
+
if (observedPid === process.pid && observedReclaimGeneration !== undefined) {
|
|
390
394
|
if (activeReclaims.has(observedReclaimGeneration)) return false;
|
|
391
395
|
} else if (isProcessAlive(observedPid)) {
|
|
392
396
|
return false;
|
|
@@ -565,10 +569,7 @@ function recoverAbandonedLockTransaction(
|
|
|
565
569
|
}
|
|
566
570
|
|
|
567
571
|
const recoveryGuardPath = `${path}.recovery`;
|
|
568
|
-
const recoveryOwner = acquireLegacyRecoveryGuard(
|
|
569
|
-
recoveryGuardPath,
|
|
570
|
-
options,
|
|
571
|
-
);
|
|
572
|
+
const recoveryOwner = acquireLegacyRecoveryGuard(recoveryGuardPath, options);
|
|
572
573
|
if (!recoveryOwner) return undefined;
|
|
573
574
|
let recoveredOwner: TelegramLockTransactionOwner | undefined;
|
|
574
575
|
try {
|
|
@@ -607,24 +608,28 @@ function acquireLockTransaction(
|
|
|
607
608
|
path: string,
|
|
608
609
|
options: TelegramFileTransactionOptions = {},
|
|
609
610
|
): TelegramLockTransactionOwner {
|
|
611
|
+
const attempts = Math.max(
|
|
612
|
+
1,
|
|
613
|
+
options.attempts ?? TELEGRAM_LOCK_TRANSACTION_ATTEMPTS,
|
|
614
|
+
);
|
|
615
|
+
const retryDelayMs = Math.max(
|
|
616
|
+
0,
|
|
617
|
+
options.retryDelayMs ?? TELEGRAM_LOCK_TRANSACTION_RETRY_DELAY_MS,
|
|
618
|
+
);
|
|
610
619
|
mkdirSync(dirname(path), { recursive: true });
|
|
611
|
-
for (
|
|
612
|
-
let attempt = 0;
|
|
613
|
-
attempt < TELEGRAM_LOCK_TRANSACTION_ATTEMPTS;
|
|
614
|
-
attempt += 1
|
|
615
|
-
) {
|
|
620
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
616
621
|
try {
|
|
617
622
|
return createLockTransactionGuard(path);
|
|
618
623
|
} catch (error) {
|
|
619
624
|
if (!isLockTransactionContentionError(error, path)) throw error;
|
|
620
625
|
const recoveredOwner = recoverAbandonedLockTransaction(path, options);
|
|
621
626
|
if (recoveredOwner !== undefined) return recoveredOwner;
|
|
622
|
-
if (attempt ===
|
|
627
|
+
if (attempt === attempts - 1) {
|
|
623
628
|
throw new Error(
|
|
624
629
|
`Timed out acquiring Telegram lock transaction: ${path}`,
|
|
625
630
|
);
|
|
626
631
|
}
|
|
627
|
-
sleepSync(
|
|
632
|
+
sleepSync(retryDelayMs);
|
|
628
633
|
}
|
|
629
634
|
}
|
|
630
635
|
throw new Error(`Failed to acquire Telegram lock transaction: ${path}`);
|
|
@@ -856,7 +861,7 @@ export function createTelegramLockRuntime<TContext extends TelegramLockContext>(
|
|
|
856
861
|
options: TelegramLockRuntimeOptions = {},
|
|
857
862
|
): TelegramLockRuntime<TContext> {
|
|
858
863
|
const key = options.key ?? TELEGRAM_LOCK_KEY;
|
|
859
|
-
const locksPath = options.locksPath ??
|
|
864
|
+
const locksPath = options.locksPath ?? getOwnersPath();
|
|
860
865
|
const pid = options.pid ?? process.pid;
|
|
861
866
|
const isAlive = options.isProcessAlive ?? isProcessAlive;
|
|
862
867
|
const getNowMs = options.getNowMs ?? Date.now;
|
|
@@ -1141,6 +1146,7 @@ export interface TelegramLockedPollingRuntimeDeps<
|
|
|
1141
1146
|
details?: Record<string, unknown>,
|
|
1142
1147
|
) => void;
|
|
1143
1148
|
ownershipCheckMs?: number;
|
|
1149
|
+
ownershipRefreshMs?: number;
|
|
1144
1150
|
}
|
|
1145
1151
|
|
|
1146
1152
|
function snapshotLockContext(ctx: TelegramLockContext): TelegramLockContext {
|
|
@@ -1152,16 +1158,20 @@ export function createTelegramLockedPollingRuntime<
|
|
|
1152
1158
|
>(
|
|
1153
1159
|
deps: TelegramLockedPollingRuntimeDeps<TContext>,
|
|
1154
1160
|
): TelegramLockedPollingRuntime<TContext> {
|
|
1155
|
-
let
|
|
1161
|
+
let ownershipCheckInterval: ReturnType<typeof setInterval> | undefined;
|
|
1162
|
+
let ownershipRefreshInterval: ReturnType<typeof setInterval> | undefined;
|
|
1156
1163
|
let ownershipStop: Promise<void> | undefined;
|
|
1157
1164
|
let takeoverCandidate: TelegramLockEntry | undefined;
|
|
1158
1165
|
let sessionAutoStartRun: Promise<void> | undefined;
|
|
1159
1166
|
let sessionAutoStartGeneration = 0;
|
|
1160
1167
|
const ownershipCheckMs = deps.ownershipCheckMs ?? 1000;
|
|
1168
|
+
const ownershipRefreshMs =
|
|
1169
|
+
deps.ownershipRefreshMs ?? TELEGRAM_OWNERSHIP_REFRESH_MS;
|
|
1161
1170
|
const stopOwnershipWatcher = () => {
|
|
1162
|
-
if (
|
|
1163
|
-
clearInterval(
|
|
1164
|
-
|
|
1171
|
+
if (ownershipCheckInterval) clearInterval(ownershipCheckInterval);
|
|
1172
|
+
if (ownershipRefreshInterval) clearInterval(ownershipRefreshInterval);
|
|
1173
|
+
ownershipCheckInterval = undefined;
|
|
1174
|
+
ownershipRefreshInterval = undefined;
|
|
1165
1175
|
};
|
|
1166
1176
|
const suspendPolling = async () => {
|
|
1167
1177
|
sessionAutoStartGeneration += 1;
|
|
@@ -1191,15 +1201,24 @@ export function createTelegramLockedPollingRuntime<
|
|
|
1191
1201
|
const startOwnershipWatcher = (ctx: TContext) => {
|
|
1192
1202
|
const owner = snapshotLockContext(ctx);
|
|
1193
1203
|
stopOwnershipWatcher();
|
|
1194
|
-
|
|
1204
|
+
ownershipCheckInterval = setInterval(() => {
|
|
1205
|
+
try {
|
|
1206
|
+
if (deps.lock.owns(owner)) return;
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
deps.recordRuntimeEvent?.("lock", error, { phase: "check" });
|
|
1209
|
+
}
|
|
1210
|
+
stopAfterOwnershipLoss();
|
|
1211
|
+
}, ownershipCheckMs);
|
|
1212
|
+
ownershipRefreshInterval = setInterval(() => {
|
|
1195
1213
|
try {
|
|
1196
1214
|
if (deps.lock.refresh(owner)) return;
|
|
1197
1215
|
} catch (error) {
|
|
1198
1216
|
deps.recordRuntimeEvent?.("lock", error, { phase: "refresh" });
|
|
1199
1217
|
}
|
|
1200
1218
|
stopAfterOwnershipLoss();
|
|
1201
|
-
},
|
|
1202
|
-
|
|
1219
|
+
}, ownershipRefreshMs);
|
|
1220
|
+
ownershipCheckInterval.unref?.();
|
|
1221
|
+
ownershipRefreshInterval.unref?.();
|
|
1203
1222
|
};
|
|
1204
1223
|
const runOwnedPollingStart = async (
|
|
1205
1224
|
ctx: TContext,
|
package/lib/logs.ts
CHANGED
|
@@ -102,6 +102,12 @@ export function createTelegramRuntimeJsonlLog(
|
|
|
102
102
|
const getNowMs = options.getNowMs ?? Date.now;
|
|
103
103
|
const scopeKeys = new Map<string, string | undefined>();
|
|
104
104
|
let pending: Promise<void> = Promise.resolve();
|
|
105
|
+
let appendScheduled = false;
|
|
106
|
+
let queuedAppends: {
|
|
107
|
+
path: string;
|
|
108
|
+
previousPath: string;
|
|
109
|
+
line: string;
|
|
110
|
+
}[] = [];
|
|
105
111
|
|
|
106
112
|
const ensureParent = (path: string) => {
|
|
107
113
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
@@ -156,27 +162,79 @@ export function createTelegramRuntimeJsonlLog(
|
|
|
156
162
|
};
|
|
157
163
|
|
|
158
164
|
const appendLine = (line: string) => {
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
queuedAppends.push({
|
|
166
|
+
path: resolvePath(),
|
|
167
|
+
previousPath: resolvePreviousPath(),
|
|
168
|
+
line,
|
|
169
|
+
});
|
|
170
|
+
if (appendScheduled) return;
|
|
171
|
+
appendScheduled = true;
|
|
161
172
|
pending = pending
|
|
162
173
|
.then(() => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
appendScheduled = false;
|
|
175
|
+
const batch = queuedAppends;
|
|
176
|
+
queuedAppends = [];
|
|
177
|
+
const groups = new Map<
|
|
178
|
+
string,
|
|
179
|
+
{ path: string; previousPath: string; lines: string[] }
|
|
180
|
+
>();
|
|
181
|
+
for (const entry of batch) {
|
|
182
|
+
const key = `${entry.path}\u0000${entry.previousPath}`;
|
|
183
|
+
const group = groups.get(key);
|
|
184
|
+
if (group) group.lines.push(entry.line);
|
|
185
|
+
else groups.set(key, { ...entry, lines: [entry.line] });
|
|
186
|
+
}
|
|
187
|
+
for (const group of groups.values()) {
|
|
188
|
+
try {
|
|
189
|
+
ensureParent(group.path);
|
|
190
|
+
withTelegramFileTransaction(`${group.path}.transaction`, () => {
|
|
191
|
+
let currentSize = existsSync(group.path)
|
|
192
|
+
? statSync(group.path).size
|
|
193
|
+
: 0;
|
|
194
|
+
let chunk = "";
|
|
195
|
+
let chunkBytes = 0;
|
|
196
|
+
const flushChunk = () => {
|
|
197
|
+
if (!chunk) return;
|
|
198
|
+
appendFileSync(group.path, chunk, { mode: 0o600 });
|
|
199
|
+
currentSize += chunkBytes;
|
|
200
|
+
chunk = "";
|
|
201
|
+
chunkBytes = 0;
|
|
202
|
+
};
|
|
203
|
+
const rotate = (): boolean => {
|
|
204
|
+
if (options.canReset && !options.canReset()) return false;
|
|
205
|
+
let rotated = false;
|
|
206
|
+
const commit = () => {
|
|
207
|
+
writeResetLocked(
|
|
208
|
+
group.path,
|
|
209
|
+
group.previousPath,
|
|
210
|
+
"max-bytes",
|
|
211
|
+
{ maxBytes },
|
|
212
|
+
);
|
|
213
|
+
rotated = true;
|
|
214
|
+
};
|
|
215
|
+
if (options.commitReset) options.commitReset(commit);
|
|
216
|
+
else commit();
|
|
217
|
+
if (rotated) currentSize = statSync(group.path).size;
|
|
218
|
+
return rotated;
|
|
219
|
+
};
|
|
220
|
+
for (const line of group.lines) {
|
|
221
|
+
const lineBytes = Buffer.byteLength(line);
|
|
222
|
+
if (
|
|
223
|
+
currentSize + chunkBytes > 0 &&
|
|
224
|
+
currentSize + chunkBytes + lineBytes > maxBytes
|
|
225
|
+
) {
|
|
226
|
+
flushChunk();
|
|
227
|
+
rotate();
|
|
228
|
+
}
|
|
229
|
+
chunk += line;
|
|
230
|
+
chunkBytes += lineBytes;
|
|
231
|
+
}
|
|
232
|
+
flushChunk();
|
|
233
|
+
});
|
|
234
|
+
} catch {
|
|
235
|
+
// Diagnostics failures for one profile must not drop other groups.
|
|
177
236
|
}
|
|
178
|
-
|
|
179
|
-
});
|
|
237
|
+
}
|
|
180
238
|
})
|
|
181
239
|
.catch(() => undefined);
|
|
182
240
|
};
|
package/lib/media.ts
CHANGED
|
@@ -390,15 +390,6 @@ export function appendTelegramReplyContext(
|
|
|
390
390
|
return text ? `${text}\n\n${replyContext}` : `_\n\n${replyContext}`;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
export function appendTelegramForwardContext(
|
|
394
|
-
text: string,
|
|
395
|
-
forwardContext: string,
|
|
396
|
-
): string {
|
|
397
|
-
if (!forwardContext) return text;
|
|
398
|
-
const forwardBlock = `[forward|${forwardContext.replace(/:\s+/g, ":")}]`;
|
|
399
|
-
return text ? `\n\n${forwardBlock} ${text}` : `\n\n${forwardBlock}`;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
393
|
export function extractTelegramMessagePromptText(
|
|
403
394
|
message: TelegramMediaMessage,
|
|
404
395
|
): string {
|
package/lib/menu-model.ts
CHANGED
|
@@ -484,7 +484,11 @@ export function createTelegramModelMenuStateBuilder<
|
|
|
484
484
|
TelegramModelMenuStateBuilderContext<TModel>,
|
|
485
485
|
>(
|
|
486
486
|
deps: TelegramModelMenuStateBuilderDeps<TModel, TContext>,
|
|
487
|
-
): (
|
|
487
|
+
): (
|
|
488
|
+
chatId: number,
|
|
489
|
+
ctx: TContext,
|
|
490
|
+
threadId?: number,
|
|
491
|
+
) => Promise<TelegramModelMenuState<TModel>> {
|
|
488
492
|
return async (chatId, ctx, threadId) => {
|
|
489
493
|
const settingsManager = deps.createSettingsManager(ctx.cwd);
|
|
490
494
|
return deps.runtime.buildState({
|
|
@@ -745,17 +749,6 @@ export function setTelegramModelScope(
|
|
|
745
749
|
return { patterns, enabled };
|
|
746
750
|
}
|
|
747
751
|
|
|
748
|
-
export function toggleTelegramModelScope(
|
|
749
|
-
state: TelegramModelMenuState,
|
|
750
|
-
model: MenuModel,
|
|
751
|
-
): { patterns: string[]; enabled: boolean } {
|
|
752
|
-
return setTelegramModelScope(
|
|
753
|
-
state,
|
|
754
|
-
model,
|
|
755
|
-
!isTelegramModelScoped(state, model),
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
752
|
export function buildTelegramModelCallbackPlan<
|
|
760
753
|
TModel extends MenuModel = MenuModel,
|
|
761
754
|
>(
|
package/lib/menu.ts
CHANGED
|
@@ -118,25 +118,6 @@ export type {
|
|
|
118
118
|
TelegramThinkingMenuOpenDeps,
|
|
119
119
|
} from "./menu-thinking.ts";
|
|
120
120
|
|
|
121
|
-
export interface TelegramMenuEffectPort<TModel extends MenuModel = MenuModel> {
|
|
122
|
-
answerCallbackQuery: (
|
|
123
|
-
callbackQueryId: string,
|
|
124
|
-
text?: string,
|
|
125
|
-
) => Promise<void>;
|
|
126
|
-
updateModelMenuMessage: () => Promise<void>;
|
|
127
|
-
updateThinkingMenuMessage: () => Promise<void>;
|
|
128
|
-
updateStatusMessage: () => Promise<void>;
|
|
129
|
-
persistScopedModelPatterns?: (patterns: string[]) => Promise<void>;
|
|
130
|
-
setModel: (model: TModel) => Promise<boolean>;
|
|
131
|
-
setCurrentModel: (model: TModel) => void;
|
|
132
|
-
setThinkingLevel: (level: ThinkingLevel) => void;
|
|
133
|
-
getCurrentThinkingLevel: () => ThinkingLevel;
|
|
134
|
-
stagePendingModelSwitch: (selection: ScopedTelegramModel<TModel>) => void;
|
|
135
|
-
restartInterruptedTelegramTurn: (
|
|
136
|
-
selection: ScopedTelegramModel<TModel>,
|
|
137
|
-
) => Promise<boolean> | boolean;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
121
|
export interface TelegramMenuCallbackEntryDeps {
|
|
141
122
|
handleStatusAction: () => Promise<boolean>;
|
|
142
123
|
handleThinkingAction: () => Promise<boolean>;
|
package/lib/outbound.ts
CHANGED
|
@@ -197,12 +197,6 @@ export function registerTelegramOutboundHandler(
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
export function hasTelegramOutboundHandler(kind: string): boolean {
|
|
201
|
-
const registry = getOrCreateOutboundHandlerRegistry();
|
|
202
|
-
const list = registry.handlers.get(kind);
|
|
203
|
-
return !!list && list.length > 0;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
200
|
export function getTelegramOutboundProgrammaticHandlers(
|
|
207
201
|
kind: string,
|
|
208
202
|
): TelegramOutboundProgrammaticHandler[] {
|
|
@@ -953,15 +947,11 @@ export function createTelegramAssistantOutputSender<
|
|
|
953
947
|
TReplyMarkup = unknown,
|
|
954
948
|
>(deps: {
|
|
955
949
|
recordOwnership?: Replies.TelegramReplyOwnershipRecorder["record"];
|
|
956
|
-
sendMessage: (
|
|
957
|
-
body: TelegramSendMessageBody,
|
|
958
|
-
) => Promise<TelegramSentMessage>;
|
|
950
|
+
sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
|
|
959
951
|
sendRichMessage: (
|
|
960
952
|
body: TelegramSendRichMessageBody,
|
|
961
953
|
) => Promise<TelegramSentMessage>;
|
|
962
|
-
editMessage: (
|
|
963
|
-
body: TelegramEditMessageTextBody,
|
|
964
|
-
) => Promise<unknown>;
|
|
954
|
+
editMessage: (body: TelegramEditMessageTextBody) => Promise<unknown>;
|
|
965
955
|
getAssistantRenderingMode: () => "rich" | "html";
|
|
966
956
|
execCommand: TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>["execCommand"];
|
|
967
957
|
getHandlers?: TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>["getHandlers"];
|
package/lib/paths.ts
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { join, resolve } from "node:path";
|
|
12
12
|
|
|
13
|
+
export const TELEGRAM_DEFAULT_PROFILE_NAME = "default";
|
|
14
|
+
|
|
13
15
|
export interface TelegramAgentDirResolutionInput {
|
|
14
16
|
env?: Partial<Pick<NodeJS.ProcessEnv, "PI_CODING_AGENT_DIR">>;
|
|
15
17
|
execPath?: string;
|
|
@@ -45,18 +47,19 @@ export function resolveTelegramConfigPath(): string {
|
|
|
45
47
|
return join(resolveAgentDir(), "telegram.json");
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
/** Telegram singleton lock file (<agentDir>/locks.json). */
|
|
49
|
-
export function resolveTelegramLocksPath(): string {
|
|
50
|
-
return join(resolveAgentDir(), "locks.json");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
50
|
/** Telegram bridge temporary directory (<agentDir>/tmp/telegram). */
|
|
54
51
|
export function resolveTelegramTempDir(agentDir = resolveAgentDir()): string {
|
|
55
52
|
return join(agentDir, "tmp", "telegram");
|
|
56
53
|
}
|
|
57
54
|
|
|
55
|
+
/** Telegram transport ownership store (<agentDir>/tmp/telegram/owners.json). */
|
|
56
|
+
export function resolveTelegramOwnersPath(): string {
|
|
57
|
+
return join(resolveTelegramTempDir(), "owners.json");
|
|
58
|
+
}
|
|
59
|
+
|
|
58
60
|
export function getTelegramProfilePathSuffix(profileName?: string): string {
|
|
59
|
-
|
|
61
|
+
if (!profileName || profileName === TELEGRAM_DEFAULT_PROFILE_NAME) return "";
|
|
62
|
+
return `.${profileName.replace(/[^a-zA-Z0-9._-]+/g, "_")}`;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
export function resolveTelegramProfileTempFilePath(
|
package/lib/pi.ts
CHANGED
|
@@ -114,10 +114,6 @@ export function getSessionCompactionReason(
|
|
|
114
114
|
: "unknown";
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
export type PiSendUserMessageOptions = NonNullable<
|
|
118
|
-
Parameters<ExtensionAPI["sendUserMessage"]>[1]
|
|
119
|
-
>;
|
|
120
|
-
|
|
121
117
|
export interface PiExtensionApiRuntimePorts {
|
|
122
118
|
sendUserMessage: ExtensionAPI["sendUserMessage"];
|
|
123
119
|
exec: ExtensionAPI["exec"];
|
package/lib/queue.ts
CHANGED
|
@@ -1764,7 +1764,6 @@ export async function shutdownTelegramSessionRuntime<TQueueItem>(
|
|
|
1764
1764
|
deps.clearPreview(activeTurnChatId, target ? { target } : undefined),
|
|
1765
1765
|
new Promise<void>((resolve) => {
|
|
1766
1766
|
timeout = setTimeout(resolve, previewTimeoutMs);
|
|
1767
|
-
timeout.unref?.();
|
|
1768
1767
|
}),
|
|
1769
1768
|
]).finally(() => {
|
|
1770
1769
|
if (timeout) clearTimeout(timeout);
|
|
@@ -2240,10 +2239,6 @@ export interface TelegramPromptDeliveryOptions {
|
|
|
2240
2239
|
deliverAs: "followUp";
|
|
2241
2240
|
}
|
|
2242
2241
|
|
|
2243
|
-
export const TELEGRAM_PROMPT_FOLLOW_UP_DELIVERY = {
|
|
2244
|
-
deliverAs: "followUp",
|
|
2245
|
-
} as const satisfies TelegramPromptDeliveryOptions;
|
|
2246
|
-
|
|
2247
2242
|
export interface TelegramDispatchRuntimeDeps<TContext = unknown> {
|
|
2248
2243
|
executeControlItem: (
|
|
2249
2244
|
item: Extract<
|
package/lib/replies.ts
CHANGED
|
@@ -871,29 +871,6 @@ export function dedupSendTextReply(
|
|
|
871
871
|
};
|
|
872
872
|
}
|
|
873
873
|
|
|
874
|
-
/** Wrap a sendMarkdownReply with reply dedup. */
|
|
875
|
-
export function dedupSendMarkdownReply<TReplyMarkup = unknown>(
|
|
876
|
-
dedup: ReplyDedupRuntime,
|
|
877
|
-
inner: (
|
|
878
|
-
chatId: number,
|
|
879
|
-
replyToMessageId: number | undefined,
|
|
880
|
-
markdown: string,
|
|
881
|
-
options?: { replyMarkup?: TReplyMarkup },
|
|
882
|
-
) => Promise<number | undefined>,
|
|
883
|
-
): (
|
|
884
|
-
chatId: number,
|
|
885
|
-
replyToMessageId: number,
|
|
886
|
-
markdown: string,
|
|
887
|
-
options?: { replyMarkup?: TReplyMarkup },
|
|
888
|
-
) => Promise<number | undefined> {
|
|
889
|
-
return async (chatId, replyToMessageId, markdown, options) => {
|
|
890
|
-
const effectiveReplyTo = dedup.shouldReply(replyToMessageId)
|
|
891
|
-
? replyToMessageId
|
|
892
|
-
: undefined;
|
|
893
|
-
return inner(chatId, effectiveReplyTo, markdown, options);
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
|
-
|
|
897
874
|
/**
|
|
898
875
|
* Guest reply sender: answers guest queries with native Rich Markdown content.
|
|
899
876
|
* Guest queries use InlineQueryResult input_message_content rather than chat
|
package/lib/runtime.ts
CHANGED
|
@@ -508,8 +508,7 @@ export async function waitForTelegramTypingLoopIdle(
|
|
|
508
508
|
await Promise.race([
|
|
509
509
|
inFlight,
|
|
510
510
|
new Promise<void>((resolve) => {
|
|
511
|
-
|
|
512
|
-
timer.unref?.();
|
|
511
|
+
setTimeout(resolve, timeoutMs);
|
|
513
512
|
}),
|
|
514
513
|
]);
|
|
515
514
|
}
|
package/lib/sections.ts
CHANGED
|
@@ -358,7 +358,7 @@ export function createTelegramExtensionSectionRegistry(): TelegramSectionRegistr
|
|
|
358
358
|
>();
|
|
359
359
|
let nextToken = 0;
|
|
360
360
|
|
|
361
|
-
const register = (section: TelegramSectionRegistration): () => void => {
|
|
361
|
+
const register = (section: TelegramSectionRegistration): (() => void) => {
|
|
362
362
|
const duplicate = [...sections.values()].find((s) => s.id === section.id);
|
|
363
363
|
if (duplicate) {
|
|
364
364
|
throw new Error(`Telegram section id already registered: ${section.id}`);
|
|
@@ -708,50 +708,3 @@ export async function handleTelegramSectionSettingsOpen(
|
|
|
708
708
|
return true;
|
|
709
709
|
}
|
|
710
710
|
}
|
|
711
|
-
|
|
712
|
-
export async function handleTelegramSectionSettingsCallback(
|
|
713
|
-
registry: TelegramSectionRegistry,
|
|
714
|
-
token: TelegramSectionToken,
|
|
715
|
-
action: string,
|
|
716
|
-
payload: string,
|
|
717
|
-
chatId: number,
|
|
718
|
-
messageId: number,
|
|
719
|
-
callbackQueryId: string,
|
|
720
|
-
deps: TelegramSectionCallbackHandlerDeps,
|
|
721
|
-
): Promise<boolean> {
|
|
722
|
-
const section = registry.getByToken(token);
|
|
723
|
-
if (!section || !section.registration.settings?.handleCallback) {
|
|
724
|
-
await deps.answerCallbackQuery(
|
|
725
|
-
callbackQueryId,
|
|
726
|
-
"This section is no longer available.",
|
|
727
|
-
);
|
|
728
|
-
return true;
|
|
729
|
-
}
|
|
730
|
-
try {
|
|
731
|
-
const ctx = buildTelegramSectionCallbackContext(
|
|
732
|
-
section.id,
|
|
733
|
-
token,
|
|
734
|
-
chatId,
|
|
735
|
-
messageId,
|
|
736
|
-
action,
|
|
737
|
-
payload,
|
|
738
|
-
callbackQueryId,
|
|
739
|
-
deps,
|
|
740
|
-
`settings:list`,
|
|
741
|
-
);
|
|
742
|
-
const result = await section.registration.settings.handleCallback(ctx);
|
|
743
|
-
if (result === "pass") {
|
|
744
|
-
await deps.answerCallbackQuery(callbackQueryId);
|
|
745
|
-
}
|
|
746
|
-
registry.clearError(token, "settings_callback");
|
|
747
|
-
return true;
|
|
748
|
-
} catch (error) {
|
|
749
|
-
const message = sectionErrorMessage(error);
|
|
750
|
-
registry.recordError(token, message, "settings_callback");
|
|
751
|
-
await deps.answerCallbackQuery(
|
|
752
|
-
callbackQueryId,
|
|
753
|
-
`Section error: ${message}`,
|
|
754
|
-
);
|
|
755
|
-
return true;
|
|
756
|
-
}
|
|
757
|
-
}
|